Browse Source

Fixed #26601 -- Improved middleware per DEP 0005.

Thanks Tim Graham for polishing the patch, updating the tests, and
writing documentation. Thanks Carl Meyer for shepherding the DEP.
Florian Apolloner 9 years ago
parent
commit
9baf692a58
81 changed files with 912 additions and 1425 deletions
  1. 5 3
      django/conf/global_settings.py
  2. 1 1
      django/conf/project_template/project_name/settings.py-tpl
  3. 3 4
      django/contrib/admin/tests.py
  4. 7 3
      django/contrib/admindocs/middleware.py
  5. 8 6
      django/contrib/auth/middleware.py
  6. 3 2
      django/contrib/flatpages/forms.py
  7. 12 1
      django/contrib/flatpages/middleware.py
  8. 2 1
      django/contrib/messages/middleware.py
  9. 2 1
      django/contrib/messages/storage/session.py
  10. 8 2
      django/contrib/redirects/middleware.py
  11. 4 2
      django/contrib/sessions/middleware.py
  12. 11 8
      django/core/checks/security/base.py
  13. 9 7
      django/core/checks/security/csrf.py
  14. 7 6
      django/core/checks/security/sessions.py
  15. 10 0
      django/core/checks/utils.py
  16. 52 25
      django/core/handlers/base.py
  17. 14 10
      django/middleware/cache.py
  18. 2 1
      django/middleware/clickjacking.py
  19. 3 2
      django/middleware/common.py
  20. 2 1
      django/middleware/csrf.py
  21. 2 1
      django/middleware/gzip.py
  22. 2 1
      django/middleware/http.py
  23. 13 1
      django/middleware/locale.py
  24. 4 2
      django/middleware/security.py
  25. 1 1
      django/test/client.py
  26. 22 0
      django/utils/deprecation.py
  27. 4 2
      django/views/debug.py
  28. 2 2
      docs/howto/auth-remote-user.txt
  29. 2 2
      docs/howto/error-reporting.txt
  30. 2 2
      docs/ref/applications.txt
  31. 6 6
      docs/ref/checks.txt
  32. 2 2
      docs/ref/clickjacking.txt
  33. 2 2
      docs/ref/contrib/admin/index.txt
  34. 2 3
      docs/ref/contrib/flatpages.txt
  35. 4 4
      docs/ref/contrib/messages.txt
  36. 4 4
      docs/ref/contrib/redirects.txt
  37. 2 2
      docs/ref/contrib/sites.txt
  38. 4 4
      docs/ref/csrf.txt
  39. 30 0
      docs/ref/middleware.txt
  40. 7 5
      docs/ref/request-response.txt
  41. 18 2
      docs/ref/settings.txt
  42. 4 0
      docs/ref/utils.txt
  43. 11 0
      docs/releases/1.10.txt
  44. 1 2
      docs/releases/1.5.txt
  45. 1 1
      docs/topics/auth/default.txt
  46. 1 1
      docs/topics/auth/index.txt
  47. 6 6
      docs/topics/cache.txt
  48. 0 957
      docs/topics/http/_images/middleware.graffle
  49. 0 2
      docs/topics/http/_images/middleware.svg
  50. 194 157
      docs/topics/http/middleware.txt
  51. 5 5
      docs/topics/http/sessions.txt
  52. 2 2
      docs/topics/http/urls.txt
  53. 3 2
      docs/topics/i18n/timezones.txt
  54. 6 6
      docs/topics/i18n/translation.txt
  55. 3 3
      docs/topics/testing/tools.txt
  56. 1 1
      tests/auth_tests/settings.py
  57. 13 3
      tests/auth_tests/test_context_processors.py
  58. 16 1
      tests/auth_tests/test_remote_user.py
  59. 77 52
      tests/check_framework/test_security.py
  60. 1 1
      tests/file_uploads/tests.py
  61. 16 1
      tests/flatpages_tests/test_csrf.py
  62. 20 2
      tests/flatpages_tests/test_forms.py
  63. 32 2
      tests/flatpages_tests/test_middleware.py
  64. 1 17
      tests/flatpages_tests/test_templatetags.py
  65. 2 2
      tests/flatpages_tests/test_views.py
  66. 11 0
      tests/handlers/tests.py
  67. 2 2
      tests/i18n/patterns/tests.py
  68. 4 4
      tests/i18n/tests.py
  69. 1 1
      tests/logging_tests/tests.py
  70. 2 2
      tests/messages_tests/base.py
  71. 3 1
      tests/middleware_exceptions/middleware.py
  72. 24 15
      tests/middleware_exceptions/tests.py
  73. 6 6
      tests/project_template/test_settings.py
  74. 14 2
      tests/redirects_tests/tests.py
  75. 3 3
      tests/runtests.py
  76. 43 14
      tests/template_tests/test_response.py
  77. 1 1
      tests/test_client/tests.py
  78. 7 6
      tests/urlpatterns_reverse/middleware.py
  79. 6 6
      tests/urlpatterns_reverse/tests.py
  80. 27 1
      tests/view_tests/tests/test_csrf.py
  81. 42 1
      tests/view_tests/tests/test_i18n.py

