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

/python/lib/Lib/site-packages/django/core/urlresolvers.py

http://github.com/JetBrains/intellij-community
Python | 435 lines | 372 code | 20 blank | 43 comment | 22 complexity | e55137b916b2acf5e6d46b248d412d68 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MPL-2.0-no-copyleft-exception, MIT, EPL-1.0, AGPL-1.0
  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. import re
  8. from django.http import Http404
  9. from django.conf import settings
  10. from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
  11. from django.utils.datastructures import MultiValueDict
  12. from django.utils.encoding import iri_to_uri, force_unicode, smart_str
  13. from django.utils.functional import memoize
  14. from django.utils.importlib import import_module
  15. from django.utils.regex_helper import normalize
  16. from django.utils.thread_support import currentThread
  17. _resolver_cache = {} # Maps URLconf modules to RegexURLResolver instances.
  18. _callable_cache = {} # Maps view and url pattern names to their view functions.
  19. # SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
  20. # the current thread (which is the only one we ever access), it is assumed to
  21. # be empty.
  22. _prefixes = {}
  23. # Overridden URLconfs for each thread are stored here.
  24. _urlconfs = {}
  25. class ResolverMatch(object):
  26. def __init__(self, func, args, kwargs, url_name=None, app_name=None, namespaces=None):
  27. self.func = func
  28. self.args = args
  29. self.kwargs = kwargs
  30. self.app_name = app_name
  31. if namespaces:
  32. self.namespaces = [x for x in namespaces if x]
  33. else:
  34. self.namespaces = []
  35. if not url_name:
  36. if not hasattr(func, '__name__'):
  37. # An instance of a callable class
  38. url_name = '.'.join([func.__class__.__module__, func.__class__.__name__])
  39. else:
  40. # A function
  41. url_name = '.'.join([func.__module__, func.__name__])
  42. self.url_name = url_name
  43. def namespace(self):
  44. return ':'.join(self.namespaces)
  45. namespace = property(namespace)
  46. def view_name(self):
  47. return ':'.join([ x for x in [ self.namespace, self.url_name ] if x ])
  48. view_name = property(view_name)
  49. def __getitem__(self, index):
  50. return (self.func, self.args, self.kwargs)[index]
  51. def __repr__(self):
  52. return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name='%s', app_name='%s', namespace='%s')" % (
  53. self.func, self.args, self.kwargs, self.url_name, self.app_name, self.namespace)
  54. class Resolver404(Http404):
  55. pass
  56. class NoReverseMatch(Exception):
  57. # Don't make this raise an error when used in a template.
  58. silent_variable_failure = True
  59. def get_callable(lookup_view, can_fail=False):
  60. """
  61. Convert a string version of a function name to the callable object.
  62. If the lookup_view is not an import path, it is assumed to be a URL pattern
  63. label and the original string is returned.
  64. If can_fail is True, lookup_view might be a URL pattern label, so errors
  65. during the import fail and the string is returned.
  66. """
  67. if not callable(lookup_view):
  68. try:
  69. # Bail early for non-ASCII strings (they can't be functions).
  70. lookup_view = lookup_view.encode('ascii')
  71. mod_name, func_name = get_mod_func(lookup_view)
  72. if func_name != '':
  73. lookup_view = getattr(import_module(mod_name), func_name)
  74. if not callable(lookup_view):
  75. raise AttributeError("'%s.%s' is not a callable." % (mod_name, func_name))
  76. except (ImportError, AttributeError):
  77. if not can_fail:
  78. raise
  79. except UnicodeEncodeError:
  80. pass
  81. return lookup_view
  82. get_callable = memoize(get_callable, _callable_cache, 1)
  83. def get_resolver(urlconf):
  84. if urlconf is None:
  85. from django.conf import settings
  86. urlconf = settings.ROOT_URLCONF
  87. return RegexURLResolver(r'^/', urlconf)
  88. get_resolver = memoize(get_resolver, _resolver_cache, 1)
  89. def get_mod_func(callback):
  90. # Converts 'django.views.news.stories.story_detail' to
  91. # ['django.views.news.stories', 'story_detail']
  92. try:
  93. dot = callback.rindex('.')
  94. except ValueError:
  95. return callback, ''
  96. return callback[:dot], callback[dot+1:]
  97. class RegexURLPattern(object):
  98. def __init__(self, regex, callback, default_args=None, name=None):
  99. # regex is a string representing a regular expression.
  100. # callback is either a string like 'foo.views.news.stories.story_detail'
  101. # which represents the path to a module and a view function name, or a
  102. # callable object (view).
  103. self.regex = re.compile(regex, re.UNICODE)
  104. if callable(callback):
  105. self._callback = callback
  106. else:
  107. self._callback = None
  108. self._callback_str = callback
  109. self.default_args = default_args or {}
  110. self.name = name
  111. def __repr__(self):
  112. return '<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)
  113. def add_prefix(self, prefix):
  114. """
  115. Adds the prefix string to a string-based callback.
  116. """
  117. if not prefix or not hasattr(self, '_callback_str'):
  118. return
  119. self._callback_str = prefix + '.' + self._callback_str
  120. def resolve(self, path):
  121. match = self.regex.search(path)
  122. if match:
  123. # If there are any named groups, use those as kwargs, ignoring
  124. # non-named groups. Otherwise, pass all non-named arguments as
  125. # positional arguments.
  126. kwargs = match.groupdict()
  127. if kwargs:
  128. args = ()
  129. else:
  130. args = match.groups()
  131. # In both cases, pass any extra_kwargs as **kwargs.
  132. kwargs.update(self.default_args)
  133. return ResolverMatch(self.callback, args, kwargs, self.name)
  134. def _get_callback(self):
  135. if self._callback is not None:
  136. return self._callback
  137. try:
  138. self._callback = get_callable(self._callback_str)
  139. except ImportError, e:
  140. mod_name, _ = get_mod_func(self._callback_str)
  141. raise ViewDoesNotExist("Could not import %s. Error was: %s" % (mod_name, str(e)))
  142. except AttributeError, e:
  143. mod_name, func_name = get_mod_func(self._callback_str)
  144. raise ViewDoesNotExist("Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e)))
  145. return self._callback
  146. callback = property(_get_callback)
  147. class RegexURLResolver(object):
  148. def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
  149. # regex is a string representing a regular expression.
  150. # urlconf_name is a string representing the module containing URLconfs.
  151. self.regex = re.compile(regex, re.UNICODE)
  152. self.urlconf_name = urlconf_name
  153. if not isinstance(urlconf_name, basestring):
  154. self._urlconf_module = self.urlconf_name
  155. self.callback = None
  156. self.default_kwargs = default_kwargs or {}
  157. self.namespace = namespace
  158. self.app_name = app_name
  159. self._reverse_dict = None
  160. self._namespace_dict = None
  161. self._app_dict = None
  162. def __repr__(self):
  163. return '<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)
  164. def _populate(self):
  165. lookups = MultiValueDict()
  166. namespaces = {}
  167. apps = {}
  168. for pattern in reversed(self.url_patterns):
  169. p_pattern = pattern.regex.pattern
  170. if p_pattern.startswith('^'):
  171. p_pattern = p_pattern[1:]
  172. if isinstance(pattern, RegexURLResolver):
  173. if pattern.namespace:
  174. namespaces[pattern.namespace] = (p_pattern, pattern)
  175. if pattern.app_name:
  176. apps.setdefault(pattern.app_name, []).append(pattern.namespace)
  177. else:
  178. parent = normalize(pattern.regex.pattern)
  179. for name in pattern.reverse_dict:
  180. for matches, pat in pattern.reverse_dict.getlist(name):
  181. new_matches = []
  182. for piece, p_args in parent:
  183. new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches])
  184. lookups.appendlist(name, (new_matches, p_pattern + pat))
  185. for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
  186. namespaces[namespace] = (p_pattern + prefix, sub_pattern)
  187. for app_name, namespace_list in pattern.app_dict.items():
  188. apps.setdefault(app_name, []).extend(namespace_list)
  189. else:
  190. bits = normalize(p_pattern)
  191. lookups.appendlist(pattern.callback, (bits, p_pattern))
  192. if pattern.name is not None:
  193. lookups.appendlist(pattern.name, (bits, p_pattern))
  194. self._reverse_dict = lookups
  195. self._namespace_dict = namespaces
  196. self._app_dict = apps
  197. def _get_reverse_dict(self):
  198. if self._reverse_dict is None:
  199. self._populate()
  200. return self._reverse_dict
  201. reverse_dict = property(_get_reverse_dict)
  202. def _get_namespace_dict(self):
  203. if self._namespace_dict is None:
  204. self._populate()
  205. return self._namespace_dict
  206. namespace_dict = property(_get_namespace_dict)
  207. def _get_app_dict(self):
  208. if self._app_dict is None:
  209. self._populate()
  210. return self._app_dict
  211. app_dict = property(_get_app_dict)
  212. def resolve(self, path):
  213. tried = []
  214. match = self.regex.search(path)
  215. if match:
  216. new_path = path[match.end():]
  217. for pattern in self.url_patterns:
  218. try:
  219. sub_match = pattern.resolve(new_path)
  220. except Resolver404, e:
  221. sub_tried = e.args[0].get('tried')
  222. if sub_tried is not None:
  223. tried.extend([[pattern] + t for t in sub_tried])
  224. else:
  225. tried.append([pattern])
  226. else:
  227. if sub_match:
  228. sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()])
  229. sub_match_dict.update(self.default_kwargs)
  230. for k, v in sub_match.kwargs.iteritems():
  231. sub_match_dict[smart_str(k)] = v
  232. 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)
  233. tried.append([pattern])
  234. raise Resolver404({'tried': tried, 'path': new_path})
  235. raise Resolver404({'path' : path})
  236. def _get_urlconf_module(self):
  237. try:
  238. return self._urlconf_module
  239. except AttributeError:
  240. self._urlconf_module = import_module(self.urlconf_name)
  241. return self._urlconf_module
  242. urlconf_module = property(_get_urlconf_module)
  243. def _get_url_patterns(self):
  244. patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  245. try:
  246. iter(patterns)
  247. except TypeError:
  248. raise ImproperlyConfigured("The included urlconf %s doesn't have any patterns in it" % self.urlconf_name)
  249. return patterns
  250. url_patterns = property(_get_url_patterns)
  251. def _resolve_special(self, view_type):
  252. callback = getattr(self.urlconf_module, 'handler%s' % view_type, None)
  253. if not callback:
  254. # No handler specified in file; use default
  255. # Lazy import, since urls.defaults imports this file
  256. from django.conf.urls import defaults
  257. callback = getattr(defaults, 'handler%s' % view_type)
  258. try:
  259. return get_callable(callback), {}
  260. except (ImportError, AttributeError), e:
  261. raise ViewDoesNotExist("Tried %s. Error was: %s" % (callback, str(e)))
  262. def resolve404(self):
  263. return self._resolve_special('404')
  264. def resolve500(self):
  265. return self._resolve_special('500')
  266. def reverse(self, lookup_view, *args, **kwargs):
  267. if args and kwargs:
  268. raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
  269. try:
  270. lookup_view = get_callable(lookup_view, True)
  271. except (ImportError, AttributeError), e:
  272. raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
  273. possibilities = self.reverse_dict.getlist(lookup_view)
  274. for possibility, pattern in possibilities:
  275. for result, params in possibility:
  276. if args:
  277. if len(args) != len(params):
  278. continue
  279. unicode_args = [force_unicode(val) for val in args]
  280. candidate = result % dict(zip(params, unicode_args))
  281. else:
  282. if set(kwargs.keys()) != set(params):
  283. continue
  284. unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
  285. candidate = result % unicode_kwargs
  286. if re.search(u'^%s' % pattern, candidate, re.UNICODE):
  287. return candidate
  288. # lookup_view can be URL label, or dotted path, or callable, Any of
  289. # these can be passed in at the top, but callables are not friendly in
  290. # error messages.
  291. m = getattr(lookup_view, '__module__', None)
  292. n = getattr(lookup_view, '__name__', None)
  293. if m is not None and n is not None:
  294. lookup_view_s = "%s.%s" % (m, n)
  295. else:
  296. lookup_view_s = lookup_view
  297. raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
  298. "arguments '%s' not found." % (lookup_view_s, args, kwargs))
  299. def resolve(path, urlconf=None):
  300. if urlconf is None:
  301. urlconf = get_urlconf()
  302. return get_resolver(urlconf).resolve(path)
  303. def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
  304. if urlconf is None:
  305. urlconf = get_urlconf()
  306. resolver = get_resolver(urlconf)
  307. args = args or []
  308. kwargs = kwargs or {}
  309. if prefix is None:
  310. prefix = get_script_prefix()
  311. if not isinstance(viewname, basestring):
  312. view = viewname
  313. else:
  314. parts = viewname.split(':')
  315. parts.reverse()
  316. view = parts[0]
  317. path = parts[1:]
  318. resolved_path = []
  319. while path:
  320. ns = path.pop()
  321. # Lookup the name to see if it could be an app identifier
  322. try:
  323. app_list = resolver.app_dict[ns]
  324. # Yes! Path part matches an app in the current Resolver
  325. if current_app and current_app in app_list:
  326. # If we are reversing for a particular app, use that namespace
  327. ns = current_app
  328. elif ns not in app_list:
  329. # The name isn't shared by one of the instances (i.e., the default)
  330. # so just pick the first instance as the default.
  331. ns = app_list[0]
  332. except KeyError:
  333. pass
  334. try:
  335. extra, resolver = resolver.namespace_dict[ns]
  336. resolved_path.append(ns)
  337. prefix = prefix + extra
  338. except KeyError, key:
  339. if resolved_path:
  340. raise NoReverseMatch("%s is not a registered namespace inside '%s'" % (key, ':'.join(resolved_path)))
  341. else:
  342. raise NoReverseMatch("%s is not a registered namespace" % key)
  343. return iri_to_uri(u'%s%s' % (prefix, resolver.reverse(view,
  344. *args, **kwargs)))
  345. def clear_url_caches():
  346. global _resolver_cache
  347. global _callable_cache
  348. _resolver_cache.clear()
  349. _callable_cache.clear()
  350. def set_script_prefix(prefix):
  351. """
  352. Sets the script prefix for the current thread.
  353. """
  354. if not prefix.endswith('/'):
  355. prefix += '/'
  356. _prefixes[currentThread()] = prefix
  357. def get_script_prefix():
  358. """
  359. Returns the currently active script prefix. Useful for client code that
  360. wishes to construct their own URLs manually (although accessing the request
  361. instance is normally going to be a lot cleaner).
  362. """
  363. return _prefixes.get(currentThread(), u'/')
  364. def set_urlconf(urlconf_name):
  365. """
  366. Sets the URLconf for the current thread (overriding the default one in
  367. settings). Set to None to revert back to the default.
  368. """
  369. thread = currentThread()
  370. if urlconf_name:
  371. _urlconfs[thread] = urlconf_name
  372. else:
  373. # faster than wrapping in a try/except
  374. if thread in _urlconfs:
  375. del _urlconfs[thread]
  376. def get_urlconf(default=None):
  377. """
  378. Returns the root URLconf to use for the current thread if it has been
  379. changed from the default one.
  380. """
  381. thread = currentThread()
  382. if thread in _urlconfs:
  383. return _urlconfs[thread]
  384. return default