Kaydet (Commit) 7d1b69db authored tarafından Carl Meyer's avatar Carl Meyer

Refs #26601 -- Improved backwards-compatibility of DEP 5 middleware exception handling.

üst 104ee2fd
from django.conf import settings from django.conf import settings
from django.contrib.flatpages.views import flatpage from django.contrib.flatpages.views import flatpage
from django.http import Http404 from django.http import Http404
from django.middleware.exception import ExceptionMiddleware from django.utils.deprecation import MiddlewareMixin
class FlatpageFallbackMiddleware(ExceptionMiddleware): class FlatpageFallbackMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
# This override makes get_response optional during the
# MIDDLEWARE_CLASSES deprecation.
super(FlatpageFallbackMiddleware, self).__init__(get_response)
def __call__(self, request):
response = super(FlatpageFallbackMiddleware, self).__call__(request)
return self.process_response(request, response)
def process_response(self, request, response): def process_response(self, request, response):
if response.status_code != 404: if response.status_code != 404:
return response # No need to check for a flatpage for non-404 responses. return response # No need to check for a flatpage for non-404 responses.
......
...@@ -6,11 +6,10 @@ from django.conf import settings ...@@ -6,11 +6,10 @@ from django.conf import settings
from django.contrib.redirects.models import Redirect from django.contrib.redirects.models import Redirect
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.middleware.exception import ExceptionMiddleware from django.utils.deprecation import MiddlewareMixin
class RedirectFallbackMiddleware(ExceptionMiddleware): class RedirectFallbackMiddleware(MiddlewareMixin):
# Defined as class-level attributes to be subclassing-friendly. # Defined as class-level attributes to be subclassing-friendly.
response_gone_class = http.HttpResponseGone response_gone_class = http.HttpResponseGone
response_redirect_class = http.HttpResponsePermanentRedirect response_redirect_class = http.HttpResponsePermanentRedirect
...@@ -23,10 +22,6 @@ class RedirectFallbackMiddleware(ExceptionMiddleware): ...@@ -23,10 +22,6 @@ class RedirectFallbackMiddleware(ExceptionMiddleware):
) )
super(RedirectFallbackMiddleware, self).__init__(get_response) super(RedirectFallbackMiddleware, self).__init__(get_response)
def __call__(self, request):
response = super(RedirectFallbackMiddleware, self).__call__(request)
return self.process_response(request, response)
def process_response(self, request, response): def process_response(self, request, response):
# No need to check for a redirect for non-404 responses. # No need to check for a redirect for non-404 responses.
if response.status_code != 404: if response.status_code != 404:
......
...@@ -9,12 +9,15 @@ from django.conf import settings ...@@ -9,12 +9,15 @@ from django.conf import settings
from django.core import signals from django.core import signals
from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed
from django.db import connections, transaction from django.db import connections, transaction
from django.middleware.exception import ExceptionMiddleware
from django.urls import get_resolver, get_urlconf, set_urlconf from django.urls import get_resolver, get_urlconf, set_urlconf
from django.utils import six from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.module_loading import import_string from django.utils.module_loading import import_string
from django.views import debug
from .exception import (
convert_exception_to_response, get_exception_response,
handle_uncaught_exception,
)
logger = logging.getLogger('django.request') logger = logging.getLogger('django.request')
...@@ -48,7 +51,7 @@ class BaseHandler(object): ...@@ -48,7 +51,7 @@ class BaseHandler(object):
"deprecated. Update your middleware and use settings.MIDDLEWARE " "deprecated. Update your middleware and use settings.MIDDLEWARE "
"instead.", RemovedInDjango20Warning "instead.", RemovedInDjango20Warning
) )
handler = self._legacy_get_response handler = convert_exception_to_response(self._legacy_get_response)
for middleware_path in settings.MIDDLEWARE_CLASSES: for middleware_path in settings.MIDDLEWARE_CLASSES:
mw_class = import_string(middleware_path) mw_class = import_string(middleware_path)
try: try:
...@@ -72,7 +75,7 @@ class BaseHandler(object): ...@@ -72,7 +75,7 @@ class BaseHandler(object):
if hasattr(mw_instance, 'process_exception'): if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.insert(0, mw_instance.process_exception) self._exception_middleware.insert(0, mw_instance.process_exception)
else: else:
handler = self._get_response handler = convert_exception_to_response(self._get_response)
for middleware_path in reversed(settings.MIDDLEWARE): for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path) middleware = import_string(middleware_path)
try: try:
...@@ -94,10 +97,10 @@ class BaseHandler(object): ...@@ -94,10 +97,10 @@ class BaseHandler(object):
self._view_middleware.insert(0, mw_instance.process_view) self._view_middleware.insert(0, mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'): if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.append(mw_instance.process_template_response) self._template_response_middleware.append(mw_instance.process_template_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.append(mw_instance.process_exception)
handler = mw_instance handler = convert_exception_to_response(mw_instance)
handler = ExceptionMiddleware(handler, self)
# We only assign to this when initialization is complete as it is used # We only assign to this when initialization is complete as it is used
# as a flag for initialization being complete. # as a flag for initialization being complete.
...@@ -111,25 +114,7 @@ class BaseHandler(object): ...@@ -111,25 +114,7 @@ class BaseHandler(object):
return view return view
def get_exception_response(self, request, resolver, status_code, exception): def get_exception_response(self, request, resolver, status_code, exception):
try: return get_exception_response(request, resolver, status_code, exception, self.__class__)
callback, param_dict = resolver.resolve_error_handler(status_code)
# Unfortunately, inspect.getargspec result is not trustable enough
# depending on the callback wrapping in decorators (frequent for handlers).
# Falling back on try/except:
try:
response = callback(request, **dict(param_dict, exception=exception))
except TypeError:
warnings.warn(
"Error handlers should accept an exception parameter. Update "
"your code as this parameter will be required in Django 2.0",
RemovedInDjango20Warning, stacklevel=2
)
response = callback(request, **param_dict)
except Exception:
signals.got_request_exception.send(sender=self.__class__, request=request)
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
return response
def get_response(self, request): def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest.""" """Return an HttpResponse object for the given HttpRequest."""
...@@ -138,6 +123,8 @@ class BaseHandler(object): ...@@ -138,6 +123,8 @@ class BaseHandler(object):
response = self._middleware_chain(request) response = self._middleware_chain(request)
# This block is only needed for legacy MIDDLEWARE_CLASSES; if
# MIDDLEWARE is used, self._response_middleware will be empty.
try: try:
# Apply response middleware, regardless of the response # Apply response middleware, regardless of the response
for middleware_method in self._response_middleware: for middleware_method in self._response_middleware:
...@@ -168,6 +155,11 @@ class BaseHandler(object): ...@@ -168,6 +155,11 @@ class BaseHandler(object):
return response return response
def _get_response(self, request): def _get_response(self, request):
"""
Resolve and call the view, then apply view, exception, and
template_response middleware. This method is everything that happens
inside the request/response middleware.
"""
response = None response = None
if hasattr(request, 'urlconf'): if hasattr(request, 'urlconf'):
...@@ -237,35 +229,14 @@ class BaseHandler(object): ...@@ -237,35 +229,14 @@ class BaseHandler(object):
raise raise
def handle_uncaught_exception(self, request, resolver, exc_info): def handle_uncaught_exception(self, request, resolver, exc_info):
""" """Allow subclasses to override uncaught exception handling."""
Processing for any otherwise uncaught exceptions (those that will return handle_uncaught_exception(request, resolver, exc_info)
generate HTTP 500 responses). Can be overridden by subclasses who want
customised 500 handling.
Be *very* careful when overriding this because the error could be
caused by anything, so assuming something like the database is always
available would be an error.
"""
if settings.DEBUG_PROPAGATE_EXCEPTIONS:
raise
logger.error(
'Internal Server Error: %s', request.path,
exc_info=exc_info,
extra={'status_code': 500, 'request': request},
)
if settings.DEBUG:
return debug.technical_500_response(request, *exc_info)
# If Http500 handler is not installed, re-raise last exception
if resolver.urlconf_module is None:
six.reraise(*exc_info)
# Return an HttpResponse that displays a friendly error message.
callback, param_dict = resolver.resolve_error_handler(500)
return callback(request, **param_dict)
def _legacy_get_response(self, request): def _legacy_get_response(self, request):
"""
Apply process_request() middleware and call the main _get_response(),
if needed. Used only for legacy MIDDLEWARE_CLASSES.
"""
response = None response = None
# Apply request middleware # Apply request middleware
for middleware_method in self._request_middleware: for middleware_method in self._request_middleware:
......
from __future__ import unicode_literals
import logging
import sys
import warnings
from functools import wraps
from django.conf import settings
from django.core import signals
from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.http import Http404
from django.http.multipartparser import MultiPartParserError
from django.urls import get_resolver, get_urlconf
from django.utils import six
from django.utils.decorators import available_attrs
from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import force_text
from django.views import debug
logger = logging.getLogger('django.request')
def convert_exception_to_response(get_response):
"""
Wrap the given get_response callable in exception-to-response conversion.
All exceptions will be converted. All known 4xx exceptions (Http404,
PermissionDenied, MultiPartParserError, SuspiciousOperation) will be
converted to the appropriate response, and all other exceptions will be
converted to 500 responses.
This decorator is automatically applied to all middleware to ensure that
no middleware leaks an exception and that the next middleware in the stack
can rely on getting a response instead of an exception.
"""
@wraps(get_response, assigned=available_attrs(get_response))
def inner(request):
try:
response = get_response(request)
except Exception as exc:
response = response_for_exception(request, exc)
return response
return inner
def response_for_exception(request, exc):
if isinstance(exc, Http404):
if settings.DEBUG:
response = debug.technical_404_response(request, exc)
else:
response = get_exception_response(request, get_resolver(get_urlconf()), 404, exc)
elif isinstance(exc, PermissionDenied):
logger.warning(
'Forbidden (Permission denied): %s', request.path,
extra={'status_code': 403, 'request': request},
)
response = get_exception_response(request, get_resolver(get_urlconf()), 403, exc)
elif isinstance(exc, MultiPartParserError):
logger.warning(
'Bad request (Unable to parse request body): %s', request.path,
extra={'status_code': 400, 'request': request},
)
response = get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
elif isinstance(exc, SuspiciousOperation):
# The request logger receives events for any problematic request
# The security logger receives events for all SuspiciousOperations
security_logger = logging.getLogger('django.security.%s' % exc.__class__.__name__)
security_logger.error(
force_text(exc),
extra={'status_code': 400, 'request': request},
)
if settings.DEBUG:
response = debug.technical_500_response(request, *sys.exc_info(), status_code=400)
else:
response = get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
elif isinstance(exc, SystemExit):
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
raise
else:
signals.got_request_exception.send(sender=None, request=request)
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
return response
def get_exception_response(request, resolver, status_code, exception, sender=None):
try:
callback, param_dict = resolver.resolve_error_handler(status_code)
# Unfortunately, inspect.getargspec result is not trustable enough
# depending on the callback wrapping in decorators (frequent for handlers).
# Falling back on try/except:
try:
response = callback(request, **dict(param_dict, exception=exception))
except TypeError:
warnings.warn(
"Error handlers should accept an exception parameter. Update "
"your code as this parameter will be required in Django 2.0",
RemovedInDjango20Warning, stacklevel=2
)
response = callback(request, **param_dict)
except Exception:
signals.got_request_exception.send(sender=sender, request=request)
response = handle_uncaught_exception(request, resolver, sys.exc_info())
return response
def handle_uncaught_exception(request, resolver, exc_info):
"""
Processing for any otherwise uncaught exceptions (those that will
generate HTTP 500 responses).
"""
if settings.DEBUG_PROPAGATE_EXCEPTIONS:
raise
logger.error(
'Internal Server Error: %s', request.path,
exc_info=exc_info,
extra={'status_code': 500, 'request': request},
)
if settings.DEBUG:
return debug.technical_500_response(request, *exc_info)
# If Http500 handler is not installed, reraise the last exception.
if resolver.urlconf_module is None:
six.reraise(*exc_info)
# Return an HttpResponse that displays a friendly error message.
callback, param_dict = resolver.resolve_error_handler(500)
return callback(request, **param_dict)
from __future__ import unicode_literals
import logging
import sys
from django.conf import settings
from django.core import signals
from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.http import Http404
from django.http.multipartparser import MultiPartParserError
from django.urls import get_resolver, get_urlconf
from django.utils.encoding import force_text
from django.views import debug
logger = logging.getLogger('django.request')
class ExceptionMiddleware(object):
"""
Convert selected exceptions to HTTP responses.
For example, convert Http404 to a 404 response either through handler404
or through the debug view if settings.DEBUG=True. To ensure that
exceptions raised by other middleware are converted to the appropriate
response, this middleware is always automatically applied as the outermost
middleware.
"""
def __init__(self, get_response, handler=None):
from django.core.handlers.base import BaseHandler
self.get_response = get_response
self.handler = handler or BaseHandler()
def __call__(self, request):
try:
response = self.get_response(request)
except Http404 as exc:
if settings.DEBUG:
response = debug.technical_404_response(request, exc)
else:
response = self.handler.get_exception_response(request, get_resolver(get_urlconf()), 404, exc)
except PermissionDenied as exc:
logger.warning(
'Forbidden (Permission denied): %s', request.path,
extra={'status_code': 403, 'request': request},
)
response = self.handler.get_exception_response(request, get_resolver(get_urlconf()), 403, exc)
except MultiPartParserError as exc:
logger.warning(
'Bad request (Unable to parse request body): %s', request.path,
extra={'status_code': 400, 'request': request},
)
response = self.handler.get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
except SuspiciousOperation as exc:
# The request logger receives events for any problematic request
# The security logger receives events for all SuspiciousOperations
security_logger = logging.getLogger('django.security.%s' % exc.__class__.__name__)
security_logger.error(
force_text(exc),
extra={'status_code': 400, 'request': request},
)
if settings.DEBUG:
return debug.technical_500_response(request, *sys.exc_info(), status_code=400)
response = self.handler.get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
except SystemExit:
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
raise
except Exception: # Handle everything else.
# Get the exception info now, in case another exception is thrown later.
signals.got_request_exception.send(sender=self.handler.__class__, request=request)
response = self.handler.handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
return response
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
from django.conf import settings from django.conf import settings
from django.conf.urls.i18n import is_language_prefix_patterns_used from django.conf.urls.i18n import is_language_prefix_patterns_used
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.middleware.exception import ExceptionMiddleware
from django.urls import get_script_prefix, is_valid_path from django.urls import get_script_prefix, is_valid_path
from django.utils import translation from django.utils import translation
from django.utils.cache import patch_vary_headers from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
class LocaleMiddleware(ExceptionMiddleware): class LocaleMiddleware(MiddlewareMixin):
""" """
This is a very simple middleware that parses a request This is a very simple middleware that parses a request
and decides what translation object to install in the current and decides what translation object to install in the current
...@@ -19,17 +19,6 @@ class LocaleMiddleware(ExceptionMiddleware): ...@@ -19,17 +19,6 @@ class LocaleMiddleware(ExceptionMiddleware):
""" """
response_redirect_class = HttpResponseRedirect response_redirect_class = HttpResponseRedirect
def __init__(self, get_response=None):
# This override makes get_response optional during the
# MIDDLEWARE_CLASSES deprecation.
super(LocaleMiddleware, self).__init__(get_response)
def __call__(self, request):
response = self.process_request(request)
if not response:
response = super(LocaleMiddleware, self).__call__(request)
return self.process_response(request, response)
def process_request(self, request): def process_request(self, request):
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF) urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
i18n_patterns_used, prefixed_default_language = is_language_prefix_patterns_used(urlconf) i18n_patterns_used, prefixed_default_language = is_language_prefix_patterns_used(urlconf)
......
...@@ -124,13 +124,7 @@ class MiddlewareMixin(object): ...@@ -124,13 +124,7 @@ class MiddlewareMixin(object):
if hasattr(self, 'process_request'): if hasattr(self, 'process_request'):
response = self.process_request(request) response = self.process_request(request)
if not response: if not response:
try: response = self.get_response(request)
response = self.get_response(request)
except Exception as e:
if hasattr(self, 'process_exception'):
return self.process_exception(request, e)
else:
raise
if hasattr(self, 'process_response'): if hasattr(self, 'process_response'):
response = self.process_response(request, response) response = self.process_response(request, response)
return response return response
This diff is collapsed.
from __future__ import unicode_literals from __future__ import unicode_literals
from django.http import HttpResponse from django.http import Http404, HttpResponse
from django.utils.deprecation import MiddlewareMixin from django.template import engines
log = []
class ProcessExceptionMiddleware(MiddlewareMixin):
class BaseMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
class ProcessExceptionMiddleware(BaseMiddleware):
def process_exception(self, request, exception): def process_exception(self, request, exception):
return HttpResponse('Exception caught') return HttpResponse('Exception caught')
class ProcessExceptionLogMiddleware(BaseMiddleware):
def process_exception(self, request, exception):
log.append('process-exception')
class ProcessExceptionExcMiddleware(BaseMiddleware):
def process_exception(self, request, exception):
raise Exception('from process-exception')
class ProcessViewMiddleware(BaseMiddleware):
def process_view(self, request, view_func, view_args, view_kwargs):
return HttpResponse('Processed view %s' % view_func.__name__)
class ProcessViewNoneMiddleware(BaseMiddleware):
def process_view(self, request, view_func, view_args, view_kwargs):
log.append('processed view %s' % view_func.__name__)
return None
class TemplateResponseMiddleware(BaseMiddleware):
def process_template_response(self, request, response):
response.template_name = engines['django'].from_string('template-response middleware')
return response
class LogMiddleware(BaseMiddleware):
def __call__(self, request):
response = self.get_response(request)
log.append((response.status_code, response.content))
return response
class NotFoundMiddleware(BaseMiddleware):
def __call__(self, request):
raise Http404('not found')
import sys import sys
from django.conf import settings
from django.core.exceptions import MiddlewareNotUsed
from django.core.signals import got_request_exception from django.core.signals import got_request_exception
from django.http import HttpResponse from django.http import HttpResponse
from django.template import engines from django.template import engines
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.test import RequestFactory, SimpleTestCase, override_settings from django.test import SimpleTestCase, override_settings
from django.test.utils import ignore_warnings, patch_logger from django.test.utils import ignore_warnings
from django.utils.deprecation import MiddlewareMixin, RemovedInDjango20Warning from django.utils.deprecation import MiddlewareMixin, RemovedInDjango20Warning
from .tests import MiddlewareNotUsedTests
class TestException(Exception): class TestException(Exception):
pass pass
...@@ -512,14 +512,6 @@ class MiddlewareTests(BaseMiddlewareExceptionTest): ...@@ -512,14 +512,6 @@ class MiddlewareTests(BaseMiddlewareExceptionTest):
# Check that the right middleware methods have been invoked # Check that the right middleware methods have been invoked
self.assert_middleware_usage(middleware, True, True, True, True, False) self.assert_middleware_usage(middleware, True, True, True, True, False)
@override_settings(MIDDLEWARE=['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 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): class BadMiddlewareTests(BaseMiddlewareExceptionTest):
...@@ -869,79 +861,6 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest): ...@@ -869,79 +861,6 @@ class BadMiddlewareTests(BaseMiddlewareExceptionTest):
self.assert_middleware_usage(middleware, True, True, True, True, False) self.assert_middleware_usage(middleware, True, True, True, True, False)
self.assert_middleware_usage(post_middleware, True, True, True, True, False) self.assert_middleware_usage(post_middleware, True, True, True, True, False)
_missing = object()
@override_settings(ROOT_URLCONF='middleware_exceptions.urls')
class RootUrlconfTests(SimpleTestCase):
@override_settings(ROOT_URLCONF=None)
def test_missing_root_urlconf(self):
# Removing ROOT_URLCONF is safe, as override_settings will restore
# the previously defined settings.
del settings.ROOT_URLCONF
with self.assertRaises(AttributeError):
self.client.get("/middleware_exceptions/view/")
class MyMiddleware(object):
def __init__(self, get_response=None):
raise MiddlewareNotUsed
def process_request(self, request):
pass
class MyMiddlewareWithExceptionMessage(object):
def __init__(self, get_response=None):
raise MiddlewareNotUsed('spam eggs')
def process_request(self, request):
pass
@override_settings(
DEBUG=True,
ROOT_URLCONF='middleware_exceptions.urls',
MIDDLEWARE=['django.middleware.common.CommonMiddleware'],
)
class MiddlewareNotUsedTests(SimpleTestCase):
rf = RequestFactory()
def test_raise_exception(self):
request = self.rf.get('middleware_exceptions/view/')
with self.assertRaises(MiddlewareNotUsed):
MyMiddleware().process_request(request)
@override_settings(MIDDLEWARE=['middleware_exceptions.test_legacy.MyMiddleware'])
def test_log(self):
with patch_logger('django.request', 'debug') as calls:
self.client.get('/middleware_exceptions/view/')
self.assertEqual(len(calls), 1)
self.assertEqual(
calls[0],
"MiddlewareNotUsed: 'middleware_exceptions.test_legacy.MyMiddleware'"
)
@override_settings(MIDDLEWARE=['middleware_exceptions.test_legacy.MyMiddlewareWithExceptionMessage'])
def test_log_custom_message(self):
with patch_logger('django.request', 'debug') as calls:
self.client.get('/middleware_exceptions/view/')
self.assertEqual(len(calls), 1)
self.assertEqual(
calls[0],
"MiddlewareNotUsed('middleware_exceptions.test_legacy.MyMiddlewareWithExceptionMessage'): spam eggs"
)
@override_settings(DEBUG=False)
def test_do_not_log_when_debug_is_false(self):
with patch_logger('django.request', 'debug') as calls:
self.client.get('/middleware_exceptions/view/')
self.assertEqual(len(calls), 0)
@ignore_warnings(category=RemovedInDjango20Warning) @ignore_warnings(category=RemovedInDjango20Warning)
@override_settings( @override_settings(
......
from django.conf import settings
from django.core.exceptions import MiddlewareNotUsed
from django.test import RequestFactory, SimpleTestCase, override_settings
from django.test.utils import patch_logger
from . import middleware as mw
@override_settings(ROOT_URLCONF='middleware_exceptions.urls')
class MiddlewareTests(SimpleTestCase):
def tearDown(self):
mw.log = []
@override_settings(MIDDLEWARE=['middleware_exceptions.middleware.ProcessViewNoneMiddleware'])
def test_process_view_return_none(self):
response = self.client.get('/middleware_exceptions/view/')
self.assertEqual(mw.log, ['processed view normal_view'])
self.assertEqual(response.content, b'OK')
@override_settings(MIDDLEWARE=['middleware_exceptions.middleware.ProcessViewMiddleware'])
def test_process_view_return_response(self):
response = self.client.get('/middleware_exceptions/view/')
self.assertEqual(response.content, b'Processed view normal_view')
@override_settings(MIDDLEWARE=['middleware_exceptions.middleware.TemplateResponseMiddleware'])
def test_process_template_response(self):
response = self.client.get('/middleware_exceptions/template_response/')
self.assertEqual(response.content, b'template-response middleware')
@override_settings(MIDDLEWARE=['middleware_exceptions.middleware.LogMiddleware'])
def test_view_exception_converted_before_middleware(self):
response = self.client.get('/middleware_exceptions/permission_denied/')
self.assertEqual(mw.log, [(response.status_code, response.content)])
self.assertEqual(response.status_code, 403)
@override_settings(MIDDLEWARE=['middleware_exceptions.middleware.ProcessExceptionMiddleware'])
def test_view_exception_handled_by_process_exception(self):
response = self.client.get('/middleware_exceptions/error/')
self.assertEqual(response.content, b'Exception caught')
@override_settings(MIDDLEWARE=[
'middleware_exceptions.middleware.ProcessExceptionLogMiddleware',
'middleware_exceptions.middleware.ProcessExceptionMiddleware',
])
def test_response_from_process_exception_short_circuits_remainder(self):
response = self.client.get('/middleware_exceptions/error/')
self.assertEqual(mw.log, [])
self.assertEqual(response.content, b'Exception caught')
@override_settings(MIDDLEWARE=[
'middleware_exceptions.middleware.LogMiddleware',
'middleware_exceptions.middleware.NotFoundMiddleware',
])
def test_exception_in_middleware_converted_before_prior_middleware(self):
response = self.client.get('/middleware_exceptions/view/')
self.assertEqual(mw.log, [(404, response.content)])
self.assertEqual(response.status_code, 404)
@override_settings(MIDDLEWARE=['middleware_exceptions.middleware.ProcessExceptionMiddleware'])
def test_exception_in_render_passed_to_process_exception(self):
response = self.client.get('/middleware_exceptions/exception_in_render/')
self.assertEqual(response.content, b'Exception caught')
@override_settings(ROOT_URLCONF='middleware_exceptions.urls')
class RootUrlconfTests(SimpleTestCase):
@override_settings(ROOT_URLCONF=None)
def test_missing_root_urlconf(self):
# Removing ROOT_URLCONF is safe, as override_settings will restore
# the previously defined settings.
del settings.ROOT_URLCONF
with self.assertRaises(AttributeError):
self.client.get("/middleware_exceptions/view/")
class MyMiddleware(object):
def __init__(self, get_response=None):
raise MiddlewareNotUsed
def process_request(self, request):
pass
class MyMiddlewareWithExceptionMessage(object):
def __init__(self, get_response=None):
raise MiddlewareNotUsed('spam eggs')
def process_request(self, request):
pass
@override_settings(
DEBUG=True,
ROOT_URLCONF='middleware_exceptions.urls',
MIDDLEWARE=['django.middleware.common.CommonMiddleware'],
)
class MiddlewareNotUsedTests(SimpleTestCase):
rf = RequestFactory()
def test_raise_exception(self):
request = self.rf.get('middleware_exceptions/view/')
with self.assertRaises(MiddlewareNotUsed):
MyMiddleware().process_request(request)
@override_settings(MIDDLEWARE=['middleware_exceptions.tests.MyMiddleware'])
def test_log(self):
with patch_logger('django.request', 'debug') as calls:
self.client.get('/middleware_exceptions/view/')
self.assertEqual(len(calls), 1)
self.assertEqual(
calls[0],
"MiddlewareNotUsed: 'middleware_exceptions.tests.MyMiddleware'"
)
@override_settings(MIDDLEWARE=['middleware_exceptions.tests.MyMiddlewareWithExceptionMessage'])
def test_log_custom_message(self):
with patch_logger('django.request', 'debug') as calls:
self.client.get('/middleware_exceptions/view/')
self.assertEqual(len(calls), 1)
self.assertEqual(
calls[0],
"MiddlewareNotUsed('middleware_exceptions.tests.MyMiddlewareWithExceptionMessage'): spam eggs"
)
@override_settings(DEBUG=False)
def test_do_not_log_when_debug_is_false(self):
with patch_logger('django.request', 'debug') as calls:
self.client.get('/middleware_exceptions/view/')
self.assertEqual(len(calls), 0)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment