Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #15900 -- Calls to reverse with nested namespaced urls are esca…

…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...
commit a0721a30178d621501756db543258bc2d527af21 1 parent 4c37685
authored December 22, 2011
17  django/core/urlresolvers.py
@@ -351,6 +351,9 @@ def resolve500(self):
351 351
         return self._resolve_special('500')
352 352
 
353 353
     def reverse(self, lookup_view, *args, **kwargs):
  354
+        return self._reverse_with_prefix(lookup_view, '', *args, **kwargs)
  355
+
  356
+    def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
354 357
         if args and kwargs:
355 358
             raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
356 359
         try:
@@ -358,15 +361,16 @@ def reverse(self, lookup_view, *args, **kwargs):
358 361
         except (ImportError, AttributeError), e:
359 362
             raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
360 363
         possibilities = self.reverse_dict.getlist(lookup_view)
  364
+        prefix_norm, prefix_args = normalize(_prefix)[0]
361 365
         for possibility, pattern, defaults in possibilities:
362 366
             for result, params in possibility:
363 367
                 if args:
364  
-                    if len(args) != len(params):
  368
+                    if len(args) != len(params) + len(prefix_args):
365 369
                         continue
366 370
                     unicode_args = [force_unicode(val) for val in args]
367  
-                    candidate =  result % dict(zip(params, unicode_args))
  371
+                    candidate =  (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args))
368 372
                 else:
369  
-                    if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys()):
  373
+                    if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys() + prefix_args):
370 374
                         continue
371 375
                     matches = True
372 376
                     for k, v in defaults.items():
@@ -376,8 +380,8 @@ def reverse(self, lookup_view, *args, **kwargs):
376 380
                     if not matches:
377 381
                         continue
378 382
                     unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
379  
-                    candidate = result % unicode_kwargs
380  
-                if re.search(u'^%s' % pattern, candidate, re.UNICODE):
  383
+                    candidate = (prefix_norm + result) % unicode_kwargs
  384
+                if re.search(u'^%s%s' % (_prefix, pattern), candidate, re.UNICODE):
381 385
                     return candidate
382 386
         # lookup_view can be URL label, or dotted path, or callable, Any of
383 387
         # these can be passed in at the top, but callables are not friendly in
@@ -469,8 +473,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
469 473
         if ns_pattern:
470 474
             resolver = get_ns_resolver(ns_pattern, resolver)
471 475
 
472  
-    return iri_to_uri(u'%s%s' %
473  
-                      (prefix, resolver.reverse(view, *args, **kwargs)))
  476
+    return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
474 477
 
475 478
 reverse_lazy = lazy(reverse, str)
476 479
 
5  tests/regressiontests/urlpatterns_reverse/namespace_urls.py
@@ -44,12 +44,13 @@ def urls(self):
44 44
     (r'^default/', include(default_testobj.urls)),
45 45
 
46 46
     (r'^other1/', include(otherobj1.urls)),
47  
-    (r'^other2/', include(otherobj2.urls)),
  47
+    (r'^other[246]/', include(otherobj2.urls)),
48 48
 
49  
-    (r'^ns-included1/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')),
  49
+    (r'^ns-included[135]/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')),
50 50
     (r'^ns-included2/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')),
51 51
 
52 52
     (r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')),
  53
+    (r'^inc(?P<outer>\d+)/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns5')),
53 54
 
54 55
     (r'^ns-outer/(?P<outer>\d+)/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-outer')),
55 56
 
11  tests/regressiontests/urlpatterns_reverse/tests.py
@@ -48,6 +48,10 @@
48 48
     # Nested namespaces
49 49
     ('/ns-included1/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),
50 50
     ('/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'}),
  51
+
  52
+    # Namespaces capturing variables
  53
+    ('/inc70/', 'inner-nothing', None, 'inc-ns5', views.empty_view, tuple(), {'outer': '70'}),
  54
+    ('/inc78/extra/foobar/', 'inner-extra', None, 'inc-ns5', views.empty_view, tuple(), {'outer':'78', 'extra':'foobar'}),
51 55
 )
52 56
 
53 57
 test_data = (
@@ -379,6 +383,13 @@ def test_special_chars_namespace(self):
379 383
         self.assertEqual('/+%5C$*/included/normal/42/37/', reverse('special:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
380 384
         self.assertEqual('/+%5C$*/included/+%5C$*/', reverse('special:inc-special-view'))
381 385
 
  386
+    def test_namespaces_with_variables(self):
  387
+        "Namespace prefixes can capture variables: see #15900"
  388
+        self.assertEqual('/inc70/', reverse('inc-ns5:inner-nothing', kwargs={'outer': '70'}))
  389
+        self.assertEqual('/inc78/extra/foobar/', reverse('inc-ns5:inner-extra', kwargs={'outer':'78', 'extra':'foobar'}))
  390
+        self.assertEqual('/inc70/', reverse('inc-ns5:inner-nothing', args=['70']))
  391
+        self.assertEqual('/inc78/extra/foobar/', reverse('inc-ns5:inner-extra', args=['78','foobar']))
  392
+
382 393
 class RequestURLconfTests(TestCase):
383 394
     def setUp(self):
384 395
         self.root_urlconf = settings.ROOT_URLCONF

0 notes on commit a0721a3

Please sign in to comment.
Something went wrong with that request. Please try again.