From bc77eb6d0858652e197c08c299efaeb06c51efee Mon Sep 17 00:00:00 2001 From: Marten Kenbeek Date: Wed, 3 Jun 2015 19:12:27 +0200 Subject: [PATCH] Fixed #24906 -- Fixed ResolverMatch.app_name for nested namespaces. Set ResolverMatch.app_name to the full path of nested application namespaces. --- django/core/urlresolvers.py | 14 +++++---- docs/ref/urlresolvers.txt | 9 ++++++ docs/releases/1.9.txt | 7 +++++ tests/urlpatterns_reverse/namespace_urls.py | 2 ++ tests/urlpatterns_reverse/tests.py | 32 +++++++++++---------- 5 files changed, 44 insertions(+), 20 deletions(-) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 6b69a3c7ef..fff758d92f 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -37,12 +37,16 @@ _urlconfs = local() class ResolverMatch(object): - def __init__(self, func, args, kwargs, url_name=None, app_name=None, namespaces=None): + def __init__(self, func, args, kwargs, url_name=None, app_names=None, namespaces=None): self.func = func self.args = args self.kwargs = kwargs self.url_name = url_name - self.app_name = app_name + + # If a URLRegexResolver doesn't have a namespace or app_name, it passes + # in an empty value. + self.app_names = [x for x in app_names if x] if app_names else [] + self.app_name = ':'.join(self.app_names) if namespaces: self.namespaces = [x for x in namespaces if x] @@ -64,8 +68,8 @@ class ResolverMatch(object): return (self.func, self.args, self.kwargs)[index] def __repr__(self): - return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name=%s, app_name=%s, namespaces=%s)" % ( - self._func_path, self.args, self.kwargs, self.url_name, self.app_name, self.namespaces) + return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name=%s, app_names=%s, namespaces=%s)" % ( + self._func_path, self.args, self.kwargs, self.url_name, self.app_names, self.namespaces) class Resolver404(Http404): @@ -395,7 +399,7 @@ class RegexURLResolver(LocaleRegexProvider): sub_match_args, sub_match_dict, sub_match.url_name, - self.app_name or sub_match.app_name, + [self.app_name] + sub_match.app_names, [self.namespace] + sub_match.namespaces ) tried.append([pattern]) diff --git a/docs/ref/urlresolvers.txt b/docs/ref/urlresolvers.txt index 79c219a8d8..60a6192a90 100644 --- a/docs/ref/urlresolvers.txt +++ b/docs/ref/urlresolvers.txt @@ -155,6 +155,15 @@ If the URL does not resolve, the function raises a The application namespace for the URL pattern that matches the URL. + .. attribute:: ResolverMatch.app_names + + .. versionadded:: 1.9 + + The list of individual namespace components in the full + application namespace for the URL pattern that matches the URL. + For example, if the ``app_name`` is ``'foo:bar'``, then ``app_names`` + will be ``['foo', 'bar']``. + .. attribute:: ResolverMatch.namespace The instance namespace for the URL pattern that matches the diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 6b42418ee4..2ee1dae246 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -597,6 +597,13 @@ Miscellaneous :attr:`~django.test.SimpleTestCase.allow_database_queries` class attribute to ``True`` on your test class. +* :attr:`ResolverMatch.app_name + ` was changed to contain + the full namespace path in the case of nested namespaces. For consistency + with :attr:`ResolverMatch.namespace + `, the empty value is now + an empty string instead of ``None``. + .. _deprecated-features-1.9: Features deprecated in 1.9 diff --git a/tests/urlpatterns_reverse/namespace_urls.py b/tests/urlpatterns_reverse/namespace_urls.py index d8a1dbc103..9efd0e9d64 100644 --- a/tests/urlpatterns_reverse/namespace_urls.py +++ b/tests/urlpatterns_reverse/namespace_urls.py @@ -48,6 +48,8 @@ urlpatterns = [ url(r'^ns-included[135]/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')), url(r'^ns-included2/', include('urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')), + url(r'^app-included/', include('urlpatterns_reverse.included_namespace_urls', 'inc-app', 'inc-app')), + url(r'^included/', include('urlpatterns_reverse.included_namespace_urls')), url(r'^inc(?P[0-9]+)/', include('urlpatterns_reverse.included_urls', namespace='inc-ns5')), url(r'^included/([0-9]+)/', include('urlpatterns_reverse.included_namespace_urls')), diff --git a/tests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py index f2137da6ad..c9378c784e 100644 --- a/tests/urlpatterns_reverse/tests.py +++ b/tests/urlpatterns_reverse/tests.py @@ -34,29 +34,29 @@ from .views import empty_view resolve_test_data = ( # These entries are in the format: (path, url_name, app_name, namespace, view_name, func, args, kwargs) # Simple case - ('/normal/42/37/', 'normal-view', None, '', 'normal-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), - ('/view_class/42/37/', 'view-class', None, '', 'view-class', views.view_class_instance, tuple(), {'arg1': '42', 'arg2': '37'}), - ('/included/normal/42/37/', 'inc-normal-view', None, '', 'inc-normal-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), - ('/included/view_class/42/37/', 'inc-view-class', None, '', 'inc-view-class', views.view_class_instance, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/normal/42/37/', 'normal-view', '', '', 'normal-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/view_class/42/37/', 'view-class', '', '', 'view-class', views.view_class_instance, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/included/normal/42/37/', 'inc-normal-view', '', '', 'inc-normal-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/included/view_class/42/37/', 'inc-view-class', '', '', 'inc-view-class', views.view_class_instance, tuple(), {'arg1': '42', 'arg2': '37'}), # Unnamed args are dropped if you have *any* kwargs in a pattern - ('/mixed_args/42/37/', 'mixed-args', None, '', 'mixed-args', views.empty_view, tuple(), {'arg2': '37'}), - ('/included/mixed_args/42/37/', 'inc-mixed-args', None, '', 'inc-mixed-args', views.empty_view, tuple(), {'arg2': '37'}), - ('/included/12/mixed_args/42/37/', 'inc-mixed-args', None, '', 'inc-mixed-args', views.empty_view, tuple(), {'arg2': '37'}), + ('/mixed_args/42/37/', 'mixed-args', '', '', 'mixed-args', views.empty_view, tuple(), {'arg2': '37'}), + ('/included/mixed_args/42/37/', 'inc-mixed-args', '', '', 'inc-mixed-args', views.empty_view, tuple(), {'arg2': '37'}), + ('/included/12/mixed_args/42/37/', 'inc-mixed-args', '', '', 'inc-mixed-args', views.empty_view, tuple(), {'arg2': '37'}), # Unnamed views should have None as the url_name. Regression data for #21157. - ('/unnamed/normal/42/37/', None, None, '', 'urlpatterns_reverse.views.empty_view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), - ('/unnamed/view_class/42/37/', None, None, '', 'urlpatterns_reverse.views.ViewClass', views.view_class_instance, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/unnamed/normal/42/37/', None, '', '', 'urlpatterns_reverse.views.empty_view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/unnamed/view_class/42/37/', None, '', '', 'urlpatterns_reverse.views.ViewClass', views.view_class_instance, tuple(), {'arg1': '42', 'arg2': '37'}), # If you have no kwargs, you get an args list. - ('/no_kwargs/42/37/', 'no-kwargs', None, '', 'no-kwargs', views.empty_view, ('42', '37'), {}), - ('/included/no_kwargs/42/37/', 'inc-no-kwargs', None, '', 'inc-no-kwargs', views.empty_view, ('42', '37'), {}), - ('/included/12/no_kwargs/42/37/', 'inc-no-kwargs', None, '', 'inc-no-kwargs', views.empty_view, ('12', '42', '37'), {}), + ('/no_kwargs/42/37/', 'no-kwargs', '', '', 'no-kwargs', views.empty_view, ('42', '37'), {}), + ('/included/no_kwargs/42/37/', 'inc-no-kwargs', '', '', 'inc-no-kwargs', views.empty_view, ('42', '37'), {}), + ('/included/12/no_kwargs/42/37/', 'inc-no-kwargs', '', '', 'inc-no-kwargs', views.empty_view, ('12', '42', '37'), {}), # Namespaces ('/test1/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns1', 'test-ns1:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), ('/included/test3/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns3', 'test-ns3:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), - ('/ns-included1/normal/42/37/', 'inc-normal-view', None, 'inc-ns1', 'inc-ns1:inc-normal-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/ns-included1/normal/42/37/', 'inc-normal-view', '', 'inc-ns1', 'inc-ns1:inc-normal-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), ('/included/test3/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns3', 'test-ns3:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), ('/default/inner/42/37/', 'urlobject-view', 'testapp', 'testapp', 'testapp:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), ('/other2/inner/42/37/', 'urlobject-view', 'nodefault', 'other-ns2', 'other-ns2:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), @@ -65,10 +65,12 @@ resolve_test_data = ( # Nested namespaces ('/ns-included1/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:test-ns3', 'inc-ns1:test-ns3:urlobject-view', views.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', 'inc-ns1:inc-ns4:inc-ns2:test-ns3:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/app-included/test3/inner/42/37/', 'urlobject-view', 'inc-app:testapp', 'inc-app:test-ns3', 'inc-app:test-ns3:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), + ('/app-included/ns-included4/ns-included2/test3/inner/42/37/', 'urlobject-view', 'inc-app:testapp', 'inc-app:inc-ns4:inc-ns2:test-ns3', 'inc-app:inc-ns4:inc-ns2:test-ns3:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}), # Namespaces capturing variables - ('/inc70/', 'inner-nothing', None, 'inc-ns5', 'inc-ns5:inner-nothing', views.empty_view, tuple(), {'outer': '70'}), - ('/inc78/extra/foobar/', 'inner-extra', None, 'inc-ns5', 'inc-ns5:inner-extra', views.empty_view, tuple(), {'outer': '78', 'extra': 'foobar'}), + ('/inc70/', 'inner-nothing', '', 'inc-ns5', 'inc-ns5:inner-nothing', views.empty_view, tuple(), {'outer': '70'}), + ('/inc78/extra/foobar/', 'inner-extra', '', 'inc-ns5', 'inc-ns5:inner-extra', views.empty_view, tuple(), {'outer': '78', 'extra': 'foobar'}), ) test_data = (