Fixed #13525 -- Added tests and docs for nested parameters in URL patterns.
When reversing, only outer parameters are used if captured parameters are nested. Added tests to check the edge cases and documentation for the behavior with an example to avoid it.
This commit is contained in:
parent
f2e4d39a71
commit
23a5d64f40
|
@ -368,6 +368,42 @@ the following example is valid::
|
||||||
In the above example, the captured ``"username"`` variable is passed to the
|
In the above example, the captured ``"username"`` variable is passed to the
|
||||||
included URLconf, as expected.
|
included URLconf, as expected.
|
||||||
|
|
||||||
|
Nested arguments
|
||||||
|
================
|
||||||
|
|
||||||
|
Regular expressions allow nested arguments, and Django will resolve them and
|
||||||
|
pass them to the view. When reversing, Django will try to fill in all outer
|
||||||
|
captured arguments, ignoring any nested captured arguments. Consider the
|
||||||
|
following URL patterns which optionally take a page argument::
|
||||||
|
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'blog/(page-(\d+)/)?$', blog_articles), # bad
|
||||||
|
url(r'comments/(?:page-(?P<page_number>\d+)/)?$', comments), # good
|
||||||
|
]
|
||||||
|
|
||||||
|
Both patterns use nested arguments and will resolve: for example,
|
||||||
|
``blog/page-2/`` will result in a match to ``blog_articles`` with two
|
||||||
|
positional arguments: ``page-2/`` and ``2``. The second pattern for
|
||||||
|
``comments`` will match ``comments/page-2/`` with keyword argument
|
||||||
|
``page_number`` set to 2. The outer argument in this case is a non-capturing
|
||||||
|
argument ``(?:...)``.
|
||||||
|
|
||||||
|
The ``blog_articles`` view needs the outermost captured argument to be reversed,
|
||||||
|
``page-2/`` or no arguments in this case, while ``comments`` can be reversed
|
||||||
|
with either no arguments or a value for ``page_number``.
|
||||||
|
|
||||||
|
Nested captured arguments create a strong coupling between the view arguments
|
||||||
|
and the URL as illustrated by ``blog_articles``: the view receives part of the
|
||||||
|
URL (``page-2/``) instead of only the value the view is interested in. This
|
||||||
|
coupling is even more pronounced when reversing, since to reverse the view we
|
||||||
|
need to pass the piece of URL instead of the page number.
|
||||||
|
|
||||||
|
As a rule of thumb, only capture the values the view needs to work with and
|
||||||
|
use non-capturing arguments when the regular expression needs an argument but
|
||||||
|
the view ignores it.
|
||||||
|
|
||||||
.. _views-extra-options:
|
.. _views-extra-options:
|
||||||
|
|
||||||
Passing extra options to view functions
|
Passing extra options to view functions
|
||||||
|
|
|
@ -97,6 +97,12 @@ test_data = (
|
||||||
('people_backref', '/people/nate-nate/', [], {'name': 'nate'}),
|
('people_backref', '/people/nate-nate/', [], {'name': 'nate'}),
|
||||||
('optional', '/optional/fred/', [], {'name': 'fred'}),
|
('optional', '/optional/fred/', [], {'name': 'fred'}),
|
||||||
('optional', '/optional/fred/', ['fred'], {}),
|
('optional', '/optional/fred/', ['fred'], {}),
|
||||||
|
('named_optional', '/optional/1/', [1], {}),
|
||||||
|
('named_optional', '/optional/1/', [], {'arg1': 1}),
|
||||||
|
('named_optional', '/optional/1/2/', [1, 2], {}),
|
||||||
|
('named_optional', '/optional/1/2/', [], {'arg1': 1, 'arg2': 2}),
|
||||||
|
('named_optional_terminated', '/optional/1/2/', [1, 2], {}),
|
||||||
|
('named_optional_terminated', '/optional/1/2/', [], {'arg1': 1, 'arg2': 2}),
|
||||||
('hardcoded', '/hardcoded/', [], {}),
|
('hardcoded', '/hardcoded/', [], {}),
|
||||||
('hardcoded2', '/hardcoded/doc.pdf', [], {}),
|
('hardcoded2', '/hardcoded/doc.pdf', [], {}),
|
||||||
('people3', '/people/il/adrian/', [], {'state': 'il', 'name': 'adrian'}),
|
('people3', '/people/il/adrian/', [], {'state': 'il', 'name': 'adrian'}),
|
||||||
|
@ -145,6 +151,17 @@ test_data = (
|
||||||
('part2', '/prefix/xx/part2/one/', [], {'value': 'one', 'prefix': 'xx'}),
|
('part2', '/prefix/xx/part2/one/', [], {'value': 'one', 'prefix': 'xx'}),
|
||||||
('part2', '/prefix/xx/part2/', [], {'prefix': 'xx'}),
|
('part2', '/prefix/xx/part2/', [], {'prefix': 'xx'}),
|
||||||
|
|
||||||
|
# Tests for nested groups. Nested capturing groups will only work if you
|
||||||
|
# *only* supply the correct outer group.
|
||||||
|
('nested-noncapture', '/nested/noncapture/opt', [], {'p': 'opt'}),
|
||||||
|
('nested-capture', '/nested/capture/opt/', ['opt/'], {}),
|
||||||
|
('nested-capture', NoReverseMatch, [], {'p': 'opt'}),
|
||||||
|
('nested-mixedcapture', '/nested/capture/mixed/opt', ['opt'], {}),
|
||||||
|
('nested-mixedcapture', NoReverseMatch, [], {'p': 'opt'}),
|
||||||
|
('nested-namedcapture', '/nested/capture/named/opt/', [], {'outer': 'opt/'}),
|
||||||
|
('nested-namedcapture', NoReverseMatch, [], {'outer': 'opt/', 'inner': 'opt'}),
|
||||||
|
('nested-namedcapture', NoReverseMatch, [], {'inner': 'opt'}),
|
||||||
|
|
||||||
# Regression for #9038
|
# Regression for #9038
|
||||||
# These views are resolved by method name. Each method is deployed twice -
|
# These views are resolved by method name. Each method is deployed twice -
|
||||||
# once with an explicit argument, and once using the default value on
|
# once with an explicit argument, and once using the default value on
|
||||||
|
|
|
@ -32,6 +32,12 @@ with warnings.catch_warnings():
|
||||||
url(r'^people/(?:name/(\w+)/)?', empty_view, name="people2a"),
|
url(r'^people/(?:name/(\w+)/)?', empty_view, name="people2a"),
|
||||||
url(r'^people/(?P<name>\w+)-(?P=name)/$', empty_view, name="people_backref"),
|
url(r'^people/(?P<name>\w+)-(?P=name)/$', empty_view, name="people_backref"),
|
||||||
url(r'^optional/(?P<name>.*)/(?:.+/)?', empty_view, name="optional"),
|
url(r'^optional/(?P<name>.*)/(?:.+/)?', empty_view, name="optional"),
|
||||||
|
url(r'^optional/(?P<arg1>\d+)/(?:(?P<arg2>\d+)/)?', absolute_kwargs_view, name="named_optional"),
|
||||||
|
url(r'^optional/(?P<arg1>\d+)/(?:(?P<arg2>\d+)/)?$', absolute_kwargs_view, name="named_optional_terminated"),
|
||||||
|
url(r'^nested/noncapture/(?:(?P<p>\w+))$', empty_view, name='nested-noncapture'),
|
||||||
|
url(r'^nested/capture/((\w+)/)?$', empty_view, name='nested-capture'),
|
||||||
|
url(r'^nested/capture/mixed/((?P<p>\w+))$', empty_view, name='nested-mixedcapture'),
|
||||||
|
url(r'^nested/capture/named/(?P<outer>(?P<inner>\w+)/)?$', empty_view, name='nested-namedcapture'),
|
||||||
url(r'^hardcoded/$', empty_view, name="hardcoded"),
|
url(r'^hardcoded/$', empty_view, name="hardcoded"),
|
||||||
url(r'^hardcoded/doc\.pdf$', empty_view, name="hardcoded2"),
|
url(r'^hardcoded/doc\.pdf$', empty_view, name="hardcoded2"),
|
||||||
url(r'^people/(?P<state>\w\w)/(?P<name>\w+)/$', empty_view, name="people3"),
|
url(r'^people/(?P<state>\w\w)/(?P<name>\w+)/$', empty_view, name="people3"),
|
||||||
|
|
Loading…
Reference in New Issue