summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXaroth Brook <xaroth@xaroth.nl>2018-05-26 22:56:17 +0200
committerTim Graham <timograham@gmail.com>2018-05-26 20:13:48 -0400
commit39283c8edbc5991b589d48a8e17152042193f2df (patch)
treed6d9252b66050f139c70a56ca4c7490aecc1273c
parente01fa015c0fca32bb740b575c8fbfd388d8f4957 (diff)
downloaddjango-39283c8edbc5991b589d48a8e17152042193f2df.tar.gz
Fixed #29415 -- Fixed detection of custom URL converters in included patterns.
-rw-r--r--AUTHORS1
-rw-r--r--django/urls/base.py4
-rw-r--r--django/urls/resolvers.py8
-rw-r--r--docs/releases/2.0.6.txt3
-rw-r--r--tests/urlpatterns/path_base64_urls.py8
-rw-r--r--tests/urlpatterns/tests.py30
6 files changed, 42 insertions, 12 deletions
diff --git a/AUTHORS b/AUTHORS
index 34e729bb1f..9be6412d19 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -773,6 +773,7 @@ answer newbie questions, and generally made Django that much better:
Stephan Jaekel <steph@rdev.info>
Stephen Burrows <stephen.r.burrows@gmail.com>
Steven L. Smith (fvox13) <steven@stevenlsmith.com>
+ Steven Noorbergen (Xaroth) <xaroth+django@xaroth.nl>
Stuart Langridge <http://www.kryogenix.org/>
Sujay S Kumar <sujay.skumar141295@gmail.com>
Sune Kirkeby <http://ibofobi.dk/>
diff --git a/django/urls/base.py b/django/urls/base.py
index 6dccdd2e7d..1e11e05bec 100644
--- a/django/urls/base.py
+++ b/django/urls/base.py
@@ -49,6 +49,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
resolved_path = []
ns_pattern = ''
+ ns_converters = {}
while path:
ns = path.pop()
current_ns = current_path.pop() if current_path else None
@@ -74,6 +75,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
extra, resolver = resolver.namespace_dict[ns]
resolved_path.append(ns)
ns_pattern = ns_pattern + extra
+ ns_converters.update(resolver.pattern.converters)
except KeyError as key:
if resolved_path:
raise NoReverseMatch(
@@ -83,7 +85,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
else:
raise NoReverseMatch("%s is not a registered namespace" % key)
if ns_pattern:
- resolver = get_ns_resolver(ns_pattern, resolver)
+ resolver = get_ns_resolver(ns_pattern, resolver, tuple(ns_converters.items()))
return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py
index 7343f6616a..ce8c7ffa32 100644
--- a/django/urls/resolvers.py
+++ b/django/urls/resolvers.py
@@ -68,11 +68,13 @@ def get_resolver(urlconf=None):
@functools.lru_cache(maxsize=None)
-def get_ns_resolver(ns_pattern, resolver):
+def get_ns_resolver(ns_pattern, resolver, converters):
# 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 = URLResolver(RegexPattern(ns_pattern), resolver.url_patterns)
+ pattern = RegexPattern(ns_pattern)
+ pattern.converters = dict(converters)
+ ns_resolver = URLResolver(pattern, resolver.url_patterns)
return URLResolver(RegexPattern(r'^/'), [ns_resolver])
@@ -439,7 +441,7 @@ class URLResolver:
new_matches,
p_pattern + pat,
{**defaults, **url_pattern.default_kwargs},
- {**self.pattern.converters, **converters}
+ {**self.pattern.converters, **url_pattern.pattern.converters, **converters}
)
)
for namespace, (prefix, sub_pattern) in url_pattern.namespace_dict.items():
diff --git a/docs/releases/2.0.6.txt b/docs/releases/2.0.6.txt
index 5d401a7ea9..1c9d0982fa 100644
--- a/docs/releases/2.0.6.txt
+++ b/docs/releases/2.0.6.txt
@@ -11,3 +11,6 @@ Bugfixes
* Fixed a regression that broke custom template filters that use decorators
(:ticket:`29400`).
+
+* Fixed detection of custom URL converters in included patterns
+ (:ticket:`29415`).
diff --git a/tests/urlpatterns/path_base64_urls.py b/tests/urlpatterns/path_base64_urls.py
index 872636f06c..9b69f929fe 100644
--- a/tests/urlpatterns/path_base64_urls.py
+++ b/tests/urlpatterns/path_base64_urls.py
@@ -1,9 +1,15 @@
-from django.urls import path, register_converter
+from django.urls import include, path, register_converter
from . import converters, views
register_converter(converters.Base64Converter, 'base64')
+subpatterns = [
+ path('<base64:value>/', views.empty_view, name='subpattern-base64'),
+]
+
urlpatterns = [
path('base64/<base64:value>/', views.empty_view, name='base64'),
+ path('base64/<base64:base>/subpatterns/', include(subpatterns)),
+ path('base64/<base64:base>/namespaced/', include((subpatterns, 'namespaced-base64'))),
]
diff --git a/tests/urlpatterns/tests.py b/tests/urlpatterns/tests.py
index b200aed06d..b3d97ec5b9 100644
--- a/tests/urlpatterns/tests.py
+++ b/tests/urlpatterns/tests.py
@@ -8,6 +8,15 @@ from django.urls import Resolver404, path, resolve, reverse
from .converters import DynamicConverter
from .views import empty_view
+included_kwargs = {'base': b'hello', 'value': b'world'}
+converter_test_data = (
+ # ('url', ('url_name', 'app_name', {kwargs})),
+ # aGVsbG8= is 'hello' encoded in base64.
+ ('/base64/aGVsbG8=/', ('base64', '', {'value': b'hello'})),
+ ('/base64/aGVsbG8=/subpatterns/d29ybGQ=/', ('subpattern-base64', '', included_kwargs)),
+ ('/base64/aGVsbG8=/namespaced/d29ybGQ=/', ('subpattern-base64', 'namespaced-base64', included_kwargs)),
+)
+
@override_settings(ROOT_URLCONF='urlpatterns.path_urls')
class SimplifiedURLTests(SimpleTestCase):
@@ -44,15 +53,22 @@ class SimplifiedURLTests(SimpleTestCase):
self.assertEqual(url, '/articles/2015/4/12/')
@override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls')
- def test_non_identical_converter_resolve(self):
- match = resolve('/base64/aGVsbG8=/') # base64 of 'hello'
- self.assertEqual(match.url_name, 'base64')
- self.assertEqual(match.kwargs, {'value': b'hello'})
+ def test_converter_resolve(self):
+ for url, (url_name, app_name, kwargs) in converter_test_data:
+ with self.subTest(url=url):
+ match = resolve(url)
+ self.assertEqual(match.url_name, url_name)
+ self.assertEqual(match.app_name, app_name)
+ self.assertEqual(match.kwargs, kwargs)
@override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls')
- def test_non_identical_converter_reverse(self):
- url = reverse('base64', kwargs={'value': b'hello'})
- self.assertEqual(url, '/base64/aGVsbG8=/')
+ def test_converter_reverse(self):
+ for expected, (url_name, app_name, kwargs) in converter_test_data:
+ if app_name:
+ url_name = '%s:%s' % (app_name, url_name)
+ with self.subTest(url=url_name):
+ url = reverse(url_name, kwargs=kwargs)
+ self.assertEqual(url, expected)
def test_path_inclusion_is_matchable(self):
match = resolve('/included_urls/extra/something/')