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 google
.appengine
._internal
.django
.http
import Http404
13 from google
.appengine
._internal
.django
.conf
import settings
14 from google
.appengine
._internal
.django
.core
.exceptions
import ImproperlyConfigured
, ViewDoesNotExist
15 from google
.appengine
._internal
.django
.utils
.datastructures
import MultiValueDict
16 from google
.appengine
._internal
.django
.utils
.encoding
import iri_to_uri
, force_unicode
, smart_str
17 from google
.appengine
._internal
.django
.utils
.functional
import memoize
18 from google
.appengine
._internal
.django
.utils
.importlib
import import_module
19 from google
.appengine
._internal
.django
.utils
.regex_helper
import normalize
20 from google
.appengine
._internal
.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 google
.appengine
._internal
.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 if pattern
.name
is not None:
187 lookups
.appendlist(pattern
.name
, (bits
, p_pattern
))
188 self
._reverse
_dict
= lookups
189 self
._namespace
_dict
= namespaces
190 self
._app
_dict
= apps
192 def _get_reverse_dict(self
):
193 if self
._reverse
_dict
is None:
195 return self
._reverse
_dict
196 reverse_dict
= property(_get_reverse_dict
)
198 def _get_namespace_dict(self
):
199 if self
._namespace
_dict
is None:
201 return self
._namespace
_dict
202 namespace_dict
= property(_get_namespace_dict
)
204 def _get_app_dict(self
):
205 if self
._app
_dict
is None:
207 return self
._app
_dict
208 app_dict
= property(_get_app_dict
)
210 def resolve(self
, path
):
212 match
= self
.regex
.search(path
)
214 new_path
= path
[match
.end():]
215 for pattern
in self
.url_patterns
:
217 sub_match
= pattern
.resolve(new_path
)
218 except Resolver404
, e
:
219 sub_tried
= e
.args
[0].get('tried')
220 if sub_tried
is not None:
221 tried
.extend([(pattern
.regex
.pattern
+ ' ' + t
) for t
in sub_tried
])
223 tried
.append(pattern
.regex
.pattern
)
226 sub_match_dict
= dict([(smart_str(k
), v
) for k
, v
in match
.groupdict().items()])
227 sub_match_dict
.update(self
.default_kwargs
)
228 for k
, v
in sub_match
[2].iteritems():
229 sub_match_dict
[smart_str(k
)] = v
230 return sub_match
[0], sub_match
[1], sub_match_dict
231 tried
.append(pattern
.regex
.pattern
)
232 raise Resolver404({'tried': tried
, 'path': new_path
})
233 raise Resolver404({'path' : path
})
235 def _get_urlconf_module(self
):
237 return self
._urlconf
_module
238 except AttributeError:
239 self
._urlconf
_module
= import_module(self
.urlconf_name
)
240 return self
._urlconf
_module
241 urlconf_module
= property(_get_urlconf_module
)
243 def _get_url_patterns(self
):
244 patterns
= getattr(self
.urlconf_module
, "urlpatterns", self
.urlconf_module
)
248 raise ImproperlyConfigured("The included urlconf %s doesn't have any patterns in it" % self
.urlconf_name
)
250 url_patterns
= property(_get_url_patterns
)
252 def _resolve_special(self
, view_type
):
253 callback
= getattr(self
.urlconf_module
, 'handler%s' % view_type
)
255 return get_callable(callback
), {}
256 except (ImportError, AttributeError), e
:
257 raise ViewDoesNotExist("Tried %s. Error was: %s" % (callback
, str(e
)))
259 def resolve404(self
):
260 return self
._resolve
_special
('404')
262 def resolve500(self
):
263 return self
._resolve
_special
('500')
265 def reverse(self
, lookup_view
, *args
, **kwargs
):
267 raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
269 lookup_view
= get_callable(lookup_view
, True)
270 except (ImportError, AttributeError), e
:
271 raise NoReverseMatch("Error importing '%s': %s." % (lookup_view
, e
))
272 possibilities
= self
.reverse_dict
.getlist(lookup_view
)
273 for possibility
, pattern
in possibilities
:
274 for result
, params
in possibility
:
276 if len(args
) != len(params
):
278 unicode_args
= [force_unicode(val
) for val
in args
]
279 candidate
= result
% dict(zip(params
, unicode_args
))
281 if set(kwargs
.keys()) != set(params
):
283 unicode_kwargs
= dict([(k
, force_unicode(v
)) for (k
, v
) in kwargs
.items()])
284 candidate
= result
% unicode_kwargs
285 if re
.search(u
'^%s' % pattern
, candidate
, re
.UNICODE
):
287 # lookup_view can be URL label, or dotted path, or callable, Any of
288 # these can be passed in at the top, but callables are not friendly in
290 m
= getattr(lookup_view
, '__module__', None)
291 n
= getattr(lookup_view
, '__name__', None)
292 if m
is not None and n
is not None:
293 lookup_view_s
= "%s.%s" % (m
, n
)
295 lookup_view_s
= lookup_view
296 raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
297 "arguments '%s' not found." % (lookup_view_s
, args
, kwargs
))
299 def resolve(path
, urlconf
=None):
301 urlconf
= get_urlconf()
302 return get_resolver(urlconf
).resolve(path
)
304 def reverse(viewname
, urlconf
=None, args
=None, kwargs
=None, prefix
=None, current_app
=None):
306 urlconf
= get_urlconf()
307 resolver
= get_resolver(urlconf
)
309 kwargs
= kwargs
or {}
312 prefix
= get_script_prefix()
314 if not isinstance(viewname
, basestring
):
317 parts
= viewname
.split(':')
326 # Lookup the name to see if it could be an app identifier
328 app_list
= resolver
.app_dict
[ns
]
329 # Yes! Path part matches an app in the current Resolver
330 if current_app
and current_app
in app_list
:
331 # If we are reversing for a particular app, use that namespace
333 elif ns
not in app_list
:
334 # The name isn't shared by one of the instances (i.e., the default)
335 # so just pick the first instance as the default.
341 extra
, resolver
= resolver
.namespace_dict
[ns
]
342 resolved_path
.append(ns
)
343 prefix
= prefix
+ extra
344 except KeyError, key
:
346 raise NoReverseMatch("%s is not a registered namespace inside '%s'" % (key
, ':'.join(resolved_path
)))
348 raise NoReverseMatch("%s is not a registered namespace" % key
)
350 return iri_to_uri(u
'%s%s' % (prefix
, resolver
.reverse(view
,
353 def clear_url_caches():
354 global _resolver_cache
355 global _callable_cache
356 _resolver_cache
.clear()
357 _callable_cache
.clear()
359 def set_script_prefix(prefix
):
361 Sets the script prefix for the current thread.
363 if not prefix
.endswith('/'):
365 _prefixes
[currentThread()] = prefix
367 def get_script_prefix():
369 Returns the currently active script prefix. Useful for client code that
370 wishes to construct their own URLs manually (although accessing the request
371 instance is normally going to be a lot cleaner).
373 return _prefixes
.get(currentThread(), u
'/')
375 def set_urlconf(urlconf_name
):
377 Sets the URLconf for the current thread (overriding the default one in
378 settings). Set to None to revert back to the default.
380 thread
= currentThread()
382 _urlconfs
[thread
] = urlconf_name
384 # faster than wrapping in a try/except
385 if thread
in _urlconfs
:
386 del _urlconfs
[thread
]
388 def get_urlconf(default
=None):
390 Returns the root URLconf to use for the current thread if it has been
391 changed from the default one.
393 thread
= currentThread()
394 if thread
in _urlconfs
:
395 return _urlconfs
[thread
]