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 Defines decorators for use in ssify-enabled projects.
8 from __future__ import unicode_literals
10 from inspect import getargspec
12 from django.template.base import parse_bits
13 from django.utils.translation import get_language, activate
14 from .cache import cache_include, DEFAULT_TIMEOUT
15 from . import exceptions
16 from .variables import SsiVariable
19 def ssi_included(view=None, use_lang=True,
20 timeout=DEFAULT_TIMEOUT, version=None,
21 get_ssi_vars=None, patch_response=None):
23 Marks a view to be used as a snippet to be included with SSI.
25 If use_lang is True (which is default), the URL pattern for such
26 a view must provide a keyword argument named 'lang' for language.
27 SSI included views don't use language or content negotiation, so
28 everything they need to know has to be included in the URL.
30 get_ssi_vars should be a callable which takes the view's arguments
31 and returns the names of SSI variables it uses.
35 @functools.wraps(view)
36 def new_view(request, *args, **kwargs):
39 lang = kwargs.pop('lang')
41 raise exceptions.NoLangFieldError(request)
42 current_lang = get_language()
44 request.LANGUAGE_CODE = lang
45 response = view(request, *args, **kwargs)
47 activate(current_lang)
48 if response.status_code == 200:
49 # We don't want this view to be cached in
50 # UpdateCacheMiddleware. We'll just cache the contents
51 # ourselves, and point the webserver to use this cache.
52 request._cache_update_cache = False
54 def _check_included_vars(response):
55 used_vars = request.ssi_vars_needed
57 # Remove the ssi vars that should be provided
58 # by the including view.
59 pass_vars = get_ssi_vars(*args, **kwargs)
62 if not isinstance(var, SsiVariable):
63 var = SsiVariable(*var)
65 del used_vars[var.name]
68 exceptions.UnusedSsiVarsWarning(
71 raise exceptions.UndeclaredSsiVarsError(
73 request.ssi_vars_needed = {}
75 # Don't use default django response caching for this view,
76 # just save the contents instead.
77 cache_include(request.path, response.content,
78 timeout=timeout, version=version)
80 if hasattr(response, 'render') and callable(response.render):
81 response.add_post_render_callback(_check_included_vars)
83 _check_included_vars(response)
87 # Remember get_ssi_vars so that in can be computed from args/kwargs
89 new_view.get_ssi_vars = get_ssi_vars
90 new_view.ssi_patch_response = patch_response
92 return dec(view) if view else dec
95 def ssi_variable(register, name=None, patch_response=None):
97 Creates a template tag representing an SSI variable from a function.
99 The function must take 'request' as its first argument.
100 It may take other arguments, which should be provided when using
108 function_name = (name or
109 getattr(func, '_decorated_function', func).__name__)
110 lib_name = func.__module__.rsplit('.', 1)[-1]
111 tagpath = "%s.%s" % (lib_name, function_name)
112 # Make sure the function takes request parameter.
113 params, varargs, varkw, defaults = getargspec(func)
114 assert params and params[0] == 'request', '%s is decorated with '\
115 'request_info_tag, so it must take `request` for '\
116 'its first argument.' % (tagpath)
118 @register.tag(name=function_name)
119 def _ssi_var_tag(parser, token):
121 Creates a SSI variable reference for a request-dependent info.
126 {% ri_tag args... as variable %}
129 {% ssi_include 'some-snippet' variable %}
135 bits = token.split_contents()[1:]
137 # Is it the 'as' form?
138 if len(bits) >= 2 and bits[-2] == 'as':
144 # Parse the arguments like Django's generic tags do.
145 args, kwargs = parse_bits(parser, bits,
146 ['context'] + params[1:], varargs, varkw,
147 defaults, takes_context=True,
149 return SsiVariableNode(tagpath, args, kwargs, patch_response, asvar)
150 _ssi_var_tag.get_value = func
157 from .variables import SsiVariableNode