Fixed #27648 -- Deprecated (iLmsu) regex groups in url() patterns.

This commit is contained in:
Tim Graham 2016-12-27 15:59:13 -05:00 committed by GitHub
parent 544b2ef29f
commit 51cde873d9
7 changed files with 60 additions and 9 deletions

View File

@ -7,7 +7,10 @@ should be good enough for a large class of URLS, however.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import warnings
from django.utils import six from django.utils import six
from django.utils.deprecation import RemovedInDjango21Warning
from django.utils.six.moves import zip from django.utils.six.moves import zip
# Mapping of an escape character to a representative of that class. So, e.g., # Mapping of an escape character to a representative of that class. So, e.g.,
@ -59,9 +62,7 @@ def normalize(pattern):
(3) Select the first (essentially an arbitrary) element from any character (3) Select the first (essentially an arbitrary) element from any character
class. Select an arbitrary character for any unordered class (e.g. '.' class. Select an arbitrary character for any unordered class (e.g. '.'
or '\w') in the pattern. or '\w') in the pattern.
(4) Ignore comments, look-ahead and look-behind assertions, and any of the (4) Ignore look-ahead and look-behind assertions.
reg-exp flags that won't change what we construct ("iLmsu"). "(?x)" is
an error, however.
(5) Raise an error on any disjunctive ('|') constructs. (5) Raise an error on any disjunctive ('|') constructs.
Django's URLs for forward resolving are either all positional arguments or Django's URLs for forward resolving are either all positional arguments or
@ -128,10 +129,16 @@ def normalize(pattern):
walk_to_end(ch, pattern_iter) walk_to_end(ch, pattern_iter)
else: else:
ch, escaped = next(pattern_iter) ch, escaped = next(pattern_iter)
if ch in "iLmsu#!=<": if ch in '!=<':
# All of these are ignorable. Walk to the end of the # All of these are ignorable. Walk to the end of the
# group. # group.
walk_to_end(ch, pattern_iter) walk_to_end(ch, pattern_iter)
elif ch in 'iLmsu#':
warnings.warn(
'Using (?%s) in url() patterns is deprecated.' % ch,
RemovedInDjango21Warning
)
walk_to_end(ch, pattern_iter)
elif ch == ':': elif ch == ':':
# Non-capturing group # Non-capturing group
non_capturing_groups.append(len(result)) non_capturing_groups.append(len(result))

View File

@ -48,6 +48,9 @@ details on these changes.
* The ``Model._meta.has_auto_field`` attribute will be removed. * The ``Model._meta.has_auto_field`` attribute will be removed.
* Support for regular expression groups with ``iLmsu#`` in ``url()`` will be
removed.
.. _deprecation-removed-in-2.0: .. _deprecation-removed-in-2.0:
2.0 2.0

View File

@ -747,3 +747,10 @@ Miscellaneous
* ``Model._meta.has_auto_field`` is deprecated in favor of checking if * ``Model._meta.has_auto_field`` is deprecated in favor of checking if
``Model._meta.auto_field is not None``. ``Model._meta.auto_field is not None``.
* Using regular expression groups with ``iLmsu#`` in ``url()`` is deprecated.
The only group that's useful is ``(?i)`` for case-insensitive URLs, however,
case-insensitive URLs aren't a good practice because they create multiple
entries for search engines, for example. An alternative solution could be to
create a :data:`~django.conf.urls.handler404` that looks for uppercase
characters in the URL and redirects to a lowercase equivalent.

View File

