2
This module converts requested URLs to callback view functions.
4
RegexURLResolver is the main class here. Its resolve() method takes a URL (as
5
a string) and returns a tuple in this format:
7
(view_function, function_args, function_kwargs)
9
from __future__ import unicode_literals
12
from threading import local
14
from django.http import Http404
15
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
16
from django.utils.datastructures import MultiValueDict
17
from django.utils.encoding import force_str, force_text, iri_to_uri
18
from django.utils.functional import memoize, lazy
19
from django.utils.http import urlquote
20
from django.utils.importlib import import_module
21
from django.utils.module_loading import module_has_submodule
22
from django.utils.regex_helper import normalize
23
from django.utils import six
24
from django.utils.translation import get_language
27
_resolver_cache = {} # Maps URLconf modules to RegexURLResolver instances.
28
_ns_resolver_cache = {} # Maps namespaces to RegexURLResolver instances.
29
_callable_cache = {} # Maps view and url pattern names to their view functions.
31
# SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
32
# the current thread (which is the only one we ever access), it is assumed to
36
# Overridden URLconfs for each thread are stored here.
40
class ResolverMatch(object):
41
def __init__(self, func, args, kwargs, url_name=None, app_name=None, namespaces=None):
45
self.app_name = app_name
47
self.namespaces = [x for x in namespaces if x]
51
if not hasattr(func, '__name__'):
52
# An instance of a callable class
53
url_name = '.'.join([func.__class__.__module__, func.__class__.__name__])
56
url_name = '.'.join([func.__module__, func.__name__])
57
self.url_name = url_name
61
return ':'.join(self.namespaces)
65
return ':'.join([ x for x in [ self.namespace, self.url_name ] if x ])
67
def __getitem__(self, index):
68
return (self.func, self.args, self.kwargs)[index]
71
return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name='%s', app_name='%s', namespace='%s')" % (
72
self.func, self.args, self.kwargs, self.url_name, self.app_name, self.namespace)
74
class Resolver404(Http404):
77
class NoReverseMatch(Exception):
78
# Don't make this raise an error when used in a template.
79
silent_variable_failure = True
81
def get_callable(lookup_view, can_fail=False):
83
Convert a string version of a function name to the callable object.
85
If the lookup_view is not an import path, it is assumed to be a URL pattern
86
label and the original string is returned.
88
If can_fail is True, lookup_view might be a URL pattern label, so errors
89
during the import fail and the string is returned.
91
if not callable(lookup_view):
92
mod_name, func_name = get_mod_func(lookup_view)
97
mod = import_module(mod_name)
99
parentmod, submod = get_mod_func(mod_name)
100
if (not can_fail and submod != '' and
101
not module_has_submodule(import_module(parentmod), submod)):
102
raise ViewDoesNotExist(
103
"Could not import %s. Parent module %s does not exist." %
104
(lookup_view, mod_name))
109
lookup_view = getattr(mod, func_name)
110
if not callable(lookup_view):
111
raise ViewDoesNotExist(
112
"Could not import %s.%s. View is not callable." %
113
(mod_name, func_name))
114
except AttributeError:
116
raise ViewDoesNotExist(
117
"Could not import %s. View does not exist in module %s." %
118
(lookup_view, mod_name))
120
get_callable = memoize(get_callable, _callable_cache, 1)
122
def get_resolver(urlconf):
124
from django.conf import settings
125
urlconf = settings.ROOT_URLCONF
126
return RegexURLResolver(r'^/', urlconf)
127
get_resolver = memoize(get_resolver, _resolver_cache, 1)
129
def get_ns_resolver(ns_pattern, resolver):
130
# Build a namespaced resolver for the given parent urlconf pattern.
131
# This makes it possible to have captured parameters in the parent
133
ns_resolver = RegexURLResolver(ns_pattern,
134
resolver.url_patterns)
135
return RegexURLResolver(r'^/', [ns_resolver])
136
get_ns_resolver = memoize(get_ns_resolver, _ns_resolver_cache, 2)
138
def get_mod_func(callback):
139
# Converts 'django.views.news.stories.story_detail' to
140
# ['django.views.news.stories', 'story_detail']
142
dot = callback.rindex('.')
145
return callback[:dot], callback[dot+1:]
147
class LocaleRegexProvider(object):
149
A mixin to provide a default regex property which can vary by active
153
def __init__(self, regex):
154
# regex is either a string representing a regular expression, or a
155
# translatable string (using ugettext_lazy) representing a regular
158
self._regex_dict = {}
164
Returns a compiled regular expression, depending upon the activated
167
language_code = get_language()
168
if language_code not in self._regex_dict:
169
if isinstance(self._regex, six.string_types):
172
regex = force_text(self._regex)
174
compiled_regex = re.compile(regex, re.UNICODE)
175
except re.error as e:
176
raise ImproperlyConfigured(
177
'"%s" is not a valid regular expression: %s' %
178
(regex, six.text_type(e)))
180
self._regex_dict[language_code] = compiled_regex
181
return self._regex_dict[language_code]
184
class RegexURLPattern(LocaleRegexProvider):
185
def __init__(self, regex, callback, default_args=None, name=None):
186
LocaleRegexProvider.__init__(self, regex)
187
# callback is either a string like 'foo.views.news.stories.story_detail'
188
# which represents the path to a module and a view function name, or a
189
# callable object (view).
190
if callable(callback):
191
self._callback = callback
193
self._callback = None
194
self._callback_str = callback
195
self.default_args = default_args or {}
199
return force_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern))
201
def add_prefix(self, prefix):
203
Adds the prefix string to a string-based callback.
205
if not prefix or not hasattr(self, '_callback_str'):
207
self._callback_str = prefix + '.' + self._callback_str
209
def resolve(self, path):
210
match = self.regex.search(path)
212
# If there are any named groups, use those as kwargs, ignoring
213
# non-named groups. Otherwise, pass all non-named arguments as
214
# positional arguments.
215
kwargs = match.groupdict()
219
args = match.groups()
220
# In both cases, pass any extra_kwargs as **kwargs.
221
kwargs.update(self.default_args)
223
return ResolverMatch(self.callback, args, kwargs, self.name)
227
if self._callback is not None:
228
return self._callback
230
self._callback = get_callable(self._callback_str)
231
return self._callback
233
class RegexURLResolver(LocaleRegexProvider):
234
def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
235
LocaleRegexProvider.__init__(self, regex)
236
# urlconf_name is a string representing the module containing URLconfs.
237
self.urlconf_name = urlconf_name
238
if not isinstance(urlconf_name, six.string_types):
239
self._urlconf_module = self.urlconf_name
241
self.default_kwargs = default_kwargs or {}
242
self.namespace = namespace
243
self.app_name = app_name
244
self._reverse_dict = {}
245
self._namespace_dict = {}
247
# set of dotted paths to all functions and classes that are used in
249
self._callback_strs = set()
250
self._populated = False
253
if isinstance(self.urlconf_name, list) and len(self.urlconf_name):
254
# Don't bother to output the whole list, it can be huge
255
urlconf_repr = '<%s list>' % self.urlconf_name[0].__class__.__name__
257
urlconf_repr = repr(self.urlconf_name)
258
return str('<%s %s (%s:%s) %s>') % (
259
self.__class__.__name__, urlconf_repr, self.app_name,
260
self.namespace, self.regex.pattern)
263
lookups = MultiValueDict()
266
language_code = get_language()
267
for pattern in reversed(self.url_patterns):
268
if hasattr(pattern, '_callback_str'):
269
self._callback_strs.add(pattern._callback_str)
270
elif hasattr(pattern, '_callback'):
271
callback = pattern._callback
272
if not hasattr(callback, '__name__'):
273
lookup_str = callback.__module__ + "." + callback.__class__.__name__
275
lookup_str = callback.__module__ + "." + callback.__name__
276
self._callback_strs.add(lookup_str)
277
p_pattern = pattern.regex.pattern
278
if p_pattern.startswith('^'):
279
p_pattern = p_pattern[1:]
280
if isinstance(pattern, RegexURLResolver):
281
if pattern.namespace:
282
namespaces[pattern.namespace] = (p_pattern, pattern)
284
apps.setdefault(pattern.app_name, []).append(pattern.namespace)
286
parent = normalize(pattern.regex.pattern)
287
for name in pattern.reverse_dict:
288
for matches, pat, defaults in pattern.reverse_dict.getlist(name):
290
for piece, p_args in parent:
291
new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches])
292
lookups.appendlist(name, (new_matches, p_pattern + pat, dict(defaults, **pattern.default_kwargs)))
293
for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
294
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
295
for app_name, namespace_list in pattern.app_dict.items():
296
apps.setdefault(app_name, []).extend(namespace_list)
297
self._callback_strs.update(pattern._callback_strs)
299
bits = normalize(p_pattern)
300
lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
301
if pattern.name is not None:
302
lookups.appendlist(pattern.name, (bits, p_pattern, pattern.default_args))
303
self._reverse_dict[language_code] = lookups
304
self._namespace_dict[language_code] = namespaces
305
self._app_dict[language_code] = apps
306
self._populated = True
309
def reverse_dict(self):
310
language_code = get_language()
311
if language_code not in self._reverse_dict:
313
return self._reverse_dict[language_code]
316
def namespace_dict(self):
317
language_code = get_language()
318
if language_code not in self._namespace_dict:
320
return self._namespace_dict[language_code]
324
language_code = get_language()
325
if language_code not in self._app_dict:
327
return self._app_dict[language_code]
329
def resolve(self, path):
331
match = self.regex.search(path)
333
new_path = path[match.end():]
334
for pattern in self.url_patterns:
336
sub_match = pattern.resolve(new_path)
337
except Resolver404 as e:
338
sub_tried = e.args[0].get('tried')
339
if sub_tried is not None:
340
tried.extend([[pattern] + t for t in sub_tried])
342
tried.append([pattern])
345
sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
346
sub_match_dict.update(sub_match.kwargs)
347
return ResolverMatch(sub_match.func, sub_match.args, sub_match_dict, sub_match.url_name, self.app_name or sub_match.app_name, [self.namespace] + sub_match.namespaces)
348
tried.append([pattern])
349
raise Resolver404({'tried': tried, 'path': new_path})
350
raise Resolver404({'path' : path})
353
def urlconf_module(self):
355
return self._urlconf_module
356
except AttributeError:
357
self._urlconf_module = import_module(self.urlconf_name)
358
return self._urlconf_module
361
def url_patterns(self):
362
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
366
raise ImproperlyConfigured("The included urlconf %s doesn't have any patterns in it" % self.urlconf_name)
369
def _resolve_special(self, view_type):
370
callback = getattr(self.urlconf_module, 'handler%s' % view_type, None)
372
# No handler specified in file; use default
373
# Lazy import, since django.urls imports this file
374
from django.conf import urls
375
callback = getattr(urls, 'handler%s' % view_type)
376
return get_callable(callback), {}
378
def resolve403(self):
379
return self._resolve_special('403')
381
def resolve404(self):
382
return self._resolve_special('404')
384
def resolve500(self):
385
return self._resolve_special('500')
387
def reverse(self, lookup_view, *args, **kwargs):
388
return self._reverse_with_prefix(lookup_view, '', *args, **kwargs)
390
def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
392
raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
394
if not self._populated:
398
if lookup_view in self._callback_strs:
399
lookup_view = get_callable(lookup_view, True)
400
except (ImportError, AttributeError) as e:
401
raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
402
possibilities = self.reverse_dict.getlist(lookup_view)
404
prefix_norm, prefix_args = normalize(urlquote(_prefix))[0]
405
for possibility, pattern, defaults in possibilities:
406
for result, params in possibility:
408
if len(args) != len(params) + len(prefix_args):
410
unicode_args = [force_text(val) for val in args]
411
candidate = (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args))
413
if set(kwargs.keys()) | set(defaults.keys()) != set(params) | set(defaults.keys()) | set(prefix_args):
416
for k, v in defaults.items():
417
if kwargs.get(k, v) != v:
422
unicode_kwargs = dict([(k, force_text(v)) for (k, v) in kwargs.items()])
423
candidate = (prefix_norm.replace('%', '%%') + result) % unicode_kwargs
424
if re.search('^%s%s' % (prefix_norm, pattern), candidate, re.UNICODE):
426
# lookup_view can be URL label, or dotted path, or callable, Any of
427
# these can be passed in at the top, but callables are not friendly in
429
m = getattr(lookup_view, '__module__', None)
430
n = getattr(lookup_view, '__name__', None)
431
if m is not None and n is not None:
432
lookup_view_s = "%s.%s" % (m, n)
434
lookup_view_s = lookup_view
435
raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
436
"arguments '%s' not found." % (lookup_view_s, args, kwargs))
438
class LocaleRegexURLResolver(RegexURLResolver):
440
A URL resolver that always matches the active language code as URL prefix.
442
Rather than taking a regex argument, we just override the ``regex``
443
function to always return the active language-code as regex.
445
def __init__(self, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
446
super(LocaleRegexURLResolver, self).__init__(
447
None, urlconf_name, default_kwargs, app_name, namespace)
451
language_code = get_language()
452
if language_code not in self._regex_dict:
453
regex_compiled = re.compile('^%s/' % language_code, re.UNICODE)
454
self._regex_dict[language_code] = regex_compiled
455
return self._regex_dict[language_code]
457
def resolve(path, urlconf=None):
459
urlconf = get_urlconf()
460
return get_resolver(urlconf).resolve(path)
462
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
464
urlconf = get_urlconf()
465
resolver = get_resolver(urlconf)
467
kwargs = kwargs or {}
470
prefix = get_script_prefix()
472
if not isinstance(viewname, six.string_types):
475
parts = viewname.split(':')
485
# Lookup the name to see if it could be an app identifier
487
app_list = resolver.app_dict[ns]
488
# Yes! Path part matches an app in the current Resolver
489
if current_app and current_app in app_list:
490
# If we are reversing for a particular app,
493
elif ns not in app_list:
494
# The name isn't shared by one of the instances
495
# (i.e., the default) so just pick the first instance
502
extra, resolver = resolver.namespace_dict[ns]
503
resolved_path.append(ns)
504
ns_pattern = ns_pattern + extra
505
except KeyError as key:
507
raise NoReverseMatch(
508
"%s is not a registered namespace inside '%s'" %
509
(key, ':'.join(resolved_path)))
511
raise NoReverseMatch("%s is not a registered namespace" %
514
resolver = get_ns_resolver(ns_pattern, resolver)
516
return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
518
reverse_lazy = lazy(reverse, str)
520
def clear_url_caches():
521
global _resolver_cache
522
global _ns_resolver_cache
523
global _callable_cache
524
_resolver_cache.clear()
525
_ns_resolver_cache.clear()
526
_callable_cache.clear()
528
def set_script_prefix(prefix):
530
Sets the script prefix for the current thread.
532
if not prefix.endswith('/'):
534
_prefixes.value = prefix
536
def get_script_prefix():
538
Returns the currently active script prefix. Useful for client code that
539
wishes to construct their own URLs manually (although accessing the request
540
instance is normally going to be a lot cleaner).
542
return getattr(_prefixes, "value", '/')
544
def set_urlconf(urlconf_name):
546
Sets the URLconf for the current thread (overriding the default one in
547
settings). Set to None to revert back to the default.
550
_urlconfs.value = urlconf_name
552
if hasattr(_urlconfs, "value"):
555
def get_urlconf(default=None):
557
Returns the root URLconf to use for the current thread if it has been
558
changed from the default one.
560
return getattr(_urlconfs, "value", default)
562
def is_valid_path(path, urlconf=None):
564
Returns True if the given path resolves against the default URL resolver,
567
This is a convenience method to make working with "is this a match?" cases
568
easier, avoiding unnecessarily indented try...except blocks.
571
resolve(path, urlconf)