+ 5 - 3
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 #
 ############

+ 1 - 1
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',

+ 3 - 4
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 = [

+ 7 - 3
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()

+ 8 - 6
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:

+ 3 - 2
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."),

+ 12 - 1
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.

+ 2 - 1
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.
     """

+ 2 - 1
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):

+ 8 - 2
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.

+ 4 - 2
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
 

+ 11 - 8
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)

+ 9 - 7
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)]

+ 7 - 6
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():

+ 10 - 0
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

+ 52 - 25
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 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 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)
+        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)
 

+ 14 - 10
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,

+ 2 - 1
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.
 

+ 3 - 2
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):
         """

+ 2 - 1
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

+ 2 - 1
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

+ 2 - 1
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

+ 13 - 1
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)

+ 4 - 2
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("/")

+ 1 - 1
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)

+ 22 - 0
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

+ 4 - 2
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 %}

+ 2 - 2
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',

+ 2 - 2
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

+ 2 - 2
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

+ 6 - 6
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'``.

+ 2 - 2
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',
         ...

+ 2 - 2
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.

+ 2 - 3
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 <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

+ 4 - 4
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 <message-storage-backends>` relies on
   :doc:`sessions </topics/http/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
 ==============================

+ 4 - 4
docs/ref/contrib/redirects.txt

@@ -18,7 +18,7 @@ To install the redirects app, follow these steps:
    :ref:`is installed <enabling-the-sites-framework>`.
 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 <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
 </topics/http/middleware>`.

+ 2 - 2
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
 ===================================

+ 4 - 4
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

+ 30 - 0
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 <DEBUG>`).
+* :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 <DEBUG>`).
+* Any other exception is processed by :data:`~django.conf.urls.handler500`
+  (or a more friendly debug page if :setting:`DEBUG=True <DEBUG>`).
+
+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
 ---------------
 

+ 7 - 5
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',

+ 18 - 2
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 <upgrading-middleware>` 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
 

+ 4 - 0
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

+ 11 - 0
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 </topics/http/middleware>` to
+solve the lack of strict request/response layering of the old-style of
+middleware described in `DEP 0005
+<https://github.com/django/deps/blob/master/final/0005-improved-middleware.rst>`_.
+You'll need to :ref:`adapt old, custom middleware <upgrading-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
 --------------------------------------
 

+ 1 - 2
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
 -------------------------------

+ 1 - 1
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

+ 1 - 1
docs/topics/auth/index.txt

