PageRenderTime 44ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/django/branches/attic/multi-auth/django/core/urlresolvers.py

https://bitbucket.org/mirror/django/
Python | 203 lines | 187 code | 5 blank | 11 comment | 0 complexity | 90466b3e05b786f68f091d42c526c42d 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 django.http import Http404
  8. from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
  9. import re
  10. class Resolver404(Http404):
  11. pass
  12. class NoReverseMatch(Exception):
  13. pass
  14. def get_mod_func(callback):
  15. # Converts 'django.views.news.stories.story_detail' to
  16. # ['django.views.news.stories', 'story_detail']
  17. dot = callback.rindex('.')
  18. return callback[:dot], callback[dot+1:]
  19. def reverse_helper(regex, *args, **kwargs):
  20. """
  21. Does a "reverse" lookup -- returns the URL for the given args/kwargs.
  22. The args/kwargs are applied to the given compiled regular expression.
  23. For example:
  24. >>> reverse_helper(re.compile('^places/(\d+)/$'), 3)
  25. 'places/3/'
  26. >>> reverse_helper(re.compile('^places/(?P<id>\d+)/$'), id=3)
  27. 'places/3/'
  28. >>> reverse_helper(re.compile('^people/(?P<state>\w\w)/(\w+)/$'), 'adrian', state='il')
  29. 'people/il/adrian/'
  30. Raises NoReverseMatch if the args/kwargs aren't valid for the regex.
  31. """
  32. # TODO: Handle nested parenthesis in the following regex.
  33. result = re.sub(r'\(([^)]+)\)', MatchChecker(args, kwargs), regex.pattern)
  34. return result.replace('^', '').replace('$', '')
  35. class MatchChecker(object):
  36. "Class used in reverse RegexURLPattern lookup."
  37. def __init__(self, args, kwargs):
  38. self.args, self.kwargs = args, kwargs
  39. self.current_arg = 0
  40. def __call__(self, match_obj):
  41. # match_obj.group(1) is the contents of the parenthesis.
  42. # First we need to figure out whether it's a named or unnamed group.
  43. #
  44. grouped = match_obj.group(1)
  45. m = re.search(r'^\?P<(\w+)>(.*?)$', grouped)
  46. if m: # If this was a named group...
  47. # m.group(1) is the name of the group
  48. # m.group(2) is the regex.
  49. try:
  50. value = self.kwargs[m.group(1)]
  51. except KeyError:
  52. # It was a named group, but the arg was passed in as a
  53. # positional arg or not at all.
  54. try:
  55. value = self.args[self.current_arg]
  56. self.current_arg += 1
  57. except IndexError:
  58. # The arg wasn't passed in.
  59. raise NoReverseMatch('Not enough positional arguments passed in')
  60. test_regex = m.group(2)
  61. else: # Otherwise, this was a positional (unnamed) group.
  62. try:
  63. value = self.args[self.current_arg]
  64. self.current_arg += 1
  65. except IndexError:
  66. # The arg wasn't passed in.
  67. raise NoReverseMatch('Not enough positional arguments passed in')
  68. test_regex = grouped
  69. # Note we're using re.match here on purpose because the start of
  70. # to string needs to match.
  71. if not re.match(test_regex + '$', str(value)): # TODO: Unicode?
  72. raise NoReverseMatch("Value %r didn't match regular expression %r" % (value, test_regex))
  73. return str(value) # TODO: Unicode?
  74. class RegexURLPattern(object):
  75. def __init__(self, regex, callback, default_args=None):
  76. # regex is a string representing a regular expression.
  77. # callback is something like 'foo.views.news.stories.story_detail',
  78. # which represents the path to a module and a view function name.
  79. self.regex = re.compile(regex)
  80. self.callback = callback
  81. self.default_args = default_args or {}
  82. def resolve(self, path):
  83. match = self.regex.search(path)
  84. if match:
  85. # If there are any named groups, use those as kwargs, ignoring
  86. # non-named groups. Otherwise, pass all non-named arguments as
  87. # positional arguments.
  88. kwargs = match.groupdict()
  89. if kwargs:
  90. args = ()
  91. if not kwargs:
  92. args = match.groups()
  93. # In both cases, pass any extra_kwargs as **kwargs.
  94. kwargs.update(self.default_args)
  95. try: # Lazily load self.func.
  96. return self.func, args, kwargs
  97. except AttributeError:
  98. self.func = self.get_callback()
  99. return self.func, args, kwargs
  100. def get_callback(self):
  101. mod_name, func_name = get_mod_func(self.callback)
  102. try:
  103. return getattr(__import__(mod_name, '', '', ['']), func_name)
  104. except ImportError, e:
  105. raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e))
  106. except AttributeError, e:
  107. raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e))
  108. def reverse(self, viewname, *args, **kwargs):
  109. if viewname != self.callback:
  110. raise NoReverseMatch
  111. return self.reverse_helper(*args, **kwargs)
  112. def reverse_helper(self, *args, **kwargs):
  113. return reverse_helper(self.regex, *args, **kwargs)
  114. class RegexURLResolver(object):
  115. def __init__(self, regex, urlconf_name):
  116. # regex is a string representing a regular expression.
  117. # urlconf_name is a string representing the module containing urlconfs.
  118. self.regex = re.compile(regex)
  119. self.urlconf_name = urlconf_name
  120. self.callback = None
  121. def resolve(self, path):
  122. tried = []
  123. match = self.regex.search(path)
  124. if match:
  125. new_path = path[match.end():]
  126. for pattern in self.urlconf_module.urlpatterns:
  127. try:
  128. sub_match = pattern.resolve(new_path)
  129. except Resolver404, e:
  130. tried.extend([(pattern.regex.pattern + ' ' + t) for t in e.args[0]['tried']])
  131. else:
  132. if sub_match:
  133. return sub_match[0], sub_match[1], dict(match.groupdict(), **sub_match[2])
  134. tried.append(pattern.regex.pattern)
  135. raise Resolver404, {'tried': tried, 'path': new_path}
  136. def _get_urlconf_module(self):
  137. try:
  138. return self._urlconf_module
  139. except AttributeError:
  140. try:
  141. self._urlconf_module = __import__(self.urlconf_name, '', '', [''])
  142. except ValueError, e:
  143. # Invalid urlconf_name, such as "foo.bar." (note trailing period)
  144. raise ImproperlyConfigured, "Error while importing URLconf %r: %s" % (self.urlconf_name, e)
  145. return self._urlconf_module
  146. urlconf_module = property(_get_urlconf_module)
  147. def _get_url_patterns(self):
  148. return self.urlconf_module.urlpatterns
  149. url_patterns = property(_get_url_patterns)
  150. def _resolve_special(self, view_type):
  151. callback = getattr(self.urlconf_module, 'handler%s' % view_type)
  152. mod_name, func_name = get_mod_func(callback)
  153. try:
  154. return getattr(__import__(mod_name, '', '', ['']), func_name), {}
  155. except (ImportError, AttributeError), e:
  156. raise ViewDoesNotExist, "Tried %s. Error was: %s" % (callback, str(e))
  157. def resolve404(self):
  158. return self._resolve_special('404')
  159. def resolve500(self):
  160. return self._resolve_special('500')
  161. def reverse(self, viewname, *args, **kwargs):
  162. for pattern in self.urlconf_module.urlpatterns:
  163. if isinstance(pattern, RegexURLResolver):
  164. try:
  165. return pattern.reverse_helper(viewname, *args, **kwargs)
  166. except NoReverseMatch:
  167. continue
  168. elif pattern.callback == viewname:
  169. try:
  170. return pattern.reverse_helper(*args, **kwargs)
  171. except NoReverseMatch:
  172. continue
  173. raise NoReverseMatch
  174. def reverse_helper(self, viewname, *args, **kwargs):
  175. sub_match = self.reverse(viewname, *args, **kwargs)
  176. result = reverse_helper(self.regex, *args, **kwargs)
  177. return result + sub_match