[3.0.x] Fixed #31061 -- Ignored positional args in django.urls.resolve() when all optional named parameters are missing.

Regression in 76b993a117.

Thanks Claude Paroz for the report and Carlton Gibson for reviews.
Backport of 82a88d2f48 from master
This commit is contained in:
Mariusz Felisiak 2019-12-06 09:32:51 +01:00
parent 6ede5a3cb7
commit e986e49e66
5 changed files with 22 additions and 2 deletions

View File

@ -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

View File

@ -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`).

View File

@ -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

View File

@ -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'),
]

View File

@ -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')