diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 1b71b1b1b83..09af2fcda4f 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -433,14 +433,16 @@ SECURE_PROXY_SSL_HEADER = None # MIDDLEWARE # ############## -# List of middleware classes to use. Order is important; in the request phase, -# this middleware classes will be applied in the order given, and in the -# response phase the middleware will be applied in reverse order. +# List of middleware to use. Order is important; in the request phase, these +# middleware will be applied in the order given, and in the response +# phase the middleware will be applied in reverse order. MIDDLEWARE_CLASSES = [ 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', ] +MIDDLEWARE = None + ############ # SESSIONS # ############ diff --git a/django/conf/project_template/project_name/settings.py-tpl b/django/conf/project_template/project_name/settings.py-tpl index 3ef0ab79000..7dfe186929c 100644 --- a/django/conf/project_template/project_name/settings.py-tpl +++ b/django/conf/project_template/project_name/settings.py-tpl @@ -39,7 +39,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', ] -MIDDLEWARE_CLASSES = [ +MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', diff --git a/django/contrib/admin/tests.py b/django/contrib/admin/tests.py index e569df396b8..7043ed80b84 100644 --- a/django/contrib/admin/tests.py +++ b/django/contrib/admin/tests.py @@ -1,19 +1,18 @@ from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.test import modify_settings from django.test.selenium import SeleniumTestCase +from django.utils.deprecation import MiddlewareMixin from django.utils.translation import ugettext as _ -class CSPMiddleware(object): +class CSPMiddleware(MiddlewareMixin): """The admin's JavaScript should be compatible with CSP.""" def process_response(self, request, response): response['Content-Security-Policy'] = "default-src 'self'" return response -@modify_settings( - MIDDLEWARE_CLASSES={'append': 'django.contrib.admin.tests.CSPMiddleware'}, -) +@modify_settings(MIDDLEWARE={'append': 'django.contrib.admin.tests.CSPMiddleware'}) class AdminSeleniumTestCase(SeleniumTestCase, StaticLiveServerTestCase): available_apps = [ diff --git a/django/contrib/admindocs/middleware.py b/django/contrib/admindocs/middleware.py index 2e4cede0b90..4bdd4f45b5b 100644 --- a/django/contrib/admindocs/middleware.py +++ b/django/contrib/admindocs/middleware.py @@ -1,8 +1,9 @@ from django import http from django.conf import settings +from django.utils.deprecation import MiddlewareMixin -class XViewMiddleware(object): +class XViewMiddleware(MiddlewareMixin): """ Adds an X-View header to internal HEAD requests -- used by the documentation system. """ @@ -15,8 +16,11 @@ class XViewMiddleware(object): """ assert hasattr(request, 'user'), ( "The XView middleware requires authentication middleware to be " - "installed. Edit your MIDDLEWARE_CLASSES setting to insert " - "'django.contrib.auth.middleware.AuthenticationMiddleware'.") + "installed. Edit your MIDDLEWARE%s setting to insert " + "'django.contrib.auth.middleware.AuthenticationMiddleware'." % ( + "_CLASSES" if settings.MIDDLEWARE is None else "" + ) + ) if request.method == 'HEAD' and (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or (request.user.is_active and request.user.is_staff)): response = http.HttpResponse() diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py index 476729c1472..8c56e645fe6 100644 --- a/django/contrib/auth/middleware.py +++ b/django/contrib/auth/middleware.py @@ -1,7 +1,9 @@ +from django.conf import settings from django.contrib import auth from django.contrib.auth import load_backend from django.contrib.auth.backends import RemoteUserBackend from django.core.exceptions import ImproperlyConfigured +from django.utils.deprecation import MiddlewareMixin from django.utils.functional import SimpleLazyObject @@ -11,18 +13,18 @@ def get_user(request): return request._cached_user -class AuthenticationMiddleware(object): +class AuthenticationMiddleware(MiddlewareMixin): def process_request(self, request): assert hasattr(request, 'session'), ( "The Django authentication middleware requires session middleware " - "to be installed. Edit your MIDDLEWARE_CLASSES setting to insert " + "to be installed. Edit your MIDDLEWARE%s setting to insert " "'django.contrib.sessions.middleware.SessionMiddleware' before " "'django.contrib.auth.middleware.AuthenticationMiddleware'." - ) + ) % ("_CLASSES" if settings.MIDDLEWARE is None else "") request.user = SimpleLazyObject(lambda: get_user(request)) -class SessionAuthenticationMiddleware(object): +class SessionAuthenticationMiddleware(MiddlewareMixin): """ Formerly, a middleware for invalidating a user's sessions that don't correspond to the user's current session authentication hash. However, it @@ -35,7 +37,7 @@ class SessionAuthenticationMiddleware(object): pass -class RemoteUserMiddleware(object): +class RemoteUserMiddleware(MiddlewareMixin): """ Middleware for utilizing Web-server-provided authentication. @@ -61,7 +63,7 @@ class RemoteUserMiddleware(object): raise ImproperlyConfigured( "The Django remote user auth middleware requires the" " authentication middleware to be installed. Edit your" - " MIDDLEWARE_CLASSES setting to insert" + " MIDDLEWARE setting to insert" " 'django.contrib.auth.middleware.AuthenticationMiddleware'" " before the RemoteUserMiddleware class.") try: diff --git a/django/contrib/flatpages/forms.py b/django/contrib/flatpages/forms.py index 19f3b6309ca..fbc7da04ded 100644 --- a/django/contrib/flatpages/forms.py +++ b/django/contrib/flatpages/forms.py @@ -29,8 +29,9 @@ class FlatpageForm(forms.ModelForm): ugettext("URL is missing a leading slash."), code='missing_leading_slash', ) - if (settings.APPEND_SLASH and - 'django.middleware.common.CommonMiddleware' in settings.MIDDLEWARE_CLASSES and + if (settings.APPEND_SLASH and ( + (settings.MIDDLEWARE and 'django.middleware.common.CommonMiddleware' in settings.MIDDLEWARE) or + 'django.middleware.common.CommonMiddleware' in settings.MIDDLEWARE_CLASSES) and not url.endswith('/')): raise forms.ValidationError( ugettext("URL is missing a trailing slash."), diff --git a/django/contrib/flatpages/middleware.py b/django/contrib/flatpages/middleware.py index 32d881ffa4a..4c6760535d2 100644 --- a/django/contrib/flatpages/middleware.py +++ b/django/contrib/flatpages/middleware.py @@ -1,9 +1,20 @@ from django.conf import settings from django.contrib.flatpages.views import flatpage from django.http import Http404 +from django.middleware.exception import ExceptionMiddleware -class FlatpageFallbackMiddleware(object): +class FlatpageFallbackMiddleware(ExceptionMiddleware): + + 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): if response.status_code != 404: return response # No need to check for a flatpage for non-404 responses. diff --git a/django/contrib/messages/middleware.py b/django/contrib/messages/middleware.py index a84dc6cb114..4cdbf63c642 100644 --- a/django/contrib/messages/middleware.py +++ b/django/contrib/messages/middleware.py @@ -1,8 +1,9 @@ from django.conf import settings from django.contrib.messages.storage import default_storage +from django.utils.deprecation import MiddlewareMixin -class MessageMiddleware(object): +class MessageMiddleware(MiddlewareMixin): """ Middleware that handles temporary messages. """ diff --git a/django/contrib/messages/storage/session.py b/django/contrib/messages/storage/session.py index 7903fb03c19..624de42b8ce 100644 --- a/django/contrib/messages/storage/session.py +++ b/django/contrib/messages/storage/session.py @@ -1,5 +1,6 @@ import json +from django.conf import settings from django.contrib.messages.storage.base import BaseStorage from django.contrib.messages.storage.cookie import ( MessageDecoder, MessageEncoder, @@ -17,7 +18,7 @@ class SessionStorage(BaseStorage): assert hasattr(request, 'session'), "The session-based temporary "\ "message storage requires session middleware to be installed, "\ "and come before the message middleware in the "\ - "MIDDLEWARE_CLASSES list." + "MIDDLEWARE%s list." % ("_CLASSES" if settings.MIDDLEWARE is None else "") super(SessionStorage, self).__init__(request, *args, **kwargs) def _get(self, *args, **kwargs): diff --git a/django/contrib/redirects/middleware.py b/django/contrib/redirects/middleware.py index dc8d94bc967..e3ee982a8b2 100644 --- a/django/contrib/redirects/middleware.py +++ b/django/contrib/redirects/middleware.py @@ -6,20 +6,26 @@ from django.conf import settings from django.contrib.redirects.models import Redirect from django.contrib.sites.shortcuts import get_current_site from django.core.exceptions import ImproperlyConfigured +from django.middleware.exception import ExceptionMiddleware -class RedirectFallbackMiddleware(object): +class RedirectFallbackMiddleware(ExceptionMiddleware): # Defined as class-level attributes to be subclassing-friendly. response_gone_class = http.HttpResponseGone response_redirect_class = http.HttpResponsePermanentRedirect - def __init__(self): + def __init__(self, get_response=None): if not apps.is_installed('django.contrib.sites'): raise ImproperlyConfigured( "You cannot use RedirectFallbackMiddleware when " "django.contrib.sites is not installed." ) + 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): # No need to check for a redirect for non-404 responses. diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 73ffe5a2a2c..4871b480755 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -5,11 +5,13 @@ from django.conf import settings from django.contrib.sessions.backends.base import UpdateError from django.shortcuts import redirect from django.utils.cache import patch_vary_headers +from django.utils.deprecation import MiddlewareMixin from django.utils.http import cookie_date -class SessionMiddleware(object): - def __init__(self): +class SessionMiddleware(MiddlewareMixin): + def __init__(self, get_response=None): + self.get_response = get_response engine = import_module(settings.SESSION_ENGINE) self.SessionStore = engine.SessionStore diff --git a/django/core/checks/security/base.py b/django/core/checks/security/base.py index eacbb908439..eee7f854c8d 100644 --- a/django/core/checks/security/base.py +++ b/django/core/checks/security/base.py @@ -1,13 +1,14 @@ from django.conf import settings from .. import Tags, Warning, register +from ..utils import patch_middleware_message SECRET_KEY_MIN_LENGTH = 50 SECRET_KEY_MIN_UNIQUE_CHARACTERS = 5 W001 = Warning( "You do not have 'django.middleware.security.SecurityMiddleware' " - "in your MIDDLEWARE_CLASSES so the SECURE_HSTS_SECONDS, " + "in your MIDDLEWARE so the SECURE_HSTS_SECONDS, " "SECURE_CONTENT_TYPE_NOSNIFF, " "SECURE_BROWSER_XSS_FILTER, and SECURE_SSL_REDIRECT settings " "will have no effect.", @@ -17,7 +18,7 @@ W001 = Warning( W002 = Warning( "You do not have " "'django.middleware.clickjacking.XFrameOptionsMiddleware' in your " - "MIDDLEWARE_CLASSES, so your pages will not be served with an " + "MIDDLEWARE, so your pages will not be served with an " "'x-frame-options' header. Unless there is a good reason for your " "site to be served in a frame, you should consider enabling this " "header to help prevent clickjacking attacks.", @@ -88,7 +89,7 @@ W018 = Warning( W019 = Warning( "You have " "'django.middleware.clickjacking.XFrameOptionsMiddleware' in your " - "MIDDLEWARE_CLASSES, but X_FRAME_OPTIONS is not set to 'DENY'. " + "MIDDLEWARE, but X_FRAME_OPTIONS is not set to 'DENY'. " "The default is 'SAMEORIGIN', but unless there is a good reason for " "your site to serve other parts of itself in a frame, you should " "change it to 'DENY'.", @@ -102,23 +103,25 @@ W020 = Warning( def _security_middleware(): - return "django.middleware.security.SecurityMiddleware" in settings.MIDDLEWARE_CLASSES + return ("django.middleware.security.SecurityMiddleware" in settings.MIDDLEWARE_CLASSES or + settings.MIDDLEWARE and "django.middleware.security.SecurityMiddleware" in settings.MIDDLEWARE) def _xframe_middleware(): - return "django.middleware.clickjacking.XFrameOptionsMiddleware" in settings.MIDDLEWARE_CLASSES + return ("django.middleware.clickjacking.XFrameOptionsMiddleware" in settings.MIDDLEWARE_CLASSES or + settings.MIDDLEWARE and "django.middleware.clickjacking.XFrameOptionsMiddleware" in settings.MIDDLEWARE) @register(Tags.security, deploy=True) def check_security_middleware(app_configs, **kwargs): passed_check = _security_middleware() - return [] if passed_check else [W001] + return [] if passed_check else [patch_middleware_message(W001)] @register(Tags.security, deploy=True) def check_xframe_options_middleware(app_configs, **kwargs): passed_check = _xframe_middleware() - return [] if passed_check else [W002] + return [] if passed_check else [patch_middleware_message(W002)] @register(Tags.security, deploy=True) @@ -186,7 +189,7 @@ def check_xframe_deny(app_configs, **kwargs): not _xframe_middleware() or settings.X_FRAME_OPTIONS == 'DENY' ) - return [] if passed_check else [W019] + return [] if passed_check else [patch_middleware_message(W019)] @register(Tags.security, deploy=True) diff --git a/django/core/checks/security/csrf.py b/django/core/checks/security/csrf.py index 3effbc44983..b90308a0162 100644 --- a/django/core/checks/security/csrf.py +++ b/django/core/checks/security/csrf.py @@ -1,19 +1,20 @@ from django.conf import settings from .. import Tags, Warning, register +from ..utils import patch_middleware_message W003 = Warning( "You don't appear to be using Django's built-in " "cross-site request forgery protection via the middleware " "('django.middleware.csrf.CsrfViewMiddleware' is not in your " - "MIDDLEWARE_CLASSES). Enabling the middleware is the safest approach " + "MIDDLEWARE). Enabling the middleware is the safest approach " "to ensure you don't leave any holes.", id='security.W003', ) W016 = Warning( "You have 'django.middleware.csrf.CsrfViewMiddleware' in your " - "MIDDLEWARE_CLASSES, but you have not set CSRF_COOKIE_SECURE to True. " + "MIDDLEWARE, but you have not set CSRF_COOKIE_SECURE to True. " "Using a secure-only CSRF cookie makes it more difficult for network " "traffic sniffers to steal the CSRF token.", id='security.W016', @@ -21,7 +22,7 @@ W016 = Warning( W017 = Warning( "You have 'django.middleware.csrf.CsrfViewMiddleware' in your " - "MIDDLEWARE_CLASSES, but you have not set CSRF_COOKIE_HTTPONLY to True. " + "MIDDLEWARE, but you have not set CSRF_COOKIE_HTTPONLY to True. " "Using an HttpOnly CSRF cookie makes it more difficult for cross-site " "scripting attacks to steal the CSRF token.", id='security.W017', @@ -29,13 +30,14 @@ W017 = Warning( def _csrf_middleware(): - return "django.middleware.csrf.CsrfViewMiddleware" in settings.MIDDLEWARE_CLASSES + return ("django.middleware.csrf.CsrfViewMiddleware" in settings.MIDDLEWARE_CLASSES or + settings.MIDDLEWARE and "django.middleware.csrf.CsrfViewMiddleware" in settings.MIDDLEWARE) @register(Tags.security, deploy=True) def check_csrf_middleware(app_configs, **kwargs): passed_check = _csrf_middleware() - return [] if passed_check else [W003] + return [] if passed_check else [patch_middleware_message(W003)] @register(Tags.security, deploy=True) @@ -44,7 +46,7 @@ def check_csrf_cookie_secure(app_configs, **kwargs): not _csrf_middleware() or settings.CSRF_COOKIE_SECURE ) - return [] if passed_check else [W016] + return [] if passed_check else [patch_middleware_message(W016)] @register(Tags.security, deploy=True) @@ -53,4 +55,4 @@ def check_csrf_cookie_httponly(app_configs, **kwargs): not _csrf_middleware() or settings.CSRF_COOKIE_HTTPONLY ) - return [] if passed_check else [W017] + return [] if passed_check else [patch_middleware_message(W017)] diff --git a/django/core/checks/security/sessions.py b/django/core/checks/security/sessions.py index 7e857d74d84..5fae4ed4d99 100644 --- a/django/core/checks/security/sessions.py +++ b/django/core/checks/security/sessions.py @@ -1,6 +1,7 @@ from django.conf import settings from .. import Tags, Warning, register +from ..utils import patch_middleware_message def add_session_cookie_message(message): @@ -20,7 +21,7 @@ W010 = Warning( W011 = Warning( add_session_cookie_message( "You have 'django.contrib.sessions.middleware.SessionMiddleware' " - "in your MIDDLEWARE_CLASSES, but you have not set " + "in your MIDDLEWARE, but you have not set " "SESSION_COOKIE_SECURE to True." ), id='security.W011', @@ -50,7 +51,7 @@ W013 = Warning( W014 = Warning( add_httponly_message( "You have 'django.contrib.sessions.middleware.SessionMiddleware' " - "in your MIDDLEWARE_CLASSES, but you have not set " + "in your MIDDLEWARE, but you have not set " "SESSION_COOKIE_HTTPONLY to True." ), id='security.W014', @@ -69,7 +70,7 @@ def check_session_cookie_secure(app_configs, **kwargs): if _session_app(): errors.append(W010) if _session_middleware(): - errors.append(W011) + errors.append(patch_middleware_message(W011)) if len(errors) > 1: errors = [W012] return errors @@ -82,15 +83,15 @@ def check_session_cookie_httponly(app_configs, **kwargs): if _session_app(): errors.append(W013) if _session_middleware(): - errors.append(W014) + errors.append(patch_middleware_message(W014)) if len(errors) > 1: errors = [W015] return errors def _session_middleware(): - return ("django.contrib.sessions.middleware.SessionMiddleware" in - settings.MIDDLEWARE_CLASSES) + return ("django.contrib.sessions.middleware.SessionMiddleware" in settings.MIDDLEWARE_CLASSES or + settings.MIDDLEWARE and "django.contrib.sessions.middleware.SessionMiddleware" in settings.MIDDLEWARE) def _session_app(): diff --git a/django/core/checks/utils.py b/django/core/checks/utils.py new file mode 100644 index 00000000000..995d8432c9b --- /dev/null +++ b/django/core/checks/utils.py @@ -0,0 +1,10 @@ +import copy + +from django.conf import settings + + +def patch_middleware_message(error): + if settings.MIDDLEWARE is None: + error = copy.copy(error) + error.msg = error.msg.replace('MIDDLEWARE', 'MIDDLEWARE_CLASSES') + return error diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 65cb59357ca..b206c70b058 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -7,7 +7,7 @@ import warnings from django.conf import settings from django.core import signals -from django.core.exceptions import MiddlewareNotUsed +from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed from django.db import connections, transaction from django.middleware.exception import ExceptionMiddleware from django.urls import get_resolver, get_urlconf, set_urlconf @@ -31,7 +31,8 @@ class BaseHandler(object): def load_middleware(self): """ - Populate middleware lists from settings.MIDDLEWARE_CLASSES. + Populate middleware lists from settings.MIDDLEWARE (or the deprecated + MIDDLEWARE_CLASSES). Must be called after the environment is fixed (see __call__ in subclasses). """ @@ -41,29 +42,55 @@ class BaseHandler(object): self._response_middleware = [] self._exception_middleware = [] - handler = self._legacy_get_response - for middleware_path in settings.MIDDLEWARE_CLASSES: - mw_class = import_string(middleware_path) - try: - mw_instance = mw_class() - except MiddlewareNotUsed as exc: - if settings.DEBUG: - if six.text_type(exc): - logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc) - else: - logger.debug('MiddlewareNotUsed: %r', middleware_path) - continue + if settings.MIDDLEWARE is None: + handler = self._legacy_get_response + for middleware_path in settings.MIDDLEWARE_CLASSES: + mw_class = import_string(middleware_path) + try: + mw_instance = mw_class() + except MiddlewareNotUsed as exc: + if settings.DEBUG: + if six.text_type(exc): + logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc) + else: + logger.debug('MiddlewareNotUsed: %r', middleware_path) + continue - if hasattr(mw_instance, 'process_request'): - self._request_middleware.append(mw_instance.process_request) - if hasattr(mw_instance, 'process_view'): - self._view_middleware.append(mw_instance.process_view) - if hasattr(mw_instance, 'process_template_response'): - self._template_response_middleware.insert(0, mw_instance.process_template_response) - if hasattr(mw_instance, 'process_response'): - self._response_middleware.insert(0, mw_instance.process_response) - if hasattr(mw_instance, 'process_exception'): - self._exception_middleware.insert(0, mw_instance.process_exception) + if hasattr(mw_instance, 'process_request'): + self._request_middleware.append(mw_instance.process_request) + if hasattr(mw_instance, 'process_view'): + self._view_middleware.append(mw_instance.process_view) + if hasattr(mw_instance, 'process_template_response'): + self._template_response_middleware.insert(0, mw_instance.process_template_response) + if hasattr(mw_instance, 'process_response'): + self._response_middleware.insert(0, mw_instance.process_response) + if hasattr(mw_instance, 'process_exception'): + self._exception_middleware.insert(0, mw_instance.process_exception) + else: + handler = self._get_response + for middleware_path in reversed(settings.MIDDLEWARE): + middleware = import_string(middleware_path) + try: + mw_instance = middleware(handler) + except MiddlewareNotUsed as exc: + if settings.DEBUG: + if six.text_type(exc): + logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc) + else: + logger.debug('MiddlewareNotUsed: %r', middleware_path) + continue + + if mw_instance is None: + raise ImproperlyConfigured( + 'Middleware factory %s returned None.' % middleware_path + ) + + if hasattr(mw_instance, 'process_view'): + self._view_middleware.insert(0, mw_instance.process_view) + if hasattr(mw_instance, 'process_template_response'): + self._template_response_middleware.append(mw_instance.process_template_response) + + handler = mw_instance handler = ExceptionMiddleware(handler, self) diff --git a/django/middleware/cache.py b/django/middleware/cache.py index 11a65cd9284..8ee3f514e85 100644 --- a/django/middleware/cache.py +++ b/django/middleware/cache.py @@ -4,7 +4,7 @@ URL. The canonical way to enable cache middleware is to set ``UpdateCacheMiddleware`` as your first piece of middleware, and ``FetchFromCacheMiddleware`` as the last:: - MIDDLEWARE_CLASSES = [ + MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', ... 'django.middleware.cache.FetchFromCacheMiddleware' @@ -49,22 +49,24 @@ from django.utils.cache import ( get_cache_key, get_max_age, has_vary_header, learn_cache_key, patch_response_headers, ) +from django.utils.deprecation import MiddlewareMixin -class UpdateCacheMiddleware(object): +class UpdateCacheMiddleware(MiddlewareMixin): """ Response-phase cache middleware that updates the cache if the response is cacheable. Must be used as part of the two-part update/fetch cache middleware. - UpdateCacheMiddleware must be the first piece of middleware in - MIDDLEWARE_CLASSES so that it'll get called last during the response phase. + UpdateCacheMiddleware must be the first piece of middleware in MIDDLEWARE + so that it'll get called last during the response phase. """ - def __init__(self): + def __init__(self, get_response=None): self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS self.cache = caches[self.cache_alias] + self.get_response = get_response def _should_update_cache(self, request, response): return hasattr(request, '_cache_update_cache') and request._cache_update_cache @@ -104,18 +106,19 @@ class UpdateCacheMiddleware(object): return response -class FetchFromCacheMiddleware(object): +class FetchFromCacheMiddleware(MiddlewareMixin): """ Request-phase cache middleware that fetches a page from the cache. Must be used as part of the two-part update/fetch cache middleware. - FetchFromCacheMiddleware must be the last piece of middleware in - MIDDLEWARE_CLASSES so that it'll get called last during the request phase. + FetchFromCacheMiddleware must be the last piece of middleware in MIDDLEWARE + so that it'll get called last during the request phase. """ - def __init__(self): + def __init__(self, get_response=None): self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS self.cache = caches[self.cache_alias] + self.get_response = get_response def process_request(self, request): """ @@ -153,7 +156,8 @@ class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware): Also used as the hook point for the cache decorator, which is generated using the decorator-from-middleware utility. """ - def __init__(self, cache_timeout=None, **kwargs): + def __init__(self, get_response=None, cache_timeout=None, **kwargs): + self.get_response = get_response # We need to differentiate between "provided, but using default value", # and "not provided". If the value is provided using a default, then # we fall back to system defaults. If it is not provided at all, diff --git a/django/middleware/clickjacking.py b/django/middleware/clickjacking.py index acd0705703b..8659646c614 100644 --- a/django/middleware/clickjacking.py +++ b/django/middleware/clickjacking.py @@ -6,9 +6,10 @@ malicious site loading resources from your site in a hidden frame. """ from django.conf import settings +from django.utils.deprecation import MiddlewareMixin -class XFrameOptionsMiddleware(object): +class XFrameOptionsMiddleware(MiddlewareMixin): """ Middleware that sets the X-Frame-Options HTTP header in HTTP responses. diff --git a/django/middleware/common.py b/django/middleware/common.py index b7db74a018c..4cec6f0e417 100644 --- a/django/middleware/common.py +++ b/django/middleware/common.py @@ -7,6 +7,7 @@ from django.core.exceptions import PermissionDenied from django.core.mail import mail_managers from django.urls import is_valid_path from django.utils.cache import get_conditional_response, set_response_etag +from django.utils.deprecation import MiddlewareMixin from django.utils.encoding import force_text from django.utils.http import unquote_etag from django.utils.six.moves.urllib.parse import urlparse @@ -14,7 +15,7 @@ from django.utils.six.moves.urllib.parse import urlparse logger = logging.getLogger('django.request') -class CommonMiddleware(object): +class CommonMiddleware(MiddlewareMixin): """ "Common" middleware for taking care of some basic operations: @@ -129,7 +130,7 @@ class CommonMiddleware(object): return response -class BrokenLinkEmailsMiddleware(object): +class BrokenLinkEmailsMiddleware(MiddlewareMixin): def process_response(self, request, response): """ diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index f2d6a497087..ba9f63ec3d9 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -13,6 +13,7 @@ from django.conf import settings from django.urls import get_callable from django.utils.cache import patch_vary_headers from django.utils.crypto import constant_time_compare, get_random_string +from django.utils.deprecation import MiddlewareMixin from django.utils.encoding import force_text from django.utils.http import is_same_domain from django.utils.six.moves.urllib.parse import urlparse @@ -78,7 +79,7 @@ def _sanitize_token(token): return token -class CsrfViewMiddleware(object): +class CsrfViewMiddleware(MiddlewareMixin): """ Middleware that requires a present and correct csrfmiddlewaretoken for POST requests that have a CSRF cookie, and sets an outgoing diff --git a/django/middleware/gzip.py b/django/middleware/gzip.py index e772cb326dd..642e3188d3b 100644 --- a/django/middleware/gzip.py +++ b/django/middleware/gzip.py @@ -1,12 +1,13 @@ import re from django.utils.cache import patch_vary_headers +from django.utils.deprecation import MiddlewareMixin from django.utils.text import compress_sequence, compress_string re_accepts_gzip = re.compile(r'\bgzip\b') -class GZipMiddleware(object): +class GZipMiddleware(MiddlewareMixin): """ This middleware compresses content if the browser allows gzip compression. It sets the Vary header accordingly, so that caches will base their storage diff --git a/django/middleware/http.py b/django/middleware/http.py index 7912200634c..a3030463c4c 100644 --- a/django/middleware/http.py +++ b/django/middleware/http.py @@ -1,8 +1,9 @@ from django.utils.cache import get_conditional_response +from django.utils.deprecation import MiddlewareMixin from django.utils.http import http_date, parse_http_date_safe, unquote_etag -class ConditionalGetMiddleware(object): +class ConditionalGetMiddleware(MiddlewareMixin): """ Handles conditional GET operations. If the response has an ETag or Last-Modified header, and the request has If-None-Match or diff --git a/django/middleware/locale.py b/django/middleware/locale.py index f8b16b84756..1a95dbc8b21 100644 --- a/django/middleware/locale.py +++ b/django/middleware/locale.py @@ -3,12 +3,13 @@ from django.conf import settings from django.conf.urls.i18n import is_language_prefix_patterns_used from django.http import HttpResponseRedirect +from django.middleware.exception import ExceptionMiddleware from django.urls import get_script_prefix, is_valid_path from django.utils import translation from django.utils.cache import patch_vary_headers -class LocaleMiddleware(object): +class LocaleMiddleware(ExceptionMiddleware): """ This is a very simple middleware that parses a request and decides what translation object to install in the current @@ -18,6 +19,17 @@ class LocaleMiddleware(object): """ 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): urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF) i18n_patterns_used, prefixed_default_language = is_language_prefix_patterns_used(urlconf) diff --git a/django/middleware/security.py b/django/middleware/security.py index 46afb68f577..02a59ad6e45 100644 --- a/django/middleware/security.py +++ b/django/middleware/security.py @@ -2,10 +2,11 @@ import re from django.conf import settings from django.http import HttpResponsePermanentRedirect +from django.utils.deprecation import MiddlewareMixin -class SecurityMiddleware(object): - def __init__(self): +class SecurityMiddleware(MiddlewareMixin): + def __init__(self, get_response=None): self.sts_seconds = settings.SECURE_HSTS_SECONDS self.sts_include_subdomains = settings.SECURE_HSTS_INCLUDE_SUBDOMAINS self.content_type_nosniff = settings.SECURE_CONTENT_TYPE_NOSNIFF @@ -13,6 +14,7 @@ class SecurityMiddleware(object): self.redirect = settings.SECURE_SSL_REDIRECT self.redirect_host = settings.SECURE_SSL_HOST self.redirect_exempt = [re.compile(r) for r in settings.SECURE_REDIRECT_EXEMPT] + self.get_response = get_response def process_request(self, request): path = request.path.lstrip("/") diff --git a/django/test/client.py b/django/test/client.py index e6a3650701c..dc813716d61 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -125,7 +125,7 @@ class ClientHandler(BaseHandler): def __call__(self, environ): # Set up middleware if needed. We couldn't do this earlier, because # settings weren't available. - if self._request_middleware is None: + if self._middleware_chain is None: self.load_middleware() request_started.disconnect(close_old_connections) diff --git a/django/utils/deprecation.py b/django/utils/deprecation.py index 70216e8c5ba..7627426080b 100644 --- a/django/utils/deprecation.py +++ b/django/utils/deprecation.py @@ -109,3 +109,25 @@ class CallableBool: CallableFalse = CallableBool(False) CallableTrue = CallableBool(True) + + +class MiddlewareMixin(object): + def __init__(self, get_response=None): + self.get_response = get_response + super(MiddlewareMixin, self).__init__() + + def __call__(self, request): + response = None + if hasattr(self, 'process_request'): + response = self.process_request(request) + if not response: + try: + 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'): + response = self.process_response(request, response) + return response diff --git a/django/views/debug.py b/django/views/debug.py index 73fedce68d0..56329325d3b 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -852,7 +852,8 @@ Python Version: {{ sys_version_info }} Installed Applications: {{ settings.INSTALLED_APPS|pprint }} Installed Middleware: -{{ settings.MIDDLEWARE_CLASSES|pprint }} +{% if settings.MIDDLEWARE is not None %}{{ settings.MIDDLEWARE|pprint }}""" +"""{% else %}{{ settings.MIDDLEWARE_CLASSES|pprint }}{% endif %} {% if template_does_not_exist %}Template loader postmortem {% if postmortem %}Django tried loading these templates, in this order: @@ -1059,7 +1060,8 @@ Server time: {{server_time|date:"r"}} Installed Applications: {{ settings.INSTALLED_APPS|pprint }} Installed Middleware: -{{ settings.MIDDLEWARE_CLASSES|pprint }} +{% if settings.MIDDLEWARE is not None %}{{ settings.MIDDLEWARE|pprint }}""" +"""{% else %}{{ settings.MIDDLEWARE_CLASSES|pprint }}{% endif %} {% if template_does_not_exist %}Template loader postmortem {% if postmortem %}Django tried loading these templates, in this order: {% for entry in postmortem %} diff --git a/docs/howto/auth-remote-user.txt b/docs/howto/auth-remote-user.txt index 1680baa92b1..5bfe210458d 100644 --- a/docs/howto/auth-remote-user.txt +++ b/docs/howto/auth-remote-user.txt @@ -29,10 +29,10 @@ Configuration First, you must add the :class:`django.contrib.auth.middleware.RemoteUserMiddleware` to the -:setting:`MIDDLEWARE_CLASSES` setting **after** the +:setting:`MIDDLEWARE` setting **after** the :class:`django.contrib.auth.middleware.AuthenticationMiddleware`:: - MIDDLEWARE_CLASSES = [ + MIDDLEWARE = [ '...', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.RemoteUserMiddleware', diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index d07404a1e9b..7a513385396 100644 --- a/docs/howto/error-reporting.txt +++ b/docs/howto/error-reporting.txt @@ -57,7 +57,7 @@ not found" errors). Django sends emails about 404 errors when: * :setting:`DEBUG` is ``False``; -* Your :setting:`MIDDLEWARE_CLASSES` setting includes +* Your :setting:`MIDDLEWARE` setting includes :class:`django.middleware.common.BrokenLinkEmailsMiddleware`. If those conditions are met, Django will email the users listed in the @@ -78,7 +78,7 @@ behavior is from broken Web bots too. before other middleware that intercepts 404 errors, such as :class:`~django.middleware.locale.LocaleMiddleware` or :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware`. - Put it towards the top of your :setting:`MIDDLEWARE_CLASSES` setting. + Put it towards the top of your :setting:`MIDDLEWARE` setting. You can tell Django to stop reporting particular 404s by tweaking the :setting:`IGNORABLE_404_URLS` setting. It should be a list of compiled diff --git a/docs/ref/applications.txt b/docs/ref/applications.txt index be761505fe4..53d9bcd9b55 100644 --- a/docs/ref/applications.txt +++ b/docs/ref/applications.txt @@ -37,8 +37,8 @@ projects. Applications include some combination of models, views, templates, template tags, static files, URLs, middleware, etc. They're generally wired into projects with the :setting:`INSTALLED_APPS` setting and optionally with other -mechanisms such as URLconfs, the :setting:`MIDDLEWARE_CLASSES` setting, or -template inheritance. +mechanisms such as URLconfs, the :setting:`MIDDLEWARE` setting, or template +inheritance. It is important to understand that a Django application is just a set of code that interacts with various parts of the framework. There's no such thing as diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index e4af02c1a28..5057ef18b6b 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -491,19 +491,19 @@ The following checks are run if you use the :option:`check --deploy` option: * **security.W001**: You do not have :class:`django.middleware.security.SecurityMiddleware` in your - :setting:`MIDDLEWARE_CLASSES` so the :setting:`SECURE_HSTS_SECONDS`, + :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES` so the :setting:`SECURE_HSTS_SECONDS`, :setting:`SECURE_CONTENT_TYPE_NOSNIFF`, :setting:`SECURE_BROWSER_XSS_FILTER`, and :setting:`SECURE_SSL_REDIRECT` settings will have no effect. * **security.W002**: You do not have :class:`django.middleware.clickjacking.XFrameOptionsMiddleware` in your - :setting:`MIDDLEWARE_CLASSES`, so your pages will not be served with an + :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES`, so your pages will not be served with an ``'x-frame-options'`` header. Unless there is a good reason for your site to be served in a frame, you should consider enabling this header to help prevent clickjacking attacks. * **security.W003**: You don't appear to be using Django's built-in cross-site request forgery protection via the middleware (:class:`django.middleware.csrf.CsrfViewMiddleware` is not in your - :setting:`MIDDLEWARE_CLASSES`). Enabling the middleware is the safest + :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES`). Enabling the middleware is the safest approach to ensure you don't leave any holes. * **security.W004**: You have not set a value for the :setting:`SECURE_HSTS_SECONDS` setting. If your entire site is served only @@ -540,7 +540,7 @@ The following checks are run if you use the :option:`check --deploy` option: sessions. * **security.W011**: You have :class:`django.contrib.sessions.middleware.SessionMiddleware` in your - :setting:`MIDDLEWARE_CLASSES`, but you have not set + :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES`, but you have not set :setting:`SESSION_COOKIE_SECURE` to ``True``. Using a secure-only session cookie makes it more difficult for network traffic sniffers to hijack user sessions. @@ -554,7 +554,7 @@ The following checks are run if you use the :option:`check --deploy` option: sessions. * **security.W014**: You have :class:`django.contrib.sessions.middleware.SessionMiddleware` in your - :setting:`MIDDLEWARE_CLASSES`, but you have not set + :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES`, but you have not set :setting:`SESSION_COOKIE_HTTPONLY` to ``True``. Using an ``HttpOnly`` session cookie makes it more difficult for cross-site scripting attacks to hijack user sessions. @@ -571,7 +571,7 @@ The following checks are run if you use the :option:`check --deploy` option: deployment. * **security.W019**: You have :class:`django.middleware.clickjacking.XFrameOptionsMiddleware` in your - :setting:`MIDDLEWARE_CLASSES`, but :setting:`X_FRAME_OPTIONS` is not set to + :setting:`MIDDLEWARE`/:setting:`MIDDLEWARE_CLASSES`, but :setting:`X_FRAME_OPTIONS` is not set to ``'DENY'``. The default is ``'SAMEORIGIN'``, but unless there is a good reason for your site to serve other parts of itself in a frame, you should change it to ``'DENY'``. diff --git a/docs/ref/clickjacking.txt b/docs/ref/clickjacking.txt index abac81dcd83..2f72e18d2f1 100644 --- a/docs/ref/clickjacking.txt +++ b/docs/ref/clickjacking.txt @@ -56,9 +56,9 @@ Setting ``X-Frame-Options`` for all responses To set the same ``X-Frame-Options`` value for all responses in your site, put ``'django.middleware.clickjacking.XFrameOptionsMiddleware'`` to -:setting:`MIDDLEWARE_CLASSES`:: +:setting:`MIDDLEWARE`:: - MIDDLEWARE_CLASSES = [ + MIDDLEWARE = [ ... 'django.middleware.clickjacking.XFrameOptionsMiddleware', ... diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index eed7d4451c9..66b116ad356 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -41,8 +41,8 @@ For reference, here are the requirements: defined in your :setting:`TEMPLATES` as well as :class:`django.contrib.auth.middleware.AuthenticationMiddleware` and :class:`django.contrib.messages.middleware.MessageMiddleware` to - :setting:`MIDDLEWARE_CLASSES`. (These are all active by default, so - you only need to do this if you've manually tweaked the settings.) + :setting:`MIDDLEWARE`. These are all active by default, so you only need to + do this if you've manually tweaked the settings. 4. Determine which of your application's models should be editable in the admin interface. diff --git a/docs/ref/contrib/flatpages.txt b/docs/ref/contrib/flatpages.txt index 3326a93ea85..580f864c34b 100644 --- a/docs/ref/contrib/flatpages.txt +++ b/docs/ref/contrib/flatpages.txt @@ -53,7 +53,7 @@ Then either: or: 3. Add ``'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'`` - to your :setting:`MIDDLEWARE_CLASSES` setting. + to your :setting:`MIDDLEWARE` setting. 4. Run the command :djadmin:`manage.py migrate `. @@ -144,8 +144,7 @@ can do all of the work. methods. Only requests which are successfully routed to a view via normal URL resolution apply view middleware. -Note that the order of :setting:`MIDDLEWARE_CLASSES` matters. Generally, you -can put +Note that the order of :setting:`MIDDLEWARE` matters. Generally, you can put :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware` at the end of the list. This means it will run first when processing the response, and ensures that any other response-processing middlewares see the real flatpage diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index d425899cd91..414cf211160 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -27,14 +27,14 @@ already contains all the settings required to enable message functionality: * ``'django.contrib.messages'`` is in :setting:`INSTALLED_APPS`. -* :setting:`MIDDLEWARE_CLASSES` contains +* :setting:`MIDDLEWARE` contains ``'django.contrib.sessions.middleware.SessionMiddleware'`` and ``'django.contrib.messages.middleware.MessageMiddleware'``. The default :ref:`storage backend ` relies on :doc:`sessions `. That's why ``SessionMiddleware`` must be enabled and appear before ``MessageMiddleware`` in - :setting:`MIDDLEWARE_CLASSES`. + :setting:`MIDDLEWARE`. * The ``'context_processors'`` option of the ``DjangoTemplates`` backend defined in your :setting:`TEMPLATES` setting contains @@ -42,8 +42,8 @@ already contains all the settings required to enable message functionality: If you don't want to use messages, you can remove ``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`, the -``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, and the -``messages`` context processor from :setting:`TEMPLATES`. +``MessageMiddleware`` line from :setting:`MIDDLEWARE`, and the ``messages`` +context processor from :setting:`TEMPLATES`. Configuring the message engine ============================== diff --git a/docs/ref/contrib/redirects.txt b/docs/ref/contrib/redirects.txt index 2aadf8a45cc..8af48ba8b24 100644 --- a/docs/ref/contrib/redirects.txt +++ b/docs/ref/contrib/redirects.txt @@ -18,7 +18,7 @@ To install the redirects app, follow these steps: :ref:`is installed `. 2. Add ``'django.contrib.redirects'`` to your :setting:`INSTALLED_APPS` setting. 3. Add ``'django.contrib.redirects.middleware.RedirectFallbackMiddleware'`` - to your :setting:`MIDDLEWARE_CLASSES` setting. + to your :setting:`MIDDLEWARE` setting. 4. Run the command :djadmin:`manage.py migrate `. How it works @@ -49,9 +49,9 @@ given ``old_path`` with a site ID that corresponds to the The middleware only gets activated for 404s -- not for 500s or responses of any other status code. -Note that the order of :setting:`MIDDLEWARE_CLASSES` matters. Generally, you -can put :class:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware` -at the end of the list, because it's a last resort. +Note that the order of :setting:`MIDDLEWARE` matters. Generally, you can put +:class:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware` at the +end of the list, because it's a last resort. For more on middleware, read the :doc:`middleware docs `. diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index 2f312d04e0a..ec2c1bf2b26 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -405,8 +405,8 @@ If you often use this pattern:: there is simple way to avoid repetitions. Add :class:`django.contrib.sites.middleware.CurrentSiteMiddleware` to -:setting:`MIDDLEWARE_CLASSES`. The middleware sets the ``site`` attribute on -every request object, so you can use ``request.site`` to get the current site. +:setting:`MIDDLEWARE`. The middleware sets the ``site`` attribute on every +request object, so you can use ``request.site`` to get the current site. How Django uses the sites framework =================================== diff --git a/docs/ref/csrf.txt b/docs/ref/csrf.txt index f59b45646dd..277fd857205 100644 --- a/docs/ref/csrf.txt +++ b/docs/ref/csrf.txt @@ -27,10 +27,10 @@ How to use it To take advantage of CSRF protection in your views, follow these steps: -1. The CSRF middleware is activated by default in the - :setting:`MIDDLEWARE_CLASSES` setting. If you override that setting, remember - that ``'django.middleware.csrf.CsrfViewMiddleware'`` should come before any - view middleware that assume that CSRF attacks have been dealt with. +1. The CSRF middleware is activated by default in the :setting:`MIDDLEWARE` + setting. If you override that setting, remember that + ``'django.middleware.csrf.CsrfViewMiddleware'`` should come before any view + middleware that assume that CSRF attacks have been dealt with. If you disabled it, which is not recommended, you can use :func:`~django.views.decorators.csrf.csrf_protect` on particular views diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index 49062d34748..1e582d5724d 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -77,6 +77,36 @@ issued by the middleware. * Sends broken link notification emails to :setting:`MANAGERS` (see :doc:`/howto/error-reporting`). +Exception middleware +-------------------- + +.. module:: django.middleware.exception + :synopsis: Middleware to return responses for exceptions. + +.. class:: ExceptionMiddleware + +.. versionadded:: 1.10 + +Catches exceptions raised during the request/response cycle and returns the +appropriate response. + +* :class:`~django.http.Http404` is processed by + :data:`~django.conf.urls.handler404` (or a more friendly debug page if + :setting:`DEBUG=True `). +* :class:`~django.core.exceptions.PermissionDenied` is processed + by :data:`~django.conf.urls.handler403`. +* ``MultiPartParserError`` is processed by :data:`~django.conf.urls.handler400`. +* :class:`~django.core.exceptions.SuspiciousOperation` is processed by + :data:`~django.conf.urls.handler400` (or a more friendly debug page if + :setting:`DEBUG=True `). +* Any other exception is processed by :data:`~django.conf.urls.handler500` + (or a more friendly debug page if :setting:`DEBUG=True `). + +Django uses this middleware regardless of whether or not you include it in +:setting:`MIDDLEWARE`, however, you may want to subclass if your own middleware +needs to transform any of these exceptions into the appropriate responses. +:class:`~django.middleware.locale.LocaleMiddleware` does this, for example. + GZip middleware --------------- diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 64bd5960cd4..96f5300f8b9 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -176,9 +176,9 @@ All attributes should be considered read-only, unless stated otherwise. An instance of :class:`~django.urls.ResolverMatch` representing the resolved URL. This attribute is only set after URL resolving took place, - which means it's available in all views but not in middleware methods - which are executed before URL resolving takes place (like - ``process_request()``, you can use ``process_view()`` instead). + which means it's available in all views but not in middleware which are + executed before URL resolving takes place (you can use it in + :meth:`process_view` though). Attributes set by application code ---------------------------------- @@ -210,7 +210,7 @@ Attributes set by middleware Some of the middleware included in Django's contrib apps set attributes on the request. If you don't see the attribute on a request, be sure the appropriate -middleware class is listed in :setting:`MIDDLEWARE_CLASSES`. +middleware class is listed in :setting:`MIDDLEWARE`. .. attribute:: HttpRequest.session @@ -257,7 +257,9 @@ Methods behind multiple proxies. One solution is to use middleware to rewrite the proxy headers, as in the following example:: - class MultipleProxyMiddleware(object): + from django.django.utils.deprecation import MiddlewareMixin + + class MultipleProxyMiddleware(MiddlewareMixin): FORWARDED_FOR_FIELDS = [ 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED_HOST', diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 838dde808af..a7801f715c3 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -77,7 +77,7 @@ can be used as a subdomain wildcard: ``'.example.com'`` will match ``example.com``. A value of ``'*'`` will match anything; in this case you are responsible to provide your own validation of the ``Host`` header (perhaps in a middleware; if so this middleware must be listed first in -:setting:`MIDDLEWARE_CLASSES`). +:setting:`MIDDLEWARE`). Django also allows the `fully qualified domain name (FQDN)`_ of any entries. Some browsers include a trailing dot in the ``Host`` header which Django @@ -1844,6 +1844,17 @@ Example: ``"http://media.example.com/"`` :setting:`MEDIA_URL` and :setting:`STATIC_URL` must have different values. See :setting:`MEDIA_ROOT` for more details. +.. setting:: MIDDLEWARE + +``MIDDLEWARE`` +-------------- + +.. versionadded:: 1.10 + +Default:: ``None`` + +A list of middleware to use. See :doc:`/topics/http/middleware`. + .. setting:: MIDDLEWARE_CLASSES ``MIDDLEWARE_CLASSES`` @@ -1856,7 +1867,11 @@ Default:: 'django.middleware.csrf.CsrfViewMiddleware', ] -A list of middleware classes to use. See :doc:`/topics/http/middleware`. +A list of middleware classes to use. This was the default setting used in +Django 1.9 and earlier. Django 1.10 introduced a new style of middleware. If +you have an older project using this setting you should :ref:`update any +middleware you've written yourself ` to the new style +and then use the :setting:`MIDDLEWARE` setting. .. setting:: MIGRATION_MODULES @@ -3312,6 +3327,7 @@ HTTP * :setting:`DISALLOWED_USER_AGENTS` * :setting:`FORCE_SCRIPT_NAME` * :setting:`INTERNAL_IPS` +* :setting:`MIDDLEWARE` * :setting:`MIDDLEWARE_CLASSES` * Security diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index f95ff1017f8..1b527277afa 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -173,6 +173,10 @@ The functions defined in this module share the following properties: middleware functionality on a per-view basis. The middleware is created with no params passed. + It assumes middleware that's compatible with the old style of Django 1.9 + and earlier (having methods like ``process_request()``, + ``process_exception()``, and ``process_response()``). + .. function:: decorator_from_middleware_with_args(middleware_class) Like ``decorator_from_middleware``, but returns a function diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index b6ac9f5c899..13e5f44d5bf 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -37,6 +37,17 @@ It also now includes trigram support, using the :lookup:`trigram_similar` lookup, and the :class:`~django.contrib.postgres.search.TrigramSimilarity` and :class:`~django.contrib.postgres.search.TrigramDistance` expressions. +New-style middleware +-------------------- + +:doc:`A new style of middleware is introduced ` to +solve the lack of strict request/response layering of the old-style of +middleware described in `DEP 0005 +`_. +You'll need to :ref:`adapt old, custom middleware ` and +switch from the ``MIDDLEWARE_CLASSES`` setting to the new :setting:`MIDDLEWARE` +setting to take advantage of the improvements. + Official support for Unicode usernames -------------------------------------- diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 3ecdcca12e3..21da0dca611 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -188,8 +188,7 @@ is an iterator. Since :class:`~django.http.StreamingHttpResponse` does not have a ``content`` attribute, middleware that needs access to the response content must test for -streaming responses and behave accordingly. See :ref:`response-middleware` for -more information. +streaming responses and behave accordingly. ``{% verbatim %}`` template tag ------------------------------- diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index 45526eb8310..7e6794a97b3 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -838,7 +838,7 @@ Session invalidation on password change ``SessionAuthenticationMiddleware`` is enabled. In older versions, this protection only applies if ``django.contrib.auth.middleware.SessionAuthenticationMiddleware`` - is enabled in :setting:`MIDDLEWARE_CLASSES`. + is enabled in :setting:`MIDDLEWARE`. If your :setting:`AUTH_USER_MODEL` inherits from :class:`~django.contrib.auth.models.AbstractBaseUser` or implements its own diff --git a/docs/topics/auth/index.txt b/docs/topics/auth/index.txt index b57c2e7436f..3224af74c7b 100644 --- a/docs/topics/auth/index.txt +++ b/docs/topics/auth/index.txt @@ -60,7 +60,7 @@ startproject `, these consist of two items listed in your `, which allows permissions to be associated with models you create. -and these items in your :setting:`MIDDLEWARE_CLASSES` setting: +and these items in your :setting:`MIDDLEWARE` setting: 1. :class:`~django.contrib.sessions.middleware.SessionMiddleware` manages :doc:`sessions ` across requests. diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index ad353e65e0b..d955b853b4c 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -447,9 +447,9 @@ Once the cache is set up, the simplest way to use caching is to cache your entire site. You'll need to add ``'django.middleware.cache.UpdateCacheMiddleware'`` and ``'django.middleware.cache.FetchFromCacheMiddleware'`` to your -:setting:`MIDDLEWARE_CLASSES` setting, as in this example:: +:setting:`MIDDLEWARE` setting, as in this example:: - MIDDLEWARE_CLASSES = [ + MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware', @@ -459,7 +459,7 @@ entire site. You'll need to add No, that's not a typo: the "update" middleware must be first in the list, and the "fetch" middleware must be last. The details are a bit obscure, but - see `Order of MIDDLEWARE_CLASSES`_ below if you'd like the full story. + see `Order of MIDDLEWARE`_ below if you'd like the full story. Then, add the following required settings to your Django settings file: @@ -1217,11 +1217,11 @@ Example:: def myview(request): ... -Order of ``MIDDLEWARE_CLASSES`` -=============================== +Order of ``MIDDLEWARE`` +======================= If you use caching middleware, it's important to put each half in the right -place within the :setting:`MIDDLEWARE_CLASSES` setting. That's because the cache +place within the :setting:`MIDDLEWARE` setting. That's because the cache middleware needs to know which headers by which to vary the cache storage. Middleware always adds something to the ``Vary`` response header when it can. diff --git a/docs/topics/http/_images/middleware.graffle b/docs/topics/http/_images/middleware.graffle deleted file mode 100644 index cdefd4b8101..00000000000 --- a/docs/topics/http/_images/middleware.graffle +++ /dev/null @@ -1,957 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGrafflePro - 139.16.0.171715 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {559.28997802734375, 782.8900146484375}} - Class - SolidGraphic - ID - 2 - Style - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2012-12-09 18:55:12 +0000 - Creator - Aymeric Augustin - DisplayScale - 1.000 cm = 1.000 cm - GraphDocumentVersion - 8 - GraphicsList - - - Bounds - {{144, 405}, {369, 27}} - Class - ShapedGraphic - ID - 33 - Shape - Bezier - ShapeData - - UnitPoints - - {-0.5, -0.5} - {-0.5, -0.5} - {0.47959183673469341, -0.5} - {0.47959183673469408, -0.5} - {0.47959183673469341, -0.5} - {0.5, 0} - {0.5, 0} - {0.5, 0} - {0.47959183673469408, 0.5} - {0.47959183673469408, 0.5} - {0.47959183673469408, 0.5} - {-0.5, 0.5} - {-0.5, 0.5} - {-0.5, 0.5} - {-0.47560975609756084, 0} - {-0.47560975609756084, 0} - {-0.47560975609756084, 0} - {-0.5, -0.5} - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 view function} - - - - Bounds - {{229.5, 238.5}, {297, 36}} - Class - ShapedGraphic - ID - 31 - Rotation - 270 - Shape - AdjustableArrow - ShapeData - - width - 27 - - Style - - fill - - Color - - a - 0.8 - b - 1 - g - 1 - r - 1 - - MiddleFraction - 0.70634919404983521 - - shadow - - Color - - a - 0.4 - b - 0 - g - 0 - r - 0 - - Draws - NO - Fuzziness - 0.0 - ShadowVector - {0, 2} - - stroke - - Color - - b - 0 - g - 0 - r - 1 - - Pattern - 1 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;\red255\green0\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf2 process_exception} - - TextRelativeArea - {{0.125, 0.25}, {0.75, 0.5}} - isConnectedShape - - - - Bounds - {{328.5, 229.5}, {315, 36}} - Class - ShapedGraphic - ID - 30 - Rotation - 270 - Shape - AdjustableArrow - ShapeData - - width - 27 - - Style - - fill - - Color - - a - 0.8 - b - 1 - g - 1 - r - 1 - - MiddleFraction - 0.70634919404983521 - - shadow - - Color - - a - 0.4 - b - 0 - g - 0 - r - 0 - - Draws - NO - Fuzziness - 0.0 - ShadowVector - {0, 2} - - stroke - - Color - - b - 0 - g - 0.501961 - r - 0 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf2 process_response} - - TextRelativeArea - {{0.125, 0.25}, {0.75, 0.5}} - isConnectedShape - - - - Bounds - {{283.5, 238.5}, {297, 36}} - Class - ShapedGraphic - ID - 29 - Rotation - 270 - Shape - AdjustableArrow - ShapeData - - width - 27 - - Style - - fill - - Color - - a - 0.8 - b - 1 - g - 1 - r - 1 - - MiddleFraction - 0.70634919404983521 - - shadow - - Color - - a - 0.4 - b - 0 - g - 0 - r - 0 - - Draws - NO - Fuzziness - 0.0 - ShadowVector - {0, 2} - - stroke - - Color - - b - 0 - g - 0.501961 - r - 0 - - Pattern - 1 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf2 process_template_response} - - TextRelativeArea - {{0.125, 0.25}, {0.75, 0.5}} - isConnectedShape - - - - Bounds - {{27, 243}, {288, 36}} - Class - ShapedGraphic - ID - 28 - Rotation - 90 - Shape - AdjustableArrow - ShapeData - - width - 27 - - Style - - fill - - Color - - a - 0.8 - b - 1 - g - 1 - r - 1 - - MiddleFraction - 0.70634919404983521 - - shadow - - Color - - a - 0.4 - b - 0 - g - 0 - r - 0 - - Draws - NO - Fuzziness - 0.0 - ShadowVector - {0, 2} - - stroke - - Color - - b - 0 - g - 0.501961 - r - 0 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf2 process_view} - - TextRelativeArea - {{0.125, 0.25}, {0.75, 0.5}} - isConnectedShape - - - - Bounds - {{-40.500000000767386, 220.49999999804004}, {297, 36}} - Class - ShapedGraphic - ID - 27 - Rotation - 90 - Shape - AdjustableArrow - ShapeData - - width - 27 - - Style - - fill - - Color - - a - 0.8 - b - 1 - g - 1 - r - 1 - - MiddleFraction - 0.70634919404983521 - - shadow - - Color - - a - 0.4 - b - 0 - g - 0 - r - 0 - - Draws - NO - Fuzziness - 0.0 - ShadowVector - {0, 2} - - stroke - - Color - - b - 0 - g - 0.501961 - r - 0 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf2 process_request} - - TextRelativeArea - {{0.125, 0.25}, {0.75, 0.5}} - isConnectedShape - - - - Bounds - {{360, 63}, {144, 27}} - Class - ShapedGraphic - ID - 12 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 HttpResponse} - - - - Bounds - {{72, 63}, {144, 27}} - Class - ShapedGraphic - ID - 11 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 HttpRequest} - - - - Bounds - {{72, 324}, {432, 27}} - Class - ShapedGraphic - ID - 10 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 MessageMiddleware} - - - - Bounds - {{72, 279}, {432, 27}} - Class - ShapedGraphic - ID - 9 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 AuthenticationMiddleware} - - - - Bounds - {{72, 234}, {432, 27}} - Class - ShapedGraphic - ID - 8 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 CsrfViewMiddleware} - - - - Bounds - {{72, 189}, {432, 27}} - Class - ShapedGraphic - ID - 7 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 SessionMiddleware} - - - - Bounds - {{72, 144}, {432, 27}} - Class - ShapedGraphic - ID - 6 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 CommonMiddleware} - - - - GridInfo - - ShowsGrid - YES - SnapsToGrid - YES - - GuidesLocked - NO - GuidesVisible - YES - HPages - 1 - ImageCounter - 1 - KeepToScale - - Layers - - - Lock - NO - Name - Calque 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - dot - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2012-12-09 19:48:54 +0000 - Modifier - Aymeric Augustin - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - PageBreaks - YES - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {595.28997802734375, 841.8900146484375} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - Canevas 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - Frame - {{248, 4}, {694, 874}} - ListView - - OutlineWidth - 142 - RightSidebar - - ShowRuler - - Sidebar - - SidebarWidth - 120 - VisibleRegion - {{0, 0}, {559, 735}} - Zoom - 1 - ZoomValues - - - Canevas 1 - 1 - 1 - - - - - diff --git a/docs/topics/http/_images/middleware.svg b/docs/topics/http/_images/middleware.svg deleted file mode 100644 index 4da8d22da89..00000000000 --- a/docs/topics/http/_images/middleware.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -2012-12-09 19:48ZCanevas 1Calque 1CommonMiddlewareSessionMiddlewareCsrfViewMiddlewareAuthenticationMiddlewareMessageMiddlewareHttpRequestHttpResponseprocess_requestprocess_viewprocess_template_responseprocess_responseprocess_exceptionview function diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt index 16fdace8255..1f5414e95bc 100644 --- a/docs/topics/http/middleware.txt +++ b/docs/topics/http/middleware.txt @@ -16,18 +16,128 @@ how to write your own middleware. Django ships with some built-in middleware you can use right out of the box. They're documented in the :doc:`built-in middleware reference `. +.. versionchanged:: 1.10 + + A new style of middleware was introduced for use with the new + :setting:`MIDDLEWARE` setting. If you're using the old + :setting:`MIDDLEWARE_CLASSES` setting, you'll need to :ref:`adapt old, + custom middleware ` before using the new setting. + This document describes new-style middleware. Refer to this page in older + versions of the documentation for a description of how old-style middleware + works. + +Writing your own middleware +=========================== + +A middleware factory is a callable that takes a ``get_response`` callable and +returns a middleware. A middleware is a callable that takes a request and +returns a response, just like a view. + +A middleware can be written as a function that looks like this:: + + def simple_middleware(get_response): + # One-time configuration and initialization. + + def middleware(request): + # Code to be executed for each request before + # the view is called. + + try: + 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 + # the view is called. + + return response + + return middleware + +Or it can be written as a class with a ``__call__()`` method, like this:: + + class SimpleMiddleware(object): + def __init__(self, get_response): + self.get_response = get_response + # One-time configuration and initialization. + + def __call__(self, request): + # Code to be executed for each request before + # the view is called. + + try: + 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 + # the view is called. + + 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 +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, +just that it represents whatever comes next. + +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 +method from the handler which takes care of applying :ref:`view middleware +`, calling the view with appropriate URL arguments, and +applying :ref:`template-response ` middleware. + +Middleware can live anywhere on your Python path. + +``__init__(get_response)`` +-------------------------- + +Middleware classes must accept a ``get_response`` argument. You can also +initialize some global state for the middleware. Keep in mind a couple of +caveats: + +* Django initializes your middleware with only the ``get_response`` argument, + so you can't define ``__init__()`` as requiring any other arguments. + +* Unlike the ``__call__()`` method which get called once per request, + ``__init__()`` is called only *once*, when the Web server starts. + +.. versionchanged:: 1.10 + + In older versions, ``__init__`` was not called until the Web server + responded to its first request. + + If you want to allow your middleware to be used in Django 1.9 and earlier, + make ``get_response`` an optional argument (``get_response=None``). + +Marking middleware as unused +---------------------------- + +It's sometimes useful to determine at startup time whether a piece of +middleware should be used. In these cases, your middleware's ``__init__()`` +method may raise :exc:`~django.core.exceptions.MiddlewareNotUsed`. Django will +then remove that middleware from the middleware process and log a debug message +to the :ref:`django-request-logger` logger when :setting:`DEBUG` is ``True``. + Activating middleware ===================== -To activate a middleware component, add it to the -:setting:`MIDDLEWARE_CLASSES` list in your Django settings. +To activate a middleware component, add it to the :setting:`MIDDLEWARE` list in +your Django settings. -In :setting:`MIDDLEWARE_CLASSES`, each middleware component is represented by -a string: the full Python path to the middleware's class name. For example, +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, here's the default value created by :djadmin:`django-admin startproject `:: - MIDDLEWARE_CLASSES = [ + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -37,13 +147,12 @@ here's the default value created by :djadmin:`django-admin startproject 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] -A Django installation doesn't require any middleware — -:setting:`MIDDLEWARE_CLASSES` can be empty, if you'd like — but it's strongly -suggested that you at least use +A Django installation doesn't require any middleware — :setting:`MIDDLEWARE` +can be empty, if you'd like — but it's strongly suggested that you at least use :class:`~django.middleware.common.CommonMiddleware`. -The order in :setting:`MIDDLEWARE_CLASSES` matters because a middleware can -depend on other middleware. For instance, +The order in :setting:`MIDDLEWARE` matters because a middleware can depend on +other middleware. For instance, :class:`~django.contrib.auth.middleware.AuthenticationMiddleware` stores the authenticated user in the session; therefore, it must run after :class:`~django.contrib.sessions.middleware.SessionMiddleware`. See @@ -54,55 +163,21 @@ Hooks and application order =========================== During the request phase, before calling the view, Django applies middleware -in the order it's defined in :setting:`MIDDLEWARE_CLASSES`, top-down. Two -hooks are available: +in the order it's defined in :setting:`MIDDLEWARE`, top-down. You can think of +it like an onion: each middleware class is a "layer" that wraps the view. -* :meth:`process_request` -* :meth:`process_view` +Middleware see only the changes made by middleware that run before it. A +middleware (and the view) is skipped entirely if a preceding middleware +short-circuits by returning a response without ever calling ``get_response``. +That response will only pass through the middleware that have already run. -During the response phase, after calling the view, middleware are applied in -reverse order, from the bottom up. Three hooks are available: +Similarly, a middleware that sees the request on the way in and doesn't return +a response is guaranteed that it will always see the response on the way back +out. If the middleware also wants to see any uncaught exception on the way out, +it can wrap its call to ``get_response()`` in a ``try``/``except``. -* :meth:`process_exception` (only if the view raised an exception) -* :meth:`process_template_response` (only for template responses) -* :meth:`process_response` - -.. image:: _images/middleware.* - :alt: middleware application order - :width: 481 - :height: 409 - -If you prefer, you can also think of it like an onion: each middleware class -is a "layer" that wraps the view. - -The behavior of each hook is described below. - -Writing your own middleware -=========================== - -Writing your own middleware is easy. Each middleware component is a single -Python class that defines one or more of the following methods: - -.. _request-middleware: - -``process_request()`` ---------------------- - -.. method:: process_request(request) - -``request`` is an :class:`~django.http.HttpRequest` object. - -``process_request()`` is called on each request, before Django decides which -view to execute. - -It should return either ``None`` or an :class:`~django.http.HttpResponse` -object. If it returns ``None``, Django will continue processing this request, -executing any other ``process_request()`` middleware, then, ``process_view()`` -middleware, and finally, the appropriate view. If it returns an -:class:`~django.http.HttpResponse` object, Django won't bother calling any -other request, view or exception middleware, or the appropriate view; it'll -apply response middleware to that :class:`~django.http.HttpResponse`, and -return the result. +Besides the middleware pattern described earlier, you can add two other methods +to class-based middleware: .. _view-middleware: @@ -125,14 +200,13 @@ It should return either ``None`` or an :class:`~django.http.HttpResponse` object. If it returns ``None``, Django will continue processing this request, executing any other ``process_view()`` middleware and, then, the appropriate view. If it returns an :class:`~django.http.HttpResponse` object, Django won't -bother calling any other view or exception middleware, or the appropriate -view; it'll apply response middleware to that -:class:`~django.http.HttpResponse`, and return the result. +bother calling the appropriate view; it'll apply response middleware to that +:class:`~django.http.HttpResponse` and return the result. .. note:: Accessing :attr:`request.POST ` inside - middleware from ``process_request`` or ``process_view`` will prevent any + middleware before the view runs or in ``process_view()`` will prevent any view running after the middleware from being able to :ref:`modify the upload handlers for the request `, and should normally be avoided. @@ -170,41 +244,8 @@ called. Middleware are run in reverse order during the response phase, which includes ``process_template_response()``. -.. _response-middleware: - -``process_response()`` ----------------------- - -.. method:: process_response(request, response) - -``request`` is an :class:`~django.http.HttpRequest` object. ``response`` is -the :class:`~django.http.HttpResponse` or -:class:`~django.http.StreamingHttpResponse` object returned by a Django view -or by a middleware. - -``process_response()`` is called on all responses before they're returned to -the browser. - -It must return an :class:`~django.http.HttpResponse` or -:class:`~django.http.StreamingHttpResponse` object. It could alter the given -``response``, or it could create and return a brand-new -:class:`~django.http.HttpResponse` or -:class:`~django.http.StreamingHttpResponse`. - -Unlike the ``process_request()`` and ``process_view()`` methods, the -``process_response()`` method is always called, even if the -``process_request()`` and ``process_view()`` methods of the same middleware -class were skipped (because an earlier middleware method returned an -:class:`~django.http.HttpResponse`). In particular, this means that your -``process_response()`` method cannot rely on setup done in -``process_request()``. - -Finally, remember that during the response phase, middleware are applied in -reverse order, from the bottom up. This means classes defined at the end of -:setting:`MIDDLEWARE_CLASSES` will be run first. - Dealing with streaming responses -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +================================ Unlike :class:`~django.http.HttpResponse`, :class:`~django.http.StreamingHttpResponse` does not have a ``content`` @@ -229,66 +270,62 @@ must test for streaming responses and adjust their behavior accordingly:: .. _exception-middleware: -``process_exception()`` ------------------------ +Exception middleware +==================== -.. method:: process_exception(request, exception) +A middleware that does some custom exception handling might looks like this:: -``request`` is an :class:`~django.http.HttpRequest` object. ``exception`` is an -``Exception`` object raised by the view function. + class ExceptionMiddleware(object): + def __init__(self, get_response): + self.get_response = get_response -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, default exception handling kicks in. + def __call__(self, request): + try: + response = self.get_response(request) + except Exception as e: + # Do something with the exception and possibly reraise it + # unless you wish to silence it. + ... + return response -Again, middleware are run in reverse order during the response phase, which -includes ``process_exception``. If an exception middleware returns a response, -the middleware classes above that middleware will not be called at all. +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. -``__init__()`` --------------- +.. _upgrading-middleware: -Most middleware classes won't need an initializer since middleware classes are -essentially placeholders for the ``process_*`` methods. If you do need some -global state you may use ``__init__`` to set up. However, keep in mind a couple -of caveats: +Upgrading pre-Django 1.10-style middleware +========================================== -* Django initializes your middleware without any arguments, so you can't - define ``__init__`` as requiring any arguments. +.. class:: django.utils.deprecation.MiddlewareMixin + :module: -* Unlike the ``process_*`` methods which get called once per request, - ``__init__`` gets called only *once*, when the Web server starts. +Django provides ``django.utils.deprecation.MiddlewareMixin`` to ease providing +the existing built-in middleware in both new-style and old-style forms and to +ease similar conversions of third-party middleware. -.. versionchanged:: 1.10 +In most cases, this mixin will be sufficient to convert a middleware with +sufficient backwards-compatibility; the new short-circuiting semantics will be +harmless or even beneficial to the existing middleware. - In older versions, ``__init__`` was not called until the Web server - responded to its first request. +In a few cases, a middleware class may need more invasive changes to adjust to +the new semantics. -Marking middleware as unused -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +For example, in the current request-handling logic, the handler transforms any +exception that passes through all ``process_exception`` middleware uncaught +into a response with appropriate status code (e.g. 404, 403, 400, or 500), and +then passes that response through the full chain of ``process_response`` +middleware. -It's sometimes useful to determine at run-time whether a piece of middleware -should be used. In these cases, your middleware's ``__init__`` method may -raise :exc:`django.core.exceptions.MiddlewareNotUsed`. Django will then remove -that piece of middleware from the middleware process and a debug message will -be logged to the ``django.request`` logger when :setting:`DEBUG` is set to -``True``. +In new-style middleware, a given middleware only gets one shot at a given +response or uncaught exception "on the way out," and will see either a returned +response or an uncaught exception, but not both. -Guidelines ----------- - -* Middleware classes don't have to subclass anything. - -* The middleware class can live anywhere on your Python path. All Django - cares about is that the :setting:`MIDDLEWARE_CLASSES` setting includes - the path to it. - -* Feel free to look at :doc:`Django's available middleware - ` for examples. - -* If you write a middleware component that you think would be useful to - other people, contribute to the community! :doc:`Let us know - `, and we'll consider adding it to Django. +This means that certain middleware which want to do something with all 404 +responses (for example, the ``RedirectFallbackMiddleware`` and +``FlatpageFallbackMiddleware`` in ``django.contrib.redirects`` and +``django.contrib.flatpages``) now need to watch out for both a 404 response +and an uncaught ``Http404`` exception. They do this by subclassing +:class:`~django.middleware.exception.ExceptionMiddleware`. diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 34a1b28b73d..1a948397aea 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -18,13 +18,13 @@ Sessions are implemented via a piece of :doc:`middleware `. To enable session functionality, do the following: -* Edit the :setting:`MIDDLEWARE_CLASSES` setting and make sure - it contains ``'django.contrib.sessions.middleware.SessionMiddleware'``. - The default ``settings.py`` created by ``django-admin startproject`` - has ``SessionMiddleware`` activated. +* Edit the :setting:`MIDDLEWARE` setting and make sure it contains + ``'django.contrib.sessions.middleware.SessionMiddleware'``. The default + ``settings.py`` created by ``django-admin startproject`` has + ``SessionMiddleware`` activated. If you don't want to use sessions, you might as well remove the -``SessionMiddleware`` line from :setting:`MIDDLEWARE_CLASSES` and +``SessionMiddleware`` line from :setting:`MIDDLEWARE` and ``'django.contrib.sessions'`` from your :setting:`INSTALLED_APPS`. It'll save you a small bit of overhead. diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 81dc4a0d946..ae5b7147261 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -41,8 +41,8 @@ algorithm the system follows to determine which Python code to execute: 1. Django determines the root URLconf module to use. Ordinarily, this is the value of the :setting:`ROOT_URLCONF` setting, but if the incoming ``HttpRequest`` object has a :attr:`~django.http.HttpRequest.urlconf` - attribute (set by middleware :ref:`request processing `), - its value will be used in place of the :setting:`ROOT_URLCONF` setting. + attribute (set by middleware), its value will be used in place of the + :setting:`ROOT_URLCONF` setting. 2. Django loads that Python module and looks for the variable ``urlpatterns``. This should be a Python list of :func:`django.conf.urls.url` diff --git a/docs/topics/i18n/timezones.txt b/docs/topics/i18n/timezones.txt index 8ea63f2908f..674edc1a194 100644 --- a/docs/topics/i18n/timezones.txt +++ b/docs/topics/i18n/timezones.txt @@ -175,13 +175,14 @@ the most likely choices. Here's an example that stores the current timezone in the session. (It skips error handling entirely for the sake of simplicity.) -Add the following middleware to :setting:`MIDDLEWARE_CLASSES`:: +Add the following middleware to :setting:`MIDDLEWARE`:: import pytz from django.utils import timezone + from django.django.utils.deprecation import MiddlewareMixin - class TimezoneMiddleware(object): + class TimezoneMiddleware(MiddlewareMixin): def process_request(self, request): tzname = request.session.get('django_timezone') if tzname: diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 43184a13a1c..3f13021bc84 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -38,7 +38,7 @@ make some optimizations so as not to load the internationalization machinery. .. note:: Make sure you've activated translation for your project (the fastest way is - to check if :setting:`MIDDLEWARE_CLASSES` includes + to check if :setting:`MIDDLEWARE` includes :mod:`django.middleware.locale.LocaleMiddleware`). If you haven't yet, see :ref:`how-django-discovers-language-preference`. @@ -1422,7 +1422,7 @@ Django provides two mechanisms to internationalize URL patterns: Using either one of these features requires that an active language be set for each request; in other words, you need to have :class:`django.middleware.locale.LocaleMiddleware` in your - :setting:`MIDDLEWARE_CLASSES` setting. + :setting:`MIDDLEWARE` setting. Language prefix in URL patterns ------------------------------- @@ -2065,8 +2065,8 @@ prefer, then you also need to use the ``LocaleMiddleware``. It customizes content for each user. To use ``LocaleMiddleware``, add ``'django.middleware.locale.LocaleMiddleware'`` -to your :setting:`MIDDLEWARE_CLASSES` setting. Because middleware order -matters, you should follow these guidelines: +to your :setting:`MIDDLEWARE` setting. Because middleware order matters, follow +these guidelines: * Make sure it's one of the first middlewares installed. * It should come after ``SessionMiddleware``, because ``LocaleMiddleware`` @@ -2075,9 +2075,9 @@ matters, you should follow these guidelines: to resolve the requested URL. * If you use ``CacheMiddleware``, put ``LocaleMiddleware`` after it. -For example, your :setting:`MIDDLEWARE_CLASSES` might look like this:: +For example, your :setting:`MIDDLEWARE` might look like this:: - MIDDLEWARE_CLASSES = [ + MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 8db06c15a3e..d2ef214c36d 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -1182,7 +1182,7 @@ easy:: class MiddlewareTestCase(TestCase): def test_cache_middleware(self): - with self.modify_settings(MIDDLEWARE_CLASSES={ + with self.modify_settings(MIDDLEWARE={ 'append': 'django.middleware.cache.FetchFromCacheMiddleware', 'prepend': 'django.middleware.cache.UpdateCacheMiddleware', 'remove': [ @@ -1233,7 +1233,7 @@ decorator:: class MiddlewareTestCase(TestCase): - @modify_settings(MIDDLEWARE_CLASSES={ + @modify_settings(MIDDLEWARE={ 'append': 'django.middleware.cache.FetchFromCacheMiddleware', 'prepend': 'django.middleware.cache.UpdateCacheMiddleware', }) @@ -1245,7 +1245,7 @@ The decorator can also be applied to test case classes:: from django.test import TestCase, modify_settings - @modify_settings(MIDDLEWARE_CLASSES={ + @modify_settings(MIDDLEWARE={ 'append': 'django.middleware.cache.FetchFromCacheMiddleware', 'prepend': 'django.middleware.cache.UpdateCacheMiddleware', }) diff --git a/tests/auth_tests/settings.py b/tests/auth_tests/settings.py index 7da6144d4be..8c295387c43 100644 --- a/tests/auth_tests/settings.py +++ b/tests/auth_tests/settings.py @@ -2,7 +2,7 @@ import os from django.utils._os import upath -AUTH_MIDDLEWARE_CLASSES = [ +AUTH_MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', ] diff --git a/tests/auth_tests/test_context_processors.py b/tests/auth_tests/test_context_processors.py index 3a8d800c740..d1db63c7b2d 100644 --- a/tests/auth_tests/test_context_processors.py +++ b/tests/auth_tests/test_context_processors.py @@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType from django.db.models import Q from django.test import SimpleTestCase, TestCase, override_settings -from .settings import AUTH_MIDDLEWARE_CLASSES, AUTH_TEMPLATES +from .settings import AUTH_MIDDLEWARE, AUTH_TEMPLATES class MockUser(object): @@ -67,7 +67,7 @@ class AuthContextProcessorTests(TestCase): def setUpTestData(cls): cls.superuser = User.objects.create_superuser(username='super', password='secret', email='super@example.com') - @override_settings(MIDDLEWARE_CLASSES=AUTH_MIDDLEWARE_CLASSES) + @override_settings(MIDDLEWARE=AUTH_MIDDLEWARE) def test_session_not_accessed(self): """ Tests that the session is not accessed simply by including @@ -76,7 +76,12 @@ class AuthContextProcessorTests(TestCase): response = self.client.get('/auth_processor_no_attr_access/') self.assertContains(response, "Session not accessed") - @override_settings(MIDDLEWARE_CLASSES=AUTH_MIDDLEWARE_CLASSES) + @override_settings(MIDDLEWARE_CLASSES=AUTH_MIDDLEWARE, MIDDLEWARE=None) + def test_session_not_accessed_middleware_classes(self): + response = self.client.get('/auth_processor_no_attr_access/') + self.assertContains(response, "Session not accessed") + + @override_settings(MIDDLEWARE=AUTH_MIDDLEWARE) def test_session_is_accessed(self): """ Tests that the session is accessed if the auth context processor @@ -85,6 +90,11 @@ class AuthContextProcessorTests(TestCase): response = self.client.get('/auth_processor_attr_access/') self.assertContains(response, "Session accessed") + @override_settings(MIDDLEWARE_CLASSES=AUTH_MIDDLEWARE, MIDDLEWARE=None) + def test_session_is_accessed_middleware_classes(self): + response = self.client.get('/auth_processor_attr_access/') + self.assertContains(response, "Session accessed") + def test_perms_attrs(self): u = User.objects.create_user(username='normal', password='secret') u.user_permissions.add( diff --git a/tests/auth_tests/test_remote_user.py b/tests/auth_tests/test_remote_user.py index 3d77ea3b89d..1cb7666a49d 100644 --- a/tests/auth_tests/test_remote_user.py +++ b/tests/auth_tests/test_remote_user.py @@ -23,7 +23,7 @@ class RemoteUserTest(TestCase): def setUp(self): self.patched_settings = modify_settings( AUTHENTICATION_BACKENDS={'append': self.backend}, - MIDDLEWARE_CLASSES={'append': self.middleware}, + MIDDLEWARE={'append': self.middleware}, ) self.patched_settings.enable() @@ -151,6 +151,21 @@ class RemoteUserTest(TestCase): self.assertTrue(response.context['user'].is_anonymous) +@override_settings(MIDDLEWARE=None) +class RemoteUserTestMiddlewareClasses(RemoteUserTest): + + def setUp(self): + self.patched_settings = modify_settings( + AUTHENTICATION_BACKENDS={'append': self.backend}, + MIDDLEWARE_CLASSES={'append': [ + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + self.middleware, + ]}, + ) + self.patched_settings.enable() + + class RemoteUserNoCreateBackend(RemoteUserBackend): """Backend that doesn't create unknown users.""" create_unknown_user = False diff --git a/tests/check_framework/test_security.py b/tests/check_framework/test_security.py index 34254b66d5a..b4591030f5b 100644 --- a/tests/check_framework/test_security.py +++ b/tests/check_framework/test_security.py @@ -1,5 +1,6 @@ from django.conf import settings from django.core.checks.security import base, csrf, sessions +from django.core.checks.utils import patch_middleware_message from django.test import SimpleTestCase from django.test.utils import override_settings @@ -13,7 +14,7 @@ class CheckSessionCookieSecureTest(SimpleTestCase): @override_settings( SESSION_COOKIE_SECURE=False, INSTALLED_APPS=["django.contrib.sessions"], - MIDDLEWARE_CLASSES=[]) + MIDDLEWARE=[]) def test_session_cookie_secure_with_installed_app(self): """ Warn if SESSION_COOKIE_SECURE is off and "django.contrib.sessions" is @@ -21,22 +22,38 @@ class CheckSessionCookieSecureTest(SimpleTestCase): """ self.assertEqual(self.func(None), [sessions.W010]) + @override_settings( + SESSION_COOKIE_SECURE=False, + INSTALLED_APPS=["django.contrib.sessions"], + MIDDLEWARE=None, + MIDDLEWARE_CLASSES=[]) + def test_session_cookie_secure_with_installed_app_middleware_classes(self): + self.assertEqual(self.func(None), [sessions.W010]) + @override_settings( SESSION_COOKIE_SECURE=False, INSTALLED_APPS=[], - MIDDLEWARE_CLASSES=["django.contrib.sessions.middleware.SessionMiddleware"]) + MIDDLEWARE=["django.contrib.sessions.middleware.SessionMiddleware"]) def test_session_cookie_secure_with_middleware(self): """ Warn if SESSION_COOKIE_SECURE is off and "django.contrib.sessions.middleware.SessionMiddleware" is in - MIDDLEWARE_CLASSES. + MIDDLEWARE. """ self.assertEqual(self.func(None), [sessions.W011]) @override_settings( SESSION_COOKIE_SECURE=False, - INSTALLED_APPS=["django.contrib.sessions"], + INSTALLED_APPS=[], + MIDDLEWARE=None, MIDDLEWARE_CLASSES=["django.contrib.sessions.middleware.SessionMiddleware"]) + def test_session_cookie_secure_with_middleware_middleware_classes(self): + self.assertEqual(self.func(None), [patch_middleware_message(sessions.W011)]) + + @override_settings( + SESSION_COOKIE_SECURE=False, + INSTALLED_APPS=["django.contrib.sessions"], + MIDDLEWARE=["django.contrib.sessions.middleware.SessionMiddleware"]) def test_session_cookie_secure_both(self): """ If SESSION_COOKIE_SECURE is off and we find both the session app and @@ -44,10 +61,18 @@ class CheckSessionCookieSecureTest(SimpleTestCase): """ self.assertEqual(self.func(None), [sessions.W012]) + @override_settings( + SESSION_COOKIE_SECURE=False, + INSTALLED_APPS=["django.contrib.sessions"], + MIDDLEWARE=None, + MIDDLEWARE_CLASSES=["django.contrib.sessions.middleware.SessionMiddleware"]) + def test_session_cookie_secure_both_middleware_classes(self): + self.assertEqual(self.func(None), [sessions.W012]) + @override_settings( SESSION_COOKIE_SECURE=True, INSTALLED_APPS=["django.contrib.sessions"], - MIDDLEWARE_CLASSES=["django.contrib.sessions.middleware.SessionMiddleware"]) + MIDDLEWARE=["django.contrib.sessions.middleware.SessionMiddleware"]) def test_session_cookie_secure_true(self): """ If SESSION_COOKIE_SECURE is on, there's no warning about it. @@ -64,7 +89,7 @@ class CheckSessionCookieHttpOnlyTest(SimpleTestCase): @override_settings( SESSION_COOKIE_HTTPONLY=False, INSTALLED_APPS=["django.contrib.sessions"], - MIDDLEWARE_CLASSES=[]) + MIDDLEWARE=[]) def test_session_cookie_httponly_with_installed_app(self): """ Warn if SESSION_COOKIE_HTTPONLY is off and "django.contrib.sessions" @@ -75,19 +100,19 @@ class CheckSessionCookieHttpOnlyTest(SimpleTestCase): @override_settings( SESSION_COOKIE_HTTPONLY=False, INSTALLED_APPS=[], - MIDDLEWARE_CLASSES=["django.contrib.sessions.middleware.SessionMiddleware"]) + MIDDLEWARE=["django.contrib.sessions.middleware.SessionMiddleware"]) def test_session_cookie_httponly_with_middleware(self): """ Warn if SESSION_COOKIE_HTTPONLY is off and "django.contrib.sessions.middleware.SessionMiddleware" is in - MIDDLEWARE_CLASSES. + MIDDLEWARE. """ self.assertEqual(self.func(None), [sessions.W014]) @override_settings( SESSION_COOKIE_HTTPONLY=False, INSTALLED_APPS=["django.contrib.sessions"], - MIDDLEWARE_CLASSES=["django.contrib.sessions.middleware.SessionMiddleware"]) + MIDDLEWARE=["django.contrib.sessions.middleware.SessionMiddleware"]) def test_session_cookie_httponly_both(self): """ If SESSION_COOKIE_HTTPONLY is off and we find both the session app and @@ -98,7 +123,7 @@ class CheckSessionCookieHttpOnlyTest(SimpleTestCase): @override_settings( SESSION_COOKIE_HTTPONLY=True, INSTALLED_APPS=["django.contrib.sessions"], - MIDDLEWARE_CLASSES=["django.contrib.sessions.middleware.SessionMiddleware"]) + MIDDLEWARE=["django.contrib.sessions.middleware.SessionMiddleware"]) def test_session_cookie_httponly_true(self): """ If SESSION_COOKIE_HTTPONLY is on, there's no warning about it. @@ -112,15 +137,15 @@ class CheckCSRFMiddlewareTest(SimpleTestCase): from django.core.checks.security.csrf import check_csrf_middleware return check_csrf_middleware - @override_settings(MIDDLEWARE_CLASSES=[]) + @override_settings(MIDDLEWARE=[], MIDDLEWARE_CLASSES=[]) def test_no_csrf_middleware(self): """ - Warn if CsrfViewMiddleware isn't in MIDDLEWARE_CLASSES. + Warn if CsrfViewMiddleware isn't in MIDDLEWARE. """ self.assertEqual(self.func(None), [csrf.W003]) @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.csrf.CsrfViewMiddleware"]) + MIDDLEWARE=["django.middleware.csrf.CsrfViewMiddleware"]) def test_with_csrf_middleware(self): self.assertEqual(self.func(None), []) @@ -132,25 +157,25 @@ class CheckCSRFCookieSecureTest(SimpleTestCase): return check_csrf_cookie_secure @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.csrf.CsrfViewMiddleware"], + MIDDLEWARE=["django.middleware.csrf.CsrfViewMiddleware"], CSRF_COOKIE_SECURE=False) def test_with_csrf_cookie_secure_false(self): """ - Warn if CsrfViewMiddleware is in MIDDLEWARE_CLASSES but + Warn if CsrfViewMiddleware is in MIDDLEWARE but CSRF_COOKIE_SECURE isn't True. """ self.assertEqual(self.func(None), [csrf.W016]) - @override_settings(MIDDLEWARE_CLASSES=[], CSRF_COOKIE_SECURE=False) + @override_settings(MIDDLEWARE=[], MIDDLEWARE_CLASSES=[], CSRF_COOKIE_SECURE=False) def test_with_csrf_cookie_secure_false_no_middleware(self): """ - No warning if CsrfViewMiddleware isn't in MIDDLEWARE_CLASSES, even if + No warning if CsrfViewMiddleware isn't in MIDDLEWARE, even if CSRF_COOKIE_SECURE is False. """ self.assertEqual(self.func(None), []) @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.csrf.CsrfViewMiddleware"], + MIDDLEWARE=["django.middleware.csrf.CsrfViewMiddleware"], CSRF_COOKIE_SECURE=True) def test_with_csrf_cookie_secure_true(self): self.assertEqual(self.func(None), []) @@ -163,25 +188,25 @@ class CheckCSRFCookieHttpOnlyTest(SimpleTestCase): return check_csrf_cookie_httponly @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.csrf.CsrfViewMiddleware"], + MIDDLEWARE=["django.middleware.csrf.CsrfViewMiddleware"], CSRF_COOKIE_HTTPONLY=False) def test_with_csrf_cookie_httponly_false(self): """ - Warn if CsrfViewMiddleware is in MIDDLEWARE_CLASSES but + Warn if CsrfViewMiddleware is in MIDDLEWARE but CSRF_COOKIE_HTTPONLY isn't True. """ self.assertEqual(self.func(None), [csrf.W017]) - @override_settings(MIDDLEWARE_CLASSES=[], CSRF_COOKIE_HTTPONLY=False) + @override_settings(MIDDLEWARE=[], MIDDLEWARE_CLASSES=[], CSRF_COOKIE_HTTPONLY=False) def test_with_csrf_cookie_httponly_false_no_middleware(self): """ - No warning if CsrfViewMiddleware isn't in MIDDLEWARE_CLASSES, even if + No warning if CsrfViewMiddleware isn't in MIDDLEWARE, even if CSRF_COOKIE_HTTPONLY is False. """ self.assertEqual(self.func(None), []) @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.csrf.CsrfViewMiddleware"], + MIDDLEWARE=["django.middleware.csrf.CsrfViewMiddleware"], CSRF_COOKIE_HTTPONLY=True) def test_with_csrf_cookie_httponly_true(self): self.assertEqual(self.func(None), []) @@ -193,15 +218,15 @@ class CheckSecurityMiddlewareTest(SimpleTestCase): from django.core.checks.security.base import check_security_middleware return check_security_middleware - @override_settings(MIDDLEWARE_CLASSES=[]) + @override_settings(MIDDLEWARE=[]) def test_no_security_middleware(self): """ - Warn if SecurityMiddleware isn't in MIDDLEWARE_CLASSES. + Warn if SecurityMiddleware isn't in MIDDLEWARE. """ self.assertEqual(self.func(None), [base.W001]) @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"]) + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"]) def test_with_security_middleware(self): self.assertEqual(self.func(None), []) @@ -213,7 +238,7 @@ class CheckStrictTransportSecurityTest(SimpleTestCase): return check_sts @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"], + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"], SECURE_HSTS_SECONDS=0) def test_no_sts(self): """ @@ -222,7 +247,7 @@ class CheckStrictTransportSecurityTest(SimpleTestCase): self.assertEqual(self.func(None), [base.W004]) @override_settings( - MIDDLEWARE_CLASSES=[], + MIDDLEWARE=[], SECURE_HSTS_SECONDS=0) def test_no_sts_no_middleware(self): """ @@ -232,7 +257,7 @@ class CheckStrictTransportSecurityTest(SimpleTestCase): self.assertEqual(self.func(None), []) @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"], + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"], SECURE_HSTS_SECONDS=3600) def test_with_sts(self): self.assertEqual(self.func(None), []) @@ -245,7 +270,7 @@ class CheckStrictTransportSecuritySubdomainsTest(SimpleTestCase): return check_sts_include_subdomains @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"], + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"], SECURE_HSTS_INCLUDE_SUBDOMAINS=False, SECURE_HSTS_SECONDS=3600) def test_no_sts_subdomains(self): @@ -255,7 +280,7 @@ class CheckStrictTransportSecuritySubdomainsTest(SimpleTestCase): self.assertEqual(self.func(None), [base.W005]) @override_settings( - MIDDLEWARE_CLASSES=[], + MIDDLEWARE=[], SECURE_HSTS_INCLUDE_SUBDOMAINS=False, SECURE_HSTS_SECONDS=3600) def test_no_sts_subdomains_no_middleware(self): @@ -265,7 +290,7 @@ class CheckStrictTransportSecuritySubdomainsTest(SimpleTestCase): self.assertEqual(self.func(None), []) @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"], + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"], SECURE_SSL_REDIRECT=False, SECURE_HSTS_SECONDS=None) def test_no_sts_subdomains_no_seconds(self): @@ -275,7 +300,7 @@ class CheckStrictTransportSecuritySubdomainsTest(SimpleTestCase): self.assertEqual(self.func(None), []) @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"], + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"], SECURE_HSTS_INCLUDE_SUBDOMAINS=True, SECURE_HSTS_SECONDS=3600) def test_with_sts_subdomains(self): @@ -288,14 +313,14 @@ class CheckXFrameOptionsMiddlewareTest(SimpleTestCase): from django.core.checks.security.base import check_xframe_options_middleware return check_xframe_options_middleware - @override_settings(MIDDLEWARE_CLASSES=[]) + @override_settings(MIDDLEWARE=[]) def test_middleware_not_installed(self): """ - Warn if XFrameOptionsMiddleware isn't in MIDDLEWARE_CLASSES. + Warn if XFrameOptionsMiddleware isn't in MIDDLEWARE. """ self.assertEqual(self.func(None), [base.W002]) - @override_settings(MIDDLEWARE_CLASSES=["django.middleware.clickjacking.XFrameOptionsMiddleware"]) + @override_settings(MIDDLEWARE=["django.middleware.clickjacking.XFrameOptionsMiddleware"]) def test_middleware_installed(self): self.assertEqual(self.func(None), []) @@ -307,26 +332,26 @@ class CheckXFrameOptionsDenyTest(SimpleTestCase): return check_xframe_deny @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.clickjacking.XFrameOptionsMiddleware"], + MIDDLEWARE=["django.middleware.clickjacking.XFrameOptionsMiddleware"], X_FRAME_OPTIONS='SAMEORIGIN', ) def test_x_frame_options_not_deny(self): """ - Warn if XFrameOptionsMiddleware is in MIDDLEWARE_CLASSES but + Warn if XFrameOptionsMiddleware is in MIDDLEWARE but X_FRAME_OPTIONS isn't 'DENY'. """ self.assertEqual(self.func(None), [base.W019]) - @override_settings(MIDDLEWARE_CLASSES=[], X_FRAME_OPTIONS='SAMEORIGIN') + @override_settings(MIDDLEWARE=[], X_FRAME_OPTIONS='SAMEORIGIN') def test_middleware_not_installed(self): """ - No error if XFrameOptionsMiddleware isn't in MIDDLEWARE_CLASSES even if + No error if XFrameOptionsMiddleware isn't in MIDDLEWARE even if X_FRAME_OPTIONS isn't 'DENY'. """ self.assertEqual(self.func(None), []) @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.clickjacking.XFrameOptionsMiddleware"], + MIDDLEWARE=["django.middleware.clickjacking.XFrameOptionsMiddleware"], X_FRAME_OPTIONS='DENY', ) def test_xframe_deny(self): @@ -340,7 +365,7 @@ class CheckContentTypeNosniffTest(SimpleTestCase): return check_content_type_nosniff @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"], + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"], SECURE_CONTENT_TYPE_NOSNIFF=False) def test_no_content_type_nosniff(self): """ @@ -349,17 +374,17 @@ class CheckContentTypeNosniffTest(SimpleTestCase): self.assertEqual(self.func(None), [base.W006]) @override_settings( - MIDDLEWARE_CLASSES=[], + MIDDLEWARE=[], SECURE_CONTENT_TYPE_NOSNIFF=False) def test_no_content_type_nosniff_no_middleware(self): """ Don't warn if SECURE_CONTENT_TYPE_NOSNIFF isn't True and - SecurityMiddleware isn't in MIDDLEWARE_CLASSES. + SecurityMiddleware isn't in MIDDLEWARE. """ self.assertEqual(self.func(None), []) @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"], + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"], SECURE_CONTENT_TYPE_NOSNIFF=True) def test_with_content_type_nosniff(self): self.assertEqual(self.func(None), []) @@ -372,7 +397,7 @@ class CheckXssFilterTest(SimpleTestCase): return check_xss_filter @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"], + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"], SECURE_BROWSER_XSS_FILTER=False) def test_no_xss_filter(self): """ @@ -381,17 +406,17 @@ class CheckXssFilterTest(SimpleTestCase): self.assertEqual(self.func(None), [base.W007]) @override_settings( - MIDDLEWARE_CLASSES=[], + MIDDLEWARE=[], SECURE_BROWSER_XSS_FILTER=False) def test_no_xss_filter_no_middleware(self): """ Don't warn if SECURE_BROWSER_XSS_FILTER isn't True and - SecurityMiddleware isn't in MIDDLEWARE_CLASSES. + SecurityMiddleware isn't in MIDDLEWARE. """ self.assertEqual(self.func(None), []) @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"], + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"], SECURE_BROWSER_XSS_FILTER=True) def test_with_xss_filter(self): self.assertEqual(self.func(None), []) @@ -404,7 +429,7 @@ class CheckSSLRedirectTest(SimpleTestCase): return check_ssl_redirect @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"], + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"], SECURE_SSL_REDIRECT=False) def test_no_ssl_redirect(self): """ @@ -413,7 +438,7 @@ class CheckSSLRedirectTest(SimpleTestCase): self.assertEqual(self.func(None), [base.W008]) @override_settings( - MIDDLEWARE_CLASSES=[], + MIDDLEWARE=[], SECURE_SSL_REDIRECT=False) def test_no_ssl_redirect_no_middleware(self): """ @@ -423,7 +448,7 @@ class CheckSSLRedirectTest(SimpleTestCase): self.assertEqual(self.func(None), []) @override_settings( - MIDDLEWARE_CLASSES=["django.middleware.security.SecurityMiddleware"], + MIDDLEWARE=["django.middleware.security.SecurityMiddleware"], SECURE_SSL_REDIRECT=True) def test_with_ssl_redirect(self): self.assertEqual(self.func(None), []) diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py index 924b13bec15..2712cf668a3 100644 --- a/tests/file_uploads/tests.py +++ b/tests/file_uploads/tests.py @@ -27,7 +27,7 @@ MEDIA_ROOT = sys_tempfile.mkdtemp() UPLOAD_TO = os.path.join(MEDIA_ROOT, 'test_upload') -@override_settings(MEDIA_ROOT=MEDIA_ROOT, ROOT_URLCONF='file_uploads.urls', MIDDLEWARE_CLASSES=[]) +@override_settings(MEDIA_ROOT=MEDIA_ROOT, ROOT_URLCONF='file_uploads.urls', MIDDLEWARE=[]) class FileUploadTests(TestCase): @classmethod diff --git a/tests/flatpages_tests/test_csrf.py b/tests/flatpages_tests/test_csrf.py index 0ffc567c48c..b4ae186d0c2 100644 --- a/tests/flatpages_tests/test_csrf.py +++ b/tests/flatpages_tests/test_csrf.py @@ -9,7 +9,7 @@ from .settings import FLATPAGES_TEMPLATES @modify_settings(INSTALLED_APPS={'append': 'django.contrib.flatpages'}) @override_settings( LOGIN_URL='/accounts/login/', - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -97,3 +97,18 @@ class FlatpageCSRFTests(TestCase): "POSTing to an unknown page isn't caught as a 403 CSRF error" response = self.client.post('/no_such_page/') self.assertEqual(response.status_code, 404) + + +@override_settings( + MIDDLEWARE=None, + MIDDLEWARE_CLASSES=[ + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', + ], +) +class FlatpageCSRFMiddlewareClassesTests(FlatpageCSRFTests): + pass diff --git a/tests/flatpages_tests/test_forms.py b/tests/flatpages_tests/test_forms.py index ae9da29f5cc..acf6687ea26 100644 --- a/tests/flatpages_tests/test_forms.py +++ b/tests/flatpages_tests/test_forms.py @@ -47,18 +47,36 @@ class FlatpageAdminFormTests(TestCase): self.assertFalse(form.is_valid()) self.assertEqual(form.errors['url'], ["URL is missing a leading slash."]) - @override_settings(APPEND_SLASH=True, MIDDLEWARE_CLASSES=['django.middleware.common.CommonMiddleware']) + @override_settings(APPEND_SLASH=True, MIDDLEWARE=['django.middleware.common.CommonMiddleware']) def test_flatpage_requires_trailing_slash_with_append_slash(self): form = FlatpageForm(data=dict(url='/no_trailing_slash', **self.form_data)) with translation.override('en'): self.assertFalse(form.is_valid()) self.assertEqual(form.errors['url'], ["URL is missing a trailing slash."]) - @override_settings(APPEND_SLASH=False, MIDDLEWARE_CLASSES=['django.middleware.common.CommonMiddleware']) + @override_settings(APPEND_SLASH=False, MIDDLEWARE=['django.middleware.common.CommonMiddleware']) def test_flatpage_doesnt_requires_trailing_slash_without_append_slash(self): form = FlatpageForm(data=dict(url='/no_trailing_slash', **self.form_data)) self.assertTrue(form.is_valid()) + @override_settings( + APPEND_SLASH=True, MIDDLEWARE=None, + MIDDLEWARE_CLASSES=['django.middleware.common.CommonMiddleware'], + ) + def test_flatpage_requires_trailing_slash_with_append_slash_middleware_classes(self): + form = FlatpageForm(data=dict(url='/no_trailing_slash', **self.form_data)) + with translation.override('en'): + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['url'], ["URL is missing a trailing slash."]) + + @override_settings( + APPEND_SLASH=False, MIDDLEWARE=None, + MIDDLEWARE_CLASSES=['django.middleware.common.CommonMiddleware'], + ) + def test_flatpage_doesnt_requires_trailing_slash_without_append_slash_middleware_classes(self): + form = FlatpageForm(data=dict(url='/no_trailing_slash', **self.form_data)) + self.assertTrue(form.is_valid()) + def test_flatpage_admin_form_url_uniqueness_validation(self): "The flatpage admin form correctly enforces url uniqueness among flatpages of the same site" data = dict(url='/myflatpage1/', **self.form_data) diff --git a/tests/flatpages_tests/test_middleware.py b/tests/flatpages_tests/test_middleware.py index 37e789d1b12..8d04076af6f 100644 --- a/tests/flatpages_tests/test_middleware.py +++ b/tests/flatpages_tests/test_middleware.py @@ -40,7 +40,7 @@ class TestDataMixin(object): @modify_settings(INSTALLED_APPS={'append': 'django.contrib.flatpages'}) @override_settings( LOGIN_URL='/accounts/login/', - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -107,11 +107,26 @@ class FlatpageMiddlewareTests(TestDataMixin, TestCase): self.assertContains(response, "

