Fixed #15900 -- Calls to reverse with nested namespaced urls are escaped 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
This commit is contained in:
parent
4c376852fe
commit
a0721a3017
|
@ -351,6 +351,9 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
return self._resolve_special('500')
|
return self._resolve_special('500')
|
||||||
|
|
||||||
def reverse(self, lookup_view, *args, **kwargs):
|
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:
|
if args and kwargs:
|
||||||
raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
|
raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
|
||||||
try:
|
try:
|
||||||
|
@ -358,15 +361,16 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
except (ImportError, AttributeError), e:
|
except (ImportError, AttributeError), e:
|
||||||
raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
|
raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
|
||||||
possibilities = self.reverse_dict.getlist(lookup_view)
|
possibilities = self.reverse_dict.getlist(lookup_view)
|
||||||
|
prefix_norm, prefix_args = normalize(_prefix)[0]
|
||||||
for possibility, pattern, defaults in possibilities:
|
for possibility, pattern, defaults in possibilities:
|
||||||
for result, params in possibility:
|
for result, params in possibility:
|
||||||
if args:
|
if args:
|
||||||
if len(args) != len(params):
|
if len(args) != len(params) + len(prefix_args):
|
||||||
continue
|
continue
|
||||||
unicode_args = [force_unicode(val) for val in args]
|
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:
|
else:
|
||||||
if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys()):
|
if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys() + prefix_args):
|
||||||
continue
|
continue
|
||||||
matches = True
|
matches = True
|
||||||
for k, v in defaults.items():
|
for k, v in defaults.items():
|
||||||
|
@ -376,8 +380,8 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
if not matches:
|
if not matches:
|
||||||
continue
|
continue
|
||||||
unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
|
unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
|
||||||
candidate = result % unicode_kwargs
|
candidate = (prefix_norm + result) % unicode_kwargs
|
||||||
if re.search(u'^%s' % pattern, candidate, re.UNICODE):
|
if re.search(u'^%s%s' % (_prefix, pattern), candidate, re.UNICODE):
|
||||||
return candidate
|
return candidate
|
||||||
# lookup_view can be URL label, or dotted path, or callable, Any of
|
# 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
|
# 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
|
||||||
if ns_pattern:
|
if ns_pattern:
|
||||||
resolver = get_ns_resolver(ns_pattern, resolver)
|
resolver = get_ns_resolver(ns_pattern, resolver)
|
||||||
|
|
||||||
return iri_to_uri(u'%s%s' %
|
return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
|
||||||
(prefix, resolver.reverse(view, *args, **kwargs)))
|
|
||||||
|
|
||||||
reverse_lazy = lazy(reverse, str)
|
reverse_lazy = lazy(reverse, str)
|
||||||
|
|
||||||
|
|
|
@ -44,12 +44,13 @@ urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
|
||||||
(r'^default/', include(default_testobj.urls)),
|
(r'^default/', include(default_testobj.urls)),
|
||||||
|
|
||||||
(r'^other1/', include(otherobj1.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'^ns-included2/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')),
|
||||||
|
|
||||||
(r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')),
|
(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')),
|
(r'^ns-outer/(?P<outer>\d+)/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-outer')),
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,10 @@ resolve_test_data = (
|
||||||
# Nested namespaces
|
# Nested namespaces
|
||||||
('/ns-included1/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),
|
('/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'}),
|
('/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 = (
|
test_data = (
|
||||||
|
@ -379,6 +383,13 @@ class NamespaceTests(TestCase):
|
||||||
self.assertEqual('/+%5C$*/included/normal/42/37/', reverse('special:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
|
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'))
|
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):
|
class RequestURLconfTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.root_urlconf = settings.ROOT_URLCONF
|
self.root_urlconf = settings.ROOT_URLCONF
|
||||||
|
|
Loading…
Reference in New Issue