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
This commit is contained in:
parent
351d5da69b
commit
02dcbe3317
|
@ -22,6 +22,7 @@ from django.utils.translation import get_language
|
||||||
|
|
||||||
|
|
||||||
_resolver_cache = {} # Maps URLconf modules to RegexURLResolver instances.
|
_resolver_cache = {} # Maps URLconf modules to RegexURLResolver instances.
|
||||||
|
_ns_resolver_cache = {} # Maps namespaces to RegexURLResolver instances.
|
||||||
_callable_cache = {} # Maps view and url pattern names to their view functions.
|
_callable_cache = {} # Maps view and url pattern names to their view functions.
|
||||||
|
|
||||||
# SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
|
# 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):
|
||||||
lookup_view = getattr(import_module(mod_name), func_name)
|
lookup_view = getattr(import_module(mod_name), func_name)
|
||||||
if not callable(lookup_view):
|
if not callable(lookup_view):
|
||||||
raise ViewDoesNotExist(
|
raise ViewDoesNotExist(
|
||||||
"Could not import %s.%s. View is not callable."
|
"Could not import %s.%s. View is not callable." %
|
||||||
% (mod_name, func_name))
|
(mod_name, func_name))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
if not can_fail:
|
if not can_fail:
|
||||||
raise ViewDoesNotExist(
|
raise ViewDoesNotExist(
|
||||||
"Could not import %s. View does not exist in module %s."
|
"Could not import %s. View does not exist in module %s." %
|
||||||
% (lookup_view, mod_name))
|
(lookup_view, mod_name))
|
||||||
except ImportError:
|
except ImportError:
|
||||||
parentmod, submod = get_mod_func(mod_name)
|
parentmod, submod = get_mod_func(mod_name)
|
||||||
if (not can_fail and submod != '' and
|
if (not can_fail and submod != '' and
|
||||||
not module_has_submodule(import_module(parentmod), submod)):
|
not module_has_submodule(import_module(parentmod), submod)):
|
||||||
raise ViewDoesNotExist(
|
raise ViewDoesNotExist(
|
||||||
"Could not import %s. Parent module %s does not exist."
|
"Could not import %s. Parent module %s does not exist." %
|
||||||
% (lookup_view, mod_name))
|
(lookup_view, mod_name))
|
||||||
if not can_fail:
|
if not can_fail:
|
||||||
raise
|
raise
|
||||||
return lookup_view
|
return lookup_view
|
||||||
|
@ -117,6 +118,15 @@ def get_resolver(urlconf):
|
||||||
return RegexURLResolver(r'^/', urlconf)
|
return RegexURLResolver(r'^/', urlconf)
|
||||||
get_resolver = memoize(get_resolver, _resolver_cache, 1)
|
get_resolver = memoize(get_resolver, _resolver_cache, 1)
|
||||||
|
|
||||||
|
def get_ns_resolver(ns_pattern, resolver):
|
||||||
|
# Build a namespaced resolver for the given parent urlconf pattern.
|
||||||
|
# This makes it possible to have captured parameters in the parent
|
||||||
|
# urlconf pattern.
|
||||||
|
ns_resolver = RegexURLResolver(ns_pattern,
|
||||||
|
resolver.url_patterns)
|
||||||
|
return RegexURLResolver(r'^/', [ns_resolver])
|
||||||
|
get_ns_resolver = memoize(get_ns_resolver, _ns_resolver_cache, 2)
|
||||||
|
|
||||||
def get_mod_func(callback):
|
def get_mod_func(callback):
|
||||||
# Converts 'django.views.news.stories.story_detail' to
|
# Converts 'django.views.news.stories.story_detail' to
|
||||||
# ['django.views.news.stories', 'story_detail']
|
# ['django.views.news.stories', 'story_detail']
|
||||||
|
@ -424,6 +434,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
|
||||||
path = parts[1:]
|
path = parts[1:]
|
||||||
|
|
||||||
resolved_path = []
|
resolved_path = []
|
||||||
|
ns_pattern = ''
|
||||||
while path:
|
while path:
|
||||||
ns = path.pop()
|
ns = path.pop()
|
||||||
|
|
||||||
|
@ -432,11 +443,13 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
|
||||||
app_list = resolver.app_dict[ns]
|
app_list = resolver.app_dict[ns]
|
||||||
# Yes! Path part matches an app in the current Resolver
|
# Yes! Path part matches an app in the current Resolver
|
||||||
if current_app and current_app in app_list:
|
if current_app and current_app in app_list:
|
||||||
# If we are reversing for a particular app, use that namespace
|
# If we are reversing for a particular app,
|
||||||
|
# use that namespace
|
||||||
ns = current_app
|
ns = current_app
|
||||||
elif ns not in app_list:
|
elif ns not in app_list:
|
||||||
# The name isn't shared by one of the instances (i.e., the default)
|
# The name isn't shared by one of the instances
|
||||||
# so just pick the first instance as the default.
|
# (i.e., the default) so just pick the first instance
|
||||||
|
# as the default.
|
||||||
ns = app_list[0]
|
ns = app_list[0]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
@ -444,22 +457,29 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current
|
||||||
try:
|
try:
|
||||||
extra, resolver = resolver.namespace_dict[ns]
|
extra, resolver = resolver.namespace_dict[ns]
|
||||||
resolved_path.append(ns)
|
resolved_path.append(ns)
|
||||||
prefix = prefix + extra
|
ns_pattern = ns_pattern + extra
|
||||||
except KeyError, key:
|
except KeyError, key:
|
||||||
if resolved_path:
|
if resolved_path:
|
||||||
raise NoReverseMatch("%s is not a registered namespace inside '%s'" % (key, ':'.join(resolved_path)))
|
raise NoReverseMatch(
|
||||||
|
"%s is not a registered namespace inside '%s'" %
|
||||||
|
(key, ':'.join(resolved_path)))
|
||||||
else:
|
else:
|
||||||
raise NoReverseMatch("%s is not a registered namespace" % key)
|
raise NoReverseMatch("%s is not a registered namespace" %
|
||||||
|
key)
|
||||||
|
if ns_pattern:
|
||||||
|
resolver = get_ns_resolver(ns_pattern, resolver)
|
||||||
|
|
||||||
return iri_to_uri(u'%s%s' % (prefix, resolver.reverse(view,
|
return iri_to_uri(u'%s%s' %
|
||||||
*args, **kwargs)))
|
(prefix, resolver.reverse(view, *args, **kwargs)))
|
||||||
|
|
||||||
reverse_lazy = lazy(reverse, str)
|
reverse_lazy = lazy(reverse, str)
|
||||||
|
|
||||||
def clear_url_caches():
|
def clear_url_caches():
|
||||||
global _resolver_cache
|
global _resolver_cache
|
||||||
|
global _ns_resolver_cache
|
||||||
global _callable_cache
|
global _callable_cache
|
||||||
_resolver_cache.clear()
|
_resolver_cache.clear()
|
||||||
|
_ns_resolver_cache.clear()
|
||||||
_callable_cache.clear()
|
_callable_cache.clear()
|
||||||
|
|
||||||
def set_script_prefix(prefix):
|
def set_script_prefix(prefix):
|
||||||
|
|
|
@ -44,4 +44,6 @@ urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
|
||||||
|
|
||||||
(r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')),
|
(r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')),
|
||||||
|
|
||||||
|
(r'^ns-outer/(?P<outer>\d+)/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-outer')),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -327,6 +327,13 @@ class NamespaceTests(TestCase):
|
||||||
self.assertEqual('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37,42]))
|
self.assertEqual('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37,42]))
|
||||||
self.assertEqual('/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
|
self.assertEqual('/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
|
||||||
|
|
||||||
|
def test_namespace_pattern_with_variable_prefix(self):
|
||||||
|
"When using a include with namespaces when there is a regex variable in front of it"
|
||||||
|
self.assertEqual('/ns-outer/42/normal/', reverse('inc-outer:inc-normal-view', kwargs={'outer':42}))
|
||||||
|
self.assertEqual('/ns-outer/42/normal/', reverse('inc-outer:inc-normal-view', args=[42]))
|
||||||
|
self.assertEqual('/ns-outer/42/normal/37/4/', reverse('inc-outer:inc-normal-view', kwargs={'outer':42, 'arg1': 37, 'arg2': 4}))
|
||||||
|
self.assertEqual('/ns-outer/42/normal/37/4/', reverse('inc-outer:inc-normal-view', args=[42, 37, 4]))
|
||||||
|
|
||||||
def test_multiple_namespace_pattern(self):
|
def test_multiple_namespace_pattern(self):
|
||||||
"Namespaces can be embedded"
|
"Namespaces can be embedded"
|
||||||
self.assertEqual('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view'))
|
self.assertEqual('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view'))
|
||||||
|
|
Loading…
Reference in New Issue