Test on pypy.
[django-ssify.git] / ssify / templatetags / ssify.py
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.
4 #
5 from __future__ import absolute_import, unicode_literals
6 from django.conf import settings
7 from django.core.urlresolvers import NoReverseMatch, reverse, resolve
8 from django.middleware.csrf import get_token, _sanitize_token, rotate_token
9 from django import template
10 from django.utils.translation import get_language
11 from ssify.decorators import ssi_variable
12 from ssify.utils import ssi_vary_on_cookie
13 from ssify.variables import SsiVariable
14
15
16 register = template.Library()
17
18
19 @register.simple_tag(takes_context=True)
20 def ssi_include(context, name_, **kwargs):
21     """
22     Inserts an SSI include statement for an URL.
23
24     Works similarly to {% url %}, but only use keyword arguments are
25     supported.
26
27     In addition to just outputting the SSI include statement, it
28     remembers any request-info the included piece declares as needed.
29
30     """
31     b_kwargs = {'lang': get_language()}
32     subst = {}
33     num = 0
34     for k, value in kwargs.items():
35         if isinstance(value, SsiVariable):
36             numstr = '%04d' % num
37             b_kwargs[k] = numstr
38             subst[numstr] = value
39             num += 1
40         else:
41             b_kwargs[k] = value
42
43     try:
44         url = reverse(name_, kwargs=b_kwargs)
45     except NoReverseMatch:
46         b_kwargs.pop('lang')
47         url = reverse(name_, kwargs=b_kwargs)
48     view = resolve(url).func
49
50     for numstr, orig in subst.items():
51         url = url.replace(numstr, orig.as_var())
52     request = context['request']
53
54     # Remember the SSI vars the included view says it needs.
55     get_ssi_vars = getattr(view, 'get_ssi_vars', None)
56     if get_ssi_vars:
57         pass_vars = get_ssi_vars(**kwargs)
58         for var in pass_vars:
59             if not isinstance(var, SsiVariable):
60                 var = SsiVariable(*var)
61             if not hasattr(request, 'ssi_vars_needed'):
62                 request.ssi_vars_needed = {}
63             request.ssi_vars_needed[var.name] = var
64
65     # Remember the decorators to use on the including view.
66     patch_response = getattr(view, 'ssi_patch_response', None)
67     if patch_response:
68         if not hasattr(request, 'ssi_patch_response'):
69             request.ssi_patch_response = []
70         request.ssi_patch_response.extend(patch_response)
71
72     # Output the SSI include.
73     return "<!--#include file='%s'-->" % url
74
75
76 @ssi_variable(register, patch_response=[ssi_vary_on_cookie])
77 def get_csrf_token(request):
78     """
79     CsrfViewMiddleware.process_view is never called for cached
80     responses, and we still need to provide a CSRF token as an
81     ssi variable, we must make sure here that the CSRF token
82     is in request.META['CSRF_COOKIE'].
83
84     """
85     token = get_token(request)
86     if token:
87         # CSRF token is already in place, just return it.
88         return token
89
90     # Mimicking CsrfViewMiddleware.process_view.
91     try:
92         token = _sanitize_token(request.COOKIES[settings.CSRF_COOKIE_NAME])
93         request.META['CSRF_COOKIE'] = token
94     except KeyError:
95         # Create new CSRF token.
96         rotate_token(request)
97         token = get_token(request)
98
99     return token
100
101
102 @register.inclusion_tag('ssify/csrf_token.html', takes_context=True)
103 def ssi_csrf_token(context):
104     return {'request': context['request']}