Refs #22384 -- Removed the ability to reverse URLs by dotted path per deprecation timeline.

This commit is contained in:
Tim Graham 2015-08-17 13:45:07 -04:00
parent d79122f40b
commit 785cc71d5b
15 changed files with 93 additions and 298 deletions

View File

@ -79,5 +79,7 @@ def url(regex, view, kwargs=None, name=None):
# For include(...) processing. # For include(...) processing.
urlconf_module, app_name, namespace = view urlconf_module, app_name, namespace = view
return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace) return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
else: elif callable(view):
return RegexURLPattern(regex, view, kwargs, name) return RegexURLPattern(regex, view, kwargs, name)
else:
raise TypeError('view must be a callable or a list/tuple in the case of include().')

View File

@ -9,7 +9,6 @@ from __future__ import unicode_literals
import functools import functools
import re import re
import warnings
from importlib import import_module from importlib import import_module
from threading import local from threading import local
@ -17,7 +16,6 @@ from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.http import Http404 from django.http import Http404
from django.utils import lru_cache, six from django.utils import lru_cache, six
from django.utils.datastructures import MultiValueDict 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.encoding import force_str, force_text, iri_to_uri
from django.utils.functional import cached_property, lazy from django.utils.functional import cached_property, lazy
from django.utils.http import RFC3986_SUBDELIMS, urlquote from django.utils.http import RFC3986_SUBDELIMS, urlquote
@ -80,67 +78,50 @@ class NoReverseMatch(Exception):
@lru_cache.lru_cache(maxsize=None) @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 Return a callable corresponding to lookup_view.
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.
If lookup_view is already a callable, return it. * If lookup_view is already a callable, return it.
If lookup_view is a string import path that can be resolved to a callable, * If lookup_view is a string import path that can be resolved to a callable,
import that callable and return it. import that callable and return it, otherwise raise an exception
If lookup_view is some other kind of string and can_fail is True, the string (ImportError or ViewDoesNotExist).
is returned as is. If can_fail is False, an exception is raised (either
ImportError or ViewDoesNotExist).
""" """
if callable(lookup_view): if callable(lookup_view):
return lookup_view return lookup_view
if not isinstance(lookup_view, six.string_types): if not isinstance(lookup_view, six.string_types):
raise ViewDoesNotExist( raise ViewDoesNotExist("'%s' is not a callable or a dot-notation path" % lookup_view)
"'%s' is not a callable or a dot-notation path" % lookup_view
)
mod_name, func_name = get_mod_func(lookup_view) mod_name, func_name = get_mod_func(lookup_view)
if not func_name: # No '.' in lookup_view if not func_name: # No '.' in lookup_view
if can_fail: raise ImportError("Could not import '%s'. The path must be fully qualified." % lookup_view)
return lookup_view
else:
raise ImportError(
"Could not import '%s'. The path must be fully qualified." %
lookup_view)
try: try:
mod = import_module(mod_name) mod = import_module(mod_name)
except ImportError: except ImportError:
if can_fail: parentmod, submod = get_mod_func(mod_name)
return lookup_view 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: else:
parentmod, submod = get_mod_func(mod_name) raise
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
else: else:
try: try:
view_func = getattr(mod, func_name) view_func = getattr(mod, func_name)
except AttributeError: except AttributeError:
if can_fail: raise ViewDoesNotExist(
return lookup_view "Could not import '%s'. View does not exist in module %s." %
else: (lookup_view, mod_name)
raise ViewDoesNotExist( )
"Could not import '%s'. View does not exist in module %s." %
(lookup_view, mod_name))
else: else:
if not callable(view_func): if not callable(view_func):
# For backwards compatibility this is raised regardless of can_fail
raise ViewDoesNotExist( raise ViewDoesNotExist(
"Could not import '%s.%s'. View is not callable." % "Could not import '%s.%s'. View is not callable." %
(mod_name, func_name)) (mod_name, func_name)
)
return view_func return view_func
@ -209,14 +190,7 @@ class LocaleRegexProvider(object):
class RegexURLPattern(LocaleRegexProvider): class RegexURLPattern(LocaleRegexProvider):
def __init__(self, regex, callback, default_args=None, name=None): def __init__(self, regex, callback, default_args=None, name=None):
LocaleRegexProvider.__init__(self, regex) LocaleRegexProvider.__init__(self, regex)
# callback is either a string like 'foo.views.news.stories.story_detail' self.callback = callback # the view
# 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.default_args = default_args or {} self.default_args = default_args or {}
self.name = name self.name = name
@ -239,13 +213,19 @@ class RegexURLPattern(LocaleRegexProvider):
return ResolverMatch(self.callback, args, kwargs, self.name) return ResolverMatch(self.callback, args, kwargs, self.name)
@property @cached_property
def callback(self): def lookup_str(self):
if self._callback is not None: """
return self._callback A string that identifies the view (e.g. 'path.to.view_function' or
'path.to.ClassBasedView').
self._callback = get_callable(self._callback_str) """
return self._callback 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): class RegexURLResolver(LocaleRegexProvider):
@ -283,18 +263,8 @@ class RegexURLResolver(LocaleRegexProvider):
apps = {} apps = {}
language_code = get_language() language_code = get_language()
for pattern in reversed(self.url_patterns): for pattern in reversed(self.url_patterns):
if hasattr(pattern, '_callback_str'): if isinstance(pattern, RegexURLPattern):
self._callback_strs.add(pattern._callback_str) self._callback_strs.add(pattern.lookup_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)
p_pattern = pattern.regex.pattern p_pattern = pattern.regex.pattern
if p_pattern.startswith('^'): if p_pattern.startswith('^'):
p_pattern = p_pattern[1:] p_pattern = p_pattern[1:]
@ -427,9 +397,6 @@ class RegexURLResolver(LocaleRegexProvider):
callback = getattr(urls, 'handler%s' % view_type) callback = getattr(urls, 'handler%s' % view_type)
return get_callable(callback), {} 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): def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
if args and kwargs: if args and kwargs:
raise ValueError("Don't mix *args and **kwargs in call to reverse()!") raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
@ -439,18 +406,6 @@ class RegexURLResolver(LocaleRegexProvider):
if not self._populated: if not self._populated:
self._populate() 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) possibilities = self.reverse_dict.getlist(lookup_view)
for possibility, pattern, defaults in possibilities: for possibility, pattern, defaults in possibilities:
@ -484,9 +439,8 @@ class RegexURLResolver(LocaleRegexProvider):
if url.startswith('//'): if url.startswith('//'):
url = '/%%2F%s' % url[2:] url = '/%%2F%s' % url[2:]
return url return url
# lookup_view can be URL label, or dotted path, or callable, Any of # lookup_view can be URL name or callable, but callables are not
# these can be passed in at the top, but callables are not friendly in # friendly in error messages.
# error messages.
m = getattr(lookup_view, '__module__', None) m = getattr(lookup_view, '__module__', None)
n = getattr(lookup_view, '__name__', None) n = getattr(lookup_view, '__name__', None)
if m is not None and n is not None: if m is not None and n is not None:

