Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add support for reversing namespaced view references.

  • Loading branch information...
commit e0cd4506b369e3d7cc9f02f382eae8e3a39e1eb5 1 parent 74527ba
@bradleyayers authored
View
39 django/core/urlresolvers.py
@@ -8,6 +8,7 @@
"""
from __future__ import unicode_literals
+from itertools import ifilter
import re
from threading import local
@@ -34,6 +35,10 @@
# Overridden URLconfs for each thread are stored here.
_urlconfs = local()
+# Stores which resolvers are populating as a result of a parent resolver.
+# Allows recursive URLconfs to be detected and handled.
+_populating = local()
+
class ResolverMatch(object):
def __init__(self, func, args, kwargs, url_name=None, app_name=None, namespaces=None):
@@ -236,6 +241,8 @@ def __repr__(self):
return smart_str('<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern))
def _populate(self):
+ if not hasattr(_populating, "value"):
+ _populating.value = set()
lookups = MultiValueDict()
namespaces = {}
apps = {}
@@ -249,6 +256,25 @@ def _populate(self):
namespaces[pattern.namespace] = (p_pattern, pattern)
if pattern.app_name:
apps.setdefault(pattern.app_name, []).append(pattern.namespace)
+ key = (self, pattern)
+ if key not in _populating.value:
+ _populating.value.add(key)
+ # Since a namespace has been provided for the included resolver,
+ # we can rely on urlresolvers.reverse() to traverse the tree
+ # to honor and find the correct resolver to finally be able to
+ # call resolver.reverse(name).
+ # However when reversing a callable, no namespace information
+ # is attached to the callable so all lookups for all descendant
+ # views functions *must* be included in each resolver.
+ include_lookups = ifilter(callable, pattern.reverse_dict)
+ # If there's recursion in the URLconf, it's possible
+ # that this resolver is now populated. If that's the
+ # case, we can bail out.
+ if language_code in self._reverse_dict:
+ _populating.value.remove(key)
+ return
+ else:
+ include_lookups = []
else:
parent = normalize(pattern.regex.pattern)
for name in pattern.reverse_dict:
@@ -261,6 +287,19 @@ def _populate(self):
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
for app_name, namespace_list in pattern.app_dict.items():
apps.setdefault(app_name, []).extend(namespace_list)
+ # When no namespace is provided, *all* the patterns in the
+ # included resolver should be available in this resolver.
+ include_lookups = iter(pattern.reverse_dict)
+ # Add each of the child lookups to this resolver, being careful
+ # to add appropriate URL and regex prefixes so reversing results
+ # are correct.
+ parent = normalize(pattern.regex.pattern)
+ for name in include_lookups:
+ for matches, pat, defaults 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])
+ lookups.appendlist(name, (new_matches, p_pattern + pat, dict(defaults, **pattern.default_kwargs)))
else:
bits = normalize(p_pattern)
lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
View
2  tests/regressiontests/urlpatterns_reverse/tests.py
@@ -279,7 +279,7 @@ class NamespaceTests(TestCase):
urls = 'regressiontests.urlpatterns_reverse.namespace_urls'
def test_function_reference(self):
- self.assertEqual(reverse(views.namespaced_view_class_instance), '/ns-only-included/deeper/view_class/')
+ self.assertEqual(reverse(views.namespaced_view_class_instance), '/+%5C$*/ns-only-included/deeper/view_class/')
def test_ambiguous_object(self):
"Names deployed via dynamic URL objects that require namespaces can't be resolved"
Please sign in to comment.
Something went wrong with that request. Please try again.