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)
12 from django
.http
import Http404
13 from django
.conf
import settings
14 from django
.core
.exceptions
import ImproperlyConfigured
, ViewDoesNotExist
15 from django
.utils
.datastructures
import MultiValueDict
16 from django
.utils
.encoding
import iri_to_uri
, force_unicode
, smart_str
17 from django
.utils
.functional
import memoize
18 from django
.utils
.importlib
import import_module
19 from django
.utils
.regex_helper
import normalize
20 from django
.utils
.thread_support
import currentThread
22 _resolver_cache
= {} # Maps URLconf modules to RegexURLResolver instances.
23 _callable_cache
= {} # Maps view and url pattern names to their view functions.
25 # SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
26 # the current thread (which is the only one we ever access), it is assumed to
30 # Overridden URLconfs for each thread are stored here.
33 class Resolver404(Http404
):
36 class NoReverseMatch(Exception):
37 # Don't make this raise an error when used in a template.
38 silent_variable_failure
= True
40 def get_callable(lookup_view
, can_fail
=False):
42 Convert a string version of a function name to the callable object.
44 If the lookup_view is not an import path, it is assumed to be a URL pattern
45 label and the original string is returned.
47 If can_fail is True, lookup_view might be a URL pattern label, so errors
48 during the import fail and the string is returned.
50 if not callable(lookup_view
):
52 # Bail early for non-ASCII strings (they can't be functions).
53 lookup_view
= lookup_view
.encode('ascii')
54 mod_name
, func_name
= get_mod_func(lookup_view
)
56 lookup_view
= getattr(import_module(mod_name
), func_name
)
57 if not callable(lookup_view
):
58 raise AttributeError("'%s.%s' is not a callable." % (mod_name
, func_name
))
59 except (ImportError, AttributeError):
62 except UnicodeEncodeError:
65 get_callable
= memoize(get_callable
, _callable_cache
, 1)
67 def get_resolver(urlconf
):
69 from django
.conf
import settings
70 urlconf
= settings
.ROOT_URLCONF
71 return RegexURLResolver(r
'^/', urlconf
)
72 get_resolver
= memoize(get_resolver
, _resolver_cache
, 1)
74 def get_mod_func(callback
):
75 # Converts 'django.views.news.stories.story_detail' to
76 # ['django.views.news.stories', 'story_detail']
78 dot
= callback
.rindex('.')
81 return callback
[:dot
], callback
[dot
+1:]
83 class RegexURLPattern(object):
84 def __init__(self
, regex
, callback
, default_args
=None, name
=None):
85 # regex is a string representing a regular expression.
86 # callback is either a string like 'foo.views.news.stories.story_detail'
87 # which represents the path to a module and a view function name, or a
88 # callable object (view).
89 self
.regex
= re
.compile(regex
, re
.UNICODE
)
90 if callable(callback
):
91 self
._callback
= callback
94 self
._callback
_str
= callback
95 self
.default_args
= default_args
or {}
99 return '<%s %s %s>' % (self
.__class
__.__name
__, self
.name
, self
.regex
.pattern
)
101 def add_prefix(self
, prefix
):
103 Adds the prefix string to a string-based callback.
105 if not prefix
or not hasattr(self
, '_callback_str'):
107 self
._callback
_str
= prefix
+ '.' + self
._callback
_str
109 def resolve(self
, path
):
110 match
= self
.regex
.search(path
)
112 # If there are any named groups, use those as kwargs, ignoring
113 # non-named groups. Otherwise, pass all non-named arguments as
114 # positional arguments.
115 kwargs
= match
.groupdict()
119 args
= match
.groups()
120 # In both cases, pass any extra_kwargs as **kwargs.
121 kwargs
.update(self
.default_args
)
123 return self
.callback
, args
, kwargs
125 def _get_callback(self
):
126 if self
._callback
is not None:
127 return self
._callback
129 self
._callback
= get_callable(self
._callback
_str
)
130 except ImportError, e
:
131 mod_name
, _
= get_mod_func(self
._callback
_str
)
132 raise ViewDoesNotExist("Could not import %s. Error was: %s" % (mod_name
, str(e
)))
133 except AttributeError, e
:
134 mod_name
, func_name
= get_mod_func(self
._callback
_str
)
135 raise ViewDoesNotExist("Tried %s in module %s. Error was: %s" % (func_name
, mod_name
, str(e
)))
136 return self
._callback
137 callback
= property(_get_callback
)
139 class RegexURLResolver(object):
140 def __init__(self
, regex
, urlconf_name
, default_kwargs
=None, app_name
=None, namespace
=None):
141 # regex is a string representing a regular expression.
142 # urlconf_name is a string representing the module containing URLconfs.
143 self
.regex
= re
.compile(regex
, re
.UNICODE
)
144 self
.urlconf_name
= urlconf_name
145 if not isinstance(urlconf_name
, basestring
):
146 self
._urlconf
_module
= self
.urlconf_name
148 self
.default_kwargs
= default_kwargs
or {}
149 self
.namespace
= namespace
150 self
.app_name
= app_name
151 self
._reverse
_dict
= None
152 self
._namespace
_dict
= None
153 self
._app
_dict
= None
156 return '<%s %s (%s:%s) %s>' % (self
.__class
__.__name
__, self
.urlconf_name
, self
.app_name
, self
.namespace
, self
.regex
.pattern
)
159 lookups
= MultiValueDict()
162 for pattern
in reversed(self
.url_patterns
):
163 p_pattern
= pattern
.regex
.pattern
164 if p_pattern
.startswith('^'):
165 p_pattern
= p_pattern
[1:]
166 if isinstance(pattern
, RegexURLResolver
):
167 if pattern
.namespace
:
168 namespaces
[pattern
.namespace
] = (p_pattern
, pattern
)
170 apps
.setdefault(pattern
.app_name
, []).append(pattern
.namespace
)
172 parent
= normalize(pattern
.regex
.pattern
)
173 for name
in pattern
.reverse_dict
:
174 for matches
, pat
in pattern
.reverse_dict
.getlist(name
):
176 for piece
, p_args
in parent
:
177 new_matches
.extend([(piece
+ suffix
, p_args
+ args
) for (suffix
, args
) in matches
])
178 lookups
.appendlist(name
, (new_matches
, p_pattern
+ pat
))
179 for namespace
, (prefix
, sub_pattern
) in pattern
.namespace_dict
.items():
180 namespaces
[namespace
] = (p_pattern
+ prefix
, sub_pattern
)
181 for app_name
, namespace_list
in pattern
.app_dict
.items():
182 apps
.setdefault(app_name
, []).extend(namespace_list
)
184 bits
= normalize(p_pattern
)
185 lookups
.appendlist(pattern
.callback
, (bits
, p_pattern
))
186 lookups
.appendlist(pattern
.name
, (bits
, p_pattern
))
187 self
._reverse
_dict
= lookups
188 self
._namespace
_dict
= namespaces
189 self
._app
_dict
= apps
191 def _get_reverse_dict(self
):
192 if self
._reverse
_dict
is None:
194 return self
._reverse
_dict
195 reverse_dict
= property(_get_reverse_dict
)
197 def _get_namespace_dict(self
):
198 if self
._namespace
_dict
is None:
200 return self
._namespace
_dict
201 namespace_dict
= property(_get_namespace_dict
)
203 def _get_app_dict(self
):
204 if self
._app
_dict
is None:
206 return self
._app
_dict
207 app_dict
= property(_get_app_dict
)
209 def resolve(self
, path
):
211 match
= self
.regex
.search(path
)
213 new_path
= path
[match
.end():]
214 for pattern
in self
.url_patterns
:
216 sub_match
= pattern
.resolve(new_path
)
217 except Resolver404
, e
:
218 sub_tried
= e
.args
[0].get('tried')
219 if sub_tried
is not None:
220 tried
.extend([(pattern
.regex
.pattern
+ ' ' + t
) for t
in sub_tried
])
222 tried
.append(pattern
.regex
.pattern
)
225 sub_match_dict
= dict([(smart_str(k
), v
) for k
, v
in match
.groupdict().items()])
226 sub_match_dict
.update(self
.default_kwargs
)
227 for k
, v
in sub_match
[2].iteritems():
228 sub_match_dict
[smart_str(k
)] = v
229 return sub_match
[0], sub_match
[1], sub_match_dict
230 tried
.append(pattern
.regex
.pattern
)
231 raise Resolver404({'tried': tried
, 'path': new_path
})
232 raise Resolver404({'path' : path
})
234 def _get_urlconf_module(self
):
236 return self
._urlconf
_module
237 except AttributeError:
238 self
._urlconf
_module
= import_module(self
.urlconf_name
)
239 return self
._urlconf
_module
240 urlconf_module
= property(_get_urlconf_module
)
242 def _get_url_patterns(self
):
243 patterns
= getattr(self
.urlconf_module
, "urlpatterns", self
.urlconf_module
)
247 raise ImproperlyConfigured("The included urlconf %s doesn't have any patterns in it" % self
.urlconf_name
)
249 url_patterns
= property(_get_url_patterns
)
251 def _resolve_special(self
, view_type
):
252 callback
= getattr(self
.urlconf_module
, 'handler%s' % view_type
)
254 return get_callable(callback
), {}
255 except (ImportError, AttributeError), e
:
256 raise ViewDoesNotExist("Tried %s. Error was: %s" % (callback
, str(e
)))
258 def resolve404(self
):
259 return self
._resolve
_special
('404')
261 def resolve500(self
):
262 return self
._resolve
_special
('500')
264 def reverse(self
, lookup_view
, *args
, **kwargs
):
266 raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
268 lookup_view
= get_callable(lookup_view
, True)
269 except (ImportError, AttributeError), e
:
270 raise NoReverseMatch("Error importing '%s': %s." % (lookup_view
, e
))
271 possibilities
= self
.reverse_dict
.getlist(lookup_view
)
272 for possibility
, pattern
in possibilities
:
273 for result
, params
in possibility
:
275 if len(args
) != len(params
):
277 unicode_args
= [force_unicode(val
) for val
in args
]
278 candidate
= result
% dict(zip(params
, unicode_args
))
280 if set(kwargs
.keys()) != set(params
):
282 unicode_kwargs
= dict([(k
, force_unicode(v
)) for (k
, v
) in kwargs
.items()])
283 candidate
= result
% unicode_kwargs
284 if re
.search(u
'^%s' % pattern
, candidate
, re
.UNICODE
):
286 # lookup_view can be URL label, or dotted path, or callable, Any of
287 # these can be passed in at the top, but callables are not friendly in
289 m
= getattr(lookup_view
, '__module__', None)
290 n
= getattr(lookup_view
, '__name__', None)
291 if m
is not None and n
is not None:
292 lookup_view_s
= "%s.%s" % (m
, n
)
294 lookup_view_s
= lookup_view
295 raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
296 "arguments '%s' not found." % (lookup_view_s
, args
, kwargs
))
298 def resolve(path
, urlconf
=None):
300 urlconf
= get_urlconf()
301 return get_resolver(urlconf
).resolve(path
)
303 def reverse(viewname
, urlconf
=None, args
=None, kwargs
=None, prefix
=None, current_app
=None):
305 urlconf
= get_urlconf()
306 resolver
= get_resolver(urlconf
)
308 kwargs
= kwargs
or {}
311 prefix
= get_script_prefix()
313 if not isinstance(viewname
, basestring
):
316 parts
= viewname
.split(':')
325 # Lookup the name to see if it could be an app identifier
327 app_list
= resolver
.app_dict
[ns
]
328 # Yes! Path part matches an app in the current Resolver
329 if current_app
and current_app
in app_list
:
330 # If we are reversing for a particular app, use that namespace
332 elif ns
not in app_list
:
333 # The name isn't shared by one of the instances (i.e., the default)
334 # so just pick the first instance as the default.
340 extra
, resolver
= resolver
.namespace_dict
[ns
]
341 resolved_path
.append(ns
)
342 prefix
= prefix
+ extra
343 except KeyError, key
:
345 raise NoReverseMatch("%s is not a registered namespace inside '%s'" % (key
, ':'.join(resolved_path
)))
347 raise NoReverseMatch("%s is not a registered namespace" % key
)
349 return iri_to_uri(u
'%s%s' % (prefix
, resolver
.reverse(view
,
352 def clear_url_caches():
353 global _resolver_cache
354 global _callable_cache
355 _resolver_cache
.clear()
356 _callable_cache
.clear()
358 def set_script_prefix(prefix
):
360 Sets the script prefix for the current thread.
362 if not prefix
.endswith('/'):
364 _prefixes
[currentThread()] = prefix
366 def get_script_prefix():
368 Returns the currently active script prefix. Useful for client code that
369 wishes to construct their own URLs manually (although accessing the request
370 instance is normally going to be a lot cleaner).
372 return _prefixes
.get(currentThread(), u
'/')
374 def set_urlconf(urlconf_name
):
376 Sets the URLconf for the current thread (overriding the default one in
377 settings). Set to None to revert back to the default.
379 thread
= currentThread()
381 _urlconfs
[thread
] = urlconf_name
383 # faster than wrapping in a try/except
384 if thread
in _urlconfs
:
385 del _urlconfs
[thread
]
387 def get_urlconf(default
=None):
389 Returns the root URLconf to use for the current thread if it has been
390 changed from the default one.
392 thread
= currentThread()
393 if thread
in _urlconfs
:
394 return _urlconfs
[thread
]