--- a/app/django/core/urlresolvers.py Tue Oct 14 12:36:55 2008 +0000
+++ b/app/django/core/urlresolvers.py Tue Oct 14 16:00:59 2008 +0000
@@ -7,20 +7,30 @@
(view_function, function_args, function_kwargs)
"""
+import re
+
from django.http import Http404
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
+from django.utils.datastructures import MultiValueDict
from django.utils.encoding import iri_to_uri, force_unicode, smart_str
from django.utils.functional import memoize
-import re
+from django.utils.regex_helper import normalize
+from django.utils.thread_support import currentThread
try:
reversed
except NameError:
from django.utils.itercompat import reversed # Python 2.3 fallback
+ from sets import Set as set
_resolver_cache = {} # Maps urlconf modules to RegexURLResolver instances.
_callable_cache = {} # Maps view and url pattern names to their view functions.
+# SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
+# the current thread (which is the only one we ever access), it is assumed to
+# be empty.
+_prefixes = {}
+
class Resolver404(Http404):
pass
@@ -45,6 +55,8 @@
mod_name, func_name = get_mod_func(lookup_view)
if func_name != '':
lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
+ if not callable(lookup_view):
+ raise AttributeError("'%s.%s' is not a callable." % (mod_name, func_name))
except (ImportError, AttributeError):
if not can_fail:
raise
@@ -69,66 +81,6 @@
return callback, ''
return callback[:dot], callback[dot+1:]
-def reverse_helper(regex, *args, **kwargs):
- """
- Does a "reverse" lookup -- returns the URL for the given args/kwargs.
- The args/kwargs are applied to the given compiled regular expression.
- For example:
-
- >>> reverse_helper(re.compile('^places/(\d+)/$'), 3)
- 'places/3/'
- >>> reverse_helper(re.compile('^places/(?P<id>\d+)/$'), id=3)
- 'places/3/'
- >>> reverse_helper(re.compile('^people/(?P<state>\w\w)/(\w+)/$'), 'adrian', state='il')
- 'people/il/adrian/'
-
- Raises NoReverseMatch if the args/kwargs aren't valid for the regex.
- """
- # TODO: Handle nested parenthesis in the following regex.
- result = re.sub(r'\(([^)]+)\)', MatchChecker(args, kwargs), regex.pattern)
- return result.replace('^', '').replace('$', '')
-
-class MatchChecker(object):
- "Class used in reverse RegexURLPattern lookup."
- def __init__(self, args, kwargs):
- self.args, self.kwargs = args, kwargs
- self.current_arg = 0
-
- def __call__(self, match_obj):
- # match_obj.group(1) is the contents of the parenthesis.
- # First we need to figure out whether it's a named or unnamed group.
- #
- grouped = match_obj.group(1)
- m = re.search(r'^\?P<(\w+)>(.*?)$', grouped, re.UNICODE)
- if m: # If this was a named group...
- # m.group(1) is the name of the group
- # m.group(2) is the regex.
- try:
- value = self.kwargs[m.group(1)]
- except KeyError:
- # It was a named group, but the arg was passed in as a
- # positional arg or not at all.
- try:
- value = self.args[self.current_arg]
- self.current_arg += 1
- except IndexError:
- # The arg wasn't passed in.
- raise NoReverseMatch('Not enough positional arguments passed in')
- test_regex = m.group(2)
- else: # Otherwise, this was a positional (unnamed) group.
- try:
- value = self.args[self.current_arg]
- self.current_arg += 1
- except IndexError:
- # The arg wasn't passed in.
- raise NoReverseMatch('Not enough positional arguments passed in')
- test_regex = grouped
- # Note we're using re.match here on purpose because the start of
- # to string needs to match.
- if not re.match(test_regex + '$', force_unicode(value), re.UNICODE):
- raise NoReverseMatch("Value %r didn't match regular expression %r" % (value, test_regex))
- return force_unicode(value)
-
class RegexURLPattern(object):
def __init__(self, regex, callback, default_args=None, name=None):
# regex is a string representing a regular expression.
@@ -185,19 +137,6 @@
return self._callback
callback = property(_get_callback)
- def reverse(self, viewname, *args, **kwargs):
- mod_name, func_name = get_mod_func(viewname)
- try:
- lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
- except (ImportError, AttributeError):
- raise NoReverseMatch
- if lookup_view != self.callback:
- raise NoReverseMatch
- return self.reverse_helper(*args, **kwargs)
-
- def reverse_helper(self, *args, **kwargs):
- return reverse_helper(self.regex, *args, **kwargs)
-
class RegexURLResolver(object):
def __init__(self, regex, urlconf_name, default_kwargs=None):
# regex is a string representing a regular expression.
@@ -206,7 +145,7 @@
self.urlconf_name = urlconf_name
self.callback = None
self.default_kwargs = default_kwargs or {}
- self._reverse_dict = {}
+ self._reverse_dict = MultiValueDict()
def __repr__(self):
return '<%s %s %s>' % (self.__class__.__name__, self.urlconf_name, self.regex.pattern)
@@ -214,12 +153,21 @@
def _get_reverse_dict(self):
if not self._reverse_dict and hasattr(self.urlconf_module, 'urlpatterns'):
for pattern in reversed(self.urlconf_module.urlpatterns):
+ p_pattern = pattern.regex.pattern
+ if p_pattern.startswith('^'):
+ p_pattern = p_pattern[1:]
if isinstance(pattern, RegexURLResolver):
- for key, value in pattern.reverse_dict.iteritems():
- self._reverse_dict[key] = (pattern,) + value
+ parent = normalize(pattern.regex.pattern)
+ for name in pattern.reverse_dict:
+ for matches, pat in pattern.reverse_dict.getlist(name):
+ new_matches = []
+ for piece, p_args in parent:
+ new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches])
+ self._reverse_dict.appendlist(name, (new_matches, p_pattern + pat))
else:
- self._reverse_dict[pattern.callback] = (pattern,)
- self._reverse_dict[pattern.name] = (pattern,)
+ bits = normalize(p_pattern)
+ self._reverse_dict.appendlist(pattern.callback, (bits, p_pattern))
+ self._reverse_dict.appendlist(pattern.name, (bits, p_pattern))
return self._reverse_dict
reverse_dict = property(_get_reverse_dict)
@@ -247,12 +195,7 @@
try:
return self._urlconf_module
except AttributeError:
- try:
- self._urlconf_module = __import__(self.urlconf_name, {}, {}, [''])
- except Exception, e:
- # Either an invalid urlconf_name, such as "foo.bar.", or some
- # kind of problem during the actual import.
- raise ImproperlyConfigured, "Error while importing URLconf %r: %s" % (self.urlconf_name, e)
+ self._urlconf_module = __import__(self.urlconf_name, {}, {}, [''])
return self._urlconf_module
urlconf_module = property(_get_urlconf_module)
@@ -275,24 +218,60 @@
return self._resolve_special('500')
def reverse(self, lookup_view, *args, **kwargs):
+ if args and kwargs:
+ raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
try:
lookup_view = get_callable(lookup_view, True)
- except (ImportError, AttributeError):
- raise NoReverseMatch
- if lookup_view in self.reverse_dict:
- return u''.join([reverse_helper(part.regex, *args, **kwargs) for part in self.reverse_dict[lookup_view]])
- raise NoReverseMatch
-
- def reverse_helper(self, lookup_view, *args, **kwargs):
- sub_match = self.reverse(lookup_view, *args, **kwargs)
- result = reverse_helper(self.regex, *args, **kwargs)
- return result + sub_match
+ except (ImportError, AttributeError), e:
+ raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
+ possibilities = self.reverse_dict.getlist(lookup_view)
+ for possibility, pattern in possibilities:
+ for result, params in possibility:
+ if args:
+ if len(args) != len(params):
+ continue
+ unicode_args = [force_unicode(val) for val in args]
+ candidate = result % dict(zip(params, unicode_args))
+ else:
+ if set(kwargs.keys()) != set(params):
+ continue
+ unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
+ candidate = result % unicode_kwargs
+ if re.search(u'^%s' % pattern, candidate, re.UNICODE):
+ return candidate
+ raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
+ "arguments '%s' not found." % (lookup_view, args, kwargs))
def resolve(path, urlconf=None):
return get_resolver(urlconf).resolve(path)
-def reverse(viewname, urlconf=None, args=None, kwargs=None):
+def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None):
args = args or []
kwargs = kwargs or {}
- return iri_to_uri(u'/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs))
+ if prefix is None:
+ prefix = get_script_prefix()
+ return iri_to_uri(u'%s%s' % (prefix, get_resolver(urlconf).reverse(viewname,
+ *args, **kwargs)))
+
+def clear_url_caches():
+ global _resolver_cache
+ global _callable_cache
+ _resolver_cache.clear()
+ _callable_cache.clear()
+def set_script_prefix(prefix):
+ """
+ Sets the script prefix for the current thread.
+ """
+ if not prefix.endswith('/'):
+ prefix += '/'
+ _prefixes[currentThread()] = prefix
+
+def get_script_prefix():
+ """
+ Returns the currently active script prefix. Useful for client code that
+ wishes to construct their own URLs manually (although accessing the request
+ instance is normally going to be a lot cleaner).
+ """
+ return _prefixes.get(currentThread(), u'/')
+