Code

Ticket #730: middleware_dict_single_instances_basic_sanity_checks-validation.diff

File middleware_dict_single_instances_basic_sanity_checks-validation.diff, 10.4 KB (added by mrts, 6 years ago)

Added validation as well. Consider this patch to be a draft of the general ideas, it's not even tested.

Line 
1Index: django/conf/project_template/settings.py
2===================================================================
3--- django/conf/project_template/settings.py    (revision 8255)
4+++ django/conf/project_template/settings.py    (working copy)
5@@ -57,12 +57,22 @@
6 #     'django.template.loaders.eggs.load_template_source',
7 )
8 
9-MIDDLEWARE_CLASSES = (
10-    'django.middleware.common.CommonMiddleware',
11-    'django.contrib.sessions.middleware.SessionMiddleware',
12-    'django.contrib.auth.middleware.AuthenticationMiddleware',
13-    'django.middleware.doc.XViewMiddleware',
14-)
15+MIDDLEWARE_CLASSES = {
16+    'request': (
17+        'django.middleware.common.CommonMiddleware',
18+        'django.contrib.sessions.middleware.SessionMiddleware',
19+        'django.contrib.auth.middleware.AuthenticationMiddleware',
20+    ),
21+    'response': (
22+        'django.contrib.sessions.middleware.SessionMiddleware',
23+#        'django.middleware.http.ConditionalGetMiddleware',
24+#        'django.middleware.gzip.GZipMiddleware',
25+        'django.middleware.common.CommonMiddleware',
26+    ),
27+    'view': (
28+        'django.middleware.doc.XViewMiddleware',
29+    ),
30+}
31 
32 ROOT_URLCONF = '{{ project_name }}.urls'
33 
34Index: django/conf/global_settings.py
35===================================================================
36--- django/conf/global_settings.py      (revision 8255)
37+++ django/conf/global_settings.py      (working copy)
38@@ -291,17 +291,24 @@
39 # MIDDLEWARE #
40 ##############
41 
42-# List of middleware classes to use.  Order is important; in the request phase,
43-# this middleware classes will be applied in the order given, and in the
44-# response phase the middleware will be applied in reverse order.
45-MIDDLEWARE_CLASSES = (
46-    'django.contrib.sessions.middleware.SessionMiddleware',
47-    'django.contrib.auth.middleware.AuthenticationMiddleware',
48-#     'django.middleware.http.ConditionalGetMiddleware',
49-#     'django.middleware.gzip.GZipMiddleware',
50-    'django.middleware.common.CommonMiddleware',
51-    'django.middleware.doc.XViewMiddleware',
52-)
53+# List of middleware classes to use.  Order is important; all middleware types
54+# will be applied in the order given.
55+MIDDLEWARE_CLASSES = {
56+    'request': (
57+        'django.middleware.common.CommonMiddleware',
58+        'django.contrib.sessions.middleware.SessionMiddleware',
59+        'django.contrib.auth.middleware.AuthenticationMiddleware',
60+    ),
61+    'response': (
62+        'django.contrib.sessions.middleware.SessionMiddleware',
63+#        'django.middleware.http.ConditionalGetMiddleware',
64+#        'django.middleware.gzip.GZipMiddleware',
65+        'django.middleware.common.CommonMiddleware',
66+    ),
67+    'view': (
68+        'django.middleware.doc.XViewMiddleware',
69+    ),
70+}
71 
72 ############
73 # SESSIONS #
74Index: django/core/handlers/base.py
75===================================================================
76--- django/core/handlers/base.py        (revision 8255)
77+++ django/core/handlers/base.py        (working copy)
78@@ -1,6 +1,7 @@
79 import sys
80 
81 from django import http
82+from django.core import exceptions
83 from django.core import signals
84 from django.utils.encoding import force_unicode
85 
86@@ -16,38 +17,14 @@
87     def __init__(self):
88         self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
89 
90-    def load_middleware(self):
91-        """
92-        Populate middleware lists from settings.MIDDLEWARE_CLASSES.
93-
94-        Must be called after the environment is fixed (see __call__).
95-        """
96-        from django.conf import settings
97-        from django.core import exceptions
98+    def _load_middleware_from_list(self, mw_paths):
99         self._request_middleware = []
100         self._view_middleware = []
101         self._response_middleware = []
102         self._exception_middleware = []
103-        for middleware_path in settings.MIDDLEWARE_CLASSES:
104-            try:
105-                dot = middleware_path.rindex('.')
106-            except ValueError:
107-                raise exceptions.ImproperlyConfigured, '%s isn\'t a middleware module' % middleware_path
108-            mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
109-            try:
110-                mod = __import__(mw_module, {}, {}, [''])
111-            except ImportError, e:
112-                raise exceptions.ImproperlyConfigured, 'Error importing middleware %s: "%s"' % (mw_module, e)
113-            try:
114-                mw_class = getattr(mod, mw_classname)
115-            except AttributeError:
116-                raise exceptions.ImproperlyConfigured, 'Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname)
117-
118-            try:
119-                mw_instance = mw_class()
120-            except exceptions.MiddlewareNotUsed:
121-                continue
122-
123+        mw_instances = [ _middleware_instance_from_path(mw_path) for mw_path
124+                in mw_paths ]
125+        for mw_instance in mw_instances:
126             if hasattr(mw_instance, 'process_request'):
127                 self._request_middleware.append(mw_instance.process_request)
128             if hasattr(mw_instance, 'process_view'):
129@@ -57,9 +34,43 @@
130             if hasattr(mw_instance, 'process_exception'):
131                 self._exception_middleware.insert(0, mw_instance.process_exception)
132 
133+    def _load_middleware_from_dict(self, mw_dict):
134+        # fill the instances dict so that we have only one instance of each
135+        # middleware
136+        mw_instances = _middleware_instances_from_dict(mw_dict)
137+        # it would be easy to do validation here, but it would be expensive
138+        # for mw_path, mw_inst in mw_instances.items():
139+        #   for attr in ('request', 'view', 'response', 'exception'):
140+        #       if hasattr(mw_inst, attr) and mw_path not in mw_dict[attr]:
141+        #           raise ImproperlyConfigured("")
142+
143+        self._request_middleware = _particular_middleware_from_dicts('request',
144+                mw_instances, mw_dict)
145+        self._view_middleware = _particular_middleware_from_dicts('view',
146+                mw_instances, mw_dict)
147+        self._response_middleware = _particular_middleware_from_dicts('response',
148+                mw_instances, mw_dict)
149+        self._exception_middleware = _particular_middleware_from_dicts('exception',
150+                mw_instances, mw_dict)
151+
152+    def load_middleware(self):
153+        """
154+        Populate middleware lists from settings.MIDDLEWARE_CLASSES.
155+
156+        Must be called after the environment is fixed (see __call__).
157+        """
158+        from django.conf import settings
159+        if isinstance(settings.MIDDLEWARE_CLASSES, (tuple, list)):
160+            self._load_middleware_from_list(settings.MIDDLEWARE_CLASSES)
161+        elif isinstance(settings.MIDDLEWARE_CLASSES, (dict)):
162+            self._load_middleware_from_dict(settings.MIDDLEWARE_CLASSES)
163+        else:
164+            raise exceptions.ImproperlyConfigured("MIDDLEWARE_CLASSES setting "
165+                    "has to be either a tuple, list or a dict.")
166+
167     def get_response(self, request):
168         "Returns an HttpResponse object for the given HttpRequest"
169-        from django.core import exceptions, urlresolvers
170+        from django.core import urlresolvers
171         from django.conf import settings
172 
173         # Apply request middleware
174@@ -171,6 +182,22 @@
175             response = func(request, response)
176         return response
177 
178+def validate_middleware(self):
179+    # A very rough attempt at validation, doesn't load the middleware.
180+    # Note that this assumes that if middleware has a process_foo method,
181+    # then it has to be registered in MIDDLEWARE_CLASSES['foo'].
182+    from django.conf import settings
183+    # if MIDDLEWARE_CLASSES is a list, no easy way to validate
184+    if isinstance(settings.MIDDLEWARE_CLASSES, (dict)):
185+        mw_dict = settings.MIDDLEWARE_CLASSES
186+        mw_instances = _middleware_instances_from_dict(mw_dict)
187+        for mw_path, mw_inst in mw_instances.items():
188+            for attr in ('request', 'view', 'response', 'exception'):
189+                if hasattr(mw_inst, attr) and mw_path not in mw_dict[attr]:
190+                    raise exceptions.ImproperlyConfigured("%s has to "
191+                            "be enabled for %s processing in "
192+                            "MIDDLEWARE_CLASSES setting." % (mw_path, attr))
193+
194 def get_script_name(environ):
195     """
196     Returns the equivalent of the HTTP request's SCRIPT_NAME environment
197@@ -195,3 +222,43 @@
198         return force_unicode(script_url[:-len(environ.get('PATH_INFO', ''))])
199     return force_unicode(environ.get('SCRIPT_NAME', u''))
200 
201+def _middleware_instance_from_path(middleware_path):
202+    try:
203+        dot = middleware_path.rindex('.')
204+    except ValueError:
205+        raise exceptions.ImproperlyConfigured, '%s isn\'t a middleware module' % middleware_path
206+    mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
207+    try:
208+        mod = __import__(mw_module, {}, {}, [''])
209+    except ImportError, e:
210+        raise exceptions.ImproperlyConfigured, 'Error importing middleware %s: "%s"' % (mw_module, e)
211+    try:
212+        mw_class = getattr(mod, mw_classname)
213+    except AttributeError:
214+        raise exceptions.ImproperlyConfigured, 'Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname)
215+    # I'm ignoring the MiddlewareNotUsed exception as it's not used in Django
216+    # according to grep, not documented and the only use case I can see is to
217+    # raise it in custom middleware __init__ to disable the middleware
218+    # temporarily. That can be better achieved with disabling it in settings
219+    # (where it is explicit and visible). If everybody is happy with that, the
220+    # exception should be removed from core.exceptions. See also #7820.
221+    return mw_class()
222+
223+def _particular_middleware_from_dicts(mw_type, mw_instance_dict, mw_dict):
224+    mw_instance_list = []
225+    for mw_path in mw_dict.get(mw_type, []):
226+        mw_instance = mw_instance_dict[mw_path]
227+        if not hasattr(mw_instance, 'process_' + mw_type):
228+            raise exceptions.ImproperlyConfigured("%s middleware needs "
229+                    "a 'process_%s' method, but it's missing from %s."
230+                    % (mw_type.title(), mw_type, mw_path))
231+        mw_instance_list.append(mw_instance)
232+    return mw_instance_list
233+
234+def _middleware_instances_from_dict(mw_dict):
235+    mw_instances = {}
236+    for mw_path_tuple in mw_dict.values():
237+        for mw_path in mw_path_tuple:
238+            if mw_path not in mw_instances:
239+                mw_instances[mw_path] = _middleware_instance_from_path(mw_path)
240+    return mw_instances