Initial commit.
[django-ssify.git] / ssify / middleware.py
1 from django.conf import settings
2 from django.utils.cache import patch_vary_headers
3 from django.middleware import locale
4 from .serializers import json_decode, json_encode
5 from .variables import SsiVariable, provide_vars
6 from . import DEBUG
7
8
9 class PrepareForCacheMiddleware(object):
10     @staticmethod
11     def process_response(request, response):
12         if getattr(request, 'ssi_vars_needed', None):
13             vars_needed = {k: v.definition
14                            for (k, v) in request.ssi_vars_needed.items()}
15             response['X-Ssi-Vars-Needed'] = json_encode(
16                 vars_needed, sort_keys=True)
17         return response
18
19
20 class SsiMiddleware(object):
21     def process_request(self, request):
22         request.ssi_vary = set()
23         #request.ssi_cache_control_after = set()
24
25     def process_view(self, request, view_func, view_args, view_kwargs):
26         request.ssi_vars_needed = {}
27
28     def _process_rendered_response(self, request, response):
29         # Prepend the SSI variables.
30         if hasattr(request, 'ssi_vars_needed'):
31             vars_needed = request.ssi_vars_needed
32         else:
33             vars_needed = json_decode(response.get('X-Ssi-Vars-Needed', '{}'))
34             vars_needed = {k: SsiVariable(*v)
35                            for (k, v) in vars_needed.items()}
36
37         if vars_needed:
38             response.content = provide_vars(request, vars_needed) + \
39                 response.content
40
41         # Add the Vary headers declared by all the SSI vars.
42         patch_vary_headers(response, sorted(request.ssi_vary))
43         # TODO: cache control?
44
45         # With a cached response, CsrfViewMiddleware.process_response
46         # was never called, so if we used the csrf token, we must do
47         # its job of setting the csrf token cookie on our own.
48         if (not getattr(request, 'csrf_processing_done', False)
49                 and request.META.get("CSRF_COOKIE_USED", False)):
50             response.set_cookie(settings.CSRF_COOKIE_NAME,
51                                 request.META["CSRF_COOKIE"],
52                                 max_age=getattr(settings, 'CSRF_COOKIE_AGE',
53                                                 60 * 60 * 24 * 7 * 52),
54                                 domain=settings.CSRF_COOKIE_DOMAIN,
55                                 path=settings.CSRF_COOKIE_PATH,
56                                 secure=settings.CSRF_COOKIE_SECURE,
57                                 httponly=settings.CSRF_COOKIE_HTTPONLY
58                                 )
59             request.csrf_processing_done = True
60
61     def process_response(self, request, response):
62         if hasattr(response, 'render') and callable(response.render):
63             response.add_post_render_callback(
64                 lambda r: self._process_rendered_response(request, r)
65             )
66         else:
67             self._process_rendered_response(request, response)
68
69         if DEBUG:
70             from .middleware_debug import DebugUnSsiMiddleware
71             response = DebugUnSsiMiddleware().process_response(
72                 request, response)
73
74         return response
75
76
77 class LocaleMiddleware(locale.LocaleMiddleware):
78     """
79     Version of the LocaleMiddleware for use together with the
80     SsiMiddleware if USE_I18N or USE_L10N is set.
81
82     Stock LocaleMiddleware looks for user language selection in
83     the session data and cookies, before it falls back to parsing
84     Accept-Language. The effect of accessing the session is adding
85     the `Vary: Cookie` header to the response.  While this is correct
86     behaviour, it renders the cache system useless (see
87     https://code.djangoproject.com/ticket/13217).
88
89     This version of LocaleMiddleware doesn't mark the session
90     as accessed on every request, so SessionMiddleware doesn't add the
91     Vary: Cookie header (unless something else actually uses the session
92     in a meaningful way, of course). Instead, it tells SsiMiddleware
93     to add the Vary: Cookie header to the final response.
94
95     """
96     def process_request(self, request):
97         if hasattr(request, 'session'):
98             session_accessed_before = request.session.accessed
99         else:
100             session_accessed_before = None
101         super(LocaleMiddleware, self).process_request(request)
102         if session_accessed_before is False:
103             if (request.session.accessed and
104                     (settings.USE_I18N or settings.USE_L10N)):
105                 request.session.accessed = False
106                 request.ssi_vary.add('Cookie')