@@ -60,7 +60,7 @@ startproject <startproject>`, these consist of two items listed in your
    </ref/contrib/contenttypes>`, 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 </topics/http/sessions>` across requests.

+ 6 - 6
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.
 

+ 0 - 957
docs/topics/http/_images/middleware.graffle

@@ -1,957 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>ActiveLayerIndex</key>
-	<integer>0</integer>
-	<key>ApplicationVersion</key>
-	<array>
-		<string>com.omnigroup.OmniGrafflePro</string>
-		<string>139.16.0.171715</string>
-	</array>
-	<key>AutoAdjust</key>
-	<true/>
-	<key>BackgroundGraphic</key>
-	<dict>
-		<key>Bounds</key>
-		<string>{{0, 0}, {559.28997802734375, 782.8900146484375}}</string>
-		<key>Class</key>
-		<string>SolidGraphic</string>
-		<key>ID</key>
-		<integer>2</integer>
-		<key>Style</key>
-		<dict>
-			<key>shadow</key>
-			<dict>
-				<key>Draws</key>
-				<string>NO</string>
-			</dict>
-			<key>stroke</key>
-			<dict>
-				<key>Draws</key>
-				<string>NO</string>
-			</dict>
-		</dict>
-	</dict>
-	<key>BaseZoom</key>
-	<integer>0</integer>
-	<key>CanvasOrigin</key>
-	<string>{0, 0}</string>
-	<key>ColumnAlign</key>
-	<integer>1</integer>
-	<key>ColumnSpacing</key>
-	<real>36</real>
-	<key>CreationDate</key>
-	<string>2012-12-09 18:55:12 +0000</string>
-	<key>Creator</key>
-	<string>Aymeric Augustin</string>
-	<key>DisplayScale</key>
-	<string>1.000 cm = 1.000 cm</string>
-	<key>GraphDocumentVersion</key>
-	<integer>8</integer>
-	<key>GraphicsList</key>
-	<array>
-		<dict>
-			<key>Bounds</key>
-			<string>{{144, 405}, {369, 27}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>33</integer>
-			<key>Shape</key>
-			<string>Bezier</string>
-			<key>ShapeData</key>
-			<dict>
-				<key>UnitPoints</key>
-				<array>
-					<string>{-0.5, -0.5}</string>
-					<string>{-0.5, -0.5}</string>
-					<string>{0.47959183673469341, -0.5}</string>
-					<string>{0.47959183673469408, -0.5}</string>
-					<string>{0.47959183673469341, -0.5}</string>
-					<string>{0.5, 0}</string>
-					<string>{0.5, 0}</string>
-					<string>{0.5, 0}</string>
-					<string>{0.47959183673469408, 0.5}</string>
-					<string>{0.47959183673469408, 0.5}</string>
-					<string>{0.47959183673469408, 0.5}</string>
-					<string>{-0.5, 0.5}</string>
-					<string>{-0.5, 0.5}</string>
-					<string>{-0.5, 0.5}</string>
-					<string>{-0.47560975609756084, 0}</string>
-					<string>{-0.47560975609756084, 0}</string>
-					<string>{-0.47560975609756084, 0}</string>
-					<string>{-0.5, -0.5}</string>
-				</array>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{229.5, 238.5}, {297, 36}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>31</integer>
-			<key>Rotation</key>
-			<real>270</real>
-			<key>Shape</key>
-			<string>AdjustableArrow</string>
-			<key>ShapeData</key>
-			<dict>
-				<key>width</key>
-				<real>27</real>
-			</dict>
-			<key>Style</key>
-			<dict>
-				<key>fill</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>a</key>
-						<string>0.8</string>
-						<key>b</key>
-						<string>1</string>
-						<key>g</key>
-						<string>1</string>
-						<key>r</key>
-						<string>1</string>
-					</dict>
-					<key>MiddleFraction</key>
-					<real>0.70634919404983521</real>
-				</dict>
-				<key>shadow</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>a</key>
-						<string>0.4</string>
-						<key>b</key>
-						<string>0</string>
-						<key>g</key>
-						<string>0</string>
-						<key>r</key>
-						<string>0</string>
-					</dict>
-					<key>Draws</key>
-					<string>NO</string>
-					<key>Fuzziness</key>
-					<real>0.0</real>
-					<key>ShadowVector</key>
-					<string>{0, 2}</string>
-				</dict>
-				<key>stroke</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>b</key>
-						<string>0</string>
-						<key>g</key>
-						<string>0</string>
-						<key>r</key>
-						<string>1</string>
-					</dict>
-					<key>Pattern</key>
-					<integer>1</integer>
-				</dict>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-			<key>TextRelativeArea</key>
-			<string>{{0.125, 0.25}, {0.75, 0.5}}</string>
-			<key>isConnectedShape</key>
-			<true/>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{328.5, 229.5}, {315, 36}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>30</integer>
-			<key>Rotation</key>
-			<real>270</real>
-			<key>Shape</key>
-			<string>AdjustableArrow</string>
-			<key>ShapeData</key>
-			<dict>
-				<key>width</key>
-				<real>27</real>
-			</dict>
-			<key>Style</key>
-			<dict>
-				<key>fill</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>a</key>
-						<string>0.8</string>
-						<key>b</key>
-						<string>1</string>
-						<key>g</key>
-						<string>1</string>
-						<key>r</key>
-						<string>1</string>
-					</dict>
-					<key>MiddleFraction</key>
-					<real>0.70634919404983521</real>
-				</dict>
-				<key>shadow</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>a</key>
-						<string>0.4</string>
-						<key>b</key>
-						<string>0</string>
-						<key>g</key>
-						<string>0</string>
-						<key>r</key>
-						<string>0</string>
-					</dict>
-					<key>Draws</key>
-					<string>NO</string>
-					<key>Fuzziness</key>
-					<real>0.0</real>
-					<key>ShadowVector</key>
-					<string>{0, 2}</string>
-				</dict>
-				<key>stroke</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>b</key>
-						<string>0</string>
-						<key>g</key>
-						<string>0.501961</string>
-						<key>r</key>
-						<string>0</string>
-					</dict>
-				</dict>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-			<key>TextRelativeArea</key>
-			<string>{{0.125, 0.25}, {0.75, 0.5}}</string>
-			<key>isConnectedShape</key>
-			<true/>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{283.5, 238.5}, {297, 36}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>29</integer>
-			<key>Rotation</key>
-			<real>270</real>
-			<key>Shape</key>
-			<string>AdjustableArrow</string>
-			<key>ShapeData</key>
-			<dict>
-				<key>width</key>
-				<real>27</real>
-			</dict>
-			<key>Style</key>
-			<dict>
-				<key>fill</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>a</key>
-						<string>0.8</string>
-						<key>b</key>
-						<string>1</string>
-						<key>g</key>
-						<string>1</string>
-						<key>r</key>
-						<string>1</string>
-					</dict>
-					<key>MiddleFraction</key>
-					<real>0.70634919404983521</real>
-				</dict>
-				<key>shadow</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>a</key>
-						<string>0.4</string>
-						<key>b</key>
-						<string>0</string>
-						<key>g</key>
-						<string>0</string>
-						<key>r</key>
-						<string>0</string>
-					</dict>
-					<key>Draws</key>
-					<string>NO</string>
-					<key>Fuzziness</key>
-					<real>0.0</real>
-					<key>ShadowVector</key>
-					<string>{0, 2}</string>
-				</dict>
-				<key>stroke</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>b</key>
-						<string>0</string>
-						<key>g</key>
-						<string>0.501961</string>
-						<key>r</key>
-						<string>0</string>
-					</dict>
-					<key>Pattern</key>
-					<integer>1</integer>
-				</dict>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-			<key>TextRelativeArea</key>
-			<string>{{0.125, 0.25}, {0.75, 0.5}}</string>
-			<key>isConnectedShape</key>
-			<true/>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{27, 243}, {288, 36}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>28</integer>
-			<key>Rotation</key>
-			<real>90</real>
-			<key>Shape</key>
-			<string>AdjustableArrow</string>
-			<key>ShapeData</key>
-			<dict>
-				<key>width</key>
-				<real>27</real>
-			</dict>
-			<key>Style</key>
-			<dict>
-				<key>fill</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>a</key>
-						<string>0.8</string>
-						<key>b</key>
-						<string>1</string>
-						<key>g</key>
-						<string>1</string>
-						<key>r</key>
-						<string>1</string>
-					</dict>
-					<key>MiddleFraction</key>
-					<real>0.70634919404983521</real>
-				</dict>
-				<key>shadow</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>a</key>
-						<string>0.4</string>
-						<key>b</key>
-						<string>0</string>
-						<key>g</key>
-						<string>0</string>
-						<key>r</key>
-						<string>0</string>
-					</dict>
-					<key>Draws</key>
-					<string>NO</string>
-					<key>Fuzziness</key>
-					<real>0.0</real>
-					<key>ShadowVector</key>
-					<string>{0, 2}</string>
-				</dict>
-				<key>stroke</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>b</key>
-						<string>0</string>
-						<key>g</key>
-						<string>0.501961</string>
-						<key>r</key>
-						<string>0</string>
-					</dict>
-				</dict>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-			<key>TextRelativeArea</key>
-			<string>{{0.125, 0.25}, {0.75, 0.5}}</string>
-			<key>isConnectedShape</key>
-			<true/>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{-40.500000000767386, 220.49999999804004}, {297, 36}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>27</integer>
-			<key>Rotation</key>
-			<real>90</real>
-			<key>Shape</key>
-			<string>AdjustableArrow</string>
-			<key>ShapeData</key>
-			<dict>
-				<key>width</key>
-				<real>27</real>
-			</dict>
-			<key>Style</key>
-			<dict>
-				<key>fill</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>a</key>
-						<string>0.8</string>
-						<key>b</key>
-						<string>1</string>
-						<key>g</key>
-						<string>1</string>
-						<key>r</key>
-						<string>1</string>
-					</dict>
-					<key>MiddleFraction</key>
-					<real>0.70634919404983521</real>
-				</dict>
-				<key>shadow</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>a</key>
-						<string>0.4</string>
-						<key>b</key>
-						<string>0</string>
-						<key>g</key>
-						<string>0</string>
-						<key>r</key>
-						<string>0</string>
-					</dict>
-					<key>Draws</key>
-					<string>NO</string>
-					<key>Fuzziness</key>
-					<real>0.0</real>
-					<key>ShadowVector</key>
-					<string>{0, 2}</string>
-				</dict>
-				<key>stroke</key>
-				<dict>
-					<key>Color</key>
-					<dict>
-						<key>b</key>
-						<string>0</string>
-						<key>g</key>
-						<string>0.501961</string>
-						<key>r</key>
-						<string>0</string>
-					</dict>
-				</dict>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-			<key>TextRelativeArea</key>
-			<string>{{0.125, 0.25}, {0.75, 0.5}}</string>
-			<key>isConnectedShape</key>
-			<true/>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{360, 63}, {144, 27}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>12</integer>
-			<key>Magnets</key>
-			<array>
-				<string>{0, 1}</string>
-				<string>{0, -1}</string>
-				<string>{1, 0}</string>
-				<string>{-1, 0}</string>
-			</array>
-			<key>Shape</key>
-			<string>Rectangle</string>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{72, 63}, {144, 27}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>11</integer>
-			<key>Magnets</key>
-			<array>
-				<string>{0, 1}</string>
-				<string>{0, -1}</string>
-				<string>{1, 0}</string>
-				<string>{-1, 0}</string>
-			</array>
-			<key>Shape</key>
-			<string>Rectangle</string>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{72, 324}, {432, 27}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>10</integer>
-			<key>Magnets</key>
-			<array>
-				<string>{0, 1}</string>
-				<string>{0, -1}</string>
-				<string>{1, 0}</string>
-				<string>{-1, 0}</string>
-			</array>
-			<key>Shape</key>
-			<string>Rectangle</string>
-			<key>Style</key>
-			<dict>
-				<key>fill</key>
-				<dict>
-					<key>FillType</key>
-					<integer>2</integer>
-					<key>GradientAngle</key>
-					<real>90</real>
-					<key>GradientColor</key>
-					<dict>
-						<key>w</key>
-						<string>0.666667</string>
-					</dict>
-				</dict>
-				<key>stroke</key>
-				<dict>
-					<key>CornerRadius</key>
-					<real>5</real>
-				</dict>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{72, 279}, {432, 27}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>9</integer>
-			<key>Magnets</key>
-			<array>
-				<string>{0, 1}</string>
-				<string>{0, -1}</string>
-				<string>{1, 0}</string>
-				<string>{-1, 0}</string>
-			</array>
-			<key>Shape</key>
-			<string>Rectangle</string>
-			<key>Style</key>
-			<dict>
-				<key>fill</key>
-				<dict>
-					<key>FillType</key>
-					<integer>2</integer>
-					<key>GradientAngle</key>
-					<real>90</real>
-					<key>GradientColor</key>
-					<dict>
-						<key>w</key>
-						<string>0.666667</string>
-					</dict>
-				</dict>
-				<key>stroke</key>
-				<dict>
-					<key>CornerRadius</key>
-					<real>5</real>
-				</dict>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{72, 234}, {432, 27}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>8</integer>
-			<key>Magnets</key>
-			<array>
-				<string>{0, 1}</string>
-				<string>{0, -1}</string>
-				<string>{1, 0}</string>
-				<string>{-1, 0}</string>
-			</array>
-			<key>Shape</key>
-			<string>Rectangle</string>
-			<key>Style</key>
-			<dict>
-				<key>fill</key>
-				<dict>
-					<key>FillType</key>
-					<integer>2</integer>
-					<key>GradientAngle</key>
-					<real>90</real>
-					<key>GradientColor</key>
-					<dict>
-						<key>w</key>
-						<string>0.666667</string>
-					</dict>
-				</dict>
-				<key>stroke</key>
-				<dict>
-					<key>CornerRadius</key>
-					<real>5</real>
-				</dict>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{72, 189}, {432, 27}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>7</integer>
-			<key>Magnets</key>
-			<array>
-				<string>{0, 1}</string>
-				<string>{0, -1}</string>
-				<string>{1, 0}</string>
-				<string>{-1, 0}</string>
-			</array>
-			<key>Shape</key>
-			<string>Rectangle</string>
-			<key>Style</key>
-			<dict>
-				<key>fill</key>
-				<dict>
-					<key>FillType</key>
-					<integer>2</integer>
-					<key>GradientAngle</key>
-					<real>90</real>
-					<key>GradientColor</key>
-					<dict>
-						<key>w</key>
-						<string>0.666667</string>
-					</dict>
-				</dict>
-				<key>stroke</key>
-				<dict>
-					<key>CornerRadius</key>
-					<real>5</real>
-				</dict>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{72, 144}, {432, 27}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
-			<integer>6</integer>
-			<key>Magnets</key>
-			<array>
-				<string>{0, 1}</string>
-				<string>{0, -1}</string>
-				<string>{1, 0}</string>
-				<string>{-1, 0}</string>
-			</array>
-			<key>Shape</key>
-			<string>Rectangle</string>
-			<key>Style</key>
-			<dict>
-				<key>fill</key>
-				<dict>
-					<key>FillType</key>
-					<integer>2</integer>
-					<key>GradientAngle</key>
-					<real>90</real>
-					<key>GradientColor</key>
-					<dict>
-						<key>w</key>
-						<string>0.666667</string>
-					</dict>
-				</dict>
-				<key>stroke</key>
-				<dict>
-					<key>CornerRadius</key>
-					<real>5</real>
-				</dict>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\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}</string>
-			</dict>
-		</dict>
-	</array>
-	<key>GridInfo</key>
-	<dict>
-		<key>ShowsGrid</key>
-		<string>YES</string>
-		<key>SnapsToGrid</key>
-		<string>YES</string>
-	</dict>
-	<key>GuidesLocked</key>
-	<string>NO</string>
-	<key>GuidesVisible</key>
-	<string>YES</string>
-	<key>HPages</key>
-	<integer>1</integer>
-	<key>ImageCounter</key>
-	<integer>1</integer>
-	<key>KeepToScale</key>
-	<false/>
-	<key>Layers</key>
-	<array>
-		<dict>
-			<key>Lock</key>
-			<string>NO</string>
-			<key>Name</key>
-			<string>Calque 1</string>
-			<key>Print</key>
-			<string>YES</string>
-			<key>View</key>
-			<string>YES</string>
-		</dict>
-	</array>
-	<key>LayoutInfo</key>
-	<dict>
-		<key>Animate</key>
-		<string>NO</string>
-		<key>circoMinDist</key>
-		<real>18</real>
-		<key>circoSeparation</key>
-		<real>0.0</real>
-		<key>layoutEngine</key>
-		<string>dot</string>
-		<key>neatoSeparation</key>
-		<real>0.0</real>
-		<key>twopiSeparation</key>
-		<real>0.0</real>
-	</dict>
-	<key>LinksVisible</key>
-	<string>NO</string>
-	<key>MagnetsVisible</key>
-	<string>NO</string>
-	<key>MasterSheets</key>
-	<array/>
-	<key>ModificationDate</key>
-	<string>2012-12-09 19:48:54 +0000</string>
-	<key>Modifier</key>
-	<string>Aymeric Augustin</string>
-	<key>NotesVisible</key>
-	<string>NO</string>
-	<key>Orientation</key>
-	<integer>2</integer>
-	<key>OriginVisible</key>
-	<string>NO</string>
-	<key>PageBreaks</key>
-	<string>YES</string>
-	<key>PrintInfo</key>
-	<dict>
-		<key>NSBottomMargin</key>
-		<array>
-			<string>float</string>
-			<string>41</string>
-		</array>
-		<key>NSHorizonalPagination</key>
-		<array>
-			<string>coded</string>
-			<string>BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG</string>
-		</array>
-		<key>NSLeftMargin</key>
-		<array>
-			<string>float</string>
-			<string>18</string>
-		</array>
-		<key>NSPaperSize</key>
-		<array>
-			<string>size</string>
-			<string>{595.28997802734375, 841.8900146484375}</string>
-		</array>
-		<key>NSPrintReverseOrientation</key>
-		<array>
-			<string>int</string>
-			<string>0</string>
-		</array>
-		<key>NSRightMargin</key>
-		<array>
-			<string>float</string>
-			<string>18</string>
-		</array>
-		<key>NSTopMargin</key>
-		<array>
-			<string>float</string>
-			<string>18</string>
-		</array>
-	</dict>
-	<key>PrintOnePage</key>
-	<false/>
-	<key>ReadOnly</key>
-	<string>NO</string>
-	<key>RowAlign</key>
-	<integer>1</integer>
-	<key>RowSpacing</key>
-	<real>36</real>
-	<key>SheetTitle</key>
-	<string>Canevas 1</string>
-	<key>SmartAlignmentGuidesActive</key>
-	<string>YES</string>
-	<key>SmartDistanceGuidesActive</key>
-	<string>YES</string>
-	<key>UniqueID</key>
-	<integer>1</integer>
-	<key>UseEntirePage</key>
-	<false/>
-	<key>VPages</key>
-	<integer>1</integer>
-	<key>WindowInfo</key>
-	<dict>
-		<key>CurrentSheet</key>
-		<integer>0</integer>
-		<key>ExpandedCanvases</key>
-		<array/>
-		<key>Frame</key>
-		<string>{{248, 4}, {694, 874}}</string>
-		<key>ListView</key>
-		<true/>
-		<key>OutlineWidth</key>
-		<integer>142</integer>
-		<key>RightSidebar</key>
-		<false/>
-		<key>ShowRuler</key>
-		<true/>
-		<key>Sidebar</key>
-		<true/>
-		<key>SidebarWidth</key>
-		<integer>120</integer>
-		<key>VisibleRegion</key>
-		<string>{{0, 0}, {559, 735}}</string>
-		<key>Zoom</key>
-		<real>1</real>
-		<key>ZoomValues</key>
-		<array>
-			<array>
-				<string>Canevas 1</string>
-				<real>1</real>
-				<real>1</real>
-			</array>
-		</array>
-	</dict>
-</dict>
-</plist>

File diff suppressed because it is too large
+ 0 - 2
docs/topics/http/_images/middleware.svg


+ 194 - 157
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 </ref/middleware>`.
 
