Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #11559 -- Fixed the URL resolver to be able to handle captured …

…parameters in parent URLconfs when also using namespaces. Thanks, cwb, ungenio and hvdklauw.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16608 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 02dcbe33171851bb8b658965a96460a458bc09a8 1 parent 351d5da
Jannis Leidel authored August 12, 2011
48  django/core/urlresolvers.py
@@ -22,6 +22,7 @@
22 22
 
23 23
 
24 24
 _resolver_cache = {} # Maps URLconf modules to RegexURLResolver instances.
  25
+_ns_resolver_cache = {} # Maps namespaces to RegexURLResolver instances.
25 26
 _callable_cache = {} # Maps view and url pattern names to their view functions.
26 27
 
27 28
 # SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
@@ -91,20 +92,20 @@ def get_callable(lookup_view, can_fail=False):
91 92
                 lookup_view = getattr(import_module(mod_name), func_name)
92 93
                 if not callable(lookup_view):
93 94
                     raise ViewDoesNotExist(
94  
-                        "Could not import %s.%s. View is not callable."
95  
-                    % (mod_name, func_name))
  95
+                        "Could not import %s.%s. View is not callable." %
  96
+                        (mod_name, func_name))
96 97
         except AttributeError:
97 98
             if not can_fail:
98 99
                 raise ViewDoesNotExist(
99  
-                    "Could not import %s. View does not exist in module %s."
100  
-                    % (lookup_view, mod_name))
  100
+                    "Could not import %s. View does not exist in module %s." %
  101
+                    (lookup_view, mod_name))
101 102
         except ImportError:
102 103
             parentmod, submod = get_mod_func(mod_name)
103 104
             if (not can_fail and submod != '' and
104 105
                     not module_has_submodule(import_module(parentmod), submod)):
105 106
                 raise ViewDoesNotExist(
106  
-                    "Could not import %s. Parent module %s does not exist."
107  
-                    % (lookup_view, mod_name))
  107
+                    "Could not import %s. Parent module %s does not exist." %
  108
+                    (lookup_view, mod_name))
108 109
             if not can_fail:
109 110
                 raise
110 111
     return lookup_view
@@ -117,6 +118,15 @@ def get_resolver(urlconf):
117 118
     return RegexURLResolver(r'^/', urlconf)
118 119
 get_resolver = memoize(get_resolver, _resolver_cache, 1)
119 120
 
  121
+def get_ns_resolver(ns_pattern, resolver):
  122
+    # Build a namespaced resolver for the given parent urlconf pattern.
  123
+    # This makes it possible to have captured parameters in the parent
  124
+    # urlconf pattern.
  125
+    ns_resolver = RegexURLResolver(ns_pattern,
  126
+                                          resolver.url_patterns)
  127
+    return RegexURLResolver(r'^/', [ns_resolver])
  128
+get_ns_resolver = memoize(get_ns_resolver, _ns_resolver_cache, 2)
  129
+
120 130
 def get_mod_func(callback):
121 131
     # Converts 'django.views.news.stories.story_detail' to
122 132
     # ['django.views.news.stories', 'story_detail']
@@ -424,6 +434,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
424 434
         path = parts[1:]
425 435
 
426 436
         resolved_path = []
  437
+        ns_pattern = ''
427 438
         while path:
428 439
             ns = path.pop()
429 440
 
@@ -432,11 +443,13 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
432 443
                 app_list = resolver.app_dict[ns]
433 444
                 # Yes! Path part matches an app in the current Resolver
434 445
                 if current_app and current_app in app_list:
435  
-                    # If we are reversing for a particular app, use that namespace
  446
+                    # If we are reversing for a particular app,
  447
+                    # use that namespace
436 448
                     ns = current_app
437 449
                 elif ns not in app_list:
438  
-                    # The name isn't shared by one of the instances (i.e., the default)
439  
-                    # so just pick the first instance as the default.
  450
+                    # The name isn't shared by one of the instances
  451
+                    # (i.e., the default) so just pick the first instance
  452
+                    # as the default.
440 453
                     ns = app_list[0]
441 454
             except KeyError:
442 455
                 pass
@@ -444,22 +457,29 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
444 457
             try:
445 458
                 extra, resolver = resolver.namespace_dict[ns]
446 459
                 resolved_path.append(ns)
447  
-                prefix = prefix + extra
  460
+                ns_pattern = ns_pattern + extra
448 461
             except KeyError, key:
449 462
                 if resolved_path:
450  
-                    raise NoReverseMatch("%s is not a registered namespace inside '%s'" % (key, ':'.join(resolved_path)))
  463
+                    raise NoReverseMatch(
  464
+                        "%s is not a registered namespace inside '%s'" %
  465
+                        (key, ':'.join(resolved_path)))
451 466
                 else:
452  
-                    raise NoReverseMatch("%s is not a registered namespace" % key)
  467
+                    raise NoReverseMatch("%s is not a registered namespace" %
  468
+                                         key)
  469
+        if ns_pattern:
  470
+            resolver = get_ns_resolver(ns_pattern, resolver)
453 471
 
454  
-    return iri_to_uri(u'%s%s' % (prefix, resolver.reverse(view,
455  
-            *args, **kwargs)))
  472
+    return iri_to_uri(u'%s%s' %
  473
+                      (prefix, resolver.reverse(view, *args, **kwargs)))
456 474
 
457 475
 reverse_lazy = lazy(reverse, str)
458 476
 
459 477
 def clear_url_caches():
460 478
     global _resolver_cache
  479
+    global _ns_resolver_cache
461 480
     global _callable_cache
462 481
     _resolver_cache.clear()
  482
+    _ns_resolver_cache.clear()
463 483
     _callable_cache.clear()
464 484
 
465 485
 def set_script_prefix(prefix):
2  tests/regressiontests/urlpatterns_reverse/namespace_urls.py
@@ -44,4 +44,6 @@ def urls(self):
44 44
 
45 45
     (r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')),
46 46
 
  47
+    (r'^ns-outer/(?P<outer>\d+)/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-outer')),
  48
+
47 49
 )
7  tests/regressiontests/urlpatterns_reverse/tests.py
@@ -327,6 +327,13 @@ def test_namespace_pattern(self):
327 327
         self.assertEqual('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37,42]))
328 328
         self.assertEqual('/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
329 329
 
  330
+    def test_namespace_pattern_with_variable_prefix(self):
  331
+        "When using a include with namespaces when there is a regex variable in front of it"
  332
+        self.assertEqual('/ns-outer/42/normal/', reverse('inc-outer:inc-normal-view', kwargs={'outer':42}))
  333
+        self.assertEqual('/ns-outer/42/normal/', reverse('inc-outer:inc-normal-view', args=[42]))
  334
+        self.assertEqual('/ns-outer/42/normal/37/4/', reverse('inc-outer:inc-normal-view', kwargs={'outer':42, 'arg1': 37, 'arg2': 4}))
  335
+        self.assertEqual('/ns-outer/42/normal/37/4/', reverse('inc-outer:inc-normal-view', args=[42, 37, 4]))
  336
+
330 337
     def test_multiple_namespace_pattern(self):
331 338
         "Namespaces can be embedded"
332 339
         self.assertEqual('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view'))

0 notes on commit 02dcbe3

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