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