View File

@ -1051,13 +1051,6 @@ This will follow the normal :ref:`namespaced URL resolution strategy
<topics-http-reversing-url-namespaces>`, including using any hints provided <topics-http-reversing-url-namespaces>`, including using any hints provided
by the context as to the current application. 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:: .. warning::
Don't forget to put quotes around the :func:`~django.conf.urls.url` Don't forget to put quotes around the :func:`~django.conf.urls.url`

View File

@ -12,9 +12,8 @@ your code, Django provides the following function:
.. function:: reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None) .. 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 ``viewname`` can be a :ref:`URL pattern name <naming-url-patterns>` or the
:ref:`URL pattern name <naming-url-patterns>`, or the callable view object. callable view object. For example, given the following ``url``::
For example, given the following ``url``::
from news import views 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 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. 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:: .. note::
The string returned by ``reverse()`` is already The string returned by ``reverse()`` is already

View File

@ -7,10 +7,8 @@ from xml.dom import minidom
from django.conf import settings from django.conf import settings
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.test import ( from django.test import (
TestCase, ignore_warnings, modify_settings, override_settings, TestCase, modify_settings, override_settings, skipUnlessDBFeature,
skipUnlessDBFeature,
) )
from django.utils.deprecation import RemovedInDjango110Warning
from .models import City, Country from .models import City, Country
@ -30,17 +28,9 @@ class GeoSitemapTest(TestCase):
expected = set(expected) expected = set(expected)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@ignore_warnings(category=RemovedInDjango110Warning)
def test_geositemap_kml(self): def test_geositemap_kml(self):
"Tests KML/KMZ geographic sitemaps." "Tests KML/KMZ geographic sitemaps."
for kml_type in ('kml', 'kmz'): 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) doc = minidom.parseString(self.client.get('/sitemaps/%s.xml' % kml_type).content)
# Ensuring the right sitemaps namespace is present. # Ensuring the right sitemaps namespace is present.

View File

@ -3,9 +3,8 @@ from __future__ import unicode_literals
from django.contrib.auth.views import logout from django.contrib.auth.views import logout
from django.core.urlresolvers import NoReverseMatch, reverse_lazy from django.core.urlresolvers import NoReverseMatch, reverse_lazy
from django.shortcuts import resolve_url 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 import six
from django.utils.deprecation import RemovedInDjango110Warning
from .models import UnimportantThing from .models import UnimportantThing
@ -51,8 +50,8 @@ class ResolveUrlTests(SimpleTestCase):
def test_view_function(self): def test_view_function(self):
""" """
Tests that passing a view name to ``resolve_url`` will result in the Tests that passing a view function to ``resolve_url`` will result in
URL path mapping to that view name. the URL path mapping to that view name.
""" """
resolved_url = resolve_url(logout) resolved_url = resolve_url(logout)
self.assertEqual('/accounts/logout/', resolved_url) self.assertEqual('/accounts/logout/', resolved_url)
@ -66,13 +65,12 @@ class ResolveUrlTests(SimpleTestCase):
self.assertIsInstance(resolved_url, six.text_type) self.assertIsInstance(resolved_url, six.text_type)
self.assertEqual('/accounts/logout/', resolved_url) self.assertEqual('/accounts/logout/', resolved_url)
@ignore_warnings(category=RemovedInDjango110Warning)
def test_valid_view_name(self): def test_valid_view_name(self):
""" """
Tests that passing a view function to ``resolve_url`` will result in Tests that passing a view name to ``resolve_url`` will result in the
the URL path mapping to that view. 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) self.assertEqual('/accounts/logout/', resolved_url)
def test_domain(self): def test_domain(self):

View File

@ -9,9 +9,8 @@ from django.conf import settings
from django.contrib.sitemaps import GenericSitemap, Sitemap from django.contrib.sitemaps import GenericSitemap, Sitemap
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.exceptions import ImproperlyConfigured 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._os import upath
from django.utils.deprecation import RemovedInDjango110Warning
from django.utils.formats import localize from django.utils.formats import localize
from django.utils.translation import activate, deactivate from django.utils.translation import activate, deactivate
@ -21,15 +20,8 @@ from .models import TestModel
class HTTPSitemapTests(SitemapTestsBase): class HTTPSitemapTests(SitemapTestsBase):
@ignore_warnings(category=RemovedInDjango110Warning)
def test_simple_sitemap_index(self): def test_simple_sitemap_index(self):
"A simple sitemap index can be rendered" "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') response = self.client.get('/simple/index.xml')
expected_content = """<?xml version="1.0" encoding="UTF-8"?> expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@ -38,19 +30,12 @@ class HTTPSitemapTests(SitemapTestsBase):
""" % self.base_url """ % self.base_url
self.assertXMLEqual(response.content.decode('utf-8'), expected_content) self.assertXMLEqual(response.content.decode('utf-8'), expected_content)
@ignore_warnings(category=RemovedInDjango110Warning)
@override_settings(TEMPLATES=[{ @override_settings(TEMPLATES=[{
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(os.path.dirname(upath(__file__)), 'templates')], 'DIRS': [os.path.join(os.path.dirname(upath(__file__)), 'templates')],
}]) }])
def test_simple_sitemap_custom_index(self): def test_simple_sitemap_custom_index(self):
"A simple sitemap index can be rendered with a custom template" "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') response = self.client.get('/simple/custom-index.xml')
expected_content = """<?xml version="1.0" encoding="UTF-8"?> expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<!-- This is a customised template --> <!-- This is a customised template -->
@ -194,14 +179,7 @@ class HTTPSitemapTests(SitemapTestsBase):
""" % self.base_url """ % self.base_url
self.assertXMLEqual(response.content.decode('utf-8'), expected_content) self.assertXMLEqual(response.content.decode('utf-8'), expected_content)
@ignore_warnings(category=RemovedInDjango110Warning)
def test_x_robots_sitemap(self): 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') response = self.client.get('/simple/index.xml')
self.assertEqual(response['X-Robots-Tag'], 'noindex, noodp, noarchive') self.assertEqual(response['X-Robots-Tag'], 'noindex, noodp, noarchive')

View File

@ -2,8 +2,7 @@ from __future__ import unicode_literals
from datetime import date from datetime import date
from django.test import ignore_warnings, override_settings from django.test import override_settings
from django.utils.deprecation import RemovedInDjango110Warning
from .base import SitemapTestsBase from .base import SitemapTestsBase
@ -12,15 +11,8 @@ from .base import SitemapTestsBase
class HTTPSSitemapTests(SitemapTestsBase): class HTTPSSitemapTests(SitemapTestsBase):
protocol = 'https' protocol = 'https'
@ignore_warnings(category=RemovedInDjango110Warning)
def test_secure_sitemap_index(self): def test_secure_sitemap_index(self):
"A secure sitemap index can be rendered" "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') response = self.client.get('/secure/index.xml')
expected_content = """<?xml version="1.0" encoding="UTF-8"?> expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@ -44,15 +36,8 @@ class HTTPSSitemapTests(SitemapTestsBase):
class HTTPSDetectionSitemapTests(SitemapTestsBase): class HTTPSDetectionSitemapTests(SitemapTestsBase):
extra = {'wsgi.url_scheme': 'https'} extra = {'wsgi.url_scheme': 'https'}
@ignore_warnings(category=RemovedInDjango110Warning)
def test_sitemap_index_with_https_request(self): def test_sitemap_index_with_https_request(self):
"A sitemap index requested in HTTPS is rendered with HTTPS links" "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) response = self.client.get('/simple/index.xml', **self.extra)
expected_content = """<?xml version="1.0" encoding="UTF-8"?> expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

View File

@ -1,10 +1,7 @@
# coding: utf-8 # coding: utf-8
from django.core.urlresolvers import NoReverseMatch, resolve from django.core.urlresolvers import NoReverseMatch, resolve
from django.template import RequestContext, TemplateSyntaxError from django.template import RequestContext, TemplateSyntaxError
from django.test import ( from django.test import RequestFactory, SimpleTestCase, override_settings
RequestFactory, SimpleTestCase, ignore_warnings, override_settings,
)
from django.utils.deprecation import RemovedInDjango110Warning
from ..utils import setup from ..utils import setup
@ -13,38 +10,32 @@ from ..utils import setup
class UrlTagTests(SimpleTestCase): class UrlTagTests(SimpleTestCase):
# Successes # Successes
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url01': '{% url "client" client.id %}'})
@setup({'url01': '{% url "template_tests.views.client" client.id %}'})
def test_url01(self): def test_url01(self):
output = self.engine.render_to_string('url01', {'client': {'id': 1}}) output = self.engine.render_to_string('url01', {'client': {'id': 1}})
self.assertEqual(output, '/client/1/') self.assertEqual(output, '/client/1/')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url02': '{% url "client_action" id=client.id action="update" %}'})
@setup({'url02': '{% url "template_tests.views.client_action" id=client.id action="update" %}'})
def test_url02(self): def test_url02(self):
output = self.engine.render_to_string('url02', {'client': {'id': 1}}) output = self.engine.render_to_string('url02', {'client': {'id': 1}})
self.assertEqual(output, '/client/1/update/') self.assertEqual(output, '/client/1/update/')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url02a': '{% url "client_action" client.id "update" %}'})
@setup({'url02a': '{% url "template_tests.views.client_action" client.id "update" %}'})
def test_url02a(self): def test_url02a(self):
output = self.engine.render_to_string('url02a', {'client': {'id': 1}}) output = self.engine.render_to_string('url02a', {'client': {'id': 1}})
self.assertEqual(output, '/client/1/update/') self.assertEqual(output, '/client/1/update/')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url02b': "{% url 'client_action' id=client.id action='update' %}"})
@setup({'url02b': "{% url 'template_tests.views.client_action' id=client.id action='update' %}"})
def test_url02b(self): def test_url02b(self):
output = self.engine.render_to_string('url02b', {'client': {'id': 1}}) output = self.engine.render_to_string('url02b', {'client': {'id': 1}})
self.assertEqual(output, '/client/1/update/') self.assertEqual(output, '/client/1/update/')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url02c': "{% url 'client_action' client.id 'update' %}"})
@setup({'url02c': "{% url 'template_tests.views.client_action' client.id 'update' %}"})
def test_url02c(self): def test_url02c(self):
output = self.engine.render_to_string('url02c', {'client': {'id': 1}}) output = self.engine.render_to_string('url02c', {'client': {'id': 1}})
self.assertEqual(output, '/client/1/update/') self.assertEqual(output, '/client/1/update/')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url03': '{% url "index" %}'})
@setup({'url03': '{% url "template_tests.views.index" %}'})
def test_url03(self): def test_url03(self):
output = self.engine.render_to_string('url03') output = self.engine.render_to_string('url03')
self.assertEqual(output, '/') self.assertEqual(output, '/')
@ -64,12 +55,6 @@ class UrlTagTests(SimpleTestCase):
output = self.engine.render_to_string('url06', {'v': 'Ω'}) 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/') 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 %}'}) @setup({'url08': '{% url "метка_оператора" v %}'})
def test_url08(self): def test_url08(self):
output = self.engine.render_to_string('url08', {'v': 'Ω'}) output = self.engine.render_to_string('url08', {'v': 'Ω'})
@ -80,55 +65,45 @@ class UrlTagTests(SimpleTestCase):
output = self.engine.render_to_string('url09', {'v': 'Ω'}) 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/') self.assertEqual(output, '/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url10': '{% url "client_action" id=client.id action="two words" %}'})
@setup({'url10': '{% url "template_tests.views.client_action" id=client.id action="two words" %}'})
def test_url10(self): def test_url10(self):
output = self.engine.render_to_string('url10', {'client': {'id': 1}}) output = self.engine.render_to_string('url10', {'client': {'id': 1}})
self.assertEqual(output, '/client/1/two%20words/') self.assertEqual(output, '/client/1/two%20words/')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url11': '{% url "client_action" id=client.id action="==" %}'})
@setup({'url11': '{% url "template_tests.views.client_action" id=client.id action="==" %}'})
def test_url11(self): def test_url11(self):
output = self.engine.render_to_string('url11', {'client': {'id': 1}}) output = self.engine.render_to_string('url11', {'client': {'id': 1}})
self.assertEqual(output, '/client/1/==/') self.assertEqual(output, '/client/1/==/')
@setup({'url12': '{% url "template_tests.views.client_action" ' @setup({'url12': '{% url "client_action" id=client.id action="!$&\'()*+,;=~:@," %}'})
'id=client.id action="!$&\'()*+,;=~:@," %}'})
@ignore_warnings(category=RemovedInDjango110Warning)
def test_url12(self): def test_url12(self):
output = self.engine.render_to_string('url12', {'client': {'id': 1}}) output = self.engine.render_to_string('url12', {'client': {'id': 1}})
self.assertEqual(output, '/client/1/!$&amp;&#39;()*+,;=~:@,/') self.assertEqual(output, '/client/1/!$&amp;&#39;()*+,;=~:@,/')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url13': '{% url "client_action" id=client.id action=arg|join:"-" %}'})
@setup({'url13': '{% url "template_tests.views.client_action" '
'id=client.id action=arg|join:"-" %}'})
def test_url13(self): def test_url13(self):
output = self.engine.render_to_string('url13', {'client': {'id': 1}, 'arg': ['a', 'b']}) output = self.engine.render_to_string('url13', {'client': {'id': 1}, 'arg': ['a', 'b']})
self.assertEqual(output, '/client/1/a-b/') self.assertEqual(output, '/client/1/a-b/')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url14': '{% url "client_action" client.id arg|join:"-" %}'})
@setup({'url14': '{% url "template_tests.views.client_action" client.id arg|join:"-" %}'})
def test_url14(self): def test_url14(self):
output = self.engine.render_to_string('url14', {'client': {'id': 1}, 'arg': ['a', 'b']}) output = self.engine.render_to_string('url14', {'client': {'id': 1}, 'arg': ['a', 'b']})
self.assertEqual(output, '/client/1/a-b/') self.assertEqual(output, '/client/1/a-b/')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url15': '{% url "client_action" 12 "test" %}'})
@setup({'url15': '{% url "template_tests.views.client_action" 12 "test" %}'})
def test_url15(self): def test_url15(self):
output = self.engine.render_to_string('url15') output = self.engine.render_to_string('url15')
self.assertEqual(output, '/client/12/test/') self.assertEqual(output, '/client/12/test/')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url18': '{% url "client" "1,2" %}'})
@setup({'url18': '{% url "template_tests.views.client" "1,2" %}'})
def test_url18(self): def test_url18(self):
output = self.engine.render_to_string('url18') output = self.engine.render_to_string('url18')
self.assertEqual(output, '/client/1,2/') self.assertEqual(output, '/client/1,2/')
@ignore_warnings(category=RemovedInDjango110Warning)
@setup({'url19': '{% url named_url client.id %}'}) @setup({'url19': '{% url named_url client.id %}'})
def test_url19(self): def test_url19(self):
output = self.engine.render_to_string( 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/') self.assertEqual(output, '/client/1/')
@ -138,10 +113,8 @@ class UrlTagTests(SimpleTestCase):
self.assertEqual(output, '/named-client/1/') self.assertEqual(output, '/named-client/1/')
@setup({'url21': '{% autoescape off %}' @setup({'url21': '{% autoescape off %}'
'{% url "template_tests.views.client_action" ' '{% url "client_action" id=client.id action="!$&\'()*+,;=~:@," %}'
'id=client.id action="!$&\'()*+,;=~:@," %}'
'{% endautoescape %}'}) '{% endautoescape %}'})
@ignore_warnings(category=RemovedInDjango110Warning)
def test_url21(self): def test_url21(self):
output = self.engine.render_to_string('url21', {'client': {'id': 1}}) output = self.engine.render_to_string('url21', {'client': {'id': 1}})
self.assertEqual(output, '/client/1/!$&\'()*+,;=~:@,/') self.assertEqual(output, '/client/1/!$&\'()*+,;=~:@,/')
@ -157,8 +130,7 @@ class UrlTagTests(SimpleTestCase):
with self.assertRaises(NoReverseMatch): with self.assertRaises(NoReverseMatch):
self.engine.render_to_string('url-fail02') self.engine.render_to_string('url-fail02')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url-fail03': '{% url "client" %}'})
@setup({'url-fail03': '{% url "template_tests.views.client" %}'})
def test_url_fail03(self): def test_url_fail03(self):
with self.assertRaises(NoReverseMatch): with self.assertRaises(NoReverseMatch):
self.engine.render_to_string('url-fail03') self.engine.render_to_string('url-fail03')
@ -203,7 +175,6 @@ class UrlTagTests(SimpleTestCase):
with self.assertRaises(NoReverseMatch): with self.assertRaises(NoReverseMatch):
self.engine.render_to_string('url-fail12', {'named_url': 'no_such_view'}) self.engine.render_to_string('url-fail12', {'named_url': 'no_such_view'})
@ignore_warnings(category=RemovedInDjango110Warning)
@setup({'url-fail13': '{% url named_url %}'}) @setup({'url-fail13': '{% url named_url %}'})
def test_url_fail13(self): def test_url_fail13(self):
with self.assertRaises(NoReverseMatch): with self.assertRaises(NoReverseMatch):
@ -240,14 +211,12 @@ class UrlTagTests(SimpleTestCase):
self.engine.render_to_string('url-fail19', {'named_url': 'view'}) self.engine.render_to_string('url-fail19', {'named_url': 'view'})
# {% url ... as var %} # {% url ... as var %}
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url-asvar01': '{% url "index" as url %}'})
@setup({'url-asvar01': '{% url "template_tests.views.index" as url %}'})
def test_url_asvar01(self): def test_url_asvar01(self):
output = self.engine.render_to_string('url-asvar01') output = self.engine.render_to_string('url-asvar01')
self.assertEqual(output, '') self.assertEqual(output, '')
@ignore_warnings(category=RemovedInDjango110Warning) @setup({'url-asvar02': '{% url "index" as url %}{{ url }}'})
@setup({'url-asvar02': '{% url "template_tests.views.index" as url %}{{ url }}'})
def test_url_asvar02(self): def test_url_asvar02(self):
output = self.engine.render_to_string('url-asvar02') output = self.engine.render_to_string('url-asvar02')
self.assertEqual(output, '/') self.assertEqual(output, '/')

View File

@ -7,10 +7,10 @@ from . import views
ns_patterns = [ ns_patterns = [
# Test urls for testing reverse lookups # Test urls for testing reverse lookups
url(r'^$', views.index), url(r'^$', views.index, name='index'),
url(r'^client/([0-9,]+)/$', views.client), url(r'^client/([0-9,]+)/$', views.client, name='client'),
url(r'^client/(?P<id>[0-9]+)/(?P<action>[^/]+)/$', views.client_action), url(r'^client/(?P<id>[0-9]+)/(?P<action>[^/]+)/$', views.client_action, name='client_action'),
url(r'^client/(?P<client_id>[0-9]+)/(?P<action>[^/]+)/$', views.client_action), url(r'^client/(?P<client_id>[0-9]+)/(?P<action>[^/]+)/$', views.client_action, name='client_action'),
url(r'^named-client/([0-9]+)/$', views.client2, name="named.client"), url(r'^named-client/([0-9]+)/$', views.client2, name="named.client"),
] ]

View File

@ -1,30 +1,7 @@
import warnings
from django.conf.urls import url from django.conf.urls import url
from django.utils.deprecation import RemovedInDjango110Warning
from . import views from . import views
# Test deprecated behavior of passing strings as view to url(). urlpatterns = [
# Some of these can be removed in Django 1.10 as they aren't convertable to url(r'(regex_error/$', views.empty_view),
# 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),
]

View File

@ -1,5 +0,0 @@
import non_existent # NOQA
def erroneous_view(request):
pass

View File

@ -26,9 +26,7 @@ from django.test import (
) )
from django.test.utils import override_script_prefix from django.test.utils import override_script_prefix
from django.utils import six from django.utils import six
from django.utils.deprecation import ( from django.utils.deprecation import RemovedInDjango20Warning
RemovedInDjango20Warning, RemovedInDjango110Warning,
)
from . import middleware, urlconf_outer, views from . import middleware, urlconf_outer, views
from .views import empty_view from .views import empty_view
@ -231,13 +229,6 @@ test_data = (
('nested-namedcapture', NoReverseMatch, [], {'outer': 'opt/', 'inner': 'opt'}), ('nested-namedcapture', NoReverseMatch, [], {'outer': 'opt/', 'inner': 'opt'}),
('nested-namedcapture', NoReverseMatch, [], {'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/', [], {}), ('non_path_include', '/includes/non_path_include/', [], {}),
# Tests for #13154 # Tests for #13154
@ -292,7 +283,6 @@ class NoURLPatternsTests(SimpleTestCase):
@override_settings(ROOT_URLCONF='urlpatterns_reverse.urls') @override_settings(ROOT_URLCONF='urlpatterns_reverse.urls')
class URLPatternReverse(SimpleTestCase): class URLPatternReverse(SimpleTestCase):
@ignore_warnings(category=RemovedInDjango110Warning)
def test_urlpattern_reverse(self): def test_urlpattern_reverse(self):
for name, expected, args, kwargs in test_data: for name, expected, args, kwargs in test_data:
try: try:
@ -544,11 +534,10 @@ class ReverseShortcutTests(SimpleTestCase):
redirect("urlpatterns_reverse.nonimported_module.view") redirect("urlpatterns_reverse.nonimported_module.view")
self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules) self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules)
@ignore_warnings(category=RemovedInDjango110Warning)
def test_reverse_by_path_nested(self): def test_reverse_by_path_nested(self):
# Views that are added to urlpatterns using include() should be # Views added to urlpatterns using include() should be reversible.
# reversible by dotted path. from .views import nested_view
self.assertEqual(reverse('urlpatterns_reverse.views.nested_view'), '/includes/nested_path/') self.assertEqual(reverse(nested_view), '/includes/nested_path/')
def test_redirect_view_object(self): def test_redirect_view_object(self):
from .views import absolute_kwargs_view from .views import absolute_kwargs_view
@ -982,24 +971,16 @@ class ResolverMatchTests(SimpleTestCase):
@override_settings(ROOT_URLCONF='urlpatterns_reverse.erroneous_urls') @override_settings(ROOT_URLCONF='urlpatterns_reverse.erroneous_urls')
class ErroneousViewTests(SimpleTestCase): class ErroneousViewTests(SimpleTestCase):
def test_erroneous_resolve(self): def test_noncallable_view(self):
self.assertRaises(ImportError, self.client.get, '/erroneous_inner/') # View is not a callable (explicit import; arbitrary Python object)
self.assertRaises(ImportError, self.client.get, '/erroneous_outer/') with self.assertRaisesMessage(TypeError, 'view must be a callable'):
self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_inner/') url(r'uncallable-object/$', views.uncallable)
self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_outer/')
self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable-dotted/')
self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable-object/')
# Regression test for #21157 def test_invalid_regex(self):
self.assertRaises(ImportError, self.client.get, '/erroneous_unqualified/') # Regex contains an error (refs #6170)
msg = '(regex_error/$" is not a valid regular expression'
def test_erroneous_reverse(self): with self.assertRaisesMessage(ImproperlyConfigured, msg):
""" reverse(views.empty_view)
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')
class ViewLoadingTests(SimpleTestCase): class ViewLoadingTests(SimpleTestCase):

View File

@ -2,7 +2,7 @@ from django.conf.urls import include, url
from .views import ( from .views import (
absolute_kwargs_view, defaults_view, empty_view, empty_view_partial, absolute_kwargs_view, defaults_view, empty_view, empty_view_partial,
empty_view_wrapped, kwargs_view, nested_view, empty_view_wrapped, nested_view,
) )
other_patterns = [ other_patterns = [
@ -67,10 +67,6 @@ urlpatterns = [
# This is non-reversible, but we shouldn't blow up when parsing it. # This is non-reversible, but we shouldn't blow up when parsing it.
url(r'^(?:foo|bar)(\w+)/$', empty_view, name="disjunction"), 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<arg1>[0-9]+)/$', kwargs_view),
url(r'absolute_arg_view/(?P<arg1>[0-9]+)/$', absolute_kwargs_view),
url(r'absolute_arg_view/$', absolute_kwargs_view), url(r'absolute_arg_view/$', absolute_kwargs_view),
# Tests for #13154. Mixed syntax to test both ways of defining URLs. # Tests for #13154. Mixed syntax to test both ways of defining URLs.

View File

@ -10,10 +10,6 @@ def empty_view(request, *args, **kwargs):
return HttpResponse('') return HttpResponse('')
def kwargs_view(request, arg1=1, arg2=2):
return HttpResponse('')
def absolute_kwargs_view(request, arg1=1, arg2=2): def absolute_kwargs_view(request, arg1=1, arg2=2):
return HttpResponse('') return HttpResponse('')