+.. 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 <upgrading-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
+<view-middleware>`, calling the view with appropriate URL arguments, and
+applying :ref:`template-response <template-response-middleware>` 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
 <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:
-
-* :meth:`process_request`
-* :meth:`process_view`
-
-During the response phase, after calling the view, middleware are applied in
-reverse order, from the bottom up. Three 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_exception` (only if the view raised an exception)
-* :meth:`process_template_response` (only for template responses)
-* :meth:`process_response`
+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.
 
-.. image:: _images/middleware.*
-   :alt: middleware application order
-   :width: 481
-   :height: 409
+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``.
 
-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 <django.http.HttpRequest.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 <modifying_upload_handlers_on_the_fly>`,
     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()``
------------------------
-
-.. method:: process_exception(request, exception)
-
-``request`` is an :class:`~django.http.HttpRequest` object. ``exception`` is an
-``Exception`` object raised by the view function.
-
-Django calls ``process_exception()`` when a view raises an exception.
-``process_exception()`` should return either ``None`` or an
-:class:`~django.http.HttpResponse` object. If it returns an
-:class:`~django.http.HttpResponse` object, the template response and response
-middleware will be applied, and the resulting response returned to the
-browser. Otherwise, default exception handling kicks in.
-
-Again, middleware are run in reverse order during the response phase, which
-includes ``process_exception``. If an exception middleware returns a response,
-the middleware classes above that middleware will not be called at all.
-
-``__init__()``
---------------
-
-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:
-
-* Django initializes your middleware without any arguments, so you can't
-  define ``__init__`` as requiring any arguments.
-
-* Unlike the ``process_*`` methods which get called once per request,
-  ``__init__`` gets 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.
-
-Marking middleware as unused
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-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``.
-
-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
-  </ref/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
-  </internals/contributing/index>`, and we'll consider adding it to Django.
+Exception middleware
+====================
+
+A middleware that does some custom exception handling might looks like this::
+
+    class ExceptionMiddleware(object):
+        def __init__(self, get_response):
+            self.get_response = get_response
+
+        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
+
+Middleware that wants to do something for all exception responses, an HTTP 404
+for example, need to both catch the appropriate exception (e.g. ``Http404``)
+and look for regular responses with the status code of interest. You can
+subclass  :class:`~django.middleware.exception.ExceptionMiddleware` if you want
+to transform exceptions into the appropriate response.
+
+.. _upgrading-middleware:
+
+Upgrading pre-Django 1.10-style middleware
+==========================================
+
+.. class:: django.utils.deprecation.MiddlewareMixin
+    :module:
+
+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.
+
+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 a few cases, a middleware class may need more invasive changes to adjust to
+the new semantics.
+
+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.
+
+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.
+
+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`.

+ 5 - 5
docs/topics/http/sessions.txt

@@ -18,13 +18,13 @@ Sessions are implemented via a piece of :doc:`middleware </ref/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.
 

