Loading django/utils/regex_helper.py +5 −6 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading docs/releases/1.9.txt +5 −0 Original line number Diff line number Diff line Loading @@ -205,6 +205,11 @@ Tests * ... URLs ^^^^ * Regular expression lookaround assertions are now allowed in URL patterns. Validators ^^^^^^^^^^ Loading docs/spelling_wordlist +1 −0 Original line number Diff line number Diff line Loading @@ -436,6 +436,7 @@ Login logout Logout Loïc lookaround lookup Lookup lookups Loading tests/urlpatterns_reverse/tests.py +47 −0 Original line number Diff line number Diff line Loading @@ -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'}) tests/urlpatterns_reverse/urls.py +4 −0 Original line number Diff line number Diff line Loading @@ -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"), Loading Loading
django/utils/regex_helper.py +5 −6 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading
docs/releases/1.9.txt +5 −0 Original line number Diff line number Diff line Loading @@ -205,6 +205,11 @@ Tests * ... URLs ^^^^ * Regular expression lookaround assertions are now allowed in URL patterns. Validators ^^^^^^^^^^ Loading
docs/spelling_wordlist +1 −0 Original line number Diff line number Diff line Loading @@ -436,6 +436,7 @@ Login logout Logout Loïc lookaround lookup Lookup lookups Loading
tests/urlpatterns_reverse/tests.py +47 −0 Original line number Diff line number Diff line Loading @@ -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'})
tests/urlpatterns_reverse/urls.py +4 −0 Original line number Diff line number Diff line Loading @@ -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"), Loading