Initial commit.
[django-ssify.git] / ssify / decorators.py
1 import functools
2 from inspect import getargspec
3 import warnings
4 from django.template.base import parse_bits
5 from django.utils.translation import get_language, activate
6 from .store import cache_include
7 from . import exceptions
8 from .variables import SsiVariable
9
10
11 def ssi_included(view=None, use_lang=True, get_ssi_vars=None):
12     """
13     Marks a view to be used as a snippet to be included with SSI.
14
15     If use_lang is True (which is default), the URL pattern for such
16     a view must provide a keyword argument named 'lang' for language.
17     SSI included views don't use language or content negotiation, so
18     everything they need to know has to be included in the URL.
19
20     get_ssi_vars should be a callable which takes the view's arguments
21     and returns the names of SSI variables it uses.
22
23     """
24     def dec(view):
25         @functools.wraps(view)
26         def new_view(request, *args, **kwargs):
27             if use_lang:
28                 try:
29                     lang = kwargs.pop('lang')
30                 except KeyError:
31                     raise exceptions.NoLangFieldError(request)
32                 current_lang = get_language()
33                 activate(lang)
34             response = view(request, *args, **kwargs)
35             if use_lang:
36                 activate(current_lang)
37             if response.status_code == 200:
38                 request._cache_update_cache = False
39
40                 def _check_included_vars(response):
41                     used_vars = request.ssi_vars_needed
42                     if get_ssi_vars:
43                         # Remove the ssi vars that should be provided
44                         # by the including view.
45                         pass_vars = set(get_ssi_vars(*args, **kwargs))
46
47                         for var in pass_vars:
48                             if not isinstance(var, SsiVariable):
49                                 var = SsiVariable(*var)
50                             try:
51                                 del used_vars[var.name]
52                             except KeyError:
53                                 warnings.warn(
54                                     exceptions.UnusedSsiVarsWarning(
55                                         request, var))
56                     if used_vars:
57                         raise exceptions.UndeclaredSsiVarsError(
58                             request, used_vars)
59                     request.ssi_vars_needed = {}
60
61                     # Don't use default django response caching for this view,
62                     # just save the contents instead.
63                     cache_include(request.path, response.content)
64
65                 if hasattr(response, 'render') and callable(response.render):
66                     response.add_post_render_callback(_check_included_vars)
67                 else:
68                     _check_included_vars(response)
69
70             return response
71
72         # Remember get_ssi_vars so that in can be computed from args/kwargs
73         # by including view.
74         new_view.get_ssi_vars = get_ssi_vars
75         return new_view
76     return dec(view) if view else dec
77
78
79 def ssi_variable(register, vary=None, name=None):
80     # Cache control?
81     def dec(func):
82         # Find own path.
83         function_name = (name or
84                          getattr(func, '_decorated_function', func).__name__)
85         lib_name = func.__module__.rsplit('.', 1)[-1]
86         tagpath = "%s.%s" % (lib_name, function_name)
87         # Make sure the function takes request parameter.
88         params, varargs, varkw, defaults = getargspec(func)
89         assert params and params[0] == 'request', '%s is decorated with '\
90             'request_info_tag, so it must take `request` for '\
91             'its first argument.' % (tagpath)
92
93         @register.tag(name=function_name)
94         def _ssi_var_tag(parser, token):
95             """
96             Creates a SSI variable reference for a request-dependent info.
97
98             Use as:
99                 {% ri_tag args... %}
100             or:
101                 {% ri_tag args... as variable %}
102                 {{ variable.if }}
103                     {{ variable }}, or
104                     {% ssi_include 'some-snippet' variable %}
105                 {{ variable.else }}
106                     Default text
107                 {{ variable.endif }}
108
109             """
110             bits = token.split_contents()[1:]
111
112             # Is it the 'as' form?
113             if len(bits) >= 2 and bits[-2] == 'as':
114                 asvar = bits[-1]
115                 bits = bits[:-2]
116             else:
117                 asvar = None
118
119             # Parse the arguments like Django's generic tags do.
120             args, kwargs = parse_bits(parser, bits,
121                                       ['context'] + params[1:], varargs, varkw,
122                                       defaults, takes_context=True,
123                                       name=function_name)
124             return SsiVariableNode(tagpath, args, kwargs, vary, asvar)
125         _ssi_var_tag.get_value = func
126         #return _ssi_var_tag
127         return func
128
129     return dec
130
131
132 from .variables import SsiVariableNode