Ticket #16774: 16774.diff
File 16774.diff, 18.2 KB (added by , 13 years ago) |
---|
-
django/core/handlers/base.py
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 3606e18..ce51d8a 100644
a b class BaseHandler(object): 97 97 urlresolvers.set_urlconf(urlconf) 98 98 resolver = urlresolvers.RegexURLResolver(r'^/', urlconf) 99 99 100 callback, callback_args, callback_kwargs = resolver.resolve(101 request.path_info)100 for candidate in resolver.backtracking_resolve(request.path_info): 101 callback, callback_args, callback_kwargs = candidate.func, candidate.args, candidate.kwargs 102 102 103 # Apply view middleware 104 for middleware_method in self._view_middleware: 105 response = middleware_method(request, callback, callback_args, callback_kwargs) 106 if response: 107 break 108 109 if response is None: 110 try: 111 response = callback(request, *callback_args, **callback_kwargs) 112 except Exception, e: 113 # If the view raised an exception, run it through exception 114 # middleware, and if the exception middleware returns a 115 # response, use that. Otherwise, reraise the exception. 116 for middleware_method in self._exception_middleware: 117 response = middleware_method(request, e) 103 # Apply view middleware 104 for middleware_method in self._view_middleware: 105 response = middleware_method(request, callback, callback_args, callback_kwargs) 118 106 if response: 119 107 break 108 120 109 if response is None: 121 raise 110 try: 111 response = callback(request, *callback_args, **callback_kwargs) 112 break 113 except urlresolvers.ContinueResolving: 114 # If the URL resolver candidate raised a ContinueResolving, 115 # allow the URL resolver to pick up where it left off 116 continue 117 except Exception, e: 118 # If the view raised an exception, run it through exception 119 # middleware, and if the exception middleware returns a 120 # response, use that. Otherwise, reraise the exception. 121 for middleware_method in self._exception_middleware: 122 response = middleware_method(request, e) 123 if response: 124 break 125 if response is None: 126 raise 127 128 # We need to ensure that we break out of the URL resolver 129 # search loop, since we found a URL resolver match 130 # but the matched view did not raise a ContinueResolving exception 131 # so we want to terminate this search now 132 break 122 133 123 134 # Complain if the view returned None (a common error). 124 135 if response is None: -
django/core/urlresolvers.py
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index a253319..d2fd50d 100644
a b class ResolverMatch(object): 68 68 return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name='%s', app_name='%s', namespace='%s')" % ( 69 69 self.func, self.args, self.kwargs, self.url_name, self.app_name, self.namespace) 70 70 71 class ResolverMatches(object): 72 def __init__(self, resolver): 73 self.resolver = resolver 74 self.__in_iteration = False 75 76 def __iter__(self): 77 return self 78 79 def __repr__(self): 80 return self.candidate.__repr__() 81 82 def next(self, peek=False): 83 # If this is the first time accessing our iterator, or we have 84 # already started iteration, fetch and store the next candidate, 85 # or we want to keep in on the current candidate 86 if not hasattr(self, '_candidate') or (self.__in_iteration and not peek): 87 self._candidate = self.resolver.next() 88 89 # Functions like __nonzero__ should not trigger the start of 90 # iteration over the resolver generator. They need to know 91 # if the generator is empty or not, but their peeking should not 92 # remove an item from the generator for those that attempt to 93 # iterate over the generator at a later point. 94 if not peek: 95 self.__in_iteration = True 96 return self._candidate 97 98 def candidate(self): 99 return self.next(peek=True) 100 candidate = property(candidate) 101 102 def __nonzero__(self): 103 try: 104 self.next(peek=True) 105 except Resolver404: 106 return False 107 return True 108 71 109 class Resolver404(Http404): 72 110 pass 73 111 112 class ContinueResolving(Resolver404): 113 pass 114 74 115 class NoReverseMatch(Exception): 75 116 # Don't make this raise an error when used in a template. 76 117 silent_variable_failure = True … … class RegexURLResolver(LocaleRegexProvider): 291 332 return self._app_dict[language_code] 292 333 293 334 def resolve(self, path): 294 tried = [] 335 return self.backtracking_resolve(path).next() 336 337 def backtracking_resolve(self, path, tried=None): 338 return ResolverMatches(self.__backtracking_resolve(path, tried)) 339 340 def __backtracking_resolve(self, path, tried=None): 341 if tried is None: 342 tried = [] 343 295 344 match = self.regex.search(path) 296 345 if match: 297 346 new_path = path[match.end():] 298 347 for pattern in self.url_patterns: 348 sub_tried = [] 349 sub_matches = [] 299 350 try: 300 sub_match = pattern.resolve(new_path) 351 if isinstance(pattern, RegexURLResolver): 352 sub_matches = pattern.backtracking_resolve(new_path, sub_tried) 353 else: 354 sub_match = pattern.resolve(new_path) 355 if sub_match: 356 sub_matches = [sub_match] 301 357 except Resolver404, e: 302 358 sub_tried = e.args[0].get('tried') 303 359 if sub_tried is not None: … … class RegexURLResolver(LocaleRegexProvider): 305 361 else: 306 362 tried.append([pattern]) 307 363 else: 308 if sub_match: 309 sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()]) 310 sub_match_dict.update(self.default_kwargs) 311 for k, v in sub_match.kwargs.iteritems(): 312 sub_match_dict[smart_str(k)] = v 313 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) 314 tried.append([pattern]) 364 if sub_matches: 365 for sub_match in sub_matches: 366 sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()]) 367 sub_match_dict.update(self.default_kwargs) 368 for k, v in sub_match.kwargs.iteritems(): 369 sub_match_dict[smart_str(k)] = v 370 yield ResolverMatch( 371 sub_match.func, 372 sub_match.args, 373 sub_match_dict, 374 sub_match.url_name, 375 self.app_name or sub_match.app_name, 376 [self.namespace] + sub_match.namespaces, 377 ) 378 if sub_tried: 379 tried.extend([[pattern] + t for t in sub_tried]) 380 else: 381 tried.append([pattern]) 315 382 raise Resolver404({'tried': tried, 'path': new_path}) 316 383 raise Resolver404({'path' : path}) 317 384 … … def resolve(path, urlconf=None): 415 482 urlconf = get_urlconf() 416 483 return get_resolver(urlconf).resolve(path) 417 484 485 def backtracking_resolve(path, urlconf=None): 486 if urlconf is None: 487 urlconf = get_urlconf() 488 return get_resolver(urlconf).backtracking_resolve(path) 489 418 490 def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None): 419 491 if urlconf is None: 420 492 urlconf = get_urlconf() -
docs/topics/http/urls.txt
diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 5c9711c..7a4ad98 100644
a b algorithm the system follows to determine which Python code to execute: 51 51 ``urlpatterns``. This should be a Python list, in the format returned by 52 52 the function :func:`django.conf.urls.patterns`. 53 53 54 3. Django runs through each URL pattern, in order, and stops at the first 55 one that matches the requested URL. 56 57 4. Once one of the regexes matches, Django imports and calls the given 58 view, which is a simple Python function. The view gets passed an 59 :class:`~django.http.HttpRequest` as its first argument and any values 60 captured in the regex as remaining arguments. 54 3. Django runs through each URL pattern, in order, and for each matched pattern 55 calls the corresponding view. If a view raises a 56 :class:`~django.core.urlresolvers.ContinueResolving` exception it will 57 continue calling the next matched pattern until it runs out of matches or a 58 view does not raise an :class:`~django.core.urlresolvers.ContinueResolving` 59 exception, in which case it will return the matched views response. 60 61 4. The view called by a matched pattern of the URL resolver is a simple Python 62 function that gets passed an :class:`~django.http.HttpRequest` as its first 63 argument and any values captured in the regex as remaining arguments. As 64 mentioned above, a view can raise a 65 :class:`~django.core.urlresolvers.ContinueResolving` exception to allow the 66 URL resolver to continue searching for potential URL pattern matches. 61 67 62 68 5. If no regex matches, or if an exception is raised during any 63 69 point in this process, Django invokes an appropriate -
tests/regressiontests/test_client_regress/models.py
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index b1e5bc4..d369a21 100644
a b class UnicodePayloadTests(TestCase): 899 899 content_type="application/json; charset=koi8-r") 900 900 self.assertEqual(response.content, json.encode('koi8-r')) 901 901 902 class BacktrackingResolveTests(TestCase): 903 def test_capture(self): 904 response = self.client.get("/test_client_regress/capture-foo") 905 self.assertEqual(response.content, "Success for backtracking_resolve_dynamic_capture: capture-foo") 906 907 response = self.client.get("/test_client_regress/bar-capture") 908 self.assertEqual(response.content, "Success for backtracking_resolve_dynamic_capture: bar-capture") 909 910 def test_redirect(self): 911 response = self.client.get("/test_client_regress/redirect-bar") 912 self.assertRedirects(response, "/test_client_regress/arg_view/test/", status_code=302, target_status_code=200) 913 914 def test_failed_capture(self): 915 response = self.client.get("/test_client_regress/redirect-foo") 916 self.assertEqual(response.status_code, 404) 917 902 918 class DummyFile(object): 903 919 def __init__(self, filename): 904 920 self.name = filename -
tests/regressiontests/test_client_regress/urls.py
diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py index 5590c23..996fe76 100644
a b from . import views 7 7 8 8 9 9 urlpatterns = patterns('', 10 (r'^(?P<data>.*)$', views.backtracking_resolve_dynamic_capture), 10 11 (r'^no_template_view/$', views.no_template_view), 11 12 (r'^staff_only/$', views.staff_only_view), 12 13 (r'^get_view/$', views.get_view), … … urlpatterns = patterns('', 20 21 (r'^redirect_to_non_existent_view/$', RedirectView.as_view(url='/test_client_regress/non_existent_view/')), 21 22 (r'^redirect_to_non_existent_view2/$', RedirectView.as_view(url='/test_client_regress/redirect_to_non_existent_view/')), 22 23 (r'^redirect_to_self/$', RedirectView.as_view(url='/test_client_regress/redirect_to_self/')), 24 (r'^(?P<data>.*)$', views.backtracking_resolve_dynamic_redirect), 23 25 (r'^circular_redirect_1/$', RedirectView.as_view(url='/test_client_regress/circular_redirect_2/')), 24 26 (r'^circular_redirect_2/$', RedirectView.as_view(url='/test_client_regress/circular_redirect_3/')), 25 27 (r'^circular_redirect_3/$', RedirectView.as_view(url='/test_client_regress/circular_redirect_1/')), -
tests/regressiontests/test_client_regress/views.py
diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py index b398293..6260305 100644
a b 1 1 from django.conf import settings 2 2 from django.contrib.auth.decorators import login_required 3 from django.core.urlresolvers import ContinueResolving, reverse 3 4 from django.http import HttpResponse, HttpResponseRedirect 4 5 from django.core.exceptions import SuspiciousOperation 5 6 from django.shortcuts import render_to_response … … from django.core.serializers.json import DjangoJSONEncoder 9 10 from django.test.client import CONTENT_TYPE_RE 10 11 from django.template import RequestContext 11 12 13 14 def backtracking_resolve_dynamic_capture(request, data): 15 if data in ('capture-foo', 'bar-capture',): 16 return HttpResponse("Success for backtracking_resolve_dynamic_capture: %s" % data) 17 raise ContinueResolving 18 19 def backtracking_resolve_dynamic_redirect(request, data): 20 if data in ('redirect-bar', 'baz-redirect',): 21 return HttpResponseRedirect(reverse('arg_view', kwargs={'name': 'test'})) 22 raise ContinueResolving 23 12 24 def no_template_view(request): 13 25 "A simple view that expects a GET request, and returns a rendered template" 14 26 return HttpResponse("No template used. Sample content: twice once twice. Content ends.") -
new file tests/regressiontests/urlpatterns_reverse/backtracking_urls.py
diff --git a/tests/regressiontests/urlpatterns_reverse/backtracking_urls.py b/tests/regressiontests/urlpatterns_reverse/backtracking_urls.py new file mode 100644 index 0000000..9dc468c
- + 1 from django.conf.urls.defaults import * 2 from views import continue_resolving_view, backtracking_view 3 4 urlpatterns = patterns('', 5 url(r'', continue_resolving_view, name='continue_resolving_view'), 6 url(r'^backtrack/$', backtracking_view, name='backtracking_view'), 7 ) 8 No newline at end of file -
tests/regressiontests/urlpatterns_reverse/tests.py
diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index 307d8da..bbb1cf4 100644
a b from __future__ import absolute_import 5 5 6 6 from django.conf import settings 7 7 from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist 8 from django.core.urlresolvers import (reverse, resolve, NoReverseMatch, 9 Resolver404, ResolverMatch, RegexURLResolver, RegexURLPattern) 8 from django.core.urlresolvers import (backtracking_resolve, reverse, resolve, 9 NoReverseMatch, Resolver404, ResolverMatch, RegexURLResolver, 10 RegexURLPattern) 10 11 from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect 11 12 from django.shortcuts import redirect 12 13 from django.test import TestCase 13 14 from django.utils import unittest 14 15 from django.contrib.auth.models import User 15 16 16 from . import urlconf_outer, urlconf_inner,middleware, views17 from . import urlconf_outer, middleware, views 17 18 18 19 19 20 resolve_test_data = ( … … class URLPatternReverse(TestCase): 164 165 # Reversing None should raise an error, not return the last un-named view. 165 166 self.assertRaises(NoReverseMatch, reverse, None) 166 167 168 class BacktrackingUrlTests(TestCase): 169 urls = 'regressiontests.urlpatterns_reverse.backtracking_urls' 170 171 def test_backtracking_normal_reverse(self): 172 self.assertEqual(reverse('backtracking_view'), '/backtrack/') 173 174 def test_backtracking_normal_resolve(self): 175 r = backtracking_resolve('/backtrack/') 176 first = r.next() 177 self.assertEqual(first.func.__name__, 'continue_resolving_view') 178 second = r.next() 179 self.assertEqual(second.func.__name__, 'backtracking_view') 180 self.assertRaises(Resolver404, r.next) 181 182 response = self.client.get('/backtrack/') 183 self.assertEqual(response.status_code, 200) 184 self.assertEqual(response.content, 'backtracked ok') 185 167 186 class ResolverTests(unittest.TestCase): 168 187 def test_non_regex(self): 169 188 """ -
tests/regressiontests/urlpatterns_reverse/views.py
diff --git a/tests/regressiontests/urlpatterns_reverse/views.py b/tests/regressiontests/urlpatterns_reverse/views.py index f631acf..2bb4c45 100644
a b 1 from django.core.urlresolvers import ContinueResolving 1 2 from django.http import HttpResponse 2 3 from django.views.generic import RedirectView 3 4 from django.core.urlresolvers import reverse_lazy … … def kwargs_view(request, arg1=1, arg2=2): 13 14 def absolute_kwargs_view(request, arg1=1, arg2=2): 14 15 return HttpResponse('') 15 16 17 def continue_resolving_view(request, *args, **kwargs): 18 raise ContinueResolving 19 20 def backtracking_view(request, *args, **kwargs): 21 return HttpResponse('backtracked ok') 22 16 23 def defaults_view(request, arg1, arg2): 17 24 pass 18 25