1 # -*- coding: utf-8 -*-
2 # This file is part of django-ssify, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See README.md for more information.
6 Middleware classes provide by django-ssify.
8 The main middleware you should use is SsiMiddleware. It's responsible
9 for providing the SSI variables needed for the SSI part of rendering.
11 If you're using django's UpdateCacheMiddleware, add
12 PrepareForCacheMiddleware *after it* also. It will add all the data
13 needed by SsiMiddleware to the response.
15 If you're using SessionMiddleware with LocaleMiddleware and your
16 USE_I18N or USE_L10N is True, you should also use the provided
17 LocaleMiddleware instead of the stock one.
19 And, last but not least, if using CsrfViewMiddleware, move it to the
20 top of MIDDLEWARE_CLASSES, even before SsiMiddleware, and use
21 `csrf_token` from `ssify` tags library in your templates, this way
22 your CSRF tokens will be set correctly.
24 So, you should end up with something like this:
26 MIDDLEWARE_CLASSES = [
27 'django.middleware.csrf.CsrfViewMiddleware',
28 'ssify.middleware.SsiMiddleware',
29 'django.middleware.cache.UpdateCacheMiddleware',
30 'ssify.middleware.PrepareForCacheMiddleware',
32 'ssify.middleware.LocaleMiddleware',
38 from __future__ import unicode_literals
39 from django.conf import settings
40 from django.middleware import locale
41 from django.utils.cache import patch_vary_headers
42 from .conf import conf
43 from .serializers import json_decode, json_encode
44 from .utils import ssi_vary_on_cookie
45 from .variables import SsiVariable, provide_vars
48 CACHE_HEADERS = ('Pragma', 'Cache-Control', 'Vary')
51 class PrepareForCacheMiddleware(object):
53 Patches the response object with all the data SsiMiddleware needs.
55 This should go after UpdateCacheMiddleware in MIDDLEWARE_CLASSES.
58 def process_response(request, response):
59 """Adds a 'X-Ssi-Vars-Needed' header to the response."""
60 if ('X-Ssi-Vars-Needed' not in response and
61 getattr(request, 'ssi_vars_needed', None)):
63 for (k, v) in request.ssi_vars_needed.items():
64 vars_needed[k] = v.definition
65 response['X-Ssi-Vars-Needed'] = json_encode(
66 vars_needed, sort_keys=True)
68 if ('X-ssi-restore' not in response and
69 getattr(request, 'ssi_patch_response', None)):
70 # We have some response modifiers set by ssi_includes and
71 # ssi_variables. Those are used, because unrendered SSI
72 # templates Django cache receives should have different
73 # caching headers, than pages rendered with request-specific
75 # What we do here is apply the modifiers, but restore
76 # previous values of any cache-relevant headers and set
77 # a custom header with modified values to set them
80 for field in CACHE_HEADERS:
81 original_fields[field] = response.get(field, None)
82 for modifier in request.ssi_patch_response:
85 for field in CACHE_HEADERS:
86 new_value = response.get(field, None)
87 if new_value != original_fields[field]:
88 restore_fields[field] = new_value
89 if original_fields[field] is None:
92 response[field] = original_fields[field]
93 response['X-ssi-restore'] = json_encode(restore_fields)
98 class SsiMiddleware(object):
100 The main django-ssify middleware.
102 It prepends the response content with SSI set statements,
103 providing values for any SSI variables used in the templates.
105 It also patches the Vary header with the values given by
108 If SSIFY_RENDER is set, it also passes the response through
109 SsiRenderMiddleware, which interprets and renders the SSI
110 statements, so you can see the output without an actual
111 SSI-enabled webserver.
114 def process_request(self, request):
115 request.ssi_patch_response = []
117 def process_view(self, request, view_func, view_args, view_kwargs):
118 request.ssi_vars_needed = {}
120 def _process_rendered_response(self, request, response):
121 if 'Content-Length' in response:
122 del response['Content-Length']
123 # Prepend the SSI variables.
124 if hasattr(request, 'ssi_vars_needed'):
125 vars_needed = request.ssi_vars_needed
126 elif 'X-Ssi-Vars-Needed' in response:
127 vars_needed = json_decode(response['X-Ssi-Vars-Needed'])
128 for k, v in vars_needed.items():
129 vars_needed[k] = SsiVariable(*v)
130 if not settings.DEBUG:
131 del response['X-Ssi-Vars-Needed']
136 response.content = provide_vars(request, vars_needed) + \
139 if 'X-ssi-restore' in response:
140 # The modifiers have already been applied to the response
141 # by the PrepareForCacheMiddleware.
142 # All we need to do is restore cache-relevant headers.
143 for header, content in json_decode(response['X-ssi-restore']).items():
147 response[header] = content
148 if not settings.DEBUG:
149 del response['X-ssi-restore']
151 for response_modifier in getattr(request, 'ssi_patch_response', []):
152 response_modifier(response)
154 def process_response(self, request, response):
155 if hasattr(response, 'render') and callable(response.render):
156 response.add_post_render_callback(
157 lambda r: self._process_rendered_response(request, r)
160 self._process_rendered_response(request, response)
163 from .middleware_debug import SsiRenderMiddleware
164 response = SsiRenderMiddleware().process_response(
170 class LocaleMiddleware(locale.LocaleMiddleware):
172 Version of the LocaleMiddleware for use together with the
173 SsiMiddleware if USE_I18N or USE_L10N is set.
175 Stock LocaleMiddleware looks for user language selection in
176 the session data and cookies, before it falls back to parsing
177 Accept-Language. The effect of accessing the session is adding
178 the `Vary: Cookie` header to the response. While this is correct
179 behaviour, it renders the cache system useless (see
180 https://code.djangoproject.com/ticket/13217).
182 This version of LocaleMiddleware doesn't mark the session
183 as accessed on every request, so SessionMiddleware doesn't add the
184 Vary: Cookie header (unless something else actually uses the session
185 in a meaningful way, of course). Instead, it tells SsiMiddleware
186 to add the Vary: Cookie header to the final response.
189 def process_request(self, request):
190 if hasattr(request, 'session'):
191 session_accessed_before = request.session.accessed
193 session_accessed_before = None
194 super(LocaleMiddleware, self).process_request(request)
195 if session_accessed_before is False:
196 if (request.session.accessed and
197 (settings.USE_I18N or settings.USE_L10N)):
198 request.session.accessed = False
199 if not hasattr(request, 'ssi_patch_response'):
200 request.ssi_patch_response = []
201 request.ssi_patch_response.append(ssi_vary_on_cookie)