Django

Code

root/django/branches/newforms-admin/django/core/urlresolvers.py

Revision 7809, 12.0 kB (checked in by brosner, 5 months ago)

newforms-admin: Merged from trunk up to [7808]. Fixed #7519, #7573

  • Property svn:eol-style set to native
  • Property svn:keywords set to LastChangedRevision
Line 
1 """
2 This module converts requested URLs to callback view functions.
3
4 RegexURLResolver is the main class here. Its resolve() method takes a URL (as
5 a string) and returns a tuple in this format:
6
7     (view_function, function_args, function_kwargs)
8 """
9
10 from django.http import Http404
11 from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
12 from django.utils.encoding import iri_to_uri, force_unicode, smart_str
13 from django.utils.functional import memoize
14 import re
15
16 try:
17     reversed
18 except NameError:
19     from django.utils.itercompat import reversed     # Python 2.3 fallback
20
21 _resolver_cache = {} # Maps urlconf modules to RegexURLResolver instances.
22 _callable_cache = {} # Maps view and url pattern names to their view functions.
23
24 class Resolver404(Http404):
25     pass
26
27 class NoReverseMatch(Exception):
28     # Don't make this raise an error when used in a template.
29     silent_variable_failure = True
30
31 def get_callable(lookup_view, can_fail=False):
32     """
33     Convert a string version of a function name to the callable object.
34
35     If the lookup_view is not an import path, it is assumed to be a URL pattern
36     label and the original string is returned.
37
38     If can_fail is True, lookup_view might be a URL pattern label, so errors
39     during the import fail and the string is returned.
40     """
41     if not callable(lookup_view):
42         try:
43             # Bail early for non-ASCII strings (they can't be functions).
44             lookup_view = lookup_view.encode('ascii')
45             mod_name, func_name = get_mod_func(lookup_view)
46             if func_name != '':
47                 lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
48         except (ImportError, AttributeError):
49             if not can_fail:
50                 raise
51         except UnicodeEncodeError:
52             pass
53     return lookup_view
54 get_callable = memoize(get_callable, _callable_cache, 1)
55
56 def get_resolver(urlconf):
57     if urlconf is None:
58         from django.conf import settings
59         urlconf = settings.ROOT_URLCONF
60     return RegexURLResolver(r'^/', urlconf)
61 get_resolver = memoize(get_resolver, _resolver_cache, 1)
62
63 def get_mod_func(callback):
64     # Converts 'django.views.news.stories.story_detail' to
65     # ['django.views.news.stories', 'story_detail']
66     try:
67         dot = callback.rindex('.')
68     except ValueError:
69         return callback, ''
70     return callback[:dot], callback[dot+1:]
71
72 def reverse_helper(regex, *args, **kwargs):
73     """
74     Does a "reverse" lookup -- returns the URL for the given args/kwargs.
75     The args/kwargs are applied to the given compiled regular expression.
76     For example:
77
78         >>> reverse_helper(re.compile('^places/(\d+)/$'), 3)
79         'places/3/'
80         >>> reverse_helper(re.compile('^places/(?P<id>\d+)/$'), id=3)
81         'places/3/'
82         >>> reverse_helper(re.compile('^people/(?P<state>\w\w)/(\w+)/$'), 'adrian', state='il')
83         'people/il/adrian/'
84
85     Raises NoReverseMatch if the args/kwargs aren't valid for the regex.
86     """
87     # TODO: Handle nested parenthesis in the following regex.
88     result = re.sub(r'\(([^)]+)\)', MatchChecker(args, kwargs), regex.pattern)
89     return result.replace('^', '').replace('$', '').replace('\\', '')
90
91 class MatchChecker(object):
92     "Class used in reverse RegexURLPattern lookup."
93     def __init__(self, args, kwargs):
94         self.args, self.kwargs = args, kwargs
95         self.current_arg = 0
96
97     def __call__(self, match_obj):
98         # match_obj.group(1) is the contents of the parenthesis.
99         # First we need to figure out whether it's a named or unnamed group.
100         #
101         grouped = match_obj.group(1)
102         m = re.search(r'^\?P<(\w+)>(.*?)$', grouped, re.UNICODE)
103         if m: # If this was a named group...
104             # m.group(1) is the name of the group
105             # m.group(2) is the regex.
106             try:
107                 value = self.kwargs[m.group(1)]
108             except KeyError:
109                 # It was a named group, but the arg was passed in as a
110                 # positional arg or not at all.
111                 try:
112                     value = self.args[self.current_arg]
113                     self.current_arg += 1
114                 except IndexError:
115                     # The arg wasn't passed in.
116                     raise NoReverseMatch('Not enough positional arguments passed in')
117             test_regex = m.group(2)
118         else: # Otherwise, this was a positional (unnamed) group.
119             try:
120                 value = self.args[self.current_arg]
121                 self.current_arg += 1
122             except IndexError:
123                 # The arg wasn't passed in.
124                 raise NoReverseMatch('Not enough positional arguments passed in')
125             test_regex = grouped
126         # Note we're using re.match here on purpose because the start of
127         # to string needs to match.
128         if not re.match(test_regex + '$', force_unicode(value), re.UNICODE):
129             raise NoReverseMatch("Value %r didn't match regular expression %r" % (value, test_regex))
130         return force_unicode(value)
131
132 class RegexURLPattern(object):
133     def __init__(self, regex, callback, default_args=None, name=None):
134         # regex is a string representing a regular expression.
135         # callback is either a string like 'foo.views.news.stories.story_detail'
136         # which represents the path to a module and a view function name, or a
137         # callable object (view).
138         self.regex = re.compile(regex, re.UNICODE)
139         if callable(callback):
140             self._callback = callback
141         else:
142             self._callback = None
143             self._callback_str = callback
144         self.default_args = default_args or {}
145         self.name = name
146
147     def __repr__(self):
148         return '<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)
149
150     def add_prefix(self, prefix):
151         """
152         Adds the prefix string to a string-based callback.
153         """
154         if not prefix or not hasattr(self, '_callback_str'):
155             return
156         self._callback_str = prefix + '.' + self._callback_str
157
158     def resolve(self, path):
159         match = self.regex.search(path)
160         if match:
161             # If there are any named groups, use those as kwargs, ignoring
162             # non-named groups. Otherwise, pass all non-named arguments as
163             # positional arguments.
164             kwargs = match.groupdict()
165             if kwargs:
166                 args = ()
167             else:
168                 args = match.groups()
169             # In both cases, pass any extra_kwargs as **kwargs.
170             kwargs.update(self.default_args)
171
172             return self.callback, args, kwargs
173
174     def _get_callback(self):
175         if self._callback is not None:
176             return self._callback
177         try:
178             self._callback = get_callable(self._callback_str)
179         except ImportError, e:
180             mod_name, _ = get_mod_func(self._callback_str)
181             raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e))
182         except AttributeError, e:
183             mod_name, func_name = get_mod_func(self._callback_str)
184             raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e))
185         return self._callback
186     callback = property(_get_callback)
187
188     def reverse(self, viewname, *args, **kwargs):
189         mod_name, func_name = get_mod_func(viewname)
190         try:
191             lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
192         except (ImportError, AttributeError):
193             raise NoReverseMatch
194         if lookup_view != self.callback:
195             raise NoReverseMatch
196         return self.reverse_helper(*args, **kwargs)
197
198     def reverse_helper(self, *args, **kwargs):
199         return reverse_helper(self.regex, *args, **kwargs)
200
201 class RegexURLResolver(object):
202     def __init__(self, regex, urlconf_name, default_kwargs=None):
203         # regex is a string representing a regular expression.
204         # urlconf_name is a string representing the module containing urlconfs.
205         self.regex = re.compile(regex, re.UNICODE)
206         self.urlconf_name = urlconf_name
207         self.callback = None
208         self.default_kwargs = default_kwargs or {}
209         self._reverse_dict = {}
210
211     def __repr__(self):
212         return '<%s %s %s>' % (self.__class__.__name__, self.urlconf_name, self.regex.pattern)
213
214     def _get_reverse_dict(self):
215         if not self._reverse_dict and hasattr(self.urlconf_module, 'urlpatterns'):
216             for pattern in reversed(self.urlconf_module.urlpatterns):
217                 if isinstance(pattern, RegexURLResolver):
218                     for key, value in pattern.reverse_dict.iteritems():
219                         self._reverse_dict[key] = (pattern,) + value
220                 else:
221                     self._reverse_dict[pattern.callback] = (pattern,)
222                     self._reverse_dict[pattern.name] = (pattern,)
223         return self._reverse_dict
224     reverse_dict = property(_get_reverse_dict)
225
226     def resolve(self, path):
227         tried = []
228         match = self.regex.search(path)
229         if match:
230             new_path = path[match.end():]
231             for pattern in self.urlconf_module.urlpatterns:
232                 try:
233                     sub_match = pattern.resolve(new_path)
234                 except Resolver404, e:
235                     tried.extend([(pattern.regex.pattern + '   ' + t) for t in e.args[0]['tried']])
236                 else:
237                     if sub_match:
238                         sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()])
239                         sub_match_dict.update(self.default_kwargs)
240                         for k, v in sub_match[2].iteritems():
241                             sub_match_dict[smart_str(k)] = v
242                         return sub_match[0], sub_match[1], sub_match_dict
243                     tried.append(pattern.regex.pattern)
244             raise Resolver404, {'tried': tried, 'path': new_path}
245
246     def _get_urlconf_module(self):
247         try:
248             return self._urlconf_module
249         except AttributeError:
250             try:
251                 self._urlconf_module = __import__(self.urlconf_name, {}, {}, [''])
252             except Exception, e:
253                 # Either an invalid urlconf_name, such as "foo.bar.", or some
254                 # kind of problem during the actual import.
255                 raise ImproperlyConfigured, "Error while importing URLconf %r: %s" % (self.urlconf_name, e)
256             return self._urlconf_module
257     urlconf_module = property(_get_urlconf_module)
258
259     def _get_url_patterns(self):
260         return self.urlconf_module.urlpatterns
261     url_patterns = property(_get_url_patterns)
262
263     def _resolve_special(self, view_type):
264         callback = getattr(self.urlconf_module, 'handler%s' % view_type)
265         mod_name, func_name = get_mod_func(callback)
266         try:
267             return getattr(__import__(mod_name, {}, {}, ['']), func_name), {}
268         except (ImportError, AttributeError), e:
269             raise ViewDoesNotExist, "Tried %s. Error was: %s" % (callback, str(e))
270
271     def resolve404(self):
272         return self._resolve_special('404')
273
274     def resolve500(self):
275         return self._resolve_special('500')
276
277     def reverse(self, lookup_view, *args, **kwargs):
278         try:
279             lookup_view = get_callable(lookup_view, True)
280         except (ImportError, AttributeError):
281             raise NoReverseMatch
282         if lookup_view in self.reverse_dict:
283             return u''.join([reverse_helper(part.regex, *args, **kwargs) for part in self.reverse_dict[lookup_view]])
284         raise NoReverseMatch
285
286     def reverse_helper(self, lookup_view, *args, **kwargs):
287         sub_match = self.reverse(lookup_view, *args, **kwargs)
288         result = reverse_helper(self.regex, *args, **kwargs)
289         return result + sub_match
290
291 def resolve(path, urlconf=None):
292     return get_resolver(urlconf).resolve(path)
293
294 def reverse(viewname, urlconf=None, args=None, kwargs=None):
295     args = args or []
296     kwargs = kwargs or {}
297     return iri_to_uri(u'/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs))
298
299 def clear_url_caches():
300     global _resolver_cache
301     global _callable_cache
302     _resolver_cache.clear()
303     _callable_cache.clear()
Note: See TracBrowser for help on using the browser.