Add Django-1.2.1
[frozenviper.git] / Django-1.2.1 / build / lib.linux-i686-2.6 / django / core / urlresolvers.py
blobcad57a5d9ff17db53d773ee38351f4479875ab1c
1 """
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)
8 """
10 import re
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
27 # be empty.
28 _prefixes = {}
30 # Overridden URLconfs for each thread are stored here.
31 _urlconfs = {}
33 class Resolver404(Http404):
34 pass
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):
41 """
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.
49 """
50 if not callable(lookup_view):
51 try:
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)
55 if func_name != '':
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):
60 if not can_fail:
61 raise
62 except UnicodeEncodeError:
63 pass
64 return lookup_view
65 get_callable = memoize(get_callable, _callable_cache, 1)
67 def get_resolver(urlconf):
68 if urlconf is None:
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']
77 try:
78 dot = callback.rindex('.')
79 except ValueError:
80 return callback, ''
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
92 else:
93 self._callback = None
94 self._callback_str = callback
95 self.default_args = default_args or {}
96 self.name = name
98 def __repr__(self):
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'):
106 return
107 self._callback_str = prefix + '.' + self._callback_str
109 def resolve(self, path):
110 match = self.regex.search(path)
111 if match:
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()
116 if kwargs:
117 args = ()
118 else:
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
128 try:
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
147 self.callback = None
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
155 def __repr__(self):
156 return '<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)
158 def _populate(self):
159 lookups = MultiValueDict()
160 namespaces = {}
161 apps = {}
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)
169 if pattern.app_name:
170 apps.setdefault(pattern.app_name, []).append(pattern.namespace)
171 else:
172 parent = normalize(pattern.regex.pattern)
173 for name in pattern.reverse_dict:
174 for matches, pat in pattern.reverse_dict.getlist(name):
175 new_matches = []
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)
183 else:
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:
193 self._populate()
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:
199 self._populate()
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:
205 self._populate()
206 return self._app_dict
207 app_dict = property(_get_app_dict)
209 def resolve(self, path):
210 tried = []
211 match = self.regex.search(path)
212 if match:
213 new_path = path[match.end():]
214 for pattern in self.url_patterns:
215 try:
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])
221 else:
222 tried.append(pattern.regex.pattern)
223 else:
224 if sub_match:
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):
235 try:
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)
244 try:
245 iter(patterns)
246 except TypeError:
247 raise ImproperlyConfigured("The included urlconf %s doesn't have any patterns in it" % self.urlconf_name)
248 return patterns
249 url_patterns = property(_get_url_patterns)
251 def _resolve_special(self, view_type):
252 callback = getattr(self.urlconf_module, 'handler%s' % view_type)
253 try:
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):
265 if args and kwargs:
266 raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
267 try:
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:
274 if args:
275 if len(args) != len(params):
276 continue
277 unicode_args = [force_unicode(val) for val in args]
278 candidate = result % dict(zip(params, unicode_args))
279 else:
280 if set(kwargs.keys()) != set(params):
281 continue
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):
285 return candidate
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
288 # error messages.
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)
293 else:
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):
299 if urlconf is 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):
304 if urlconf is None:
305 urlconf = get_urlconf()
306 resolver = get_resolver(urlconf)
307 args = args or []
308 kwargs = kwargs or {}
310 if prefix is None:
311 prefix = get_script_prefix()
313 if not isinstance(viewname, basestring):
314 view = viewname
315 else:
316 parts = viewname.split(':')
317 parts.reverse()
318 view = parts[0]
319 path = parts[1:]
321 resolved_path = []
322 while path:
323 ns = path.pop()
325 # Lookup the name to see if it could be an app identifier
326 try:
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
331 ns = current_app
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.
335 ns = app_list[0]
336 except KeyError:
337 pass
339 try:
340 extra, resolver = resolver.namespace_dict[ns]
341 resolved_path.append(ns)
342 prefix = prefix + extra
343 except KeyError, key:
344 if resolved_path:
345 raise NoReverseMatch("%s is not a registered namespace inside '%s'" % (key, ':'.join(resolved_path)))
346 else:
347 raise NoReverseMatch("%s is not a registered namespace" % key)
349 return iri_to_uri(u'%s%s' % (prefix, resolver.reverse(view,
350 *args, **kwargs)))
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('/'):
363 prefix += '/'
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()
380 if urlconf_name:
381 _urlconfs[thread] = urlconf_name
382 else:
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]
395 return default