2 Utilities for defining SSI variables.
4 SSI variables are a way of providing values that need to be computed
5 at request time to the prerendered templates.
8 from hashlib import md5
9 from django import template
10 from django.utils.encoding import force_unicode
11 from django.utils.functional import Promise
12 from django.utils.safestring import mark_safe
13 from .exceptions import SsiVarsDependencyCycleError, UndeclaredSsiRefError
16 class SsiVariable(object):
18 Represents a variable computed by a template tag with given arguments.
20 Instance of this class is returned from any template tag created
21 with `decorators.ssi_variable` decorator. If renders as SSI echo
22 statement, but you can also use it as an argument to {% ssi_include %},
23 to other ssi_variable, or create SSI if statements by using
24 its `if`, `else`, `endif` properties.
26 Variable's name, as used in SSI statements, is a hash of its definition,
27 so the user never has to deal with it directly.
30 ret_types = 'bool', 'int', 'unicode'
32 def __init__(self, tagpath=None, args=None, kwargs=None, name=None):
33 self.tagpath = tagpath
34 self.args = list(args or [])
35 self.kwargs = kwargs or {}
40 """Variable name is a hash of its definition."""
41 if self._name is None:
42 self._name = 'v' + md5(json_encode(self.definition)).hexdigest()
47 Sometimes there's a need to reset the variable name.
49 Typically, this is the case after finding real values for
50 variables passed as arguments to {% ssi_include %}.
57 """Variable is defined by path to template tag and its arguments."""
59 return self.tagpath, self.args, self.kwargs
61 return self.tagpath, self.args
66 return "SsiVariable(%s: %s)" % (self.name, repr(self.definition))
68 def get_value(self, request):
69 """Computes the real value of the variable, using the request."""
70 taglib, tagname = self.tagpath.rsplit('.', 1)
71 return template.get_library(taglib).tags[tagname].get_value(
72 request, *self.args, **self.kwargs)
74 def __unicode__(self):
75 return mark_safe("<!--#echo var='%s' encoding='none'-->" % self.name)
78 """Returns the form that can be used in SSI include's URL."""
79 return '${%s}' % self.name
81 # If-else-endif properties for use in templates.
82 setattr(SsiVariable, 'if',
83 lambda self: mark_safe("<!--#if expr='${%s}'-->" % self.name))
84 setattr(SsiVariable, 'else',
85 staticmethod(lambda: mark_safe("<!--#else-->")))
86 setattr(SsiVariable, 'endif',
87 staticmethod(lambda: mark_safe('<!--#endif-->')))
90 class SsiExpect(object):
91 """This class says: I want the real value of this variable here."""
92 def __init__(self, name):
96 def ssi_expect(var, type_):
98 Helper function for defining get_ssi_vars on ssi_included views.
100 The view needs a way of calculating all the needed variables from
101 the view args. But the args are probably the wrong type
102 (typically, str instead of int) or even are SsiVariables, not
103 resolved until request time.
105 This function provides a way to expect a real value of the needed type.
108 if isinstance(var, SsiVariable):
109 return SsiExpect(var.name)
114 class SsiVariableNode(template.Node):
115 """ Node for the SsiVariable tags. """
116 def __init__(self, tagpath, args, kwargs, vary=None, asvar=None):
117 self.tagpath = tagpath
124 return "<SsiVariableNode>"
126 def render(self, context):
127 """Renders the tag as SSI echo or sets the context variable."""
128 resolved_args = [var.resolve(context) for var in self.args]
129 resolved_kwargs = dict((k, v.resolve(context))
130 for k, v in self.kwargs.items())
131 var = SsiVariable(self.tagpath, resolved_args, resolved_kwargs)
133 request = context['request']
134 request.ssi_vars_needed[var.name] = var
136 request.ssi_vary.update(self.vary)
139 context.dicts[0][self.asvar] = var
145 def ssi_set_statement(var, value):
146 """Generates an SSI set statement for a variable."""
147 if isinstance(value, Promise):
148 # Yes, this is quite brutal. But we need to know
149 # the real value now, we don't know the type,
150 # and we only want to evaluate the lazy function once.
151 value = value._proxy____cast()
152 if value is False or value is None:
154 return "<!--#set var='%s' value='%s'-->" % (
156 force_unicode(value).replace(u'\\', u'\\\\').replace(u"'", u"\\'"))
159 def provide_vars(request, ssi_vars):
161 Provides all the SSI set statements for ssi_vars variables.
163 The main purpose of this function is to by called by SsifyMiddleware.
166 queue = ssi_vars.items()
167 unresolved_streak = 0
169 var_name, var = queue.pop(0)
174 for i, arg in enumerate(var.args):
175 if isinstance(arg, SsiExpect):
176 var.args[i] = resolved[arg.name]
178 for k, arg in var.kwargs.items():
179 if isinstance(arg, SsiExpect):
180 var.args[k] = resolved[arg.name]
184 # Rehash after calculating the SsiExpects with real
185 # values, because that's what the included views expect.
186 new_name = var.rehash()
188 for i, arg in enumerate(var.args):
189 if isinstance(arg, SsiVariable):
190 var.args[i] = resolved[arg.name]
191 for k, arg in var.kwargs.items():
192 if isinstance(arg, SsiVariable):
193 var.args[k] = resolved[arg.name]
196 queue.append((var_name, var))
197 unresolved_streak += 1
198 if unresolved_streak == len(queue):
199 if arg.name in ssi_vars:
200 raise SsiVarsDependencyCycleError(queue)
202 raise UndeclaredSsiRefError(request, var, arg.name)
205 resolved[new_name] = var.get_value(request)
206 unresolved_streak = 0
208 output = u"".join(ssi_set_statement(var, value)
209 for (var, value) in resolved.items()
214 from .serializers import json_encode