+ 2 - 2
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 <request-middleware>`),
-   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`

+ 3 - 2
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:

+ 6 - 6
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',

+ 3 - 3
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',
     })

+ 1 - 1
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',
 ]

+ 13 - 3
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(

+ 16 - 1
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

+ 77 - 52
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
@@ -45,9 +62,17 @@ class CheckSessionCookieSecureTest(SimpleTestCase):
         self.assertEqual(self.func(None), [sessions.W012])
 
     @override_settings(
-        SESSION_COOKIE_SECURE=True,
+        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=["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), [])

+ 1 - 1
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

+ 16 - 1
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

+ 20 - 2
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)

+ 32 - 2
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, "<p>Isn't it special!</p>")
 
 
+@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, "<p>Root</p>")
+
+
+@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

+ 1 - 17
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 django.test import TestCase
 
-from .settings import FLATPAGES_TEMPLATES
 
-
-@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

+ 2 - 2
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',

+ 11 - 0
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):

+ 2 - 2
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',
         ],

+ 4 - 4
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',
     ],

+ 1 - 1
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',
     ],

+ 2 - 2
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=[{

+ 3 - 1
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')

+ 24 - 15
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

+ 6 - 6
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/')

+ 14 - 2
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):
 

+ 3 - 3
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.

+ 43 - 14
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,16 +330,47 @@ class CustomURLConfTest(SimpleTestCase):
         self.assertContains(response, 'This is where you can find the snark: /snark/')
 
 
+@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):
+        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)
+
+
 @override_settings(
-    CACHE_MIDDLEWARE_SECONDS=2.0,
-    MIDDLEWARE_CLASSES=settings.MIDDLEWARE_CLASSES + [
+    MIDDLEWARE=None,
+    MIDDLEWARE_CLASSES=[
         'django.middleware.cache.FetchFromCacheMiddleware',
         'django.middleware.cache.UpdateCacheMiddleware',
     ],
-    ROOT_URLCONF='template_tests.alternate_urls',
+    CACHE_MIDDLEWARE_SECONDS=2.0,
+    ROOT_URLCONF='template_tests.alternate_urls'
 )
-class CacheMiddlewareTest(SimpleTestCase):
-
+class CacheMiddlewareClassesTest(SimpleTestCase):
     def test_middleware_caching(self):
         response = self.client.get('/template_response_view/')
         self.assertEqual(response.status_code, 200)

+ 1 - 1
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):

+ 7 - 6
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')

+ 6 - 6
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__,
         ]

+ 27 - 1
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')
     )

+ 42 - 1
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):

Some files were not shown because too many files changed in this diff