Refs #26601 -- Improved backwards-compatibility of DEP 5 middleware exception handling.
This commit is contained in:
parent
104ee2fdae
commit
7d1b69dbe7
|
@ -1,20 +1,10 @@
|
||||||
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
|
||||||
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):
|
||||||
)
|
)
|
||||||
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
|
||||||
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):
|
||||||
"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):
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
|
|
||||||
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):
|
||||||
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):
|
||||||
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:
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
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)
|
|
@ -1,78 +0,0 @@
|
||||||
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 @@
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
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):
|
||||||
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
|
||||||
|
|
|
@ -40,14 +40,9 @@ A middleware can be written as a function that looks like this::
|
||||||
|
|
||||||
def middleware(request):
|
def middleware(request):
|
||||||
# Code to be executed for each request before
|
# Code to be executed for each request before
|
||||||
# the view is called.
|
# the view (and later middleware) are called.
|
||||||
|
|
||||||
try:
|
|
||||||
response = get_response(request)
|
response = get_response(request)
|
||||||
except Exception as e:
|
|
||||||
# Code to handle an exception that wasn't caught
|
|
||||||
# further up the chain, if desired.
|
|
||||||
...
|
|
||||||
|
|
||||||
# Code to be executed for each request/response after
|
# Code to be executed for each request/response after
|
||||||
# the view is called.
|
# the view is called.
|
||||||
|
@ -56,7 +51,7 @@ A middleware can be written as a function that looks like this::
|
||||||
|
|
||||||
return middleware
|
return middleware
|
||||||
|
|
||||||
Or it can be written as a class with a ``__call__()`` method, like this::
|
Or it can be written as a class whose instances are callable, like this::
|
||||||
|
|
||||||
class SimpleMiddleware(object):
|
class SimpleMiddleware(object):
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
|
@ -65,24 +60,15 @@ Or it can be written as a class with a ``__call__()`` method, like this::
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
# Code to be executed for each request before
|
# Code to be executed for each request before
|
||||||
# the view is called.
|
# the view (and later middleware) are called.
|
||||||
|
|
||||||
try:
|
|
||||||
response = self.get_response(request)
|
response = self.get_response(request)
|
||||||
except Exception as e:
|
|
||||||
# Code to handle an exception that wasn't caught
|
|
||||||
# further up the chain, if desired.
|
|
||||||
...
|
|
||||||
|
|
||||||
# Code to be executed for each request/response after
|
# Code to be executed for each request/response after
|
||||||
# the view is called.
|
# the view is called.
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
In both examples, the ``try``/``except`` isn't required if the middleware
|
|
||||||
doesn't need to handle any exceptions. If it is included, it should probably
|
|
||||||
catch something more specific than ``Exception``.
|
|
||||||
|
|
||||||
The ``get_response`` callable provided by Django might be the actual view (if
|
The ``get_response`` callable provided by Django might be the actual view (if
|
||||||
this is the last listed middleware) or it might be the next middleware in the
|
this is the last listed middleware) or it might be the next middleware in the
|
||||||
chain. The current middleware doesn't need to know or care what exactly it is,
|
chain. The current middleware doesn't need to know or care what exactly it is,
|
||||||
|
@ -92,30 +78,32 @@ The above is a slight simplification -- the ``get_response`` callable for the
|
||||||
last middleware in the chain won't be the actual view but rather a wrapper
|
last middleware in the chain won't be the actual view but rather a wrapper
|
||||||
method from the handler which takes care of applying :ref:`view middleware
|
method from the handler which takes care of applying :ref:`view middleware
|
||||||
<view-middleware>`, calling the view with appropriate URL arguments, and
|
<view-middleware>`, calling the view with appropriate URL arguments, and
|
||||||
applying :ref:`template-response <template-response-middleware>` middleware.
|
applying :ref:`template-response <template-response-middleware>` and
|
||||||
|
:ref:`exception <exception-middleware>` middleware.
|
||||||
|
|
||||||
Middleware can live anywhere on your Python path.
|
Middleware can live anywhere on your Python path.
|
||||||
|
|
||||||
``__init__(get_response)``
|
``__init__(get_response)``
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
Middleware classes must accept a ``get_response`` argument. You can also
|
Middleware factories must accept a ``get_response`` argument. You can also
|
||||||
initialize some global state for the middleware. Keep in mind a couple of
|
initialize some global state for the middleware. Keep in mind a couple of
|
||||||
caveats:
|
caveats:
|
||||||
|
|
||||||
* Django initializes your middleware with only the ``get_response`` argument,
|
* Django initializes your middleware with only the ``get_response`` argument,
|
||||||
so you can't define ``__init__()`` as requiring any other arguments.
|
so you can't define ``__init__()`` as requiring any other arguments.
|
||||||
|
|
||||||
* Unlike the ``__call__()`` method which get called once per request,
|
* Unlike the ``__call__()`` method which is called once per request,
|
||||||
``__init__()`` is called only *once*, when the Web server starts.
|
``__init__()`` is called only *once*, when the Web server starts.
|
||||||
|
|
||||||
.. versionchanged:: 1.10
|
.. versionchanged:: 1.10
|
||||||
|
|
||||||
In older versions, ``__init__`` was not called until the Web server
|
In older versions, ``__init__()`` wasn't called until the Web server
|
||||||
responded to its first request.
|
responded to its first request.
|
||||||
|
|
||||||
If you want to allow your middleware to be used in Django 1.9 and earlier,
|
In older versions, ``__init__()`` didn't accept any arguments. To allow
|
||||||
make ``get_response`` an optional argument (``get_response=None``).
|
your middleware to be used in Django 1.9 and earlier, make ``get_response``
|
||||||
|
an optional argument (``get_response=None``).
|
||||||
|
|
||||||
Marking middleware as unused
|
Marking middleware as unused
|
||||||
----------------------------
|
----------------------------
|
||||||
|
@ -133,9 +121,9 @@ To activate a middleware component, add it to the :setting:`MIDDLEWARE` list in
|
||||||
your Django settings.
|
your Django settings.
|
||||||
|
|
||||||
In :setting:`MIDDLEWARE`, each middleware component is represented by a string:
|
In :setting:`MIDDLEWARE`, each middleware component is represented by a string:
|
||||||
the full Python path to the middleware's class or function name. For example,
|
the full Python path to the middleware factory's class or function name. For
|
||||||
here's the default value created by :djadmin:`django-admin startproject
|
example, here's the default value created by :djadmin:`django-admin
|
||||||
<startproject>`::
|
startproject <startproject>`::
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
@ -159,25 +147,29 @@ authenticated user in the session; therefore, it must run after
|
||||||
:ref:`middleware-ordering` for some common hints about ordering of Django
|
:ref:`middleware-ordering` for some common hints about ordering of Django
|
||||||
middleware classes.
|
middleware classes.
|
||||||
|
|
||||||
Hooks and application order
|
Middleware order and layering
|
||||||
===========================
|
=============================
|
||||||
|
|
||||||
During the request phase, before calling the view, Django applies middleware
|
During the request phase, before calling the view, Django applies middleware in
|
||||||
in the order it's defined in :setting:`MIDDLEWARE`, top-down. You can think of
|
the order it's defined in :setting:`MIDDLEWARE`, top-down.
|
||||||
it like an onion: each middleware class is a "layer" that wraps the view.
|
|
||||||
|
|
||||||
Middleware see only the changes made by middleware that run before it. A
|
You can think of it like an onion: each middleware class is a "layer" that
|
||||||
middleware (and the view) is skipped entirely if a preceding middleware
|
wraps the view, which is in the core of the onion. If the request passes
|
||||||
short-circuits by returning a response without ever calling ``get_response``.
|
through all the layers of the onion (each one calls ``get_response`` to pass
|
||||||
That response will only pass through the middleware that have already run.
|
the request in to the next layer), all the way to the view at the core, the
|
||||||
|
response will then pass through every layer (in reverse order) on the way back
|
||||||
|
out.
|
||||||
|
|
||||||
Similarly, a middleware that sees the request on the way in and doesn't return
|
If one of the layers decides to short-circuit and return a response without
|
||||||
a response is guaranteed that it will always see the response on the way back
|
ever calling its ``get_response``, none of the layers of the onion inside that
|
||||||
out. If the middleware also wants to see any uncaught exception on the way out,
|
layer (including the view) will see the request or the response. The response
|
||||||
it can wrap its call to ``get_response()`` in a ``try``/``except``.
|
will only return through the same layers that the request passed in through.
|
||||||
|
|
||||||
Besides the middleware pattern described earlier, you can add two other methods
|
Other middleware hooks
|
||||||
to class-based middleware:
|
======================
|
||||||
|
|
||||||
|
Besides the basic request/response middleware pattern described earlier, you
|
||||||
|
can add three other special methods to class-based middleware:
|
||||||
|
|
||||||
.. _view-middleware:
|
.. _view-middleware:
|
||||||
|
|
||||||
|
@ -217,6 +209,28 @@ bother calling the appropriate view; it'll apply response middleware to that
|
||||||
:func:`~django.views.decorators.csrf.csrf_protect` decorators which allow
|
:func:`~django.views.decorators.csrf.csrf_protect` decorators which allow
|
||||||
views to explicitly control at what point the CSRF validation should occur.
|
views to explicitly control at what point the CSRF validation should occur.
|
||||||
|
|
||||||
|
.. _exception-middleware:
|
||||||
|
|
||||||
|
``process_exception()``
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. method:: process_exception(request, exception)
|
||||||
|
|
||||||
|
``request`` is an :class:`~django.http.HttpRequest` object. ``exception`` is an
|
||||||
|
``Exception`` object raised by the view function.
|
||||||
|
|
||||||
|
Django calls ``process_exception()`` when a view raises an exception.
|
||||||
|
``process_exception()`` should return either ``None`` or an
|
||||||
|
:class:`~django.http.HttpResponse` object. If it returns an
|
||||||
|
:class:`~django.http.HttpResponse` object, the template response and response
|
||||||
|
middleware will be applied and the resulting response returned to the
|
||||||
|
browser. Otherwise, :ref:`default exception handling <error-views>` kicks in.
|
||||||
|
|
||||||
|
Again, middleware are run in reverse order during the response phase, which
|
||||||
|
includes ``process_exception``. If an exception middleware returns a response,
|
||||||
|
the ``process_exception`` methods of the middleware classes above that
|
||||||
|
middleware won't be called at all.
|
||||||
|
|
||||||
.. _template-response-middleware:
|
.. _template-response-middleware:
|
||||||
|
|
||||||
``process_template_response()``
|
``process_template_response()``
|
||||||
|
@ -268,31 +282,24 @@ must test for streaming responses and adjust their behavior accordingly::
|
||||||
for chunk in content:
|
for chunk in content:
|
||||||
yield alter_content(chunk)
|
yield alter_content(chunk)
|
||||||
|
|
||||||
.. _exception-middleware:
|
Exception handling
|
||||||
|
==================
|
||||||
|
|
||||||
Exception middleware
|
Django automatically converts exceptions raised by the view or by middleware
|
||||||
====================
|
into an appropriate HTTP response with an error status code. :ref:`Certain
|
||||||
|
exceptions <error-views>` are converted to 4xx status codes, while an unknown
|
||||||
|
exception is converted to a 500 status code.
|
||||||
|
|
||||||
A middleware that does some custom exception handling might looks like this::
|
This conversion takes place before and after each middleware (you can think of
|
||||||
|
it as the thin film in between each layer of the onion), so that every
|
||||||
class ExceptionMiddleware(object):
|
middleware can always rely on getting some kind of HTTP response back from
|
||||||
def __init__(self, get_response):
|
calling its ``get_response`` callable. Middleware don't need to worry about
|
||||||
self.get_response = get_response
|
wrapping their call to ``get_response`` in a ``try/except`` and handling an
|
||||||
|
exception that might have been raised by a later middleware or the view. Even
|
||||||
def __call__(self, request):
|
if the very next middleware in the chain raises an
|
||||||
try:
|
:class:`~django.http.Http404` exception, for example, your middleware won't see
|
||||||
response = self.get_response(request)
|
that exception; instead it will get an :class:`~django.http.HttpResponse`
|
||||||
except Exception as e:
|
object with a :attr:`~django.http.HttpResponse.status_code` of 404.
|
||||||
# Do something with the exception and possibly reraise it
|
|
||||||
# unless you wish to silence it.
|
|
||||||
...
|
|
||||||
return response
|
|
||||||
|
|
||||||
Middleware that wants to do something for all exception responses, an HTTP 404
|
|
||||||
for example, need to both catch the appropriate exception (e.g. ``Http404``)
|
|
||||||
and look for regular responses with the status code of interest. You can
|
|
||||||
subclass :class:`~django.middleware.exception.ExceptionMiddleware` if you want
|
|
||||||
to transform exceptions into the appropriate response.
|
|
||||||
|
|
||||||
.. _upgrading-middleware:
|
.. _upgrading-middleware:
|
||||||
|
|
||||||
|
@ -302,30 +309,57 @@ Upgrading pre-Django 1.10-style middleware
|
||||||
.. class:: django.utils.deprecation.MiddlewareMixin
|
.. class:: django.utils.deprecation.MiddlewareMixin
|
||||||
:module:
|
:module:
|
||||||
|
|
||||||
Django provides ``django.utils.deprecation.MiddlewareMixin`` to ease providing
|
Django provides ``django.utils.deprecation.MiddlewareMixin`` to ease creating
|
||||||
the existing built-in middleware in both new-style and old-style forms and to
|
middleware classes that are compatible with both :setting:`MIDDLEWARE` and the
|
||||||
ease similar conversions of third-party middleware.
|
old :setting:`MIDDLEWARE_CLASSES`.
|
||||||
|
|
||||||
In most cases, this mixin will be sufficient to convert a middleware with
|
The mixin provides an ``__init__()`` method that accepts an optional
|
||||||
sufficient backwards-compatibility; the new short-circuiting semantics will be
|
``get_response`` argument and stores it in ``self.get_response``.
|
||||||
harmless or even beneficial to the existing middleware.
|
|
||||||
|
|
||||||
In a few cases, a middleware class may need more invasive changes to adjust to
|
The ``__call__()`` method:
|
||||||
the new semantics.
|
|
||||||
|
|
||||||
For example, in the current request-handling logic, the handler transforms any
|
#. Calls ``self.process_request(request)`` (if defined).
|
||||||
exception that passes through all ``process_exception`` middleware uncaught
|
#. Calls ``self.get_response(request)`` to get the response from later
|
||||||
into a response with appropriate status code (e.g. 404, 403, 400, or 500), and
|
middleware and the view.
|
||||||
then passes that response through the full chain of ``process_response``
|
#. Calls ``self.process_response(request, response)`` (if defined).
|
||||||
middleware.
|
#. Returns the response.
|
||||||
|
|
||||||
In new-style middleware, a given middleware only gets one shot at a given
|
If used with :setting:`MIDDLEWARE_CLASSES`, the ``__call__()`` method will
|
||||||
response or uncaught exception "on the way out," and will see either a returned
|
never be used; Django calls ``process_request()`` and ``process_response()``
|
||||||
response or an uncaught exception, but not both.
|
directly.
|
||||||
|
|
||||||
This means that certain middleware which want to do something with all 404
|
In most cases, inheriting from this mixin will be sufficient to make an
|
||||||
responses (for example, the ``RedirectFallbackMiddleware`` and
|
old-style middleware compatible with the new system with sufficient
|
||||||
``FlatpageFallbackMiddleware`` in ``django.contrib.redirects`` and
|
backwards-compatibility. The new short-circuiting semantics will be harmless or
|
||||||
``django.contrib.flatpages``) now need to watch out for both a 404 response
|
even beneficial to the existing middleware. In a few cases, a middleware class
|
||||||
and an uncaught ``Http404`` exception. They do this by subclassing
|
may need some changes to adjust to the new semantics.
|
||||||
:class:`~django.middleware.exception.ExceptionMiddleware`.
|
|
||||||
|
These are the behavioral differences between using :setting:`MIDDLEWARE` and
|
||||||
|
:setting:`MIDDLEWARE_CLASSES`:
|
||||||
|
|
||||||
|
1. Under :setting:`MIDDLEWARE_CLASSES`, every middleware will always have its
|
||||||
|
``process_response`` method called, even if an earlier middleware
|
||||||
|
short-circuited by returning a response from its ``process_request``
|
||||||
|
method. Under :setting:`MIDDLEWARE`, middleware behaves more like an onion:
|
||||||
|
the layers that a response goes through on the way out are the same layers
|
||||||
|
that saw the request on the way in. If a middleware short-circuits, only
|
||||||
|
that middleware and the ones before it in :setting:`MIDDLEWARE` will see the
|
||||||
|
response.
|
||||||
|
|
||||||
|
2. Under :setting:`MIDDLEWARE_CLASSES`, ``process_exception`` is applied to
|
||||||
|
exceptions raised from a middleware ``process_request`` method. Under
|
||||||
|
:setting:`MIDDLEWARE`, ``process_exception`` applies only to exceptions
|
||||||
|
raised from the view (or from the ``render`` method of a
|
||||||
|
:class:`~django.template.response.TemplateResponse`). Exceptions raised from
|
||||||
|
a middleware are converted to the appropriate HTTP response and then passed
|
||||||
|
to the next middleware.
|
||||||
|
|
||||||
|
3. Under :setting:`MIDDLEWARE_CLASSES`, if a ``process_response`` method raises
|
||||||
|
an exception, the ``process_response`` methods of all earlier middleware are
|
||||||
|
skipped and a ``500 Internal Server Error`` HTTP response is always
|
||||||
|
returned (even if the exception raised was e.g. an
|
||||||
|
:class:`~django.http.Http404`). Under :setting:`MIDDLEWARE`, an exception
|
||||||
|
raised from a middleware will immediately be converted to the appropriate
|
||||||
|
HTTP response, and then the next middleware in line will see that
|
||||||
|
response. Middleware are never skipped due to a middleware raising an
|
||||||
|
exception.
|
||||||
|
|
|
@ -1,10 +1,58 @@
|
||||||
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')
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
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):
|
||||||
# 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):
|
||||||
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(
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
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)
|
Loading…
Reference in New Issue