From 785cc71d5b3300e2702b0b2fc7316e58ca70b563 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 17 Aug 2015 13:45:07 -0400 Subject: [PATCH] Refs #22384 -- Removed the ability to reverse URLs by dotted path per deprecation timeline. --- django/conf/urls/__init__.py | 4 +- django/core/urlresolvers.py | 124 ++++++------------ docs/ref/templates/builtins.txt | 7 - docs/ref/urlresolvers.txt | 23 +--- tests/gis_tests/geoapp/test_sitemaps.py | 12 +- tests/resolve_url/tests.py | 14 +- tests/sitemaps_tests/test_http.py | 24 +--- tests/sitemaps_tests/test_https.py | 17 +-- tests/template_tests/syntax_tests/test_url.py | 69 +++------- tests/template_tests/urls.py | 8 +- tests/urlpatterns_reverse/erroneous_urls.py | 29 +--- .../erroneous_views_module.py | 5 - tests/urlpatterns_reverse/tests.py | 45 ++----- tests/urlpatterns_reverse/urls.py | 6 +- tests/urlpatterns_reverse/views.py | 4 - 15 files changed, 93 insertions(+), 298 deletions(-) delete mode 100644 tests/urlpatterns_reverse/erroneous_views_module.py diff --git a/django/conf/urls/__init__.py b/django/conf/urls/__init__.py index f9cd305ece..512d6f79e0 100644 --- a/django/conf/urls/__init__.py +++ b/django/conf/urls/__init__.py @@ -79,5 +79,7 @@ def url(regex, view, kwargs=None, name=None): # For include(...) processing. urlconf_module, app_name, namespace = view return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace) - else: + elif callable(view): return RegexURLPattern(regex, view, kwargs, name) + else: + raise TypeError('view must be a callable or a list/tuple in the case of include().') diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 81aa9bb7ca..303901048a 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -9,7 +9,6 @@ from __future__ import unicode_literals import functools import re -import warnings from importlib import import_module from threading import local @@ -17,7 +16,6 @@ from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.http import Http404 from django.utils import lru_cache, six from django.utils.datastructures import MultiValueDict -from django.utils.deprecation import RemovedInDjango110Warning from django.utils.encoding import force_str, force_text, iri_to_uri from django.utils.functional import cached_property, lazy from django.utils.http import RFC3986_SUBDELIMS, urlquote @@ -80,67 +78,50 @@ class NoReverseMatch(Exception): @lru_cache.lru_cache(maxsize=None) -def get_callable(lookup_view, can_fail=False): +def get_callable(lookup_view): """ - Return a callable corresponding to lookup_view. This function is used - by both resolve() and reverse(), so can_fail allows the caller to choose - between returning the input as is and raising an exception when the input - string can't be interpreted as an import path. + Return a callable corresponding to lookup_view. - If lookup_view is already a callable, return it. - If lookup_view is a string import path that can be resolved to a callable, - import that callable and return it. - If lookup_view is some other kind of string and can_fail is True, the string - is returned as is. If can_fail is False, an exception is raised (either - ImportError or ViewDoesNotExist). + * If lookup_view is already a callable, return it. + * If lookup_view is a string import path that can be resolved to a callable, + import that callable and return it, otherwise raise an exception + (ImportError or ViewDoesNotExist). """ if callable(lookup_view): return lookup_view if not isinstance(lookup_view, six.string_types): - raise ViewDoesNotExist( - "'%s' is not a callable or a dot-notation path" % lookup_view - ) + raise ViewDoesNotExist("'%s' is not a callable or a dot-notation path" % lookup_view) mod_name, func_name = get_mod_func(lookup_view) if not func_name: # No '.' in lookup_view - if can_fail: - return lookup_view - else: - raise ImportError( - "Could not import '%s'. The path must be fully qualified." % - lookup_view) + raise ImportError("Could not import '%s'. The path must be fully qualified." % lookup_view) try: mod = import_module(mod_name) except ImportError: - if can_fail: - return lookup_view + parentmod, submod = get_mod_func(mod_name) + if submod and not module_has_submodule(import_module(parentmod), submod): + raise ViewDoesNotExist( + "Could not import '%s'. Parent module %s does not exist." % + (lookup_view, mod_name) + ) else: - parentmod, submod = get_mod_func(mod_name) - if submod and not module_has_submodule(import_module(parentmod), submod): - raise ViewDoesNotExist( - "Could not import '%s'. Parent module %s does not exist." % - (lookup_view, mod_name)) - else: - raise + raise else: try: view_func = getattr(mod, func_name) except AttributeError: - if can_fail: - return lookup_view - else: - raise ViewDoesNotExist( - "Could not import '%s'. View does not exist in module %s." % - (lookup_view, mod_name)) + raise ViewDoesNotExist( + "Could not import '%s'. View does not exist in module %s." % + (lookup_view, mod_name) + ) else: if not callable(view_func): - # For backwards compatibility this is raised regardless of can_fail raise ViewDoesNotExist( "Could not import '%s.%s'. View is not callable." % - (mod_name, func_name)) - + (mod_name, func_name) + ) return view_func @@ -209,14 +190,7 @@ class LocaleRegexProvider(object): class RegexURLPattern(LocaleRegexProvider): def __init__(self, regex, callback, default_args=None, name=None): LocaleRegexProvider.__init__(self, regex) - # callback is either a string like 'foo.views.news.stories.story_detail' - # which represents the path to a module and a view function name, or a - # callable object (view). - if callable(callback): - self._callback = callback - else: - self._callback = None - self._callback_str = callback + self.callback = callback # the view self.default_args = default_args or {} self.name = name @@ -239,13 +213,19 @@ class RegexURLPattern(LocaleRegexProvider): return ResolverMatch(self.callback, args, kwargs, self.name) - @property - def callback(self): - if self._callback is not None: - return self._callback - - self._callback = get_callable(self._callback_str) - return self._callback + @cached_property + def lookup_str(self): + """ + A string that identifies the view (e.g. 'path.to.view_function' or + 'path.to.ClassBasedView'). + """ + callback = self.callback + if isinstance(callback, functools.partial): + callback = callback.func + if not hasattr(callback, '__name__'): + return callback.__module__ + "." + callback.__class__.__name__ + else: + return callback.__module__ + "." + callback.__name__ class RegexURLResolver(LocaleRegexProvider): @@ -283,18 +263,8 @@ class RegexURLResolver(LocaleRegexProvider): apps = {} language_code = get_language() for pattern in reversed(self.url_patterns): - if hasattr(pattern, '_callback_str'): - self._callback_strs.add(pattern._callback_str) - elif hasattr(pattern, '_callback'): - callback = pattern._callback - if isinstance(callback, functools.partial): - callback = callback.func - - if not hasattr(callback, '__name__'): - lookup_str = callback.__module__ + "." + callback.__class__.__name__ - else: - lookup_str = callback.__module__ + "." + callback.__name__ - self._callback_strs.add(lookup_str) + if isinstance(pattern, RegexURLPattern): + self._callback_strs.add(pattern.lookup_str) p_pattern = pattern.regex.pattern if p_pattern.startswith('^'): p_pattern = p_pattern[1:] @@ -427,9 +397,6 @@ class RegexURLResolver(LocaleRegexProvider): callback = getattr(urls, 'handler%s' % view_type) return get_callable(callback), {} - def reverse(self, lookup_view, *args, **kwargs): - return self._reverse_with_prefix(lookup_view, '', *args, **kwargs) - def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs): if args and kwargs: raise ValueError("Don't mix *args and **kwargs in call to reverse()!") @@ -439,18 +406,6 @@ class RegexURLResolver(LocaleRegexProvider): if not self._populated: self._populate() - original_lookup = lookup_view - try: - if self._is_callback(lookup_view): - 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, - RemovedInDjango110Warning, stacklevel=3 - ) possibilities = self.reverse_dict.getlist(lookup_view) for possibility, pattern, defaults in possibilities: @@ -484,9 +439,8 @@ class RegexURLResolver(LocaleRegexProvider): if url.startswith('//'): url = '/%%2F%s' % url[2:] return url - # lookup_view can be URL label, or dotted path, or callable, Any of - # these can be passed in at the top, but callables are not friendly in - # error messages. + # lookup_view can be URL name or callable, but callables are not + # friendly in error messages. m = getattr(lookup_view, '__module__', None) n = getattr(lookup_view, '__name__', None) if m is not None and n is not None: diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index a21dd75caf..9bf22e4740 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1051,13 +1051,6 @@ 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 - - You can also pass a dotted Python path to a view function, but this syntax - is deprecated and will be removed in Django 1.10:: - - {% url 'path.to.some_view' v1 v2 %} - .. warning:: Don't forget to put quotes around the :func:`~django.conf.urls.url` diff --git a/docs/ref/urlresolvers.txt b/docs/ref/urlresolvers.txt index 8e9634c31d..116aa4fcd0 100644 --- a/docs/ref/urlresolvers.txt +++ b/docs/ref/urlresolvers.txt @@ -12,9 +12,8 @@ your code, Django provides the following function: .. function:: reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None) -``viewname`` can be a string containing the Python path to the view object, a -:ref:`URL pattern name `, or the callable view object. -For example, given the following ``url``:: +``viewname`` can be a :ref:`URL pattern name ` or the +callable view object. For example, given the following ``url``:: from news import views @@ -63,24 +62,6 @@ 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. - - As part of working out which URL names map to which patterns, the - ``reverse()`` function has to import all of your URLconf files and examine - the name of each view. This involves importing each view function. If - there are *any* errors whilst importing any of your view functions, it - will cause ``reverse()`` to raise an error, even if that view function is - not the one you are trying to reverse. - - Make sure that any views you reference in your URLconf files exist and can - be imported correctly. Do not include lines that reference views you - haven't written yet, because those views will not be importable. - .. note:: The string returned by ``reverse()`` is already diff --git a/tests/gis_tests/geoapp/test_sitemaps.py b/tests/gis_tests/geoapp/test_sitemaps.py index 5162b11c77..e17a48a624 100644 --- a/tests/gis_tests/geoapp/test_sitemaps.py +++ b/tests/gis_tests/geoapp/test_sitemaps.py @@ -7,10 +7,8 @@ from xml.dom import minidom from django.conf import settings from django.contrib.sites.models import Site from django.test import ( - TestCase, ignore_warnings, modify_settings, override_settings, - skipUnlessDBFeature, + TestCase, modify_settings, override_settings, skipUnlessDBFeature, ) -from django.utils.deprecation import RemovedInDjango110Warning from .models import City, Country @@ -30,17 +28,9 @@ class GeoSitemapTest(TestCase): expected = set(expected) self.assertEqual(actual, expected) - @ignore_warnings(category=RemovedInDjango110Warning) def test_geositemap_kml(self): "Tests KML/KMZ geographic sitemaps." for kml_type in ('kml', 'kmz'): - # The URL for the sitemaps in urls.py have 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.gis.sitemaps.views.(kml|kmz)', we need - # to silence the erroneous warning until reversing by dotted - # path is removed. The test will work without modification when - # it's removed. doc = minidom.parseString(self.client.get('/sitemaps/%s.xml' % kml_type).content) # Ensuring the right sitemaps namespace is present. diff --git a/tests/resolve_url/tests.py b/tests/resolve_url/tests.py index d0c9d74abe..52e800a8c0 100644 --- a/tests/resolve_url/tests.py +++ b/tests/resolve_url/tests.py @@ -3,9 +3,8 @@ from __future__ import unicode_literals from django.contrib.auth.views import logout from django.core.urlresolvers import NoReverseMatch, reverse_lazy from django.shortcuts import resolve_url -from django.test import SimpleTestCase, ignore_warnings, override_settings +from django.test import SimpleTestCase, override_settings from django.utils import six -from django.utils.deprecation import RemovedInDjango110Warning from .models import UnimportantThing @@ -51,8 +50,8 @@ class ResolveUrlTests(SimpleTestCase): def test_view_function(self): """ - Tests that passing a view name to ``resolve_url`` will result in the - URL path mapping to that view name. + Tests that passing a view function to ``resolve_url`` will result in + the URL path mapping to that view name. """ resolved_url = resolve_url(logout) self.assertEqual('/accounts/logout/', resolved_url) @@ -66,13 +65,12 @@ class ResolveUrlTests(SimpleTestCase): self.assertIsInstance(resolved_url, six.text_type) self.assertEqual('/accounts/logout/', resolved_url) - @ignore_warnings(category=RemovedInDjango110Warning) def test_valid_view_name(self): """ - Tests that passing a view function to ``resolve_url`` will result in - the URL path mapping to that view. + Tests that passing a view name to ``resolve_url`` will result in the + URL path mapping to that view. """ - resolved_url = resolve_url('django.contrib.auth.views.logout') + resolved_url = resolve_url('logout') self.assertEqual('/accounts/logout/', resolved_url) def test_domain(self): diff --git a/tests/sitemaps_tests/test_http.py b/tests/sitemaps_tests/test_http.py index a6bc46e03a..399cd35ae6 100644 --- a/tests/sitemaps_tests/test_http.py +++ b/tests/sitemaps_tests/test_http.py @@ -9,9 +9,8 @@ from django.conf import settings from django.contrib.sitemaps import GenericSitemap, Sitemap from django.contrib.sites.models import Site from django.core.exceptions import ImproperlyConfigured -from django.test import ignore_warnings, modify_settings, override_settings +from django.test import modify_settings, override_settings from django.utils._os import upath -from django.utils.deprecation import RemovedInDjango110Warning from django.utils.formats import localize from django.utils.translation import activate, deactivate @@ -21,15 +20,8 @@ from .models import TestModel class HTTPSitemapTests(SitemapTestsBase): - @ignore_warnings(category=RemovedInDjango110Warning) def test_simple_sitemap_index(self): "A simple sitemap index can be rendered" - # 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') expected_content = """ @@ -38,19 +30,12 @@ class HTTPSitemapTests(SitemapTestsBase): """ % self.base_url self.assertXMLEqual(response.content.decode('utf-8'), expected_content) - @ignore_warnings(category=RemovedInDjango110Warning) @override_settings(TEMPLATES=[{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(os.path.dirname(upath(__file__)), 'templates')], }]) def test_simple_sitemap_custom_index(self): "A simple sitemap index can be rendered with a custom template" - # 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/custom-index.xml') expected_content = """ @@ -194,14 +179,7 @@ class HTTPSitemapTests(SitemapTestsBase): """ % self.base_url self.assertXMLEqual(response.content.decode('utf-8'), expected_content) - @ignore_warnings(category=RemovedInDjango110Warning) def test_x_robots_sitemap(self): - # 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.assertEqual(response['X-Robots-Tag'], 'noindex, noodp, noarchive') diff --git a/tests/sitemaps_tests/test_https.py b/tests/sitemaps_tests/test_https.py index b3bcdc172b..3b68baac43 100644 --- a/tests/sitemaps_tests/test_https.py +++ b/tests/sitemaps_tests/test_https.py @@ -2,8 +2,7 @@ from __future__ import unicode_literals from datetime import date -from django.test import ignore_warnings, override_settings -from django.utils.deprecation import RemovedInDjango110Warning +from django.test import override_settings from .base import SitemapTestsBase @@ -12,15 +11,8 @@ from .base import SitemapTestsBase class HTTPSSitemapTests(SitemapTestsBase): protocol = 'https' - @ignore_warnings(category=RemovedInDjango110Warning) def test_secure_sitemap_index(self): "A secure sitemap index can be rendered" - # The URL for views.sitemap in tests/urls/https.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('/secure/index.xml') expected_content = """ @@ -44,15 +36,8 @@ class HTTPSSitemapTests(SitemapTestsBase): class HTTPSDetectionSitemapTests(SitemapTestsBase): extra = {'wsgi.url_scheme': 'https'} - @ignore_warnings(category=RemovedInDjango110Warning) def test_sitemap_index_with_https_request(self): "A sitemap index requested in HTTPS is rendered with HTTPS links" - # The URL for views.sitemap in tests/urls/https.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 = """ diff --git a/tests/template_tests/syntax_tests/test_url.py b/tests/template_tests/syntax_tests/test_url.py index 0877a316f1..689ffc8c08 100644 --- a/tests/template_tests/syntax_tests/test_url.py +++ b/tests/template_tests/syntax_tests/test_url.py @@ -1,10 +1,7 @@ # coding: utf-8 from django.core.urlresolvers import NoReverseMatch, resolve from django.template import RequestContext, TemplateSyntaxError -from django.test import ( - RequestFactory, SimpleTestCase, ignore_warnings, override_settings, -) -from django.utils.deprecation import RemovedInDjango110Warning +from django.test import RequestFactory, SimpleTestCase, override_settings from ..utils import setup @@ -13,38 +10,32 @@ from ..utils import setup class UrlTagTests(SimpleTestCase): # Successes - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url01': '{% url "template_tests.views.client" client.id %}'}) + @setup({'url01': '{% url "client" client.id %}'}) def test_url01(self): output = self.engine.render_to_string('url01', {'client': {'id': 1}}) self.assertEqual(output, '/client/1/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url02': '{% url "template_tests.views.client_action" id=client.id action="update" %}'}) + @setup({'url02': '{% url "client_action" id=client.id action="update" %}'}) def test_url02(self): output = self.engine.render_to_string('url02', {'client': {'id': 1}}) self.assertEqual(output, '/client/1/update/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url02a': '{% url "template_tests.views.client_action" client.id "update" %}'}) + @setup({'url02a': '{% url "client_action" client.id "update" %}'}) def test_url02a(self): output = self.engine.render_to_string('url02a', {'client': {'id': 1}}) self.assertEqual(output, '/client/1/update/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url02b': "{% url 'template_tests.views.client_action' id=client.id action='update' %}"}) + @setup({'url02b': "{% url 'client_action' id=client.id action='update' %}"}) def test_url02b(self): output = self.engine.render_to_string('url02b', {'client': {'id': 1}}) self.assertEqual(output, '/client/1/update/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url02c': "{% url 'template_tests.views.client_action' client.id 'update' %}"}) + @setup({'url02c': "{% url 'client_action' client.id 'update' %}"}) def test_url02c(self): output = self.engine.render_to_string('url02c', {'client': {'id': 1}}) self.assertEqual(output, '/client/1/update/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url03': '{% url "template_tests.views.index" %}'}) + @setup({'url03': '{% url "index" %}'}) def test_url03(self): output = self.engine.render_to_string('url03') self.assertEqual(output, '/') @@ -64,12 +55,6 @@ class UrlTagTests(SimpleTestCase): output = self.engine.render_to_string('url06', {'v': 'Ω'}) self.assertEqual(output, '/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url07': '{% url "template_tests.views.client2" tag=v %}'}) - def test_url07(self): - output = self.engine.render_to_string('url07', {'v': 'Ω'}) - self.assertEqual(output, '/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/') - @setup({'url08': '{% url "метка_оператора" v %}'}) def test_url08(self): output = self.engine.render_to_string('url08', {'v': 'Ω'}) @@ -80,55 +65,45 @@ class UrlTagTests(SimpleTestCase): output = self.engine.render_to_string('url09', {'v': 'Ω'}) self.assertEqual(output, '/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url10': '{% url "template_tests.views.client_action" id=client.id action="two words" %}'}) + @setup({'url10': '{% url "client_action" id=client.id action="two words" %}'}) def test_url10(self): output = self.engine.render_to_string('url10', {'client': {'id': 1}}) self.assertEqual(output, '/client/1/two%20words/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url11': '{% url "template_tests.views.client_action" id=client.id action="==" %}'}) + @setup({'url11': '{% url "client_action" id=client.id action="==" %}'}) def test_url11(self): output = self.engine.render_to_string('url11', {'client': {'id': 1}}) self.assertEqual(output, '/client/1/==/') - @setup({'url12': '{% url "template_tests.views.client_action" ' - 'id=client.id action="!$&\'()*+,;=~:@," %}'}) - @ignore_warnings(category=RemovedInDjango110Warning) + @setup({'url12': '{% url "client_action" id=client.id action="!$&\'()*+,;=~:@," %}'}) def test_url12(self): output = self.engine.render_to_string('url12', {'client': {'id': 1}}) self.assertEqual(output, '/client/1/!$&'()*+,;=~:@,/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url13': '{% url "template_tests.views.client_action" ' - 'id=client.id action=arg|join:"-" %}'}) + @setup({'url13': '{% url "client_action" id=client.id action=arg|join:"-" %}'}) def test_url13(self): output = self.engine.render_to_string('url13', {'client': {'id': 1}, 'arg': ['a', 'b']}) self.assertEqual(output, '/client/1/a-b/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url14': '{% url "template_tests.views.client_action" client.id arg|join:"-" %}'}) + @setup({'url14': '{% url "client_action" client.id arg|join:"-" %}'}) def test_url14(self): output = self.engine.render_to_string('url14', {'client': {'id': 1}, 'arg': ['a', 'b']}) self.assertEqual(output, '/client/1/a-b/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url15': '{% url "template_tests.views.client_action" 12 "test" %}'}) + @setup({'url15': '{% url "client_action" 12 "test" %}'}) def test_url15(self): output = self.engine.render_to_string('url15') self.assertEqual(output, '/client/12/test/') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url18': '{% url "template_tests.views.client" "1,2" %}'}) + @setup({'url18': '{% url "client" "1,2" %}'}) def test_url18(self): output = self.engine.render_to_string('url18') self.assertEqual(output, '/client/1,2/') - @ignore_warnings(category=RemovedInDjango110Warning) @setup({'url19': '{% url named_url client.id %}'}) def test_url19(self): output = self.engine.render_to_string( - 'url19', {'client': {'id': 1}, 'named_url': 'template_tests.views.client'} + 'url19', {'client': {'id': 1}, 'named_url': 'client'} ) self.assertEqual(output, '/client/1/') @@ -138,10 +113,8 @@ class UrlTagTests(SimpleTestCase): self.assertEqual(output, '/named-client/1/') @setup({'url21': '{% autoescape off %}' - '{% url "template_tests.views.client_action" ' - 'id=client.id action="!$&\'()*+,;=~:@," %}' + '{% url "client_action" id=client.id action="!$&\'()*+,;=~:@," %}' '{% endautoescape %}'}) - @ignore_warnings(category=RemovedInDjango110Warning) def test_url21(self): output = self.engine.render_to_string('url21', {'client': {'id': 1}}) self.assertEqual(output, '/client/1/!$&\'()*+,;=~:@,/') @@ -157,8 +130,7 @@ class UrlTagTests(SimpleTestCase): with self.assertRaises(NoReverseMatch): self.engine.render_to_string('url-fail02') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url-fail03': '{% url "template_tests.views.client" %}'}) + @setup({'url-fail03': '{% url "client" %}'}) def test_url_fail03(self): with self.assertRaises(NoReverseMatch): self.engine.render_to_string('url-fail03') @@ -203,7 +175,6 @@ class UrlTagTests(SimpleTestCase): with self.assertRaises(NoReverseMatch): self.engine.render_to_string('url-fail12', {'named_url': 'no_such_view'}) - @ignore_warnings(category=RemovedInDjango110Warning) @setup({'url-fail13': '{% url named_url %}'}) def test_url_fail13(self): with self.assertRaises(NoReverseMatch): @@ -240,14 +211,12 @@ class UrlTagTests(SimpleTestCase): self.engine.render_to_string('url-fail19', {'named_url': 'view'}) # {% url ... as var %} - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url-asvar01': '{% url "template_tests.views.index" as url %}'}) + @setup({'url-asvar01': '{% url "index" as url %}'}) def test_url_asvar01(self): output = self.engine.render_to_string('url-asvar01') self.assertEqual(output, '') - @ignore_warnings(category=RemovedInDjango110Warning) - @setup({'url-asvar02': '{% url "template_tests.views.index" as url %}{{ url }}'}) + @setup({'url-asvar02': '{% url "index" as url %}{{ url }}'}) def test_url_asvar02(self): output = self.engine.render_to_string('url-asvar02') self.assertEqual(output, '/') diff --git a/tests/template_tests/urls.py b/tests/template_tests/urls.py index e7a9d39a69..b0304e1c54 100644 --- a/tests/template_tests/urls.py +++ b/tests/template_tests/urls.py @@ -7,10 +7,10 @@ from . import views ns_patterns = [ # Test urls for testing reverse lookups - url(r'^$', views.index), - url(r'^client/([0-9,]+)/$', views.client), - url(r'^client/(?P[0-9]+)/(?P[^/]+)/$', views.client_action), - url(r'^client/(?P[0-9]+)/(?P[^/]+)/$', views.client_action), + url(r'^$', views.index, name='index'), + url(r'^client/([0-9,]+)/$', views.client, name='client'), + url(r'^client/(?P[0-9]+)/(?P[^/]+)/$', views.client_action, name='client_action'), + url(r'^client/(?P[0-9]+)/(?P[^/]+)/$', views.client_action, name='client_action'), url(r'^named-client/([0-9]+)/$', views.client2, name="named.client"), ] diff --git a/tests/urlpatterns_reverse/erroneous_urls.py b/tests/urlpatterns_reverse/erroneous_urls.py index 281d2bfa5b..4d75b49e8f 100644 --- a/tests/urlpatterns_reverse/erroneous_urls.py +++ b/tests/urlpatterns_reverse/erroneous_urls.py @@ -1,30 +1,7 @@ -import warnings - from django.conf.urls import url -from django.utils.deprecation import RemovedInDjango110Warning from . import views -# Test deprecated behavior of passing strings as view to url(). -# Some of these can be removed in Django 1.10 as they aren't convertable to -# callables. -with warnings.catch_warnings(): - warnings.filterwarnings('ignore', category=RemovedInDjango110Warning) - urlpatterns = [ - # View has erroneous import - url(r'erroneous_inner/$', views.erroneous_view), - # Module has erroneous import - 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 - url(r'missing_inner/$', 'urlpatterns_reverse.views.missing_view'), - # View is not a callable (string import; arbitrary Python object) - url(r'uncallable-dotted/$', 'urlpatterns_reverse.views.uncallable'), - # View is not a callable (explicit import; arbitrary Python object) - url(r'uncallable-object/$', 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/$', views.empty_view), - ] +urlpatterns = [ + url(r'(regex_error/$', views.empty_view), +] diff --git a/tests/urlpatterns_reverse/erroneous_views_module.py b/tests/urlpatterns_reverse/erroneous_views_module.py deleted file mode 100644 index 3f7f5b2ac7..0000000000 --- a/tests/urlpatterns_reverse/erroneous_views_module.py +++ /dev/null @@ -1,5 +0,0 @@ -import non_existent # NOQA - - -def erroneous_view(request): - pass diff --git a/tests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py index 292b981907..0fe61bedaa 100644 --- a/tests/urlpatterns_reverse/tests.py +++ b/tests/urlpatterns_reverse/tests.py @@ -26,9 +26,7 @@ from django.test import ( ) from django.test.utils import override_script_prefix from django.utils import six -from django.utils.deprecation import ( - RemovedInDjango20Warning, RemovedInDjango110Warning, -) +from django.utils.deprecation import RemovedInDjango20Warning from . import middleware, urlconf_outer, views from .views import empty_view @@ -231,13 +229,6 @@ test_data = ( ('nested-namedcapture', NoReverseMatch, [], {'outer': 'opt/', 'inner': 'opt'}), ('nested-namedcapture', NoReverseMatch, [], {'inner': 'opt'}), - # Regression for #9038 - # These views are resolved by method name. Each method is deployed twice - - # once with an explicit argument, and once using the default value on - # the method. This is potentially ambiguous, as you have to pick the - # correct view for the arguments provided. - ('urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/', [], {}), - ('urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/10/', [], {'arg1': 10}), ('non_path_include', '/includes/non_path_include/', [], {}), # Tests for #13154 @@ -292,7 +283,6 @@ class NoURLPatternsTests(SimpleTestCase): @override_settings(ROOT_URLCONF='urlpatterns_reverse.urls') class URLPatternReverse(SimpleTestCase): - @ignore_warnings(category=RemovedInDjango110Warning) def test_urlpattern_reverse(self): for name, expected, args, kwargs in test_data: try: @@ -544,11 +534,10 @@ class ReverseShortcutTests(SimpleTestCase): redirect("urlpatterns_reverse.nonimported_module.view") self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules) - @ignore_warnings(category=RemovedInDjango110Warning) 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/') + # Views added to urlpatterns using include() should be reversible. + from .views import nested_view + self.assertEqual(reverse(nested_view), '/includes/nested_path/') def test_redirect_view_object(self): from .views import absolute_kwargs_view @@ -982,24 +971,16 @@ class ResolverMatchTests(SimpleTestCase): @override_settings(ROOT_URLCONF='urlpatterns_reverse.erroneous_urls') class ErroneousViewTests(SimpleTestCase): - def test_erroneous_resolve(self): - self.assertRaises(ImportError, self.client.get, '/erroneous_inner/') - self.assertRaises(ImportError, self.client.get, '/erroneous_outer/') - self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_inner/') - self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_outer/') - self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable-dotted/') - self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable-object/') + def test_noncallable_view(self): + # View is not a callable (explicit import; arbitrary Python object) + with self.assertRaisesMessage(TypeError, 'view must be a callable'): + url(r'uncallable-object/$', views.uncallable) - # Regression test for #21157 - self.assertRaises(ImportError, self.client.get, '/erroneous_unqualified/') - - def test_erroneous_reverse(self): - """ - Ensure that a useful exception is raised when a regex is invalid in the - URLConf (#6170). - """ - # The regex error will be hit before NoReverseMatch can be raised - self.assertRaises(ImproperlyConfigured, reverse, 'whatever blah blah') + def test_invalid_regex(self): + # Regex contains an error (refs #6170) + msg = '(regex_error/$" is not a valid regular expression' + with self.assertRaisesMessage(ImproperlyConfigured, msg): + reverse(views.empty_view) class ViewLoadingTests(SimpleTestCase): diff --git a/tests/urlpatterns_reverse/urls.py b/tests/urlpatterns_reverse/urls.py index 4672f1800d..0ede20da3b 100644 --- a/tests/urlpatterns_reverse/urls.py +++ b/tests/urlpatterns_reverse/urls.py @@ -2,7 +2,7 @@ from django.conf.urls import include, url from .views import ( absolute_kwargs_view, defaults_view, empty_view, empty_view_partial, - empty_view_wrapped, kwargs_view, nested_view, + empty_view_wrapped, nested_view, ) other_patterns = [ @@ -67,10 +67,6 @@ urlpatterns = [ # This is non-reversible, but we shouldn't blow up when parsing it. url(r'^(?:foo|bar)(\w+)/$', empty_view, name="disjunction"), - # Regression views for #9038. See tests for more details - url(r'arg_view/$', kwargs_view), - url(r'arg_view/(?P[0-9]+)/$', kwargs_view), - url(r'absolute_arg_view/(?P[0-9]+)/$', absolute_kwargs_view), url(r'absolute_arg_view/$', absolute_kwargs_view), # Tests for #13154. Mixed syntax to test both ways of defining URLs. diff --git a/tests/urlpatterns_reverse/views.py b/tests/urlpatterns_reverse/views.py index e13d28777e..6fd1c53a00 100644 --- a/tests/urlpatterns_reverse/views.py +++ b/tests/urlpatterns_reverse/views.py @@ -10,10 +10,6 @@ def empty_view(request, *args, **kwargs): return HttpResponse('') -def kwargs_view(request, arg1=1, arg2=2): - return HttpResponse('') - - def absolute_kwargs_view(request, arg1=1, arg2=2): return HttpResponse('')