Isn't it special!

") +@override_settings( + MIDDLEWARE=None, + MIDDLEWARE_CLASSES=[ + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', + ], +) +class FlatpageMiddlewareClassesTests(FlatpageMiddlewareTests): + pass + + @modify_settings(INSTALLED_APPS={'append': 'django.contrib.flatpages'}) @override_settings( APPEND_SLASH=True, LOGIN_URL='/accounts/login/', - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -172,3 +187,18 @@ class FlatpageMiddlewareAppendSlashTests(TestDataMixin, TestCase): response = self.client.get('/') self.assertContains(response, "

Root

") + + +@override_settings( + MIDDLEWARE=None, + MIDDLEWARE_CLASSES=[ + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', + ], +) +class FlatpageAppendSlashMiddlewareClassesTests(FlatpageMiddlewareAppendSlashTests): + pass diff --git a/tests/flatpages_tests/test_templatetags.py b/tests/flatpages_tests/test_templatetags.py index d766d139648..688d85a224d 100644 --- a/tests/flatpages_tests/test_templatetags.py +++ b/tests/flatpages_tests/test_templatetags.py @@ -2,25 +2,9 @@ from django.contrib.auth.models import AnonymousUser, User from django.contrib.flatpages.models import FlatPage from django.contrib.sites.models import Site from django.template import Context, Template, TemplateSyntaxError -from django.test import TestCase, modify_settings, override_settings - -from .settings import FLATPAGES_TEMPLATES +from django.test import TestCase -@modify_settings(INSTALLED_APPS={'append': 'django.contrib.flatpages'}) -@override_settings( - MIDDLEWARE_CLASSES=[ - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', - ], - ROOT_URLCONF='flatpages_tests.urls', - TEMPLATES=FLATPAGES_TEMPLATES, - SITE_ID=1, -) class FlatpageTemplateTagTests(TestCase): @classmethod diff --git a/tests/flatpages_tests/test_views.py b/tests/flatpages_tests/test_views.py index 1a93a543b75..a5104ce6209 100644 --- a/tests/flatpages_tests/test_views.py +++ b/tests/flatpages_tests/test_views.py @@ -40,7 +40,7 @@ class TestDataMixin(object): @modify_settings(INSTALLED_APPS={'append': 'django.contrib.flatpages'}) @override_settings( LOGIN_URL='/accounts/login/', - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -102,7 +102,7 @@ class FlatpageViewTests(TestDataMixin, TestCase): @override_settings( APPEND_SLASH=True, LOGIN_URL='/accounts/login/', - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', diff --git a/tests/handlers/tests.py b/tests/handlers/tests.py index d0b161cf44a..9f01cb201aa 100644 --- a/tests/handlers/tests.py +++ b/tests/handlers/tests.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import unittest +from django.core.exceptions import ImproperlyConfigured from django.core.handlers.wsgi import WSGIHandler, WSGIRequest, get_script_name from django.core.signals import request_finished, request_started from django.db import close_old_connections, connection @@ -166,6 +167,10 @@ class SignalsTests(SimpleTestCase): self.assertEqual(self.signals, ['started', 'finished']) +def empty_middleware(get_response): + pass + + @override_settings(ROOT_URLCONF='handlers.urls') class HandlerRequestTests(SimpleTestCase): @@ -199,6 +204,12 @@ class HandlerRequestTests(SimpleTestCase): WSGIHandler()(environ, start_response) self.assertEqual(start_response.status, '200 OK') + @override_settings(MIDDLEWARE=['handlers.tests.empty_middleware']) + def test_middleware_returns_none(self): + msg = 'Middleware factory handlers.tests.empty_middleware returned None.' + with self.assertRaisesMessage(ImproperlyConfigured, msg): + self.client.get('/') + class ScriptNameTests(SimpleTestCase): def test_get_script_name(self): diff --git a/tests/i18n/patterns/tests.py b/tests/i18n/patterns/tests.py index c503a74d346..613a88701f0 100644 --- a/tests/i18n/patterns/tests.py +++ b/tests/i18n/patterns/tests.py @@ -30,7 +30,7 @@ class PermanentRedirectLocaleMiddleWare(LocaleMiddleware): ('en', 'English'), ('pt-br', 'Brazilian Portuguese'), ], - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', ], @@ -223,7 +223,7 @@ class URLRedirectTests(URLTestCaseBase): self.assertEqual(response.status_code, 200) @override_settings( - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'i18n.patterns.tests.PermanentRedirectLocaleMiddleWare', 'django.middleware.common.CommonMiddleware', ], diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index 140e2da1872..5084f2dc1d2 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -1756,7 +1756,7 @@ class MultipleLocaleActivationTests(SimpleTestCase): ('en', 'English'), ('fr', 'French'), ], - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', ], @@ -1772,7 +1772,7 @@ class LocaleMiddlewareTests(TestCase): self.assertContains(response, "Yes/No") @override_settings( - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', @@ -1792,7 +1792,7 @@ class LocaleMiddlewareTests(TestCase): ('en', 'English'), ('fr', 'French'), ], - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', ], @@ -1828,7 +1828,7 @@ class UnprefixedDefaultLanguageTests(SimpleTestCase): ('en-us', 'English'), ('pt-br', 'Portuguese (Brazil)'), ], - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', ], diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py index 058abfb4a33..1802a0ce475 100644 --- a/tests/logging_tests/tests.py +++ b/tests/logging_tests/tests.py @@ -125,7 +125,7 @@ class HandlerLoggingTests(SetupDefaultLoggingMixin, LoggingCaptureMixin, SimpleT DEBUG=True, USE_I18N=True, LANGUAGES=[('en', 'English')], - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', ], diff --git a/tests/messages_tests/base.py b/tests/messages_tests/base.py index 3284522edfc..7b6cd43a1d7 100644 --- a/tests/messages_tests/base.py +++ b/tests/messages_tests/base.py @@ -217,7 +217,7 @@ class BaseTests(object): @modify_settings( INSTALLED_APPS={'remove': 'django.contrib.messages'}, - MIDDLEWARE_CLASSES={'remove': 'django.contrib.messages.middleware.MessageMiddleware'}, + MIDDLEWARE={'remove': 'django.contrib.messages.middleware.MessageMiddleware'}, ) @override_settings( MESSAGE_LEVEL=constants.DEBUG, @@ -243,7 +243,7 @@ class BaseTests(object): @modify_settings( INSTALLED_APPS={'remove': 'django.contrib.messages'}, - MIDDLEWARE_CLASSES={'remove': 'django.contrib.messages.middleware.MessageMiddleware'}, + MIDDLEWARE={'remove': 'django.contrib.messages.middleware.MessageMiddleware'}, ) @override_settings( TEMPLATES=[{ diff --git a/tests/middleware_exceptions/middleware.py b/tests/middleware_exceptions/middleware.py index 944dd8910d3..6871d9556ea 100644 --- a/tests/middleware_exceptions/middleware.py +++ b/tests/middleware_exceptions/middleware.py @@ -1,8 +1,10 @@ from __future__ import unicode_literals from django.http import HttpResponse +from django.utils.deprecation import MiddlewareMixin -class ProcessExceptionMiddleware(object): +class ProcessExceptionMiddleware(MiddlewareMixin): + 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 ef9d19fe21f..13b33632f8e 100644 --- a/tests/middleware_exceptions/tests.py +++ b/tests/middleware_exceptions/tests.py @@ -8,6 +8,7 @@ from django.template import engines from django.template.response import TemplateResponse from django.test import RequestFactory, SimpleTestCase, override_settings from django.test.utils import patch_logger +from django.utils.deprecation import MiddlewareMixin class TestException(Exception): @@ -15,13 +16,14 @@ class TestException(Exception): # A middleware base class that tracks which methods have been called -class TestMiddleware(object): - def __init__(self): +class TestMiddleware(MiddlewareMixin): + def __init__(self, get_response=None): self.process_request_called = False self.process_view_called = False self.process_response_called = False self.process_template_response_called = False self.process_exception_called = False + self.get_response = get_response def process_request(self, request): self.process_request_called = True @@ -115,7 +117,11 @@ class NoResponseMiddleware(TestMiddleware): super(NoResponseMiddleware, self).process_response(request, response) -@override_settings(ROOT_URLCONF='middleware_exceptions.urls') +@override_settings( + ROOT_URLCONF='middleware_exceptions.urls', + MIDDLEWARE_CLASSES=['django.middleware.common.CommonMiddleware'], + MIDDLEWARE=None, +) class BaseMiddlewareExceptionTest(SimpleTestCase): def setUp(self): @@ -492,12 +498,10 @@ 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'], - ) + @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_CLASSES setting got overridden + # 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') @@ -868,7 +872,7 @@ class RootUrlconfTests(SimpleTestCase): class MyMiddleware(object): - def __init__(self): + def __init__(self, get_response=None): raise MiddlewareNotUsed def process_request(self, request): @@ -877,7 +881,7 @@ class MyMiddleware(object): class MyMiddlewareWithExceptionMessage(object): - def __init__(self): + def __init__(self, get_response=None): raise MiddlewareNotUsed('spam eggs') def process_request(self, request): @@ -887,6 +891,7 @@ class MyMiddlewareWithExceptionMessage(object): @override_settings( DEBUG=True, ROOT_URLCONF='middleware_exceptions.urls', + MIDDLEWARE=['django.middleware.common.CommonMiddleware'], ) class MiddlewareNotUsedTests(SimpleTestCase): @@ -897,9 +902,7 @@ class MiddlewareNotUsedTests(SimpleTestCase): with self.assertRaises(MiddlewareNotUsed): MyMiddleware().process_request(request) - @override_settings(MIDDLEWARE_CLASSES=[ - 'middleware_exceptions.tests.MyMiddleware', - ]) + @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/') @@ -909,9 +912,7 @@ class MiddlewareNotUsedTests(SimpleTestCase): "MiddlewareNotUsed: 'middleware_exceptions.tests.MyMiddleware'" ) - @override_settings(MIDDLEWARE_CLASSES=[ - 'middleware_exceptions.tests.MyMiddlewareWithExceptionMessage', - ]) + @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/') @@ -926,3 +927,11 @@ class MiddlewareNotUsedTests(SimpleTestCase): with patch_logger('django.request', 'debug') as calls: self.client.get('/middleware_exceptions/view/') self.assertEqual(len(calls), 0) + + +@override_settings( + MIDDLEWARE_CLASSES=['django.middleware.common.CommonMiddleware'], + MIDDLEWARE=None, +) +class MiddlewareNotUsedMiddlewareClassesTests(MiddlewareNotUsedTests): + pass diff --git a/tests/project_template/test_settings.py b/tests/project_template/test_settings.py index 702ecec8c5d..d153c4d95fc 100644 --- a/tests/project_template/test_settings.py +++ b/tests/project_template/test_settings.py @@ -26,16 +26,16 @@ class TestStartProjectSettings(TestCase): shutil.copyfile(template_settings_py, test_settings_py) self.addCleanup(os.remove, test_settings_py) - def test_middleware_classes_headers(self): + def test_middleware_headers(self): """ - Ensure headers sent by the default MIDDLEWARE_CLASSES do not - inadvertently change. For example, we never want "Vary: Cookie" to - appear in the list since it prevents the caching of responses. + Ensure headers sent by the default MIDDLEWARE don't inadvertently + change. For example, we never want "Vary: Cookie" to appear in the list + since it prevents the caching of responses. """ - from django.conf.project_template.project_name.settings import MIDDLEWARE_CLASSES + from django.conf.project_template.project_name.settings import MIDDLEWARE with self.settings( - MIDDLEWARE_CLASSES=MIDDLEWARE_CLASSES, + MIDDLEWARE=MIDDLEWARE, ROOT_URLCONF='project_template.urls', ): response = self.client.get('/empty/') diff --git a/tests/redirects_tests/tests.py b/tests/redirects_tests/tests.py index 2d9793b0a55..b4210ad49b7 100644 --- a/tests/redirects_tests/tests.py +++ b/tests/redirects_tests/tests.py @@ -8,7 +8,7 @@ from django.test import TestCase, modify_settings, override_settings from django.utils import six -@modify_settings(MIDDLEWARE_CLASSES={'append': 'django.contrib.redirects.middleware.RedirectFallbackMiddleware'}) +@modify_settings(MIDDLEWARE={'append': 'django.contrib.redirects.middleware.RedirectFallbackMiddleware'}) @override_settings(APPEND_SLASH=False, SITE_ID=1) class RedirectTests(TestCase): @@ -42,6 +42,18 @@ class RedirectTests(TestCase): response = self.client.get('/initial') self.assertEqual(response.status_code, 410) + @override_settings(MIDDLEWARE=None) + @modify_settings(MIDDLEWARE_CLASSES={'append': 'django.contrib.redirects.middleware.RedirectFallbackMiddleware'}) + def test_redirect_middleware_classes(self): + self.test_redirect() + + @override_settings(MIDDLEWARE=None) + @modify_settings(MIDDLEWARE_CLASSES={'append': 'django.contrib.redirects.middleware.RedirectFallbackMiddleware'}) + def test_more_redirects_middleware_classes(self): + self.test_redirect_with_append_slash() + self.test_redirect_with_append_slash_and_query_string() + self.test_response_gone() + @modify_settings(INSTALLED_APPS={'remove': 'django.contrib.sites'}) def test_sites_not_installed(self): with self.assertRaises(ImproperlyConfigured): @@ -54,7 +66,7 @@ class OverriddenRedirectFallbackMiddleware(RedirectFallbackMiddleware): response_redirect_class = http.HttpResponseRedirect -@modify_settings(MIDDLEWARE_CLASSES={'append': 'redirects_tests.tests.OverriddenRedirectFallbackMiddleware'}) +@modify_settings(MIDDLEWARE={'append': 'redirects_tests.tests.OverriddenRedirectFallbackMiddleware'}) @override_settings(SITE_ID=1) class OverriddenRedirectMiddlewareTests(TestCase): diff --git a/tests/runtests.py b/tests/runtests.py index aedd4aa9326..122cb73282c 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -62,7 +62,7 @@ ALWAYS_INSTALLED_APPS = [ 'django.contrib.staticfiles', ] -ALWAYS_MIDDLEWARE_CLASSES = [ +ALWAYS_MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -124,7 +124,7 @@ def setup(verbosity, test_labels, parallel): 'LANGUAGE_CODE': settings.LANGUAGE_CODE, 'STATIC_URL': settings.STATIC_URL, 'STATIC_ROOT': settings.STATIC_ROOT, - 'MIDDLEWARE_CLASSES': settings.MIDDLEWARE_CLASSES, + 'MIDDLEWARE': settings.MIDDLEWARE, } # Redirect some settings for the duration of these tests. @@ -147,7 +147,7 @@ def setup(verbosity, test_labels, parallel): }] settings.LANGUAGE_CODE = 'en' settings.SITE_ID = 1 - settings.MIDDLEWARE_CLASSES = ALWAYS_MIDDLEWARE_CLASSES + settings.MIDDLEWARE = ALWAYS_MIDDLEWARE settings.MIGRATION_MODULES = { # This lets us skip creating migrations for the test models as many of # them depend on one of the following contrib applications. diff --git a/tests/template_tests/test_response.py b/tests/template_tests/test_response.py index 85b5f4bf0ff..c2ece4b2a88 100644 --- a/tests/template_tests/test_response.py +++ b/tests/template_tests/test_response.py @@ -4,13 +4,15 @@ import pickle import time from datetime import datetime -from django.conf import settings from django.template import engines from django.template.response import ( ContentNotRenderedError, SimpleTemplateResponse, TemplateResponse, ) -from django.test import RequestFactory, SimpleTestCase, override_settings +from django.test import ( + RequestFactory, SimpleTestCase, modify_settings, override_settings, +) from django.test.utils import require_jinja2 +from django.utils.deprecation import MiddlewareMixin from .utils import TEMPLATE_DIR @@ -21,7 +23,7 @@ test_processor_name = 'template_tests.test_response.test_processor' # A test middleware that installs a temporary URLConf -class CustomURLConfMiddleware(object): +class CustomURLConfMiddleware(MiddlewareMixin): def process_request(self, request): request.urlconf = 'template_tests.alternate_urls' @@ -319,12 +321,8 @@ class TemplateResponseTest(SimpleTestCase): pickle.dumps(unpickled_response) -@override_settings( - MIDDLEWARE_CLASSES=settings.MIDDLEWARE_CLASSES + [ - 'template_tests.test_response.CustomURLConfMiddleware' - ], - ROOT_URLCONF='template_tests.urls', -) +@modify_settings(MIDDLEWARE={'append': ['template_tests.test_response.CustomURLConfMiddleware']}) +@override_settings(ROOT_URLCONF='template_tests.urls') class CustomURLConfTest(SimpleTestCase): def test_custom_urlconf(self): @@ -332,14 +330,15 @@ class CustomURLConfTest(SimpleTestCase): self.assertContains(response, 'This is where you can find the snark: /snark/') -@override_settings( - CACHE_MIDDLEWARE_SECONDS=2.0, - MIDDLEWARE_CLASSES=settings.MIDDLEWARE_CLASSES + [ - 'django.middleware.cache.FetchFromCacheMiddleware', - 'django.middleware.cache.UpdateCacheMiddleware', - ], - ROOT_URLCONF='template_tests.alternate_urls', +@modify_settings( + MIDDLEWARE={ + 'append': [ + 'django.middleware.cache.FetchFromCacheMiddleware', + 'django.middleware.cache.UpdateCacheMiddleware', + ], + }, ) +@override_settings(CACHE_MIDDLEWARE_SECONDS=2.0, ROOT_URLCONF='template_tests.alternate_urls') class CacheMiddlewareTest(SimpleTestCase): def test_middleware_caching(self): @@ -360,3 +359,33 @@ class CacheMiddlewareTest(SimpleTestCase): self.assertEqual(response2.status_code, 200) self.assertNotEqual(response.content, response2.content) + + +@override_settings( + MIDDLEWARE=None, + MIDDLEWARE_CLASSES=[ + 'django.middleware.cache.FetchFromCacheMiddleware', + 'django.middleware.cache.UpdateCacheMiddleware', + ], + CACHE_MIDDLEWARE_SECONDS=2.0, + ROOT_URLCONF='template_tests.alternate_urls' +) +class CacheMiddlewareClassesTest(SimpleTestCase): + def test_middleware_caching(self): + response = self.client.get('/template_response_view/') + self.assertEqual(response.status_code, 200) + + time.sleep(1.0) + + response2 = self.client.get('/template_response_view/') + self.assertEqual(response2.status_code, 200) + + self.assertEqual(response.content, response2.content) + + time.sleep(2.0) + + # Let the cache expire and test again + response2 = self.client.get('/template_response_view/') + self.assertEqual(response2.status_code, 200) + + self.assertNotEqual(response.content, response2.content) diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py index dff7e390806..5236846ae6c 100644 --- a/tests/test_client/tests.py +++ b/tests/test_client/tests.py @@ -694,7 +694,7 @@ class ClientTest(TestCase): @override_settings( - MIDDLEWARE_CLASSES=['django.middleware.csrf.CsrfViewMiddleware'], + MIDDLEWARE=['django.middleware.csrf.CsrfViewMiddleware'], ROOT_URLCONF='test_client.urls', ) class CSRFEnabledClientTests(SimpleTestCase): diff --git a/tests/urlpatterns_reverse/middleware.py b/tests/urlpatterns_reverse/middleware.py index 13c3d104b66..8c40125f109 100644 --- a/tests/urlpatterns_reverse/middleware.py +++ b/tests/urlpatterns_reverse/middleware.py @@ -1,37 +1,38 @@ from django.http import HttpResponse, StreamingHttpResponse from django.urls import reverse +from django.utils.deprecation import MiddlewareMixin from . import urlconf_inner -class ChangeURLconfMiddleware(object): +class ChangeURLconfMiddleware(MiddlewareMixin): def process_request(self, request): request.urlconf = urlconf_inner.__name__ -class NullChangeURLconfMiddleware(object): +class NullChangeURLconfMiddleware(MiddlewareMixin): def process_request(self, request): request.urlconf = None -class ReverseInnerInResponseMiddleware(object): +class ReverseInnerInResponseMiddleware(MiddlewareMixin): def process_response(self, *args, **kwargs): return HttpResponse(reverse('inner')) -class ReverseOuterInResponseMiddleware(object): +class ReverseOuterInResponseMiddleware(MiddlewareMixin): def process_response(self, *args, **kwargs): return HttpResponse(reverse('outer')) -class ReverseInnerInStreaming(object): +class ReverseInnerInStreaming(MiddlewareMixin): def process_view(self, *args, **kwargs): def stream(): yield reverse('inner') return StreamingHttpResponse(stream()) -class ReverseOuterInStreaming(object): +class ReverseOuterInStreaming(MiddlewareMixin): def process_view(self, *args, **kwargs): def stream(): yield reverse('outer') diff --git a/tests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py index 4c60ecd7c6d..074a573cd53 100644 --- a/tests/urlpatterns_reverse/tests.py +++ b/tests/urlpatterns_reverse/tests.py @@ -785,7 +785,7 @@ class RequestURLconfTests(SimpleTestCase): self.assertEqual(response.status_code, 404) @override_settings( - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ '%s.ChangeURLconfMiddleware' % middleware.__name__, ] ) @@ -799,7 +799,7 @@ class RequestURLconfTests(SimpleTestCase): self.assertEqual(response.content, b'outer:,inner:/second_test/') @override_settings( - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ '%s.NullChangeURLconfMiddleware' % middleware.__name__, ] ) @@ -817,7 +817,7 @@ class RequestURLconfTests(SimpleTestCase): self.assertEqual(response.status_code, 404) @override_settings( - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ '%s.ChangeURLconfMiddleware' % middleware.__name__, '%s.ReverseInnerInResponseMiddleware' % middleware.__name__, ] @@ -832,7 +832,7 @@ class RequestURLconfTests(SimpleTestCase): self.assertEqual(response.content, b'/second_test/') @override_settings( - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ '%s.ChangeURLconfMiddleware' % middleware.__name__, '%s.ReverseOuterInResponseMiddleware' % middleware.__name__, ] @@ -847,7 +847,7 @@ class RequestURLconfTests(SimpleTestCase): self.client.get('/second_test/') @override_settings( - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ '%s.ChangeURLconfMiddleware' % middleware.__name__, '%s.ReverseInnerInStreaming' % middleware.__name__, ] @@ -862,7 +862,7 @@ class RequestURLconfTests(SimpleTestCase): self.assertEqual(b''.join(response), b'/second_test/') @override_settings( - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ '%s.ChangeURLconfMiddleware' % middleware.__name__, '%s.ReverseOuterInStreaming' % middleware.__name__, ] diff --git a/tests/view_tests/tests/test_csrf.py b/tests/view_tests/tests/test_csrf.py index 4b21e590342..22bf96529f8 100644 --- a/tests/view_tests/tests/test_csrf.py +++ b/tests/view_tests/tests/test_csrf.py @@ -15,7 +15,7 @@ class CsrfViewTests(SimpleTestCase): @override_settings( USE_I18N=True, - MIDDLEWARE_CLASSES=[ + MIDDLEWARE=[ 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -38,6 +38,32 @@ class CsrfViewTests(SimpleTestCase): "CSRF-verificatie mislukt. Verzoek afgebroken.", status_code=403) + @override_settings( + USE_I18N=True, + MIDDLEWARE=None, + MIDDLEWARE_CLASSES=[ + 'django.middleware.locale.LocaleMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + ], + ) + def test_translation_middleware_classes(self): + """ + Test that an invalid request is rejected with a localized error message. + """ + response = self.client.post('/') + self.assertContains(response, "Forbidden", status_code=403) + self.assertContains(response, + "CSRF verification failed. Request aborted.", + status_code=403) + + with self.settings(LANGUAGE_CODE='nl'), override('en-us'): + response = self.client.post('/') + self.assertContains(response, "Verboden", status_code=403) + self.assertContains(response, + "CSRF-verificatie mislukt. Verzoek afgebroken.", + status_code=403) + @override_settings( SECURE_PROXY_SSL_HEADER=('HTTP_X_FORWARDED_PROTO', 'https') ) diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py index 84a381909d4..a50b7d64604 100644 --- a/tests/view_tests/tests/test_i18n.py +++ b/tests/view_tests/tests/test_i18n.py @@ -123,6 +123,26 @@ class I18NTests(TestCase): # we force saving language to a cookie rather than a session # by excluding session middleware and those which do require it test_settings = dict( + MIDDLEWARE=['django.middleware.common.CommonMiddleware'], + LANGUAGE_COOKIE_NAME='mylanguage', + LANGUAGE_COOKIE_AGE=3600 * 7 * 2, + LANGUAGE_COOKIE_DOMAIN='.example.com', + LANGUAGE_COOKIE_PATH='/test/', + ) + with self.settings(**test_settings): + post_data = dict(language='pl', next='/views/') + response = self.client.post('/i18n/setlang/', data=post_data) + language_cookie = response.cookies.get('mylanguage') + self.assertEqual(language_cookie.value, 'pl') + self.assertEqual(language_cookie['domain'], '.example.com') + self.assertEqual(language_cookie['path'], '/test/') + self.assertEqual(language_cookie['max-age'], 3600 * 7 * 2) + + def test_setlang_cookie_middleware_classes(self): + # we force saving language to a cookie rather than a session + # by excluding session middleware and those which do require it + test_settings = dict( + MIDDLEWARE=None, MIDDLEWARE_CLASSES=['django.middleware.common.CommonMiddleware'], LANGUAGE_COOKIE_NAME='mylanguage', LANGUAGE_COOKIE_AGE=3600 * 7 * 2, @@ -150,7 +170,7 @@ class I18NTests(TestCase): self.assertRedirects(response, encoded_url, fetch_redirect_response=False) self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code) - @modify_settings(MIDDLEWARE_CLASSES={ + @modify_settings(MIDDLEWARE={ 'append': 'django.middleware.locale.LocaleMiddleware', }) def test_lang_from_translated_i18n_pattern(self): @@ -167,6 +187,27 @@ class I18NTests(TestCase): ) self.assertRedirects(response, '/en/translated/') + @override_settings( + MIDDLEWARE=None, + MIDDLEWARE_CLASSES=[ + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', + ], + ) + def test_lang_from_translated_i18n_pattern_middleware_classes(self): + response = self.client.post( + '/i18n/setlang/', data={'language': 'nl'}, + follow=True, HTTP_REFERER='/en/translated/' + ) + self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], 'nl') + self.assertRedirects(response, '/nl/vertaald/') + # And reverse + response = self.client.post( + '/i18n/setlang/', data={'language': 'en'}, + follow=True, HTTP_REFERER='/nl/vertaald/' + ) + self.assertRedirects(response, '/en/translated/') + @override_settings(ROOT_URLCONF='view_tests.urls') class JsI18NTests(SimpleTestCase):