Fixed #29415 -- Fixed detection of custom URL converters in included patterns.
This commit is contained in:
parent
e01fa015c0
commit
39283c8edb
1
AUTHORS
1
AUTHORS
|
@ -773,6 +773,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Stephan Jaekel <steph@rdev.info>
|
Stephan Jaekel <steph@rdev.info>
|
||||||
Stephen Burrows <stephen.r.burrows@gmail.com>
|
Stephen Burrows <stephen.r.burrows@gmail.com>
|
||||||
Steven L. Smith (fvox13) <steven@stevenlsmith.com>
|
Steven L. Smith (fvox13) <steven@stevenlsmith.com>
|
||||||
|
Steven Noorbergen (Xaroth) <xaroth+django@xaroth.nl>
|
||||||
Stuart Langridge <http://www.kryogenix.org/>
|
Stuart Langridge <http://www.kryogenix.org/>
|
||||||
Sujay S Kumar <sujay.skumar141295@gmail.com>
|
Sujay S Kumar <sujay.skumar141295@gmail.com>
|
||||||
Sune Kirkeby <http://ibofobi.dk/>
|
Sune Kirkeby <http://ibofobi.dk/>
|
||||||
|
|
|
@ -49,6 +49,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
|
||||||
|
|
||||||
resolved_path = []
|
resolved_path = []
|
||||||
ns_pattern = ''
|
ns_pattern = ''
|
||||||
|
ns_converters = {}
|
||||||
while path:
|
while path:
|
||||||
ns = path.pop()
|
ns = path.pop()
|
||||||
current_ns = current_path.pop() if current_path else None
|
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]
|
extra, resolver = resolver.namespace_dict[ns]
|
||||||
resolved_path.append(ns)
|
resolved_path.append(ns)
|
||||||
ns_pattern = ns_pattern + extra
|
ns_pattern = ns_pattern + extra
|
||||||
|
ns_converters.update(resolver.pattern.converters)
|
||||||
except KeyError as key:
|
except KeyError as key:
|
||||||
if resolved_path:
|
if resolved_path:
|
||||||
raise NoReverseMatch(
|
raise NoReverseMatch(
|
||||||
|
@ -83,7 +85,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
|
||||||
else:
|
else:
|
||||||
raise NoReverseMatch("%s is not a registered namespace" % key)
|
raise NoReverseMatch("%s is not a registered namespace" % key)
|
||||||
if ns_pattern:
|
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))
|
return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
|
||||||
|
|
||||||
|
|
|
@ -68,11 +68,13 @@ def get_resolver(urlconf=None):
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=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.
|
# Build a namespaced resolver for the given parent URLconf pattern.
|
||||||
# This makes it possible to have captured parameters in the parent
|
# This makes it possible to have captured parameters in the parent
|
||||||
# URLconf pattern.
|
# 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])
|
return URLResolver(RegexPattern(r'^/'), [ns_resolver])
|
||||||
|
|
||||||
|
|
||||||
|
@ -439,7 +441,7 @@ class URLResolver:
|
||||||
new_matches,
|
new_matches,
|
||||||
p_pattern + pat,
|
p_pattern + pat,
|
||||||
{**defaults, **url_pattern.default_kwargs},
|
{**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():
|
for namespace, (prefix, sub_pattern) in url_pattern.namespace_dict.items():
|
||||||
|
|
|
@ -11,3 +11,6 @@ Bugfixes
|
||||||
|
|
||||||
* Fixed a regression that broke custom template filters that use decorators
|
* Fixed a regression that broke custom template filters that use decorators
|
||||||
(:ticket:`29400`).
|
(:ticket:`29400`).
|
||||||
|
|
||||||
|
* Fixed detection of custom URL converters in included patterns
|
||||||
|
(:ticket:`29415`).
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
from django.urls import path, register_converter
|
from django.urls import include, path, register_converter
|
||||||
|
|
||||||
from . import converters, views
|
from . import converters, views
|
||||||
|
|
||||||
register_converter(converters.Base64Converter, 'base64')
|
register_converter(converters.Base64Converter, 'base64')
|
||||||
|
|
||||||
|
subpatterns = [
|
||||||
|
path('<base64:value>/', views.empty_view, name='subpattern-base64'),
|
||||||
|
]
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('base64/<base64:value>/', views.empty_view, name='base64'),
|
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'))),
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,6 +8,15 @@ from django.urls import Resolver404, path, resolve, reverse
|
||||||
from .converters import DynamicConverter
|
from .converters import DynamicConverter
|
||||||
from .views import empty_view
|
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')
|
@override_settings(ROOT_URLCONF='urlpatterns.path_urls')
|
||||||
class SimplifiedURLTests(SimpleTestCase):
|
class SimplifiedURLTests(SimpleTestCase):
|
||||||
|
@ -44,15 +53,22 @@ class SimplifiedURLTests(SimpleTestCase):
|
||||||
self.assertEqual(url, '/articles/2015/4/12/')
|
self.assertEqual(url, '/articles/2015/4/12/')
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls')
|
@override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls')
|
||||||
def test_non_identical_converter_resolve(self):
|
def test_converter_resolve(self):
|
||||||
match = resolve('/base64/aGVsbG8=/') # base64 of 'hello'
|
for url, (url_name, app_name, kwargs) in converter_test_data:
|
||||||
self.assertEqual(match.url_name, 'base64')
|
with self.subTest(url=url):
|
||||||
self.assertEqual(match.kwargs, {'value': b'hello'})
|
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')
|
@override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls')
|
||||||
def test_non_identical_converter_reverse(self):
|
def test_converter_reverse(self):
|
||||||
url = reverse('base64', kwargs={'value': b'hello'})
|
for expected, (url_name, app_name, kwargs) in converter_test_data:
|
||||||
self.assertEqual(url, '/base64/aGVsbG8=/')
|
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):
|
def test_path_inclusion_is_matchable(self):
|
||||||
match = resolve('/included_urls/extra/something/')
|
match = resolve('/included_urls/extra/something/')
|
||||||
|
|
Loading…
Reference in New Issue