Commit b4382b70 authored by Bas Peschier's avatar Bas Peschier Committed by Tim Graham
Browse files

Fixed #16362 -- Allowed lookaround assertions in URL patterns.

parent 74f8110e
Loading
Loading
Loading
Loading
+5 −6
Original line number Diff line number Diff line
@@ -59,11 +59,10 @@ def normalize(pattern):
    (3) Select the first (essentially an arbitrary) element from any character
        class. Select an arbitrary character for any unordered class (e.g. '.'
        or '\w') in the pattern.
    (5) Ignore comments and any of the reg-exp flags that won't change
        what we construct ("iLmsu"). "(?x)" is an error, however.
    (6) Raise an error on all other non-capturing (?...) forms (e.g.
        look-ahead and look-behind matches) and any disjunctive ('|')
        constructs.
    (4) Ignore comments, look-ahead and look-behind assertions, and any of the
        reg-exp flags that won't change what we construct ("iLmsu"). "(?x)" is
        an error, however.
    (5) Raise an error on any disjunctive ('|') constructs.

    Django's URLs for forward resolving are either all positional arguments or
    all keyword arguments. That is assumed here, as well. Although reverse
@@ -129,7 +128,7 @@ def normalize(pattern):
                    walk_to_end(ch, pattern_iter)
                else:
                    ch, escaped = next(pattern_iter)
                    if ch in "iLmsu#":
                    if ch in "iLmsu#!=<":
                        # All of these are ignorable. Walk to the end of the
                        # group.
                        walk_to_end(ch, pattern_iter)
+5 −0
Original line number Diff line number Diff line
@@ -205,6 +205,11 @@ Tests

* ...

URLs
^^^^

* Regular expression lookaround assertions are now allowed in URL patterns.

Validators
^^^^^^^^^^

+1 −0
Original line number Diff line number Diff line
@@ -436,6 +436,7 @@ Login
logout
Logout
Loïc
lookaround
lookup
Lookup
lookups
+47 −0
Original line number Diff line number Diff line
@@ -799,3 +799,50 @@ class IncludeTests(SimpleTestCase):
        msg = "Must specify a namespace if specifying app_name."
        with self.assertRaisesMessage(ValueError, msg):
            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'})
+4 −0
Original line number Diff line number Diff line
@@ -62,6 +62,10 @@ with warnings.catch_warnings():
        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('', 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.
        url(r'^partial/', empty_view_partial, name="partial"),