2
Bugfix for issue #15900: https://code.djangoproject.com/ticket/15900.
4
This code is largely reproduced from
5
https://code.djangoproject.com/browser/django/trunk/django/core/urlresolvers.py
6
and is the work of Django's authors:
7
https://code.djangoproject.com/browser/django/trunk/AUTHORS
9
It is licensed under Django's BSD license, available here:
10
https://code.djangoproject.com/browser/django/trunk/LICENSE
12
To use, simply import this code in your project's root URLconf file before
13
defining any URL patterns.
16
from django.core import urlresolvers
18
if not hasattr(urlresolvers.RegexURLResolver, "_reverse_with_prefix"):
21
from django.conf import urls
22
from django.utils.datastructures import MultiValueDict
23
from django.utils.encoding import iri_to_uri, force_unicode
24
from django.utils.regex_helper import normalize
27
lookups = MultiValueDict()
30
for pattern in reversed(self.url_patterns):
31
p_pattern = pattern.regex.pattern
32
if p_pattern.startswith('^'):
33
p_pattern = p_pattern[1:]
34
if isinstance(pattern, urlresolvers.RegexURLResolver):
36
namespaces[pattern.namespace] = (p_pattern, pattern)
38
apps.setdefault(pattern.app_name, []) \
39
.append(pattern.namespace)
41
parent = normalize(pattern.regex.pattern)
42
for name in pattern.reverse_dict:
43
for matches, pat, defaults in \
44
pattern.reverse_dict.getlist(name):
46
for piece, p_args in parent:
47
vals = [(piece + suffix, p_args + args) for \
48
(suffix, args) in matches]
49
new_matches.extend(vals)
50
lookup_list = (new_matches, p_pattern + pat,
52
**pattern.default_kwargs))
53
lookups.appendlist(name, lookup_list)
54
for namespace, (prefix, sub_pattern) in \
55
pattern.namespace_dict.items():
56
namespace_vals = (p_pattern + prefix, sub_pattern)
57
namespaces[namespace] = namespace_vals
58
for app_name, namespace_list in pattern.app_dict.items():
59
apps.setdefault(app_name, []).extend(namespace_list)
61
bits = normalize(p_pattern)
62
lookup_list = (bits, p_pattern, pattern.default_args)
63
lookups.appendlist(pattern.callback, lookup_list)
64
if pattern.name is not None:
65
lookup_list = (bits, p_pattern, pattern.default_args)
66
lookups.appendlist(pattern.name, lookup_list)
67
self._reverse_dict = lookups
68
self._namespace_dict = namespaces
71
def resolver_reverse(self, lookup_view, *args, **kwargs):
72
return self._reverse_with_prefix(lookup_view, '', *args, **kwargs)
74
def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
76
raise ValueError("Don't mix *args and **kwargs in call to "
79
lookup_view = urlresolvers.get_callable(lookup_view, True)
80
except (ImportError, AttributeError), e:
81
raise urlresolvers.NoReverseMatch("Error importing '%s': %s."
83
possibilities = self.reverse_dict.getlist(lookup_view)
84
prefix_norm, prefix_args = normalize(_prefix)[0]
85
for possibility, pattern, defaults in possibilities:
86
for result, params in possibility:
88
if len(args) != len(params) + len(prefix_args):
90
unicode_args = [force_unicode(val) for val in args]
91
candidate = (prefix_norm + result) \
92
% dict(zip(prefix_args + params, unicode_args))
94
if set(kwargs.keys() + defaults.keys()) != \
95
set(params + defaults.keys() + prefix_args):
98
for k, v in defaults.items():
99
if kwargs.get(k, v) != v:
104
unicode_kwargs = dict([(k, force_unicode(v)) for \
105
(k, v) in kwargs.items()])
106
candidate = (prefix_norm + result) % unicode_kwargs
107
if re.search(u'^%s%s' % (_prefix, pattern),
108
candidate, re.UNICODE):
110
# lookup_view can be URL label, or dotted path, or callable, Any of
111
# these can be passed in at the top, but callables are not friendly in
113
m = getattr(lookup_view, '__module__', None)
114
n = getattr(lookup_view, '__name__', None)
115
if m is not None and n is not None:
116
lookup_view_s = "%s.%s" % (m, n)
118
lookup_view_s = lookup_view
119
raise urlresolvers.NoReverseMatch("Reverse for '%s' with "
120
"arguments '%s' and keyword "
121
"arguments '%s' not found."
122
% (lookup_view_s, args, kwargs))
124
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None,
127
urlconf = urlresolvers.get_urlconf()
128
resolver = urlresolvers.get_resolver(urlconf)
130
kwargs = kwargs or {}
133
prefix = urlresolvers.get_script_prefix()
135
if not isinstance(viewname, basestring):
138
parts = viewname.split(':')
147
# Lookup the name to see if it could be an app identifier
149
app_list = resolver.app_dict[ns]
150
# Yes! Path part matches an app in the current Resolver
151
if current_app and current_app in app_list:
152
# If we are reversing for a particular app,
155
elif ns not in app_list:
156
# The name isn't shared by one of the instances
157
# (i.e., the default) so just pick the first instance
164
extra, resolver = resolver.namespace_dict[ns]
165
resolved_path.append(ns)
166
prefix = prefix + extra
167
except KeyError, key:
169
raise urlresolvers.NoReverseMatch("%s is not a "
170
"registered namespace inside %s'"
171
% (key, ':'.join(resolved_path)))
173
raise urlresolvers.NoReverseMatch("%s is not a "
177
return iri_to_uri(resolver._reverse_with_prefix(view, prefix,
180
urlresolvers.RegexURLResolver._populate = _populate
181
urlresolvers.RegexURLResolver.reverse = resolver_reverse
182
urlresolvers.RegexURLResolver._reverse_with_prefix = _reverse_with_prefix
183
urlresolvers.reverse = reverse