Skip to content

Commit

Permalink
Fixed #15900 -- Calls to reverse with nested namespaced urls are esca…
Browse files Browse the repository at this point in the history
…ped properly and capture parameters as expected.

Thanks to teolicy for the report, and dmclain for the patch.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@17251 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
Gabriel Hurley committed Dec 22, 2011
1 parent 4c37685 commit a0721a3
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 9 deletions.
17 changes: 10 additions & 7 deletions django/core/urlresolvers.py
Expand Up @@ -351,22 +351,26 @@ def resolve500(self):
return self._resolve_special('500')

def reverse(self, lookup_view, *args, **kwargs):
return self._reverse_with_prefix(lookup_view, '', *args, **kwargs)

def _reverse_with_prefix(self, lookup_view, _prefix, *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), e:
raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
possibilities = self.reverse_dict.getlist(lookup_view)
prefix_norm, prefix_args = normalize(_prefix)[0]
for possibility, pattern, defaults in possibilities:
for result, params in possibility:
if args:
if len(args) != len(params):
if len(args) != len(params) + len(prefix_args):
continue
unicode_args = [force_unicode(val) for val in args]
candidate = result % dict(zip(params, unicode_args))
candidate = (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args))
else:
if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys()):
if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys() + prefix_args):
continue
matches = True
for k, v in defaults.items():
Expand All @@ -376,8 +380,8 @@ def reverse(self, lookup_view, *args, **kwargs):
if not matches:
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):
candidate = (prefix_norm + result) % unicode_kwargs
if re.search(u'^%s%s' % (_prefix, pattern), candidate, re.UNICODE):
return candidate
# lookup_view can be URL label, or dotted path, or callable, Any of
# these can be passed in at the top, but callables are not friendly in
Expand Down Expand Up @@ -469,8 +473,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
if ns_pattern:
resolver = get_ns_resolver(ns_pattern, resolver)

return iri_to_uri(u'%s%s' %
(prefix, resolver.reverse(view, *args, **kwargs)))
return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))

reverse_lazy = lazy(reverse, str)

Expand Down
5 changes: 3 additions & 2 deletions tests/regressiontests/urlpatterns_reverse/namespace_urls.py
Expand Up @@ -44,12 +44,13 @@ def urls(self):
(r'^default/', include(default_testobj.urls)),

(r'^other1/', include(otherobj1.urls)),
(r'^other2/', include(otherobj2.urls)),
(r'^other[246]/', include(otherobj2.urls)),

(r'^ns-included1/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')),
(r'^ns-included[135]/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')),
(r'^ns-included2/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')),

(r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')),
(r'^inc(?P<outer>\d+)/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns5')),

(r'^ns-outer/(?P<outer>\d+)/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-outer')),

Expand Down
11 changes: 11 additions & 0 deletions tests/regressiontests/urlpatterns_reverse/tests.py
Expand Up @@ -48,6 +48,10 @@
# Nested namespaces
('/ns-included1/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),
('/ns-included1/ns-included4/ns-included2/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:inc-ns4:inc-ns2:test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),

# Namespaces capturing variables
('/inc70/', 'inner-nothing', None, 'inc-ns5', views.empty_view, tuple(), {'outer': '70'}),
('/inc78/extra/foobar/', 'inner-extra', None, 'inc-ns5', views.empty_view, tuple(), {'outer':'78', 'extra':'foobar'}),
)

test_data = (
Expand Down Expand Up @@ -379,6 +383,13 @@ def test_special_chars_namespace(self):
self.assertEqual('/+%5C$*/included/normal/42/37/', reverse('special:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
self.assertEqual('/+%5C$*/included/+%5C$*/', reverse('special:inc-special-view'))

def test_namespaces_with_variables(self):
"Namespace prefixes can capture variables: see #15900"
self.assertEqual('/inc70/', reverse('inc-ns5:inner-nothing', kwargs={'outer': '70'}))
self.assertEqual('/inc78/extra/foobar/', reverse('inc-ns5:inner-extra', kwargs={'outer':'78', 'extra':'foobar'}))
self.assertEqual('/inc70/', reverse('inc-ns5:inner-nothing', args=['70']))
self.assertEqual('/inc78/extra/foobar/', reverse('inc-ns5:inner-extra', args=['78','foobar']))

class RequestURLconfTests(TestCase):
def setUp(self):
self.root_urlconf = settings.ROOT_URLCONF
Expand Down

0 comments on commit a0721a3

Please sign in to comment.