summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Roskam <raiderrobert@gmail.com>2016-11-05 10:48:31 -0400
committerTim Graham <timograham@gmail.com>2017-02-11 08:58:40 -0500
commit98bcc5d81bca578f3a5b4d47907ba4ac40446887 (patch)
tree16f686641a20a3068ddddae879bb8006db3493e4
parentfb5bd38e3b83c7f0d1011de80f922fc34faf740b (diff)
Fixed #27367 -- Doc'd and tested reversing of URLs with the same name.
Thanks Reinout van Rees for contributing to the patch.
-rw-r--r--docs/topics/http/urls.txt27
-rw-r--r--tests/urlpatterns_reverse/named_urls_conflict.py17
-rw-r--r--tests/urlpatterns_reverse/tests.py17
3 files changed, 54 insertions, 7 deletions
diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt
index 59b3b698f1..6e891e72f3 100644
--- a/docs/topics/http/urls.txt
+++ b/docs/topics/http/urls.txt
@@ -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:
diff --git a/tests/urlpatterns_reverse/named_urls_conflict.py b/tests/urlpatterns_reverse/named_urls_conflict.py
new file mode 100644
index 0000000000..0c0c6eb47e
--- /dev/null
+++ b/tests/urlpatterns_reverse/named_urls_conflict.py
@@ -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'),
+]
diff --git a/tests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py
index c4c1c9775d..f013d30e15 100644
--- a/tests/urlpatterns_reverse/tests.py
+++ b/tests/urlpatterns_reverse/tests.py
@@ -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