Fixed #27367 -- Doc'd and tested reversing of URLs with the same name.

Thanks Reinout van Rees for contributing to the patch.
This commit is contained in:
Robert Roskam 2016-11-05 10:48:31 -04:00 committed by Tim Graham
parent fb5bd38e3b
commit 98bcc5d81b
3 changed files with 54 additions and 7 deletions

View File

@ -597,15 +597,28 @@ In order to perform URL reversing, you'll need to use **named URL patterns**
as done in the examples above. The string used for the URL name can contain any
characters you like. You are not restricted to valid Python names.
When you name your URL patterns, make sure you use names that are unlikely
to clash with any other application's choice of names. If you call your URL
pattern ``comment``, and another application does the same thing, there's
no guarantee which URL will be inserted into your template when you use
this name.
When naming URL patterns, choose names that are unlikely to clash with other
applications' choice of names. If you call your URL pattern ``comment``
and another application does the same thing, the URL that
:func:`~django.urls.reverse()` finds depends on whichever pattern is last in
your project's ``urlpatterns`` list.
Putting a prefix on your URL names, perhaps derived from the application
name, will decrease the chances of collision. We recommend something like
``myapp-comment`` instead of ``comment``.
name (such as ``myapp-comment`` instead of ``comment``), decreases the chance
of collision.
You can deliberately choose the *same URL name* as another application if you
want to override a view. For example, a common use case is to override the
:class:`~django.contrib.auth.views.LoginView`. Parts of Django and most
third-party apps assume that this view has a URL pattern with the name
``login``. If you have a custom login view and give its URL the name ``login``,
:func:`~django.urls.reverse()` will find your custom view as long as it's in
``urlpatterns`` after ``django.contrib.auth.urls`` is included (if that's
included at all).
You may also use the same name for multiple URL patterns if they differ in
their arguments. In addition to the URL name, :func:`~django.urls.reverse()`
matches the number of arguments and the names of the keyword arguments.
.. _topics-http-defining-url-namespaces:

View File

@ -0,0 +1,17 @@
from django.conf.urls import url
from .views import empty_view
urlpatterns = [
# No kwargs
url(r'^conflict/cannot-go-here/$', empty_view, name='name-conflict'),
url(r'^conflict/$', empty_view, name='name-conflict'),
# One kwarg
url(r'^conflict-first/(?P<first>\w+)/$', empty_view, name='name-conflict'),
url(r'^conflict-cannot-go-here/(?P<middle>\w+)/$', empty_view, name='name-conflict'),
url(r'^conflict-middle/(?P<middle>\w+)/$', empty_view, name='name-conflict'),
url(r'^conflict-last/(?P<last>\w+)/$', empty_view, name='name-conflict'),
# Two kwargs
url(r'^conflict/(?P<another>\w+)/(?P<extra>\w+)/cannot-go-here/$', empty_view, name='name-conflict'),
url(r'^conflict/(?P<extra>\w+)/(?P<another>\w+)/$', empty_view, name='name-conflict'),
]

View File

@ -386,6 +386,23 @@ class ResolverTests(SimpleTestCase):
self.assertEqual(resolver.reverse('named-url2', 'arg'), 'extra/arg/')
self.assertEqual(resolver.reverse('named-url2', extra='arg'), 'extra/arg/')
def test_resolver_reverse_conflict(self):
"""
url() name arguments don't need to be unique. The last registered
pattern takes precedence for conflicting names.
"""
resolver = get_resolver('urlpatterns_reverse.named_urls_conflict')
# Without arguments, the last URL in urlpatterns has precedence.
self.assertEqual(resolver.reverse('name-conflict'), 'conflict/')
# With an arg, the last URL in urlpatterns has precedence.
self.assertEqual(resolver.reverse('name-conflict', 'arg'), 'conflict-last/arg/')
# With a kwarg, other url()s can be reversed.
self.assertEqual(resolver.reverse('name-conflict', first='arg'), 'conflict-first/arg/')
self.assertEqual(resolver.reverse('name-conflict', middle='arg'), 'conflict-middle/arg/')
self.assertEqual(resolver.reverse('name-conflict', last='arg'), 'conflict-last/arg/')
# The number and order of the arguments don't interfere with reversing.
self.assertEqual(resolver.reverse('name-conflict', 'arg', 'arg'), 'conflict/arg/arg/')
def test_non_regex(self):
"""
A Resolver404 is raised if resolving doesn't meet the basic