Fixed #16362 -- Allowed lookaround assertions in URL patterns.
This commit is contained in:
parent
74f8110e74
commit
b4382b7055
|
@ -59,11 +59,10 @@ 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.
|
||||||
(5) Ignore comments and any of the reg-exp flags that won't change
|
(4) Ignore comments, look-ahead and look-behind assertions, and any of the
|
||||||
what we construct ("iLmsu"). "(?x)" is an error, however.
|
reg-exp flags that won't change what we construct ("iLmsu"). "(?x)" is
|
||||||
(6) Raise an error on all other non-capturing (?...) forms (e.g.
|
an error, however.
|
||||||
look-ahead and look-behind matches) and any disjunctive ('|')
|
(5) Raise an error on any disjunctive ('|') constructs.
|
||||||
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
|
||||||
all keyword arguments. That is assumed here, as well. Although reverse
|
all keyword arguments. That is assumed here, as well. Although reverse
|
||||||
|
@ -129,7 +128,7 @@ 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 "iLmsu#!=<":
|
||||||
# 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)
|
||||||
|
|
|
@ -205,6 +205,11 @@ Tests
|
||||||
|
|
||||||
* ...
|
* ...
|
||||||
|
|
||||||
|
URLs
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
* Regular expression lookaround assertions are now allowed in URL patterns.
|
||||||
|
|
||||||
Validators
|
Validators
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -436,6 +436,7 @@ Login
|
||||||
logout
|
logout
|
||||||
Logout
|
Logout
|
||||||
Loïc
|
Loïc
|
||||||
|
lookaround
|
||||||
lookup
|
lookup
|
||||||
Lookup
|
Lookup
|
||||||
lookups
|
lookups
|
||||||
|
|
|
@ -799,3 +799,50 @@ class IncludeTests(SimpleTestCase):
|
||||||
msg = "Must specify a namespace if specifying app_name."
|
msg = "Must specify a namespace if specifying app_name."
|
||||||
with self.assertRaisesMessage(ValueError, msg):
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
include('urls', app_name='bar')
|
include('urls', app_name='bar')
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF='urlpatterns_reverse.urls')
|
||||||
|
class LookaheadTests(TestCase):
|
||||||
|
def test_valid_resolve(self):
|
||||||
|
test_urls = [
|
||||||
|
'/lookahead-/a-city/',
|
||||||
|
'/lookbehind-/a-city/',
|
||||||
|
'/lookahead+/a-city/',
|
||||||
|
'/lookbehind+/a-city/',
|
||||||
|
]
|
||||||
|
for test_url in test_urls:
|
||||||
|
match = resolve(test_url)
|
||||||
|
self.assertEqual(match.kwargs, {'city': 'a-city'})
|
||||||
|
|
||||||
|
def test_invalid_resolve(self):
|
||||||
|
test_urls = [
|
||||||
|
'/lookahead-/not-a-city/',
|
||||||
|
'/lookbehind-/not-a-city/',
|
||||||
|
'/lookahead+/other-city/',
|
||||||
|
'/lookbehind+/other-city/',
|
||||||
|
]
|
||||||
|
for test_url in test_urls:
|
||||||
|
with self.assertRaises(Resolver404):
|
||||||
|
resolve(test_url)
|
||||||
|
|
||||||
|
def test_valid_reverse(self):
|
||||||
|
url = reverse('lookahead-positive', kwargs={'city': 'a-city'})
|
||||||
|
self.assertEqual(url, '/lookahead+/a-city/')
|
||||||
|
url = reverse('lookahead-negative', kwargs={'city': 'a-city'})
|
||||||
|
self.assertEqual(url, '/lookahead-/a-city/')
|
||||||
|
|
||||||
|
url = reverse('lookbehind-positive', kwargs={'city': 'a-city'})
|
||||||
|
self.assertEqual(url, '/lookbehind+/a-city/')
|
||||||
|
url = reverse('lookbehind-negative', kwargs={'city': 'a-city'})
|
||||||
|
self.assertEqual(url, '/lookbehind-/a-city/')
|
||||||
|
|
||||||
|
def test_invalid_reverse(self):
|
||||||
|
with self.assertRaises(NoReverseMatch):
|
||||||
|
reverse('lookahead-positive', kwargs={'city': 'other-city'})
|
||||||
|
with self.assertRaises(NoReverseMatch):
|
||||||
|
reverse('lookahead-negative', kwargs={'city': 'not-a-city'})
|
||||||
|
|
||||||
|
with self.assertRaises(NoReverseMatch):
|
||||||
|
reverse('lookbehind-positive', kwargs={'city': 'other-city'})
|
||||||
|
with self.assertRaises(NoReverseMatch):
|
||||||
|
reverse('lookbehind-negative', kwargs={'city': 'not-a-city'})
|
||||||
|
|
|
@ -62,6 +62,10 @@ with warnings.catch_warnings():
|
||||||
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')),
|
||||||
|
url(r'^lookahead-/(?!not-a-city)(?P<city>[^/]+)/$', empty_view, name='lookahead-negative'),
|
||||||
|
url(r'^lookahead\+/(?=a-city)(?P<city>[^/]+)/$', empty_view, name='lookahead-positive'),
|
||||||
|
url(r'^lookbehind-/(?P<city>[^/]+)(?<!not-a-city)/$', empty_view, name='lookbehind-negative'),
|
||||||
|
url(r'^lookbehind\+/(?P<city>[^/]+)(?<=a-city)/$', empty_view, name='lookbehind-positive'),
|
||||||
|
|
||||||
# Partials should be fine.
|
# Partials should be fine.
|
||||||
url(r'^partial/', empty_view_partial, name="partial"),
|
url(r'^partial/', empty_view_partial, name="partial"),
|
||||||
|
|
Loading…
Reference in New Issue