Browse Source

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.
Mariusz Felisiak 5 years ago
parent
commit
82a88d2f48

+ 2 - 1
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
 

+ 4 - 0
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`).

+ 1 - 1
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

+ 5 - 0
tests/urlpatterns/path_urls.py

@@ -12,6 +12,11 @@ urlpatterns = [
     path('included_urls/', include('urlpatterns.included_urls')),
     re_path(r'^regex/(?P<pk>[0-9]+)/$', views.empty_view, name='regex'),
     re_path(r'^regex_optional/(?P<arg1>\d+)/(?:(?P<arg2>\d+)/)?', views.empty_view, name='regex_optional'),
+    re_path(
+        r'^regex_only_optional/(?:(?P<arg1>\d+)/)?',
+        views.empty_view,
+        name='regex_only_optional',
+    ),
     path('', include('urlpatterns.more_urls')),
     path('<lang>/<path:url>/', views.empty_view, name='lang-and-path'),
 ]

+ 10 - 0
tests/urlpatterns/tests.py

@@ -68,6 +68,16 @@ class SimplifiedURLTests(SimpleTestCase):
                     r'^regex_optional/(?P<arg1>\d+)/(?:(?P<arg2>\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<arg1>\d+)/)?',
+        )
+
     def test_path_lookup_with_inclusion(self):
         match = resolve('/included_urls/extra/something/')
         self.assertEqual(match.url_name, 'inner-extra')