PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/django/core/urlresolvers.py

https://github.com/sesostris/django
Python | 547 lines | 477 code | 24 blank | 46 comment | 21 complexity | 26d43778eae2aba7da65ccc6f8101932 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. """
  2. This module converts requested URLs to callback view functions.
  3. RegexURLResolver is the main class here. Its resolve() method takes a URL (as
  4. a string) and returns a tuple in this format:
  5. (view_function, function_args, function_kwargs)
  6. """
  7. from __future__ import unicode_literals
  8. import re
  9. from threading import local
  10. from django.http import Http404
  11. from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
  12. from django.utils.datastructures import MultiValueDict
  13. from django.utils.encoding import iri_to_uri, force_text, smart_str
  14. from django.utils.functional import memoize, lazy
  15. from django.utils.importlib import import_module
  16. from django.utils.module_loading import module_has_submodule
  17. from django.utils.regex_helper import normalize
  18. from django.utils import six
  19. from django.utils.translation import get_language
  20. _resolver_cache = {} # Maps URLconf modules to RegexURLResolver instances.
  21. _ns_resolver_cache = {} # Maps namespaces to RegexURLResolver instances.
  22. _callable_cache = {} # Maps view and url pattern names to their view functions.
  23. # SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
  24. # the current thread (which is the only one we ever access), it is assumed to
  25. # be empty.
  26. _prefixes = local()
  27. # Overridden URLconfs for each thread are stored here.
  28. _urlconfs = local()
  29. class ResolverMatch(object):
  30. def __init__(self, func, args, kwargs, url_name=None, app_name=None, namespaces=None):
  31. self.func = func
  32. self.args = args
  33. self.kwargs = kwargs
  34. self.app_name = app_name
  35. if namespaces:
  36. self.namespaces = [x for x in namespaces if x]
  37. else:
  38. self.namespaces = []
  39. if not url_name:
  40. if not hasattr(func, '__name__'):
  41. # An instance of a callable class
  42. url_name = '.'.join([func.__class__.__module__, func.__class__.__name__])
  43. else:
  44. # A function
  45. url_name = '.'.join([func.__module__, func.__name__])
  46. self.url_name = url_name
  47. @property
  48. def namespace(self):
  49. return ':'.join(self.namespaces)
  50. @property
  51. def view_name(self):
  52. return ':'.join([ x for x in [ self.namespace, self.url_name ] if x ])
  53. def __getitem__(self, index):
  54. return (self.func, self.args, self.kwargs)[index]
  55. def __repr__(self):
  56. return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name='%s', app_name='%s', namespace='%s')" % (
  57. self.func, self.args, self.kwargs, self.url_name, self.app_name, self.namespace)
  58. class Resolver404(Http404):
  59. pass
  60. class NoReverseMatch(Exception):
  61. # Don't make this raise an error when used in a template.
  62. silent_variable_failure = True
  63. def get_callable(lookup_view, can_fail=False):
  64. """
  65. Convert a string version of a function name to the callable object.
  66. If the lookup_view is not an import path, it is assumed to be a URL pattern
  67. label and the original string is returned.
  68. If can_fail is True, lookup_view might be a URL pattern label, so errors
  69. during the import fail and the string is returned.
  70. """
  71. if not callable(lookup_view):
  72. mod_name, func_name = get_mod_func(lookup_view)
  73. if func_name == '':
  74. return lookup_view
  75. try:
  76. mod = import_module(mod_name)
  77. except ImportError:
  78. parentmod, submod = get_mod_func(mod_name)
  79. if (not can_fail and submod != '' and
  80. not module_has_submodule(import_module(parentmod), submod)):
  81. raise ViewDoesNotExist(
  82. "Could not import %s. Parent module %s does not exist." %
  83. (lookup_view, mod_name))
  84. if not can_fail:
  85. raise
  86. else:
  87. try:
  88. lookup_view = getattr(mod, func_name)
  89. if not callable(lookup_view):
  90. raise ViewDoesNotExist(
  91. "Could not import %s.%s. View is not callable." %
  92. (mod_name, func_name))
  93. except AttributeError:
  94. if not can_fail:
  95. raise ViewDoesNotExist(
  96. "Could not import %s. View does not exist in module %s." %
  97. (lookup_view, mod_name))
  98. return lookup_view
  99. get_callable = memoize(get_callable, _callable_cache, 1)
  100. def get_resolver(urlconf):
  101. if urlconf is None:
  102. from django.conf import settings
  103. urlconf = settings.ROOT_URLCONF
  104. return RegexURLResolver(r'^/', urlconf)
  105. get_resolver = memoize(get_resolver, _resolver_cache, 1)
  106. def get_ns_resolver(ns_pattern, resolver):
  107. # Build a namespaced resolver for the given parent urlconf pattern.
  108. # This makes it possible to have captured parameters in the parent
  109. # urlconf pattern.
  110. ns_resolver = RegexURLResolver(ns_pattern,
  111. resolver.url_patterns)
  112. return RegexURLResolver(r'^/', [ns_resolver])
  113. get_ns_resolver = memoize(get_ns_resolver, _ns_resolver_cache, 2)
  114. def get_mod_func(callback):
  115. # Converts 'django.views.news.stories.story_detail' to
  116. # ['django.views.news.stories', 'story_detail']
  117. try:
  118. dot = callback.rindex('.')
  119. except ValueError:
  120. return callback, ''
  121. return callback[:dot], callback[dot+1:]
  122. class LocaleRegexProvider(object):
  123. """
  124. A mixin to provide a default regex property which can vary by active
  125. language.
  126. """
  127. def __init__(self, regex):
  128. # regex is either a string representing a regular expression, or a
  129. # translatable string (using ugettext_lazy) representing a regular
  130. # expression.
  131. self._regex = regex
  132. self._regex_dict = {}
  133. @property
  134. def regex(self):
  135. """
  136. Returns a compiled regular expression, depending upon the activated
  137. language-code.
  138. """
  139. language_code = get_language()
  140. if language_code not in self._regex_dict:
  141. if isinstance(self._regex, six.string_types):
  142. regex = self._regex
  143. else:
  144. regex = force_text(self._regex)
  145. try:
  146. compiled_regex = re.compile(regex, re.UNICODE)
  147. except re.error as e:
  148. raise ImproperlyConfigured(
  149. '"%s" is not a valid regular expression: %s' %
  150. (regex, six.text_type(e)))
  151. self._regex_dict[language_code] = compiled_regex
  152. return self._regex_dict[language_code]
  153. class RegexURLPattern(LocaleRegexProvider):
  154. def __init__(self, regex, callback, default_args=None, name=None):
  155. LocaleRegexProvider.__init__(self, regex)
  156. # callback is either a string like 'foo.views.news.stories.story_detail'
  157. # which represents the path to a module and a view function name, or a
  158. # callable object (view).
  159. if callable(callback):
  160. self._callback = callback
  161. else:
  162. self._callback = None
  163. self._callback_str = callback
  164. self.default_args = default_args or {}
  165. self.name = name
  166. def __repr__(self):
  167. return smart_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern))
  168. def add_prefix(self, prefix):
  169. """
  170. Adds the prefix string to a string-based callback.
  171. """
  172. if not prefix or not hasattr(self, '_callback_str'):
  173. return
  174. self._callback_str = prefix + '.' + self._callback_str
  175. def resolve(self, path):
  176. match = self.regex.search(path)
  177. if match:
  178. # If there are any named groups, use those as kwargs, ignoring
  179. # non-named groups. Otherwise, pass all non-named arguments as
  180. # positional arguments.
  181. kwargs = match.groupdict()
  182. if kwargs:
  183. args = ()
  184. else:
  185. args = match.groups()
  186. # In both cases, pass any extra_kwargs as **kwargs.
  187. kwargs.update(self.default_args)
  188. return ResolverMatch(self.callback, args, kwargs, self.name)
  189. @property
  190. def callback(self):
  191. if self._callback is not None:
  192. return self._callback
  193. self._callback = get_callable(self._callback_str)
  194. return self._callback
  195. class RegexURLResolver(LocaleRegexProvider):
  196. def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
  197. LocaleRegexProvider.__init__(self, regex)
  198. # urlconf_name is a string representing the module containing URLconfs.
  199. self.urlconf_name = urlconf_name
  200. if not isinstance(urlconf_name, six.string_types):
  201. self._urlconf_module = self.urlconf_name
  202. self.callback = None
  203. self.default_kwargs = default_kwargs or {}
  204. self.namespace = namespace
  205. self.app_name = app_name
  206. self._reverse_dict = {}
  207. self._namespace_dict = {}
  208. self._app_dict = {}
  209. def __repr__(self):
  210. return smart_str('<%s %s (%s:%s) %s>' % (
  211. self.__class__.__name__, self.urlconf_name, self.app_name,
  212. self.namespace, self.regex.pattern))
  213. def _populate(self):
  214. lookups = MultiValueDict()
  215. namespaces = {}
  216. apps = {}
  217. language_code = get_language()
  218. for pattern in reversed(self.url_patterns):
  219. p_pattern = pattern.regex.pattern
  220. if p_pattern.startswith('^'):
  221. p_pattern = p_pattern[1:]
  222. if isinstance(pattern, RegexURLResolver):
  223. if pattern.namespace:
  224. namespaces[pattern.namespace] = (p_pattern, pattern)
  225. if pattern.app_name:
  226. apps.setdefault(pattern.app_name, []).append(pattern.namespace)
  227. else:
  228. parent = normalize(pattern.regex.pattern)
  229. for name in pattern.reverse_dict:
  230. for matches, pat, defaults in pattern.reverse_dict.getlist(name):
  231. new_matches = []
  232. for piece, p_args in parent:
  233. new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches])
  234. lookups.appendlist(name, (new_matches, p_pattern + pat, dict(defaults, **pattern.default_kwargs)))
  235. for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
  236. namespaces[namespace] = (p_pattern + prefix, sub_pattern)
  237. for app_name, namespace_list in pattern.app_dict.items():
  238. apps.setdefault(app_name, []).extend(namespace_list)
  239. else:
  240. bits = normalize(p_pattern)
  241. lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
  242. if pattern.name is not None:
  243. lookups.appendlist(pattern.name, (bits, p_pattern, pattern.default_args))
  244. self._reverse_dict[language_code] = lookups
  245. self._namespace_dict[language_code] = namespaces
  246. self._app_dict[language_code] = apps
  247. @property
  248. def reverse_dict(self):
  249. language_code = get_language()
  250. if language_code not in self._reverse_dict:
  251. self._populate()
  252. return self._reverse_dict[language_code]
  253. @property
  254. def namespace_dict(self):
  255. language_code = get_language()
  256. if language_code not in self._namespace_dict:
  257. self._populate()
  258. return self._namespace_dict[language_code]
  259. @property
  260. def app_dict(self):
  261. language_code = get_language()
  262. if language_code not in self._app_dict:
  263. self._populate()
  264. return self._app_dict[language_code]
  265. def resolve(self, path):
  266. tried = []
  267. match = self.regex.search(path)
  268. if match:
  269. new_path = path[match.end():]
  270. for pattern in self.url_patterns:
  271. try:
  272. sub_match = pattern.resolve(new_path)
  273. except Resolver404 as e:
  274. sub_tried = e.args[0].get('tried')
  275. if sub_tried is not None:
  276. tried.extend([[pattern] + t for t in sub_tried])
  277. else:
  278. tried.append([pattern])
  279. else:
  280. if sub_match:
  281. sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
  282. sub_match_dict.update(sub_match.kwargs)
  283. 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)
  284. tried.append([pattern])
  285. raise Resolver404({'tried': tried, 'path': new_path})
  286. raise Resolver404({'path' : path})
  287. @property
  288. def urlconf_module(self):
  289. try:
  290. return self._urlconf_module
  291. except AttributeError:
  292. self._urlconf_module = import_module(self.urlconf_name)
  293. return self._urlconf_module
  294. @property
  295. def url_patterns(self):
  296. patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  297. try:
  298. iter(patterns)
  299. except TypeError:
  300. raise ImproperlyConfigured("The included urlconf %s doesn't have any patterns in it" % self.urlconf_name)
  301. return patterns
  302. def _resolve_special(self, view_type):
  303. callback = getattr(self.urlconf_module, 'handler%s' % view_type, None)
  304. if not callback:
  305. # No handler specified in file; use default
  306. # Lazy import, since django.urls imports this file
  307. from django.conf import urls
  308. callback = getattr(urls, 'handler%s' % view_type)
  309. return get_callable(callback), {}
  310. def resolve403(self):
  311. return self._resolve_special('403')
  312. def resolve404(self):
  313. return self._resolve_special('404')
  314. def resolve500(self):
  315. return self._resolve_special('500')
  316. def reverse(self, lookup_view, *args, **kwargs):
  317. return self._reverse_with_prefix(lookup_view, '', *args, **kwargs)
  318. def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
  319. if args and kwargs:
  320. raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
  321. try:
  322. lookup_view = get_callable(lookup_view, True)
  323. except (ImportError, AttributeError) as e:
  324. raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
  325. possibilities = self.reverse_dict.getlist(lookup_view)
  326. prefix_norm, prefix_args = normalize(_prefix)[0]
  327. for possibility, pattern, defaults in possibilities:
  328. for result, params in possibility:
  329. if args:
  330. if len(args) != len(params) + len(prefix_args):
  331. continue
  332. unicode_args = [force_text(val) for val in args]
  333. candidate = (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args))
  334. else:
  335. if set(kwargs.keys()) | set(defaults.keys()) != set(params) | set(defaults.keys()) | set(prefix_args):
  336. continue
  337. matches = True
  338. for k, v in defaults.items():
  339. if kwargs.get(k, v) != v:
  340. matches = False
  341. break
  342. if not matches:
  343. continue
  344. unicode_kwargs = dict([(k, force_text(v)) for (k, v) in kwargs.items()])
  345. candidate = (prefix_norm + result) % unicode_kwargs
  346. if re.search('^%s%s' % (_prefix, pattern), candidate, re.UNICODE):
  347. return candidate
  348. # lookup_view can be URL label, or dotted path, or callable, Any of
  349. # these can be passed in at the top, but callables are not friendly in
  350. # error messages.
  351. m = getattr(lookup_view, '__module__', None)
  352. n = getattr(lookup_view, '__name__', None)
  353. if m is not None and n is not None:
  354. lookup_view_s = "%s.%s" % (m, n)
  355. else:
  356. lookup_view_s = lookup_view
  357. raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
  358. "arguments '%s' not found." % (lookup_view_s, args, kwargs))
  359. class LocaleRegexURLResolver(RegexURLResolver):
  360. """
  361. A URL resolver that always matches the active language code as URL prefix.
  362. Rather than taking a regex argument, we just override the ``regex``
  363. function to always return the active language-code as regex.
  364. """
  365. def __init__(self, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
  366. super(LocaleRegexURLResolver, self).__init__(
  367. None, urlconf_name, default_kwargs, app_name, namespace)
  368. @property
  369. def regex(self):
  370. language_code = get_language()
  371. if language_code not in self._regex_dict:
  372. regex_compiled = re.compile('^%s/' % language_code, re.UNICODE)
  373. self._regex_dict[language_code] = regex_compiled
  374. return self._regex_dict[language_code]
  375. def resolve(path, urlconf=None):
  376. if urlconf is None:
  377. urlconf = get_urlconf()
  378. return get_resolver(urlconf).resolve(path)
  379. def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
  380. if urlconf is None:
  381. urlconf = get_urlconf()
  382. resolver = get_resolver(urlconf)
  383. args = args or []
  384. kwargs = kwargs or {}
  385. if prefix is None:
  386. prefix = get_script_prefix()
  387. if not isinstance(viewname, six.string_types):
  388. view = viewname
  389. else:
  390. parts = viewname.split(':')
  391. parts.reverse()
  392. view = parts[0]
  393. path = parts[1:]
  394. resolved_path = []
  395. ns_pattern = ''
  396. while path:
  397. ns = path.pop()
  398. # Lookup the name to see if it could be an app identifier
  399. try:
  400. app_list = resolver.app_dict[ns]
  401. # Yes! Path part matches an app in the current Resolver
  402. if current_app and current_app in app_list:
  403. # If we are reversing for a particular app,
  404. # use that namespace
  405. ns = current_app
  406. elif ns not in app_list:
  407. # The name isn't shared by one of the instances
  408. # (i.e., the default) so just pick the first instance
  409. # as the default.
  410. ns = app_list[0]
  411. except KeyError:
  412. pass
  413. try:
  414. extra, resolver = resolver.namespace_dict[ns]
  415. resolved_path.append(ns)
  416. ns_pattern = ns_pattern + extra
  417. except KeyError as key:
  418. if resolved_path:
  419. raise NoReverseMatch(
  420. "%s is not a registered namespace inside '%s'" %
  421. (key, ':'.join(resolved_path)))
  422. else:
  423. raise NoReverseMatch("%s is not a registered namespace" %
  424. key)
  425. if ns_pattern:
  426. resolver = get_ns_resolver(ns_pattern, resolver)
  427. return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
  428. reverse_lazy = lazy(reverse, str)
  429. def clear_url_caches():
  430. global _resolver_cache
  431. global _ns_resolver_cache
  432. global _callable_cache
  433. _resolver_cache.clear()
  434. _ns_resolver_cache.clear()
  435. _callable_cache.clear()
  436. def set_script_prefix(prefix):
  437. """
  438. Sets the script prefix for the current thread.
  439. """
  440. if not prefix.endswith('/'):
  441. prefix += '/'
  442. _prefixes.value = prefix
  443. def get_script_prefix():
  444. """
  445. Returns the currently active script prefix. Useful for client code that
  446. wishes to construct their own URLs manually (although accessing the request
  447. instance is normally going to be a lot cleaner).
  448. """
  449. return getattr(_prefixes, "value", '/')
  450. def set_urlconf(urlconf_name):
  451. """
  452. Sets the URLconf for the current thread (overriding the default one in
  453. settings). Set to None to revert back to the default.
  454. """
  455. if urlconf_name:
  456. _urlconfs.value = urlconf_name
  457. else:
  458. if hasattr(_urlconfs, "value"):
  459. del _urlconfs.value
  460. def get_urlconf(default=None):
  461. """
  462. Returns the root URLconf to use for the current thread if it has been
  463. changed from the default one.
  464. """
  465. return getattr(_urlconfs, "value", default)
  466. def is_valid_path(path, urlconf=None):
  467. """
  468. Returns True if the given path resolves against the default URL resolver,
  469. False otherwise.
  470. This is a convenience method to make working with "is this a match?" cases
  471. easier, avoiding unnecessarily indented try...except blocks.
  472. """
  473. try:
  474. resolve(path, urlconf)
  475. return True
  476. except Resolver404:
  477. return False