diff --git a/django/contrib/flatpages/urls.py b/django/contrib/flatpages/urls.py index 4b5b9aed34..3437e823b2 100644 --- a/django/contrib/flatpages/urls.py +++ b/django/contrib/flatpages/urls.py @@ -2,5 +2,5 @@ from django.conf.urls import url from django.contrib.flatpages import views urlpatterns = [ - url(r'^(?P.*)$', views.flatpage), + url(r'^(?P.*)$', views.flatpage, name='django.contrib.flatpages.views.flatpage'), ] diff --git a/django/contrib/messages/tests/base.py b/django/contrib/messages/tests/base.py index 9d663bbf7a..e56ae971e8 100644 --- a/django/contrib/messages/tests/base.py +++ b/django/contrib/messages/tests/base.py @@ -158,10 +158,9 @@ class BaseTests(object): data = { 'messages': ['Test message %d' % x for x in range(5)], } - show_url = reverse('django.contrib.messages.tests.urls.show') + show_url = reverse('show_message') for level in ('debug', 'info', 'success', 'warning', 'error'): - add_url = reverse('django.contrib.messages.tests.urls.add', - args=(level,)) + add_url = reverse('add_message', args=(level,)) response = self.client.post(add_url, data, follow=True) self.assertRedirects(response, show_url) self.assertTrue('messages' in response.context) @@ -175,10 +174,9 @@ class BaseTests(object): data = { 'messages': ['Test message %d' % x for x in range(5)], } - show_url = reverse('django.contrib.messages.tests.urls.show_template_response') + show_url = reverse('show_template_response') for level in self.levels.keys(): - add_url = reverse('django.contrib.messages.tests.urls.add_template_response', - args=(level,)) + add_url = reverse('add_template_response', args=(level,)) response = self.client.post(add_url, data, follow=True) self.assertRedirects(response, show_url) self.assertTrue('messages' in response.context) @@ -191,7 +189,7 @@ class BaseTests(object): self.assertNotContains(response, msg) def test_context_processor_message_levels(self): - show_url = reverse('django.contrib.messages.tests.urls.show_template_response') + show_url = reverse('show_template_response') response = self.client.get(show_url) self.assertTrue('DEFAULT_MESSAGE_LEVELS' in response.context) @@ -206,12 +204,11 @@ class BaseTests(object): data = { 'messages': ['Test message %d' % x for x in range(5)], } - show_url = reverse('django.contrib.messages.tests.urls.show') + show_url = reverse('show_message') messages = [] for level in ('debug', 'info', 'success', 'warning', 'error'): messages.extend([Message(self.levels[level], msg) for msg in data['messages']]) - add_url = reverse('django.contrib.messages.tests.urls.add', - args=(level,)) + add_url = reverse('add_message', args=(level,)) self.client.post(add_url, data) response = self.client.get(show_url) self.assertTrue('messages' in response.context) @@ -233,10 +230,9 @@ class BaseTests(object): data = { 'messages': ['Test message %d' % x for x in range(5)], } - reverse('django.contrib.messages.tests.urls.show') + reverse('show_message') for level in ('debug', 'info', 'success', 'warning', 'error'): - add_url = reverse('django.contrib.messages.tests.urls.add', - args=(level,)) + add_url = reverse('add_message', args=(level,)) self.assertRaises(MessageFailure, self.client.post, add_url, data, follow=True) @@ -254,10 +250,9 @@ class BaseTests(object): 'messages': ['Test message %d' % x for x in range(5)], 'fail_silently': True, } - show_url = reverse('django.contrib.messages.tests.urls.show') + show_url = reverse('show_message') for level in ('debug', 'info', 'success', 'warning', 'error'): - add_url = reverse('django.contrib.messages.tests.urls.add', - args=(level,)) + add_url = reverse('add_message', args=(level,)) response = self.client.post(add_url, data, follow=True) self.assertRedirects(response, show_url) self.assertFalse('messages' in response.context) diff --git a/django/contrib/messages/tests/urls.py b/django/contrib/messages/tests/urls.py index f0ee16b1fb..b551cc6511 100644 --- a/django/contrib/messages/tests/urls.py +++ b/django/contrib/messages/tests/urls.py @@ -33,7 +33,7 @@ def add(request, message_type): else: getattr(messages, message_type)(request, msg) - show_url = reverse('django.contrib.messages.tests.urls.show') + show_url = reverse('show_message') return HttpResponseRedirect(show_url) @@ -42,7 +42,7 @@ def add_template_response(request, message_type): for msg in request.POST.getlist('messages'): getattr(messages, message_type)(request, msg) - show_url = reverse('django.contrib.messages.tests.urls.show_template_response') + show_url = reverse('show_template_response') return HttpResponseRedirect(show_url) @@ -69,9 +69,9 @@ class ContactFormViewWithMsg(SuccessMessageMixin, FormView): urlpatterns = [ - url('^add/(debug|info|success|warning|error)/$', add), + url('^add/(debug|info|success|warning|error)/$', add, name='add_message'), url('^add/msg/$', ContactFormViewWithMsg.as_view(), name='add_success_msg'), - url('^show/$', show), - url('^template_response/add/(debug|info|success|warning|error)/$', add_template_response), - url('^template_response/show/$', show_template_response), + url('^show/$', show, name='show_message'), + url('^template_response/add/(debug|info|success|warning|error)/$', add_template_response, name='add_template_response'), + url('^template_response/show/$', show_template_response, name='show_template_response'), ] diff --git a/django/contrib/sitemaps/tests/test_https.py b/django/contrib/sitemaps/tests/test_https.py index 1ded723d74..3de6b7bb35 100644 --- a/django/contrib/sitemaps/tests/test_https.py +++ b/django/contrib/sitemaps/tests/test_https.py @@ -1,8 +1,10 @@ from __future__ import unicode_literals from datetime import date +import warnings from django.test import override_settings +from django.utils.deprecation import RemovedInDjango20Warning from .base import SitemapTestsBase @@ -38,7 +40,15 @@ class HTTPSDetectionSitemapTests(SitemapTestsBase): def test_sitemap_index_with_https_request(self): "A sitemap index requested in HTTPS is rendered with HTTPS links" - response = self.client.get('/simple/index.xml', **self.extra) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=RemovedInDjango20Warning) + # The URL for views.sitemap in tests/urls/http.py has been updated + # with a name but since reversing by Python path is tried first + # before reversing by name and works since we're giving + # name='django.contrib.sitemaps.views.sitemap', we need to silence + # the erroneous warning until reversing by dotted path is removed. + # The test will work without modification when it's removed. + response = self.client.get('/simple/index.xml', **self.extra) expected_content = """ %s/simple/sitemap-simple.xml diff --git a/django/contrib/sitemaps/tests/urls/http.py b/django/contrib/sitemaps/tests/urls/http.py index 1ec292ce9e..50418683d8 100644 --- a/django/contrib/sitemaps/tests/urls/http.py +++ b/django/contrib/sitemaps/tests/urls/http.py @@ -72,7 +72,7 @@ urlpatterns = [ url(r'^simple/custom-index\.xml$', views.index, {'sitemaps': simple_sitemaps, 'template_name': 'custom_sitemap_index.xml'}), url(r'^simple/sitemap-(?P
.+)\.xml$', views.sitemap, - {'sitemaps': simple_sitemaps}), + {'sitemaps': simple_sitemaps}, name='django.contrib.sitemaps.views.sitemap'), url(r'^simple/sitemap\.xml$', views.sitemap, {'sitemaps': simple_sitemaps}), url(r'^simple/custom-sitemap\.xml$', views.sitemap, {'sitemaps': simple_sitemaps, 'template_name': 'custom_sitemap.xml'}), diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 2f05571687..dd98b7bb03 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -12,10 +12,12 @@ import functools from importlib import import_module import re from threading import local +import warnings from django.http import Http404 from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.utils.datastructures import MultiValueDict +from django.utils.deprecation import RemovedInDjango20Warning from django.utils.encoding import force_str, force_text, iri_to_uri from django.utils.functional import lazy from django.utils.http import urlquote @@ -424,11 +426,18 @@ class RegexURLResolver(LocaleRegexProvider): if not self._populated: self._populate() + original_lookup = lookup_view try: if lookup_view in self._callback_strs: lookup_view = get_callable(lookup_view, True) except (ImportError, AttributeError) as e: raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e)) + else: + if not callable(original_lookup) and callable(lookup_view): + warnings.warn( + 'Reversing by dotted path is deprecated (%s).' % original_lookup, + RemovedInDjango20Warning, stacklevel=3 + ) possibilities = self.reverse_dict.getlist(lookup_view) prefix_norm, prefix_args = normalize(urlquote(_prefix))[0] diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 9ccbf440c4..3cbd1e3173 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -32,6 +32,9 @@ about each item can often be found in the release notes of two versions prior. and migrations will become compulsory for all apps. This includes automatic loading of fixtures and support for initial SQL data. +* The ability to :func:`~django.core.urlresolvers.reverse` URLs using a dotted + Python path will be removed. + .. _deprecation-removed-in-1.9: 1.9 diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index 12ed61b97a..8c48651054 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -54,7 +54,8 @@ Initialization To activate sitemap generation on your Django site, add this line to your :doc:`URLconf `:: - (r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}) + url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', + {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap') This tells Django to build a sitemap when a client accesses :file:`/sitemap.xml`. @@ -284,7 +285,8 @@ Here's an example of a :doc:`URLconf ` using both:: # ... # the sitemap - url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}), + url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', + {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'), ] .. _URLconf: ../url_dispatch/ @@ -325,7 +327,8 @@ the sitemap. For example:: url(r'^about/$', 'views.about', name='about'), url(r'^license/$', 'views.license', name='license'), # ... - url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}) + url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', + {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap') ] diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 0e1266e0fb..9d4093e4da 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -982,7 +982,7 @@ resulting path will be encoded using :func:`~django.utils.encoding.iri_to_uri`. This is a way to output links without violating the DRY principle by having to hard-code URLs in your templates:: - {% url 'path.to.some_view' v1 v2 %} + {% url 'some-url-name' v1 v2 %} The first argument is a path to a view function in the format ``package.package.module.function``. It can be a quoted literal or any other @@ -991,7 +991,7 @@ should be space-separated values that will be used as arguments in the URL. The example above shows passing positional arguments. Alternatively you may use keyword syntax:: - {% url 'path.to.some_view' arg1=v1 arg2=v2 %} + {% url 'some-url-name' arg1=v1 arg2=v2 %} Do not mix both positional and keyword syntax in a single call. All arguments required by the URLconf should be present. @@ -1002,7 +1002,7 @@ takes a client ID (here, ``client()`` is a method inside the views file .. code-block:: python - ('^client/([0-9]+)/$', 'app_views.client') + ('^client/([0-9]+)/$', 'app_views.client', name='app-views-client') If this app's URLconf is included into the project's URLconf under a path such as this: @@ -1013,7 +1013,7 @@ such as this: ...then, in a template, you can create a link to this view like this:: - {% url 'app_views.client' client.id %} + {% url 'app-views-client' client.id %} The template tag will output the string ``/clients/client/123/``. @@ -1028,7 +1028,7 @@ cause your site to display an error page. If you'd like to retrieve a URL without displaying it, you can use a slightly different call:: - {% url 'path.to.view' arg arg2 as the_url %} + {% url 'some-url-name' arg arg2 as the_url %} I'm linking to {{ the_url }} @@ -1038,7 +1038,7 @@ The scope of the variable created by the ``as var`` syntax is the This ``{% url ... as var %}`` syntax will *not* cause an error if the view is missing. In practice you'll use this to link to views that are optional:: - {% url 'path.to.view' as the_url %} + {% url 'some-url-name' as the_url %} {% if the_url %} Link to optional stuff {% endif %} @@ -1051,6 +1051,13 @@ This will follow the normal :ref:`namespaced URL resolution strategy `, including using any hints provided by the context as to the current application. +.. deprecated:: 1.8 + + The dotted Python path syntax is deprecated and will be removed in + Django 2.0:: + + {% url 'path.to.some_view' v1 v2 %} + .. warning:: Don't forget to put quotes around the function path or pattern name, diff --git a/docs/ref/urlresolvers.txt b/docs/ref/urlresolvers.txt index 491c4cd14e..79c219a8d8 100644 --- a/docs/ref/urlresolvers.txt +++ b/docs/ref/urlresolvers.txt @@ -16,13 +16,12 @@ your code, Django provides the following function: :ref:`URL pattern name `, or the callable view object. For example, given the following ``url``:: - url(r'^archive/$', 'news.views.archive', name='news_archive') + from news import views + + url(r'^archive/$', views.archive, name='news_archive') you can use any of the following to reverse the URL:: - # using the Python path - reverse('news.views.archive') - # using the named URL reverse('news_archive') @@ -63,6 +62,10 @@ namespaces into URLs on specific application instances, according to the The ``urlconf`` argument is the URLconf module containing the url patterns to use for reversing. By default, the root URLconf for the current thread is used. +.. deprecated:: 1.8 + + The ability to reverse using the Python path, e.g. + ``reverse('news.views.archive')``, has been deprecated. .. admonition:: Make sure your views are all correct. diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index f716c995ca..0852acb399 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -323,3 +323,21 @@ Using an incorrect count of unpacked values in the :ttag:`for` template tag Using an incorrect count of unpacked values in :ttag:`for` tag will raise an exception rather than fail silently in Django 2.0. + +Passing a dotted path to :func:`~django.core.urlresolvers.reverse()` and :ttag:`url` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Reversing URLs by Python path is an expensive operation as it causes the +path being reversed to be imported. This behavior has also resulted in a +`security issue`_. Use :ref:`named URL patterns ` +for reversing instead. + +If you are using :mod:`django.contrib.sitemaps`, add the ``name`` argument to +the ``url`` that references :func:`django.contrib.sitemaps.views.sitemap`: + + url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', + {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap') + +to ensure compatibility when reversing by Python path is removed in Django 2.0. + +.. _security issue: https://www.djangoproject.com/weblog/2014/apr/21/security/#s-issue-unexpected-code-execution-using-reverse diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 3833021956..39c96b2e02 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -555,7 +555,7 @@ Consider again this URLconf entry:: urlpatterns = [ #... - url(r'^articles/([0-9]{4})/$', 'news.views.year_archive'), + url(r'^articles/([0-9]{4})/$', 'news.views.year_archive', name='news-year-archive'), #... ] @@ -566,11 +566,11 @@ You can obtain these in template code by using: .. code-block:: html+django - 2012 Archive + 2012 Archive {# Or with the year in a template context variable: #} @@ -583,7 +583,7 @@ Or in Python code:: # ... year = 2006 # ... - return HttpResponseRedirect(reverse('news.views.year_archive', args=(year,))) + return HttpResponseRedirect(reverse('news-year-archive', args=(year,))) If, for some reason, it was decided that the URLs where content for yearly article archives are published at should be changed then you would only need to @@ -599,65 +599,19 @@ URLs. Read the next section to know about the solution Django provides for this. Naming URL patterns =================== -It's fairly common to use the same view function in multiple URL patterns in -your URLconf. For example, these two URL patterns both point to the ``archive`` -view:: +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. - from django.conf.urls import url - from mysite.views import archive +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. - urlpatterns = [ - url(r'^archive/([0-9]{4})/$', archive), - url(r'^archive-summary/([0-9]{4})/$', archive, {'summary': True}), - ] - -This is completely valid, but it leads to problems when you try to do reverse -URL matching (through the :func:`~django.core.urlresolvers.reverse` function -or the :ttag:`url` template tag). Continuing this example, if you wanted to -retrieve the URL for the ``archive`` view, Django's reverse URL matcher would -get confused, because *two* URL patterns point at that view. - -To solve this problem, Django supports **named URL patterns**. That is, you can -give a name to a URL pattern in order to distinguish it from other patterns -using the same view and parameters. Then, you can use this name in reverse URL -matching. - -Here's the above example, rewritten to use named URL patterns:: - - from django.conf.urls import url - from mysite.views import archive - - urlpatterns = [ - url(r'^archive/([0-9]{4})/$', archive, name="full-archive"), - url(r'^archive-summary/([0-9]{4})/$', archive, {'summary': True}, name="arch-summary"), - ] - -With these names in place (``full-archive`` and ``arch-summary``), you can -target each pattern individually by using its name: - -.. code-block:: html+django - - {% url 'arch-summary' 1945 %} - {% url 'full-archive' 2007 %} - -Even though both URL patterns refer to the ``archive`` view here, using the -``name`` parameter to :func:`django.conf.urls.url` allows you to tell them -apart in templates. - -The string used for the URL name can contain any characters you like. You are -not restricted to valid Python names. - -.. note:: - - 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. - - 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``. +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``. .. _topics-http-defining-url-namespaces: diff --git a/tests/resolve_url/tests.py b/tests/resolve_url/tests.py index 542d325e8e..4ea68e460b 100644 --- a/tests/resolve_url/tests.py +++ b/tests/resolve_url/tests.py @@ -1,9 +1,11 @@ from __future__ import unicode_literals +import warnings from django.core.urlresolvers import NoReverseMatch from django.contrib.auth.views import logout from django.shortcuts import resolve_url from django.test import TestCase, override_settings +from django.utils.deprecation import RemovedInDjango20Warning from .models import UnimportantThing @@ -60,8 +62,10 @@ class ResolveUrlTests(TestCase): Tests that passing a view function to ``resolve_url`` will result in the URL path mapping to that view. """ - resolved_url = resolve_url('django.contrib.auth.views.logout') - self.assertEqual('/accounts/logout/', resolved_url) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=RemovedInDjango20Warning) + resolved_url = resolve_url('django.contrib.auth.views.logout') + self.assertEqual('/accounts/logout/', resolved_url) def test_domain(self): """ diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py index b81e63545d..b418cdac11 100644 --- a/tests/template_tests/tests.py +++ b/tests/template_tests/tests.py @@ -529,8 +529,7 @@ class TemplateTests(TestCase): # Warm the URL reversing cache. This ensures we don't pay the cost # warming the cache during one of the tests. - urlresolvers.reverse('template_tests.views.client_action', - kwargs={'id': 0, 'action': "update"}) + urlresolvers.reverse('named.client', args=(0,)) for name, vals in tests: if isinstance(vals[2], tuple): @@ -575,6 +574,7 @@ class TemplateTests(TestCase): try: with warnings.catch_warnings(): # Ignore deprecations of using the wrong number of variables with the 'for' tag. + # and warnings for {% url %} reversing by dotted path warnings.filterwarnings("ignore", category=RemovedInDjango20Warning, module="django.template.defaulttags") output = self.render(test_template, vals) except ShouldNotExecuteException: diff --git a/tests/urlpatterns_reverse/erroneous_urls.py b/tests/urlpatterns_reverse/erroneous_urls.py index 966b219e21..2fec86f6cc 100644 --- a/tests/urlpatterns_reverse/erroneous_urls.py +++ b/tests/urlpatterns_reverse/erroneous_urls.py @@ -1,18 +1,26 @@ from django.conf.urls import url +from . import views + urlpatterns = [ # View has erroneous import - url(r'erroneous_inner/$', 'urlpatterns_reverse.views.erroneous_view'), + url(r'erroneous_inner/$', views.erroneous_view), # Module has erroneous import + # Remove in Django 2.0 along with erroneous_views_module as this is only + # an issue with string in urlpatterns url(r'erroneous_outer/$', 'urlpatterns_reverse.erroneous_views_module.erroneous_view'), # Module is an unqualified string url(r'erroneous_unqualified/$', 'unqualified_view'), # View does not exist + # Remove in Django 2.0 along with erroneous_views_module as this is only + # an issue with string in urlpatterns url(r'missing_inner/$', 'urlpatterns_reverse.views.missing_view'), # View is not callable + # Remove in Django 2.0 along with erroneous_views_module as this is only + # an issue with string in urlpatterns url(r'uncallable/$', 'urlpatterns_reverse.views.uncallable'), # Module does not exist url(r'missing_outer/$', 'urlpatterns_reverse.missing_module.missing_view'), # Regex contains an error (refs #6170) - url(r'(regex_error/$', 'regressiontestes.urlpatterns_reverse.views.empty_view'), + url(r'(regex_error/$', views.empty_view), ] diff --git a/tests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py index d458d052e6..ebe04393df 100644 --- a/tests/urlpatterns_reverse/tests.py +++ b/tests/urlpatterns_reverse/tests.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import sys import unittest +import warnings from django.contrib.auth.models import User from django.conf import settings @@ -17,6 +18,7 @@ from django.http import HttpRequest, HttpResponseRedirect, HttpResponsePermanent from django.shortcuts import redirect from django.test import TestCase, override_settings from django.utils import six +from django.utils.deprecation import RemovedInDjango20Warning from admin_scripts.tests import AdminScriptTestCase @@ -173,13 +175,15 @@ class NoURLPatternsTests(TestCase): class URLPatternReverse(TestCase): def test_urlpattern_reverse(self): - for name, expected, args, kwargs in test_data: - try: - got = reverse(name, args=args, kwargs=kwargs) - except NoReverseMatch: - self.assertEqual(expected, NoReverseMatch) - else: - self.assertEqual(got, expected) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=RemovedInDjango20Warning) + for name, expected, args, kwargs in test_data: + try: + got = reverse(name, args=args, kwargs=kwargs) + except NoReverseMatch: + self.assertEqual(expected, NoReverseMatch) + else: + self.assertEqual(got, expected) def test_reverse_none(self): # Reversing None should raise an error, not return the last un-named view. @@ -373,8 +377,10 @@ class ReverseShortcutTests(TestCase): def test_reverse_by_path_nested(self): # Views that are added to urlpatterns using include() should be - # reversible by dotted path. - self.assertEqual(reverse('urlpatterns_reverse.views.nested_view'), '/includes/nested_path/') + # reversible by doted path. + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=RemovedInDjango20Warning) + self.assertEqual(reverse('urlpatterns_reverse.views.nested_view'), '/includes/nested_path/') def test_redirect_view_object(self): from .views import absolute_kwargs_view