Django

Code

root/django/trunk/django/template/defaulttags.py

Revision 8716, 38.7 kB (checked in by jacob, 1 month ago)

Merge branch 'url-tag-asvar'

  • Property svn:eol-style set to native
  • Property svn:keywords set to LastChangedRevision
Line 
1 """Default tags used by the template system, available to all templates."""
2
3 import sys
4 import re
5 from itertools import cycle as itertools_cycle
6 try:
7     reversed
8 except NameError:
9     from django.utils.itercompat import reversed     # Python 2.3 fallback
10
11 from django.template import Node, NodeList, Template, Context, Variable
12 from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
13 from django.template import get_library, Library, InvalidTemplateLibrary
14 from django.conf import settings
15 from django.utils.encoding import smart_str, smart_unicode
16 from django.utils.itercompat import groupby
17 from django.utils.safestring import mark_safe
18
19 register = Library()
20
21 class AutoEscapeControlNode(Node):
22     """Implements the actions of the autoescape tag."""
23     def __init__(self, setting, nodelist):
24         self.setting, self.nodelist = setting, nodelist
25
26     def render(self, context):
27         old_setting = context.autoescape
28         context.autoescape = self.setting
29         output = self.nodelist.render(context)
30         context.autoescape = old_setting
31         if self.setting:
32             return mark_safe(output)
33         else:
34             return output
35
36 class CommentNode(Node):
37     def render(self, context):
38         return ''
39
40 class CycleNode(Node):
41     def __init__(self, cyclevars, variable_name=None):
42         self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
43         self.variable_name = variable_name
44
45     def render(self, context):
46         value = self.cycle_iter.next().resolve(context)
47         if self.variable_name:
48             context[self.variable_name] = value
49         return value
50
51 class DebugNode(Node):
52     def render(self, context):
53         from pprint import pformat
54         output = [pformat(val) for val in context]
55         output.append('\n\n')
56         output.append(pformat(sys.modules))
57         return ''.join(output)
58
59 class FilterNode(Node):
60     def __init__(self, filter_expr, nodelist):
61         self.filter_expr, self.nodelist = filter_expr, nodelist
62
63     def render(self, context):
64         output = self.nodelist.render(context)
65         # Apply filters.
66         context.update({'var': output})
67         filtered = self.filter_expr.resolve(context)
68         context.pop()
69         return filtered
70
71 class FirstOfNode(Node):
72     def __init__(self, vars):
73         self.vars = map(Variable, vars)
74
75     def render(self, context):
76         for var in self.vars:
77             try:
78                 value = var.resolve(context)
79             except VariableDoesNotExist:
80                 continue
81             if value:
82                 return smart_unicode(value)
83         return u''
84
85 class ForNode(Node):
86     def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
87         self.loopvars, self.sequence = loopvars, sequence
88         self.is_reversed = is_reversed
89         self.nodelist_loop = nodelist_loop
90
91     def __repr__(self):
92         reversed_text = self.is_reversed and ' reversed' or ''
93         return "<For Node: for %s in %s, tail_len: %d%s>" % \
94             (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
95              reversed_text)
96
97     def __iter__(self):
98         for node in self.nodelist_loop:
99             yield node
100
101     def get_nodes_by_type(self, nodetype):
102         nodes = []
103         if isinstance(self, nodetype):
104             nodes.append(self)
105         nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
106         return nodes
107
108     def render(self, context):
109         nodelist = NodeList()
110         if 'forloop' in context:
111             parentloop = context['forloop']
112         else:
113             parentloop = {}
114         context.push()
115         try:
116             values = self.sequence.resolve(context, True)
117         except VariableDoesNotExist:
118             values = []
119         if values is None:
120             values = []
121         if not hasattr(values, '__len__'):
122             values = list(values)
123         len_values = len(values)
124         if self.is_reversed:
125             values = reversed(values)
126         unpack = len(self.loopvars) > 1
127         # Create a forloop value in the context.  We'll update counters on each
128         # iteration just below.
129         loop_dict = context['forloop'] = {'parentloop': parentloop}
130         for i, item in enumerate(values):
131             # Shortcuts for current loop iteration number.
132             loop_dict['counter0'] = i
133             loop_dict['counter'] = i+1
134             # Reverse counter iteration numbers.
135             loop_dict['revcounter'] = len_values - i
136             loop_dict['revcounter0'] = len_values - i - 1
137             # Boolean values designating first and last times through loop.
138             loop_dict['first'] = (i == 0)
139             loop_dict['last'] = (i == len_values - 1)
140
141             if unpack:
142                 # If there are multiple loop variables, unpack the item into
143                 # them.
144                 context.update(dict(zip(self.loopvars, item)))
145             else:
146                 context[self.loopvars[0]] = item
147             for node in self.nodelist_loop:
148                 nodelist.append(node.render(context))
149             if unpack:
150                 # The loop variables were pushed on to the context so pop them
151                 # off again. This is necessary because the tag lets the length
152                 # of loopvars differ to the length of each set of items and we
153                 # don't want to leave any vars from the previous loop on the
154                 # context.
155                 context.pop()
156         context.pop()
157         return nodelist.render(context)
158
159 class IfChangedNode(Node):
160     def __init__(self, nodelist_true, nodelist_false, *varlist):
161         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
162         self._last_seen = None
163         self._varlist = map(Variable, varlist)
164         self._id = str(id(self))
165
166     def render(self, context):
167         if 'forloop' in context and self._id not in context['forloop']:
168             self._last_seen = None
169             context['forloop'][self._id] = 1
170         try:
171             if self._varlist:
172                 # Consider multiple parameters.  This automatically behaves
173                 # like an OR evaluation of the multiple variables.
174                 compare_to = [var.resolve(context) for var in self._varlist]
175             else:
176                 compare_to = self.nodelist_true.render(context)
177         except VariableDoesNotExist:
178             compare_to = None
179
180         if compare_to != self._last_seen:
181             firstloop = (self._last_seen == None)
182             self._last_seen = compare_to
183             context.push()
184             context['ifchanged'] = {'firstloop': firstloop}
185             content = self.nodelist_true.render(context)
186             context.pop()
187             return content
188         elif self.nodelist_false:
189             return self.nodelist_false.render(context)
190         return ''
191
192 class IfEqualNode(Node):
193     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
194         self.var1, self.var2 = Variable(var1), Variable(var2)
195         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
196         self.negate = negate
197
198     def __repr__(self):
199         return "<IfEqualNode>"
200
201     def render(self, context):
202         try:
203             val1 = self.var1.resolve(context)
204         except VariableDoesNotExist:
205             val1 = None
206         try:
207             val2 = self.var2.resolve(context)
208         except VariableDoesNotExist:
209             val2 = None
210         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
211             return self.nodelist_true.render(context)
212         return self.nodelist_false.render(context)
213
214 class IfNode(Node):
215     def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
216         self.bool_exprs = bool_exprs
217         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
218         self.link_type = link_type
219
220     def __repr__(self):
221         return "<If node>"
222
223     def __iter__(self):
224         for node in self.nodelist_true:
225             yield node
226         for node in self.nodelist_false:
227             yield node
228
229     def get_nodes_by_type(self, nodetype):
230         nodes = []
231         if isinstance(self, nodetype):
232             nodes.append(self)
233         nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
234         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
235         return nodes
236
237     def render(self, context):
238         if self.link_type == IfNode.LinkTypes.or_:
239             for ifnot, bool_expr in self.bool_exprs:
240                 try:
241                     value = bool_expr.resolve(context, True)
242                 except VariableDoesNotExist:
243                     value = None
244                 if (value and not ifnot) or (ifnot and not value):
245                     return self.nodelist_true.render(context)
246             return self.nodelist_false.render(context)
247         else:
248             for ifnot, bool_expr in self.bool_exprs:
249                 try:
250                     value = bool_expr.resolve(context, True)
251                 except VariableDoesNotExist:
252                     value = None
253                 if not ((value and not ifnot) or (ifnot and not value)):
254                     return self.nodelist_false.render(context)
255             return self.nodelist_true.render(context)
256
257     class LinkTypes:
258         and_ = 0,
259         or_ = 1
260
261 class RegroupNode(Node):
262     def __init__(self, target, expression, var_name):
263         self.target, self.expression = target, expression
264         self.var_name = var_name
265
266     def render(self, context):
267         obj_list = self.target.resolve(context, True)
268         if obj_list == None:
269             # target variable wasn't found in context; fail silently.
270             context[self.var_name] = []
271             return ''
272         # List of dictionaries in the format:
273         # {'grouper': 'key', 'list': [list of contents]}.
274         context[self.var_name] = [
275             {'grouper': key, 'list': list(val)}
276             for key, val in
277             groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
278         ]
279         return ''
280
281 def include_is_allowed(filepath):
282     for root in settings.ALLOWED_INCLUDE_ROOTS:
283         if filepath.startswith(root):
284             return True
285     return False
286
287 class SsiNode(Node):
288     def __init__(self, filepath, parsed):
289         self.filepath, self.parsed = filepath, parsed
290
291     def render(self, context):
292         if not include_is_allowed(self.filepath):
293             if settings.DEBUG:
294                 return "[Didn't have permission to include file]"
295             else:
296                 return '' # Fail silently for invalid includes.
297         try:
298             fp = open(self.filepath, 'r')
299             output = fp.read()
300             fp.close()
301         except IOError:
302             output = ''
303         if self.parsed:
304             try:
305                 t = Template(output, name=self.filepath)
306                 return t.render(context)
307             except TemplateSyntaxError, e:
308                 if settings.DEBUG:
309                     return "[Included template had syntax error: %s]" % e
310                 else:
311                     return '' # Fail silently for invalid included templates.
312         return output
313
314 class LoadNode(Node):
315     def render(self, context):
316         return ''
317
318 class NowNode(Node):
319     def __init__(self, format_string):
320         self.format_string = format_string
321
322     def render(self, context):
323         from datetime import datetime
324         from django.utils.dateformat import DateFormat
325         df = DateFormat(datetime.now())
326         return df.format(self.format_string)
327
328 class SpacelessNode(Node):
329     def __init__(self, nodelist):
330         self.nodelist = nodelist
331
332     def render(self, context):
333         from django.utils.html import strip_spaces_between_tags
334         return strip_spaces_between_tags(self.nodelist.render(context).strip())
335
336 class TemplateTagNode(Node):
337     mapping = {'openblock': BLOCK_TAG_START,
338                'closeblock': BLOCK_TAG_END,
339                'openvariable': VARIABLE_TAG_START,
340                'closevariable': VARIABLE_TAG_END,
341                'openbrace': SINGLE_BRACE_START,
342                'closebrace': SINGLE_BRACE_END,
343                'opencomment': COMMENT_TAG_START,
344                'closecomment': COMMENT_TAG_END,
345                }
346
347     def __init__(self, tagtype):
348         self.tagtype = tagtype
349
350     def render(self, context):
351         return self.mapping.get(self.tagtype, '')
352
353 class URLNode(Node):
354     def __init__(self, view_name, args, kwargs, asvar):
355         self.view_name = view_name
356         self.args = args
357         self.kwargs = kwargs
358         self.asvar = asvar
359
360     def render(self, context):
361         from django.core.urlresolvers import reverse, NoReverseMatch
362         args = [arg.resolve(context) for arg in self.args]
363         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
364                        for k, v in self.kwargs.items()])
365        
366        
367         # Try to look up the URL twice: once given the view name, and again
368         # relative to what we guess is the "main" app. If they both fail,
369         # re-raise the NoReverseMatch unless we're using the
370         # {% url ... as var %} construct in which cause return nothing.
371         url = ''
372         try:
373             url = reverse(self.view_name, args=args, kwargs=kwargs)
374         except NoReverseMatch:
375             project_name = settings.SETTINGS_MODULE.split('.')[0]
376             try:
377                 url = reverse(project_name + '.' + self.view_name,
378                               args=args, kwargs=kwargs)
379             except NoReverseMatch:
380                 if self.asvar is None:
381                     raise
382                    
383         if self.asvar:
384             context[self.asvar] = url
385             return ''
386         else:
387             return url
388
389 class WidthRatioNode(Node):
390     def __init__(self, val_expr, max_expr, max_width):
391         self.val_expr = val_expr
392         self.max_expr = max_expr
393         self.max_width = max_width
394
395     def render(self, context):
396         try:
397             value = self.val_expr.resolve(context)
398             maxvalue = self.max_expr.resolve(context)
399         except VariableDoesNotExist:
400             return ''
401         try:
402             value = float(value)
403             maxvalue = float(maxvalue)
404             ratio = (value / maxvalue) * int(self.max_width)
405         except (ValueError, ZeroDivisionError):
406             return ''
407         return str(int(round(ratio)))
408
409 class WithNode(Node):
410     def __init__(self, var, name, nodelist):
411         self.var = var
412         self.name = name
413         self.nodelist = nodelist
414
415     def __repr__(self):
416         return "<WithNode>"
417
418     def render(self, context):
419         val = self.var.resolve(context)
420         context.push()
421         context[self.name] = val
422         output = self.nodelist.render(context)
423         context.pop()
424         return output
425
426 #@register.tag
427 def autoescape(parser, token):
428     """
429     Force autoescape behaviour for this block.
430     """
431     args = token.contents.split()
432     if len(args) != 2:
433         raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
434     arg = args[1]
435     if arg not in (u'on', u'off'):
436         raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
437     nodelist = parser.parse(('endautoescape',))
438     parser.delete_first_token()
439     return AutoEscapeControlNode((arg == 'on'), nodelist)
440 autoescape = register.tag(autoescape)
441
442 #@register.tag
443 def comment(parser, token):
444     """
445     Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
446     """
447     parser.skip_past('endcomment')
448     return CommentNode()
449 comment = register.tag(comment)
450
451 #@register.tag
452 def cycle(parser, token):
453     """
454     Cycles among the given strings each time this tag is encountered.
455
456     Within a loop, cycles among the given strings each time through
457     the loop::
458
459         {% for o in some_list %}
460             <tr class="{% cycle 'row1' 'row2' %}">
461                 ...
462             </tr>
463         {% endfor %}
464
465     Outside of a loop, give the values a unique name the first time you call
466     it, then use that name each sucessive time through::
467
468             <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
469             <tr class="{% cycle rowcolors %}">...</tr>
470             <tr class="{% cycle rowcolors %}">...</tr>
471
472     You can use any number of values, separated by spaces. Commas can also
473     be used to separate values; if a comma is used, the cycle values are
474     interpreted as literal strings.
475     """
476
477     # Note: This returns the exact same node on each {% cycle name %} call;
478     # that is, the node object returned from {% cycle a b c as name %} and the
479     # one returned from {% cycle name %} are the exact same object. This
480     # shouldn't cause problems (heh), but if it does, now you know.
481     #
482     # Ugly hack warning: This stuffs the named template dict into parser so
483     # that names are only unique within each template (as opposed to using
484     # a global variable, which would make cycle names have to be unique across
485     # *all* templates.
486
487     args = token.split_contents()
488
489     if len(args) < 2:
490         raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
491
492     if ',' in args[1]:
493         # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
494         # case.
495         args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
496
497     if len(args) == 2:
498         # {% cycle foo %} case.
499         name = args[1]
500         if not hasattr(parser, '_namedCycleNodes'):
501             raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
502         if not name in parser._namedCycleNodes:
503             raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
504         return parser._namedCycleNodes[name]
505
506     if len(args) > 4 and args[-2] == 'as':
507         name = args[-1]
508         node = CycleNode(args[1:-2], name)
509         if not hasattr(parser, '_namedCycleNodes'):
510             parser._namedCycleNodes = {}
511         parser._namedCycleNodes[name] = node
512     else:
513         node = CycleNode(args[1:])
514     return node
515 cycle = register.tag(cycle)
516
517 def debug(parser, token):
518     """
519     Outputs a whole load of debugging information, including the current
520     context and imported modules.
521
522     Sample usage::
523
524         <pre>
525             {% debug %}