Ticket #6094: 6094.3.diff
File 6094.3.diff, 22.6 KB (added by , 17 years ago) |
---|
-
tests/regressiontests/handler_exceptions/models.py
=== added directory 'tests/regressiontests/handler_exceptions' === added file 'tests/regressiontests/handler_exceptions/__init__.py' === added file 'tests/regressiontests/handler_exceptions/models.py'
1 # models file for tests to run. -
tests/regressiontests/handler_exceptions/tests.py
=== added file 'tests/regressiontests/handler_exceptions/tests.py'
1 from django.test import TestCase 2 from django.test.client import Client, ClientHandler 3 4 class GoodMiddleware(object): 5 def process_request(self, *args, **kwargs): 6 pass 7 def process_view(self, *args, **kwargs): 8 pass 9 def process_exception(self, *args, **kwargs): 10 pass 11 def process_response(self, request, response): 12 return response 13 14 15 class MiddlewareException(Exception): pass 16 class RequestMiddlewareException(MiddlewareException): pass 17 class ViewMiddlewareException(MiddlewareException): pass 18 class ExceptionMiddlewareException(MiddlewareException): pass 19 class ResponseMiddlewareException(MiddlewareException): pass 20 21 22 class BadRequestMiddleware(object): 23 def process_request(self, *args, **kwargs): 24 raise RequestMiddlewareException 25 26 class BadViewMiddleware(object): 27 def process_view(self, *args, **kwargs): 28 raise ViewMiddlewareException 29 30 class BadExceptionMiddleware(object): 31 def process_exception(self, *args, **kwargs): 32 raise ExceptionMiddlewareException 33 34 class BadResponseMiddleware(object): 35 def process_response(self, *args, **kwargs): 36 raise ResponseMiddlewareException 37 38 39 class MiddlewareResettingClientHandler(ClientHandler): 40 """ 41 A handler that clears loaded middleware after each request. 42 """ 43 def __call__(self, *args, **kwargs): 44 response = super(MiddlewareResettingClientHandler, self).__call__(*args, **kwargs) 45 # ClientHandler's __call__ method will load middleware if 46 # self._request_middleware is None. We set it to None here so that 47 # middleware will be reloaded next request. 48 self._request_middleware = None 49 return response 50 51 52 class HandlerClient(Client): 53 """ 54 A custom Client that doesn't reraise request-handling exceptions and uses 55 the MiddlewareResettingClientHandler. 56 """ 57 def __init__(self, *args, **kwargs): 58 super(HandlerClient, self).__init__(*args, **kwargs) 59 self.reraise_exceptions = False 60 self.handler = MiddlewareResettingClientHandler() 61 62 63 class HandlerExceptionTests(TestCase): 64 def setUp(self): 65 self.client = HandlerClient() 66 self.default_middleware = None 67 # Combinations that will raise errors. 68 self.bad_combinations = ( 69 # Middleware, request path 70 ('BadRequestMiddleware', '/good_view/', RequestMiddlewareException), 71 ('BadViewMiddleware', '/good_view/', ViewMiddlewareException), 72 ('BadResponseMiddleware', '/good_view/', ResponseMiddlewareException), 73 ('BadRequestMiddleware', '/bad_view/', RequestMiddlewareException), 74 ('BadViewMiddleware', '/bad_view/', ViewMiddlewareException), 75 ('BadExceptionMiddleware', '/bad_view/', ExceptionMiddlewareException), 76 ('BadResponseMiddleware', '/bad_view/', ResponseMiddlewareException), 77 ) 78 # Combinations that will not raise errors. 79 self.good_combinations = ( 80 ('GoodMiddleware', '/good_view/'), 81 # Exception middleware doesn't get run if the view doesn't raise 82 # an exception. 83 ('BadExceptionMiddleware', '/good_view/'), 84 ) 85 86 def setup_middleware(self, middleware): 87 """ 88 Append middleware to list of installed middleware. 89 """ 90 from django.conf import settings 91 # If we haven't saved the default middleware, do so now. 92 if self.default_middleware is None: 93 self.default_middleware = settings.MIDDLEWARE_CLASSES 94 middleware_path = 'regressiontests.handler_exceptions.tests.' + middleware 95 settings.MIDDLEWARE_CLASSES += (middleware_path,) 96 97 def reset_middleware(self): 98 """ 99 Restores middleware to the default installed middleware. 100 """ 101 from django.conf import settings 102 settings.MIDDLEWARE_CLASSES = self.default_middleware 103 104 def test_exception_handling(self): 105 """ 106 Tests that exceptions raised in middleware and views are properly 107 caught and render error templates. 108 """ 109 # Just for good measure, check that the good combination doesn't raise 110 # an error. 111 for middleware, path in self.good_combinations: 112 self.setup_middleware(middleware) 113 response = self.client.get('/handler_exceptions' + path) 114 self.assertContains(response, 'good view') 115 self.reset_middleware() 116 # Now test that all the bad combinations render the 500 template. 117 for middleware, path, exception in self.bad_combinations: 118 self.setup_middleware(middleware) 119 response = self.client.get('/handler_exceptions' + path) 120 self.assertContains(response, '500 Error', status_code=500) 121 self.assertTemplateUsed(response, '500.html') 122 self.reset_middleware() 123 124 def test_exception_raising(self): 125 """ 126 Tests that the correct exceptions bubble up from middleware with errors. 127 """ 128 # Turn on client's reraising of exceptions for this test. 129 self.client.reraise_exceptions = True 130 for middleware, path, exception in self.bad_combinations: 131 self.setup_middleware(middleware) 132 fail = False 133 try: 134 self.client.get('/handler_exceptions' + path) 135 # If above didn't raise an exception, then test failed. 136 fail = True 137 except exception: 138 # We got the exception we were looking for, test passes. 139 pass 140 except Exception: 141 # We got an exception that we wore not expecting. 142 from sys import exc_info 143 unexpected_exception = exc_info()[0] 144 self.fail("Expected %s to be raised, but got %s" 145 % (exception, unexpected_exception)) 146 if fail: 147 self.fail("Expected %s to be raised. Middleware: %s, Path: %s" 148 % (exception, middleware, path)) 149 self.reset_middleware() -
tests/regressiontests/handler_exceptions/urls.py
=== added file 'tests/regressiontests/handler_exceptions/urls.py'
1 from django.conf.urls.defaults import * 2 3 import views 4 5 urlpatterns = patterns('', 6 (r'^good_view/$', views.good_view), 7 (r'^bad_view/$', views.bad_view), 8 ) -
tests/regressiontests/handler_exceptions/views.py
=== added file 'tests/regressiontests/handler_exceptions/views.py'
1 from django.http import HttpResponse 2 3 def good_view(request): 4 return HttpResponse("good view") 5 6 def bad_view(request): 7 raise Exception("View raised exception") -
django/core/handlers/base.py
=== modified file 'django/core/handlers/base.py'
3 3 from django import http 4 4 from django.core import signals 5 5 from django.dispatch import dispatcher 6 from django.core.urlresolvers import RegexURLResolver 7 8 9 def resolver(request): 10 """ 11 Returns a RegexURLResolver for the request's urlconf. 12 13 If the request does not have a urlconf object, then the default of 14 settings.ROOT_URLCONF is used. 15 """ 16 from django.conf import settings 17 urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) 18 return RegexURLResolver(r'^/', urlconf) 19 6 20 7 21 class BaseHandler(object): 8 22 # Changes that are always applied to a response (in this order). … … 53 67 if hasattr(mw_instance, 'process_exception'): 54 68 self._exception_middleware.insert(0, mw_instance.process_exception) 55 69 56 def get_response(self, request): 57 "Returns an HttpResponse object for the given HttpRequest" 58 from django.core import exceptions, urlresolvers 59 from django.core.mail import mail_admins 60 from django.conf import settings 61 62 # Apply request middleware 70 def handle_request(self, request): 71 """ 72 Takes a request and returns a response. 73 74 Processing occurs according to the following diagram: 75 76 Request Response 77 | | 78 +----------+ +----------+ 79 | Request | | Response |<--------+ 80 |Middleware|---Response?--->|Middleware| | 81 +----------+ +----------+ 404/500 Response 82 | | | 83 ---------Request Exception Handler-----------------+--------- 84 | | 85 +---------+ +<-------+ 86 | URLConf | | | 87 +---------+ | +----------+ 88 | | |Exception | 89 +----------+ | |Middleware| 90 | View | | +----------+ 91 |Middleware|-----Response?------>+ | 92 +----------+ | Exception 93 | | | 94 ----------View Exception Handler------------+---------------- 95 | | 96 +------+ | 97 | View |-----Response--------->+ 98 +------+ 99 """ 100 dispatcher.send(signal=signals.request_started) 101 try: 102 try: 103 request = self.request_class(request) 104 except UnicodeDecodeError: 105 response = http.HttpResponseBadRequest() 106 else: 107 # See if the request middleware returns a response. 108 response = self.get_response(self.apply_request_middleware, 109 request) 110 # If not, then call the view. 111 if not response: 112 response = self.get_response(self.run_view_processing, 113 request) 114 # All responses go through the response middleware. 115 response = self.get_response(self.apply_response_middleware, 116 request, response) 117 response = self.apply_response_fixes(request, response) 118 return response 119 finally: 120 dispatcher.send(signal=signals.request_finished) 121 122 def apply_request_middleware(self, request): 123 """ 124 Applies request middleware one at a time until a response is returned. 125 If none of request middleware returns a response, then return None. 126 """ 63 127 for middleware_method in self._request_middleware: 64 128 response = middleware_method(request) 65 129 if response: 66 130 return response 67 131 68 # Get urlconf from request object, if available. Otherwise use default. 69 urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) 70 71 resolver = urlresolvers.RegexURLResolver(r'^/', urlconf) 132 def run_view_processing(self, request): 133 """ 134 Apply view middleware and call view. 135 """ 136 callback, callback_args, callback_kwargs = resolver(request).resolve(request.path) 137 138 # Apply view middleware 139 for middleware_method in self._view_middleware: 140 response = middleware_method(request, callback, callback_args, callback_kwargs) 141 if response: 142 return response 143 72 144 try: 73 callback, callback_args, callback_kwargs = resolver.resolve(request.path) 74 75 # Apply view middleware 76 for middleware_method in self._view_middleware: 77 response = middleware_method(request, callback, callback_args, callback_kwargs) 145 response = callback(request, *callback_args, **callback_kwargs) 146 except Exception, e: 147 # If the view raised an exception, run it through exception 148 # middleware, and if the exception middleware returns a 149 # response, use that. Otherwise, reraise the exception. 150 for middleware_method in self._exception_middleware: 151 response = middleware_method(request, e) 78 152 if response: 79 153 return response 154 raise 80 155 156 # Complain if the view returned None (a common error). 157 if response is None: 81 158 try: 82 response = callback(request, *callback_args, **callback_kwargs) 83 except Exception, e: 84 # If the view raised an exception, run it through exception 85 # middleware, and if the exception middleware returns a 86 # response, use that. Otherwise, reraise the exception. 87 for middleware_method in self._exception_middleware: 88 response = middleware_method(request, e) 89 if response: 90 return response 91 raise 92 93 # Complain if the view returned None (a common error). 159 view_name = callback.func_name # If it's a function 160 except AttributeError: 161 view_name = callback.__class__.__name__ + '.__call__' # If it's a class 162 raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name) 163 164 return response 165 166 def apply_response_middleware(self, request, response): 167 """ 168 Applies all response middleware to the response. 169 """ 170 for middleware_method in self._response_middleware: 171 response = middleware_method(request, response) 172 return response 173 174 def get_response(self, method, request, response=None): 175 """ 176 Returns an HttpResponse by calling the passed method with the given 177 HttpRequest object. If an HttpResponse is passed, it will also be 178 passed in the call to method. Exceptions raised by calling method 179 will be handled like so: 180 181 * Http404 exception - If settings.DEBUG is True, the 182 technical_404_response debug view will be called. If settings.DEBUG 183 is False, the URL resolver's handler404 view will be called. 184 * PermissionDenied exception - return HttpResponseForbidden. 185 * SystemExit exception - ignored. 186 * All other exceptions - If settings.DEBUG is True, the 187 technical_500_response debug view will be called. If settings.DEBUG 188 is False, the URL resolver's handler500 view will be called. 189 """ 190 from django.core import exceptions 191 from django.core.mail import mail_admins 192 from django.conf import settings 193 194 try: 94 195 if response is None: 95 try: 96 view_name = callback.func_name # If it's a function 97 except AttributeError: 98 view_name = callback.__class__.__name__ + '.__call__' # If it's a class 99 raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name) 100 101 return response 196 return method(request) 197 else: 198 return method(request, response) 102 199 except http.Http404, e: 103 200 if settings.DEBUG: 104 201 from django.views import debug 105 202 return debug.technical_404_response(request, e) 106 203 else: 107 callback, param_dict = resolver .resolve404()204 callback, param_dict = resolver(request).resolve404() 108 205 return callback(request, **param_dict) 109 206 except exceptions.PermissionDenied: 110 207 return http.HttpResponseForbidden('<h1>Permission denied</h1>') … … 127 224 message = "%s\n\n%s" % (self._get_traceback(exc_info), request_repr) 128 225 mail_admins(subject, message, fail_silently=True) 129 226 # Return an HttpResponse that displays a friendly error message. 130 callback, param_dict = resolver .resolve500()227 callback, param_dict = resolver(request).resolve500() 131 228 return callback(request, **param_dict) 132 229 133 230 def _get_traceback(self, exc_info=None): … … 144 241 for func in self.response_fixes: 145 242 response = func(request, response) 146 243 return response 147 -
django/core/handlers/modpython.py
=== modified file 'django/core/handlers/modpython.py'
2 2 from pprint import pformat 3 3 4 4 from django import http 5 from django.core import signals6 5 from django.core.handlers.base import BaseHandler 7 from django.dispatch import dispatcher8 6 from django.utils import datastructures 9 7 from django.utils.encoding import force_unicode 10 8 … … 151 149 if self._request_middleware is None: 152 150 self.load_middleware() 153 151 154 dispatcher.send(signal=signals.request_started) 155 try: 156 try: 157 request = self.request_class(req) 158 except UnicodeDecodeError: 159 response = http.HttpResponseBadRequest() 160 else: 161 response = self.get_response(request) 162 163 # Apply response middleware 164 for middleware_method in self._response_middleware: 165 response = middleware_method(request, response) 166 response = self.apply_response_fixes(request, response) 167 finally: 168 dispatcher.send(signal=signals.request_finished) 152 response = self.handle_request(req) 169 153 170 154 # Convert our custom HttpResponse object back into the mod_python req. 171 155 req.content_type = response['Content-Type'] -
django/core/handlers/wsgi.py
=== modified file 'django/core/handlers/wsgi.py'
6 6 from StringIO import StringIO 7 7 8 8 from django import http 9 from django.core import signals10 9 from django.core.handlers.base import BaseHandler 11 from django.dispatch import dispatcher12 10 from django.utils import datastructures 13 11 from django.utils.encoding import force_unicode 14 12 … … 195 193 self.load_middleware() 196 194 self.initLock.release() 197 195 198 dispatcher.send(signal=signals.request_started) 199 try: 200 try: 201 request = self.request_class(environ) 202 except UnicodeDecodeError: 203 response = http.HttpResponseBadRequest() 204 else: 205 response = self.get_response(request) 206 207 # Apply response middleware 208 for middleware_method in self._response_middleware: 209 response = middleware_method(request, response) 210 response = self.apply_response_fixes(request, response) 211 finally: 212 dispatcher.send(signal=signals.request_finished) 196 response = self.handle_request(environ) 213 197 214 198 try: 215 199 status_text = STATUS_CODE_TEXT[response.status_code] -
django/test/client.py
=== modified file 'django/test/client.py'
25 25 Uses the WSGI interface to compose requests, but returns 26 26 the raw HttpResponse object 27 27 """ 28 request_class = WSGIRequest 29 28 30 def __call__(self, environ): 29 31 from django.conf import settings 30 from django.core import signals31 32 32 33 # Set up middleware if needed. We couldn't do this earlier, because 33 34 # settings weren't available. 34 35 if self._request_middleware is None: 35 36 self.load_middleware() 36 37 37 dispatcher.send(signal=signals.request_started) 38 try: 39 request = WSGIRequest(environ) 40 response = self.get_response(request) 41 42 # Apply response middleware 43 for middleware_method in self._response_middleware: 44 response = middleware_method(request, response) 45 response = self.apply_response_fixes(request, response) 46 finally: 47 dispatcher.send(signal=signals.request_finished) 48 49 return response 38 return self.handle_request(environ) 50 39 51 40 def store_rendered_templates(store, signal, sender, template, context): 52 41 "A utility function for storing templates and contexts that are rendered" … … 96 85 ]) 97 86 return '\r\n'.join(lines) 98 87 99 class Client :88 class Client(object): 100 89 """ 101 90 A class that can act as a client for testing purposes. 102 91 … … 119 108 self.defaults = defaults 120 109 self.cookies = SimpleCookie() 121 110 self.exc_info = None 111 self.reraise_exceptions = True 122 112 123 113 def store_exc_info(self, *args, **kwargs): 124 114 """ … … 180 170 raise 181 171 182 172 # Look for a signalled exception and reraise it 183 if self.exc_info :173 if self.exc_info and self.reraise_exceptions: 184 174 raise self.exc_info[1], None, self.exc_info[2] 185 175 186 176 # Save the client and request that stimulated the response … … 262 252 self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None 263 253 264 254 # Save the session values 265 request.session.save() 255 request.session.save() 266 256 267 257 return True 268 258 else: -
tests/urls.py
=== modified file 'tests/urls.py'
11 11 12 12 # test urlconf for {% url %} template tag 13 13 (r'^url_tag/', include('regressiontests.templates.urls')), 14 14 15 15 # django built-in views 16 16 (r'^views/', include('regressiontests.views.urls')), 17 17 18 18 # test urlconf for middleware tests 19 19 (r'^middleware/', include('regressiontests.middleware.urls')), 20 21 # urlconf for handler_exceptions tests. 22 (r'^handler_exceptions/', include('regressiontests.handler_exceptions.urls')), 20 23 )