Ticket #16774: #16774-Backtracking_URL_resolver.diff
File #16774-Backtracking_URL_resolver.diff, 9.8 KB (added by , 11 years ago) |
---|
-
django/core/handlers/base.py
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 5911865..4c9c05b 100644
a b class BaseHandler(object): 98 144 urlresolvers.set_urlconf(urlconf) 99 145 resolver = urlresolvers.RegexURLResolver(r'^/', urlconf) 100 146 101 resolver_match = resolver.resolve(request.path_info) 147 # Use resolved to try to resolve matching URL patterns one by one 148 resolved = {} 149 150 while response is None: 151 resolver_match = resolver.resolve(request.path_info, resolved=resolved) 102 152 callback, callback_args, callback_kwargs = resolver_match 103 153 request.resolver_match = resolver_match 104 154 … … class BaseHandler(object): 108 158 if response: 109 159 break 110 160 111 if response is None: 112 wrapped_callback = self.make_view_atomic(callback) 113 try: 114 response = wrapped_callback(request, *callback_args, **callback_kwargs) 115 except Exception as e: 116 # If the view raised an exception, run it through exception 117 # middleware, and if the exception middleware returns a 118 # response, use that. Otherwise, reraise the exception. 119 for middleware_method in self._exception_middleware: 120 response = middleware_method(request, e) 121 if response: 122 break 123 if response is None: 124 raise 161 if response is None: 162 wrapped_callback = self.make_view_atomic(callback) 163 try: 164 response = wrapped_callback(request, *callback_args, **callback_kwargs) 165 except urlresolvers.DoesNotResolve: 166 # Continue resolve URLs if the view raises 167 # urlresolvers.DoesNotResolve exception to indicate 168 # the url pattern does not match. 169 continue 170 except Exception as e: 171 # If the view raised an exception, run it through exception 172 # middleware, and if the exception middleware returns a 173 # response, use that. Otherwise, reraise the exception. 174 for middleware_method in self._exception_middleware: 175 response = middleware_method(request, e) 176 if response: 177 break 178 if response is None: 179 raise 125 180 126 181 # Complain if the view returned None (a common error). 127 182 if response is None: -
django/core/urlresolvers.py
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index af3df83..055136e 100644
a b def __repr__(self): 73 73 class Resolver404(Http404): 74 74 pass 75 75 76 class DoesNotResolve(Http404): 77 pass 78 76 79 class NoReverseMatch(Exception): 77 80 # Don't make this raise an error when used in a template. 78 81 silent_variable_failure = True … … class RegexURLPattern(LocaleRegexProvider): 205 210 return 206 211 self._callback_str = prefix + '.' + self._callback_str 207 212 208 def resolve(self, path): 213 def resolve(self, path, resolved=None, caller=None): 214 if resolved is not None: 215 if caller not in resolved: 216 resolved[caller] = set() 217 resolved[caller].add(self) 209 218 match = self.regex.search(path) 210 219 if match: 211 220 # If there are any named groups, use those as kwargs, ignoring … … class RegexURLResolver(LocaleRegexProvider): 310 318 self._populate() 311 319 return self._app_dict[language_code] 312 320 313 def resolve(self, path ):321 def resolve(self, path, resolved=None, caller=None): 314 322 tried = [] 323 if resolved is None: 324 resolved = {} 325 if caller not in resolved: 326 resolved[caller] = set() 315 327 match = self.regex.search(path) 316 328 if match: 329 if self not in resolved: 330 resolved[self] = set() 317 331 new_path = path[match.end():] 318 332 for pattern in self.url_patterns: 333 if pattern in resolved[self]: 334 continue 319 335 try: 320 sub_match = pattern.resolve(new_path )336 sub_match = pattern.resolve(new_path, resolved=resolved, caller=self) 321 337 except Resolver404 as e: 322 338 sub_tried = e.args[0].get('tried') 323 339 if sub_tried is not None: … … class RegexURLResolver(LocaleRegexProvider): 330 344 sub_match_dict.update(sub_match.kwargs) 331 345 return ResolverMatch(sub_match.func, sub_match.args, sub_match_dict, sub_match.url_name, self.app_name or sub_match.app_name, [self.namespace] + sub_match.namespaces) 332 346 tried.append([pattern]) 347 resolved[self].add(pattern) 348 del resolved[self] 349 resolved[caller].add(self) 333 350 raise Resolver404({'tried': tried, 'path': new_path}) 351 resolved[caller].add(self) 334 352 raise Resolver404({'path' : path}) 335 353 336 354 @property -
tests/view_tests/tests/test_specials.py
a b class URLHandling(TestCase): 37 37 response = self.client.get('/permanent_nonascii_redirect/') 38 38 self.assertRedirects(response, self.redirect_target, status_code=301) 39 39 40 def test_overlapping_urls_reverse(self): 41 from django.core import urlresolvers 42 url = urlresolvers.reverse('overlapping_view1', kwargs={'title': 'sometitle'}) 43 self.assertEqual(url, '/overlapping_view/sometitle/') 44 url = urlresolvers.reverse('overlapping_view2', kwargs={'author': 'someauthor'}) 45 self.assertEqual(url, '/overlapping_view/someauthor/') 46 47 def test_overlapping_urls_resolve(self): 48 response = self.client.get('/overlapping_view/sometitle/') 49 self.assertContains(response, 'overlapping_view2') 50 51 def test_overlapping_urls_not_resolve(self): 52 response = self.client.get('/no_overlapping_view/sometitle/') 53 self.assertEqual(response.status_code, 404) 54 55 def test_nested_overlapping_urls_reverse(self): 56 from django.core import urlresolvers 57 url = urlresolvers.reverse('nested_overlapping_view1', kwargs={'title': 'sometitle'}) 58 self.assertEqual(url, '/nested/overlapping_view/sometitle/') 59 url = urlresolvers.reverse('nested_overlapping_view2', kwargs={'author': 'someauthor'}) 60 self.assertEqual(url, '/nested/overlapping_view/someauthor/') 61 62 def test_nested_overlapping_urls_resolve(self): 63 response = self.client.get('/nested/overlapping_view/sometitle/') 64 self.assertContains(response, 'overlapping_view2') 65 66 def test_nested_overlapping_urls_not_resolve(self): 67 response = self.client.get('/nested/no_overlapping_view/sometitle/') 68 self.assertEqual(response.status_code, 404) -
tests/view_tests/generic_urls.py
a b 1 1 # -*- coding:utf-8 -*- 2 2 from __future__ import absolute_import, unicode_literals 3 3 4 from django.conf.urls import patterns, url 4 from django.conf.urls import patterns, url, include 5 5 from django.views.generic import RedirectView 6 6 7 7 from . import views … … urlpatterns += patterns('view_tests.views', 54 54 (r'^shortcuts/render/status/$', 'render_view_with_status'), 55 55 (r'^shortcuts/render/current_app/$', 'render_view_with_current_app'), 56 56 (r'^shortcuts/render/current_app_conflict/$', 'render_view_with_current_app_conflict'), 57 (r'^nested/', include(patterns('view_tests.views', 58 url(r'^overlapping_view/(?P<title>[a-z]+)/$', 'overlapping_view1', name='nested_overlapping_view1'), 59 url(r'^overlapping_view/(?P<author>[a-z]+)/$', 'overlapping_view2', name='nested_overlapping_view2'), 60 url(r'^overlapping_view/(?P<keywords>[a-z]+)/$', 'overlapping_view3', name='nested_overlapping_view3'), 61 url(r'^no_overlapping_view/(?P<keywords>[a-z]+)/$', 'no_overlapping_view', name='nested_no_overlapping_view'), 62 ))), 63 url(r'^overlapping_view/(?P<title>[a-z]+)/$', 'overlapping_view1', name='overlapping_view1'), 64 url(r'^overlapping_view/(?P<author>[a-z]+)/$', 'overlapping_view2', name='overlapping_view2'), 65 url(r'^overlapping_view/(?P<keywords>[a-z]+)/$', 'overlapping_view3', name='overlapping_view3'), 66 url(r'^no_overlapping_view/(?P<keywords>[a-z]+)/$', 'no_overlapping_view', name='no_overlapping_view'), 57 67 ) -
tests/view_tests/views.py
a b from __future__ import absolute_import 3 3 import sys 4 4 5 5 from django.core.exceptions import PermissionDenied, SuspiciousOperation 6 from django.core.urlresolvers import get_resolver 6 from django.core.urlresolvers import get_resolver, DoesNotResolve 7 7 from django.http import HttpResponse, HttpResponseRedirect 8 8 from django.shortcuts import render_to_response, render 9 9 from django.template import Context, RequestContext, TemplateDoesNotExist … … class Klass(object): 273 273 def sensitive_method_view(request): 274 274 return Klass().method(request) 275 275 276 def overlapping_view1(request, title=None): 277 raise DoesNotResolve 278 279 def overlapping_view2(request, author=None): 280 return HttpResponse("overlapping_view2") 281 282 def overlapping_view3(request, keywords=None): 283 return HttpResponse("overlapping_view3") 284 285 def no_overlapping_view(request, keywords=None): 286 raise DoesNotResolve 276 287 277 288 @sensitive_variables('sauce') 278 289 @sensitive_post_parameters('bacon-key', 'sausage-key')