From e986e49e66c23c26012749e1a134cba1d6d0bfdd Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 6 Dec 2019 09:32:51 +0100 Subject: [PATCH] [3.0.x] Fixed #31061 -- Ignored positional args in django.urls.resolve() when all optional named parameters are missing. Regression in 76b993a117b61c41584e95149a67d8a1e9f49dd1. Thanks Claude Paroz for the report and Carlton Gibson for reviews. Backport of 82a88d2f48e13ef5d472741d5ed1c183230cfe4c from master --- django/urls/resolvers.py | 3 ++- docs/releases/3.0.1.txt | 4 ++++ docs/topics/http/urls.txt | 2 +- tests/urlpatterns/path_urls.py | 5 +++++ tests/urlpatterns/tests.py | 10 ++++++++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py index 2ff8b2c775..19d0d98986 100644 --- a/django/urls/resolvers.py +++ b/django/urls/resolvers.py @@ -158,8 +158,9 @@ class RegexPattern(CheckURLMixin): # If there are any named groups, use those as kwargs, ignoring # non-named groups. Otherwise, pass all non-named arguments as # positional arguments. - kwargs = {k: v for k, v in match.groupdict().items() if v is not None} + kwargs = match.groupdict() args = () if kwargs else match.groups() + kwargs = {k: v for k, v in kwargs.items() if v is not None} return path[match.end():], args, kwargs return None diff --git a/docs/releases/3.0.1.txt b/docs/releases/3.0.1.txt index 589ef40499..cdfdc33e40 100644 --- a/docs/releases/3.0.1.txt +++ b/docs/releases/3.0.1.txt @@ -13,3 +13,7 @@ Bugfixes inside Jupyter and other environments that force an async context, by adding and option to disable :ref:`async-safety` mechanism with ``DJANGO_ALLOW_ASYNC_UNSAFE`` environment variable (:ticket:`31056`). + +* Fixed a regression in Django 3.0 where ``RegexPattern``, used by + :func:`~django.urls.re_path`, returned positional arguments to be passed to + the view when all optional named groups were missing (:ticket:`31061`). diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 4283d6ebe1..5c1540b809 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -53,7 +53,7 @@ algorithm the system follows to determine which Python code to execute: arguments: * An instance of :class:`~django.http.HttpRequest`. - * If the matched URL pattern returned no named groups, then the + * If the matched URL pattern contained no named groups, then the matches from the regular expression are provided as positional arguments. * The keyword arguments are made up of any named parts matched by the path expression, overridden by any arguments specified in the optional diff --git a/tests/urlpatterns/path_urls.py b/tests/urlpatterns/path_urls.py index b40801b39d..afc15f30af 100644 --- a/tests/urlpatterns/path_urls.py +++ b/tests/urlpatterns/path_urls.py @@ -12,6 +12,11 @@ urlpatterns = [ path('included_urls/', include('urlpatterns.included_urls')), re_path(r'^regex/(?P[0-9]+)/$', views.empty_view, name='regex'), re_path(r'^regex_optional/(?P\d+)/(?:(?P\d+)/)?', views.empty_view, name='regex_optional'), + re_path( + r'^regex_only_optional/(?:(?P\d+)/)?', + views.empty_view, + name='regex_only_optional', + ), path('', include('urlpatterns.more_urls')), path('//', views.empty_view, name='lang-and-path'), ] diff --git a/tests/urlpatterns/tests.py b/tests/urlpatterns/tests.py index 92c4e6399e..b149e0d512 100644 --- a/tests/urlpatterns/tests.py +++ b/tests/urlpatterns/tests.py @@ -68,6 +68,16 @@ class SimplifiedURLTests(SimpleTestCase): r'^regex_optional/(?P\d+)/(?:(?P\d+)/)?', ) + def test_re_path_with_missing_optional_parameter(self): + match = resolve('/regex_only_optional/') + self.assertEqual(match.url_name, 'regex_only_optional') + self.assertEqual(match.kwargs, {}) + self.assertEqual(match.args, ()) + self.assertEqual( + match.route, + r'^regex_only_optional/(?:(?P\d+)/)?', + ) + def test_path_lookup_with_inclusion(self): match = resolve('/included_urls/extra/something/') self.assertEqual(match.url_name, 'inner-extra')