diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 628f45df9e..5d25b89448 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -146,15 +146,7 @@ class BaseHandler(object): try: response = wrapped_callback(request, *callback_args, **callback_kwargs) except Exception as e: - # If the view raised an exception, run it through exception - # middleware, and if the exception middleware returns a - # response, use that. Otherwise, reraise the exception. - for middleware_method in self._exception_middleware: - response = middleware_method(request, e) - if response: - break - if response is None: - raise + response = self.process_exception_by_middleware(e, request) # Complain if the view returned None (a common error). if response is None: @@ -176,7 +168,11 @@ class BaseHandler(object): "%s.process_template_response didn't return an " "HttpResponse object. It returned None instead." % (middleware_method.__self__.__class__.__name__)) - response = response.render() + try: + response = response.render() + except Exception as e: + response = self.process_exception_by_middleware(e, request) + response_is_rendered = True except http.Http404 as exc: @@ -257,6 +253,17 @@ class BaseHandler(object): return response + def process_exception_by_middleware(self, exception, request): + """ + Pass the exception to the exception middleware. If no middleware + return a response for this exception, raise it. + """ + for middleware_method in self._exception_middleware: + response = middleware_method(request, exception) + if response: + return response + raise + def handle_uncaught_exception(self, request, resolver, exc_info): """ Processing for any otherwise uncaught exceptions (those that will diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 75c126c38b..b5c515db09 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -480,6 +480,9 @@ Requests and Responses :class:`~django.template.response.TemplateResponse`, commonly used with class-based views. +* Exceptions raised by the ``render()`` method are now passed to the + ``process_exception()`` method of each middleware. + * Request middleware can now set :attr:`HttpRequest.urlconf ` to ``None`` to revert any changes made by previous middleware and return to using the :setting:`ROOT_URLCONF`. diff --git a/tests/middleware_exceptions/middleware.py b/tests/middleware_exceptions/middleware.py new file mode 100644 index 0000000000..944dd8910d --- /dev/null +++ b/tests/middleware_exceptions/middleware.py @@ -0,0 +1,8 @@ +from __future__ import unicode_literals + +from django.http import HttpResponse + + +class ProcessExceptionMiddleware(object): + def process_exception(self, request, exception): + return HttpResponse('Exception caught') diff --git a/tests/middleware_exceptions/tests.py b/tests/middleware_exceptions/tests.py index eb80460bce..11769277b8 100644 --- a/tests/middleware_exceptions/tests.py +++ b/tests/middleware_exceptions/tests.py @@ -486,6 +486,16 @@ class MiddlewareTests(BaseMiddlewareExceptionTest): # Check that the right middleware methods have been invoked self.assert_middleware_usage(middleware, True, True, True, True, False) + @override_settings( + MIDDLEWARE_CLASSES=['middleware_exceptions.middleware.ProcessExceptionMiddleware'], + ) + def test_exception_in_render_passed_to_process_exception(self): + # Repopulate the list of middlewares since it's already been populated + # by setUp() before the MIDDLEWARE_CLASSES setting got overridden + self.client.handler.load_middleware() + response = self.client.get('/middleware_exceptions/exception_in_render/') + self.assertEqual(response.content, b'Exception caught') + class BadMiddlewareTests(BaseMiddlewareExceptionTest): diff --git a/tests/middleware_exceptions/urls.py b/tests/middleware_exceptions/urls.py index 6fde4fd942..85c7a785ec 100644 --- a/tests/middleware_exceptions/urls.py +++ b/tests/middleware_exceptions/urls.py @@ -8,6 +8,7 @@ urlpatterns = [ url(r'^middleware_exceptions/error/$', views.server_error), url(r'^middleware_exceptions/null_view/$', views.null_view), url(r'^middleware_exceptions/permission_denied/$', views.permission_denied), + url(r'^middleware_exceptions/exception_in_render/$', views.exception_in_render), url(r'^middleware_exceptions/template_response/$', views.template_response), url(r'^middleware_exceptions/template_response_error/$', views.template_response_error), diff --git a/tests/middleware_exceptions/views.py b/tests/middleware_exceptions/views.py index d3d7fae661..891c196728 100644 --- a/tests/middleware_exceptions/views.py +++ b/tests/middleware_exceptions/views.py @@ -32,3 +32,11 @@ def null_view(request): def permission_denied(request): raise PermissionDenied() + + +def exception_in_render(request): + class CustomHttpResponse(http.HttpResponse): + def render(self): + raise Exception('Exception in HttpResponse.render()') + + return CustomHttpResponse('Error')