@ -0,0 +1,33 @@
import warnings
from django.conf.urls import url
from django.test import SimpleTestCase, override_settings
from django.urls import reverse
from .views import empty_view
urlpatterns = [
url(r'^(?i)CaseInsensitive/(\w+)', empty_view, name="insensitive"),
url(r'^(?i)test/2/?$', empty_view, name="test2"),
]
@override_settings(ROOT_URLCONF='urlpatterns_reverse.test_deprecated')
class URLPatternReverse(SimpleTestCase):
def test_urlpattern_reverse(self):
test_data = (
('insensitive', '/CaseInsensitive/fred', ['fred'], {}),
('test2', '/test/2', [], {}),
)
with warnings.catch_warnings(record=True) as warns:
warnings.simplefilter('always')
warnings.filterwarnings(
'ignore', 'Flags not at the start',
DeprecationWarning, module='django.urls.resolvers'
)
for i, (name, expected, args, kwargs) in enumerate(test_data):
got = reverse(name, args=args, kwargs=kwargs)
self.assertEqual(got, expected)
msg = str(warns[i].message)
self.assertEqual(msg, 'Using (?i) in url() patterns is deprecated.')

View File

@ -198,9 +198,7 @@ test_data = (
('repeats', '/repeats/a/', [], {}), ('repeats', '/repeats/a/', [], {}),
('repeats2', '/repeats/aa/', [], {}), ('repeats2', '/repeats/aa/', [], {}),
('repeats3', '/repeats/aa/', [], {}), ('repeats3', '/repeats/aa/', [], {}),
('insensitive', '/CaseInsensitive/fred', ['fred'], {}),
('test', '/test/1', [], {}), ('test', '/test/1', [], {}),
('test2', '/test/2', [], {}),
('inner-nothing', '/outer/42/', [], {'outer': '42'}), ('inner-nothing', '/outer/42/', [], {'outer': '42'}),
('inner-nothing', '/outer/42/', ['42'], {}), ('inner-nothing', '/outer/42/', ['42'], {}),
('inner-nothing', NoReverseMatch, ['foo'], {}), ('inner-nothing', NoReverseMatch, ['foo'], {}),

View File

@ -49,9 +49,7 @@ urlpatterns = [
url(r'^repeats/a{1,2}/$', empty_view, name="repeats"), url(r'^repeats/a{1,2}/$', empty_view, name="repeats"),
url(r'^repeats/a{2,4}/$', empty_view, name="repeats2"), url(r'^repeats/a{2,4}/$', empty_view, name="repeats2"),
url(r'^repeats/a{2}/$', empty_view, name="repeats3"), url(r'^repeats/a{2}/$', empty_view, name="repeats3"),
url(r'^(?i)CaseInsensitive/(\w+)', empty_view, name="insensitive"),
url(r'^test/1/?', empty_view, name="test"), url(r'^test/1/?', empty_view, name="test"),
url(r'^(?i)test/2/?$', empty_view, name="test2"),
url(r'^outer/(?P<outer>[0-9]+)/', include('urlpatterns_reverse.included_urls')), url(r'^outer/(?P<outer>[0-9]+)/', include('urlpatterns_reverse.included_urls')),
url(r'^outer-no-kwargs/([0-9]+)/', include('urlpatterns_reverse.included_no_kwargs_urls')), url(r'^outer-no-kwargs/([0-9]+)/', include('urlpatterns_reverse.included_no_kwargs_urls')),
url('', include('urlpatterns_reverse.extra_urls')), url('', include('urlpatterns_reverse.extra_urls')),

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import warnings
from django.utils import regex_helper from django.utils import regex_helper
@ -27,8 +28,12 @@ class NormalizeTests(unittest.TestCase):
def test_group_ignored(self): def test_group_ignored(self):
pattern = r"(?i)(?L)(?m)(?s)(?u)(?#)" pattern = r"(?i)(?L)(?m)(?s)(?u)(?#)"
expected = [('', [])] expected = [('', [])]
with warnings.catch_warnings(record=True) as warns:
warnings.simplefilter('always')
result = regex_helper.normalize(pattern) result = regex_helper.normalize(pattern)
self.assertEqual(result, expected) self.assertEqual(result, expected)
for i, char in enumerate('iLmsu#'):
self.assertEqual(str(warns[i].message), 'Using (?%s) in url() patterns is deprecated.' % char)
def test_group_noncapturing(self): def test_group_noncapturing(self):
pattern = r"(?:non-capturing)" pattern = r"(?:non-capturing)"