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