Django

Code

root/django/branches/newforms-admin/django/template/defaulttags.py

Revision 7770, 37.5 kB (checked in by brosner, 5 months ago)

newforms-admin: Merged from trunk up to [7766].

  • 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, *varlist):
161         self.nodelist = nodelist
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.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.render(context)
186             context.pop()
187             return content
188         else:
189             return ''
190
191 class IfEqualNode(Node):
192     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
193         self.var1, self.var2 = Variable(var1), Variable(var2)
194         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
195         self.negate = negate
196
197     def __repr__(self):
198         return "<IfEqualNode>"
199
200     def render(self, context):
201         try:
202             val1 = self.var1.resolve(context)
203         except VariableDoesNotExist:
204             val1 = None
205         try:
206             val2 = self.var2.resolve(context)
207         except VariableDoesNotExist:
208             val2 = None
209         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
210             return self.nodelist_true.render(context)
211         return self.nodelist_false.render(context)
212
213 class IfNode(Node):
214     def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
215         self.bool_exprs = bool_exprs
216         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
217         self.link_type = link_type
218
219     def __repr__(self):
220         return "<If node>"
221
222     def __iter__(self):
223         for node in self.nodelist_true:
224             yield node
225         for node in self.nodelist_false:
226             yield node
227
228     def get_nodes_by_type(self, nodetype):
229         nodes = []
230         if isinstance(self, nodetype):
231             nodes.append(self)
232         nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
233         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
234         return nodes
235
236     def render(self, context):
237         if self.link_type == IfNode.LinkTypes.or_:
238             for ifnot, bool_expr in self.bool_exprs:
239                 try:
240                     value = bool_expr.resolve(context, True)
241                 except VariableDoesNotExist:
242                     value = None
243                 if (value and not ifnot) or (ifnot and not value):
244                     return self.nodelist_true.render(context)
245             return self.nodelist_false.render(context)
246         else:
247             for ifnot, bool_expr in self.bool_exprs:
248                 try:
249                     value = bool_expr.resolve(context, True)
250                 except VariableDoesNotExist:
251                     value = None
252                 if not ((value and not ifnot) or (ifnot and not value)):
253                     return self.nodelist_false.render(context)
254             return self.nodelist_true.render(context)
255
256     class LinkTypes:
257         and_ = 0,
258         or_ = 1
259
260 class RegroupNode(Node):
261     def __init__(self, target, expression, var_name):
262         self.target, self.expression = target, expression
263         self.var_name = var_name
264
265     def render(self, context):
266         obj_list = self.target.resolve(context, True)
267         if obj_list == None:
268             # target variable wasn't found in context; fail silently.
269             context[self.var_name] = []
270             return ''
271         # List of dictionaries in the format:
272         # {'grouper': 'key', 'list': [list of contents]}.
273         context[self.var_name] = [
274             {'grouper': key, 'list': list(val)}
275             for key, val in
276             groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
277         ]
278         return ''
279
280 def include_is_allowed(filepath):
281     for root in settings.ALLOWED_INCLUDE_ROOTS:
282         if filepath.startswith(root):
283             return True
284     return False
285
286 class SsiNode(Node):
287     def __init__(self, filepath, parsed):
288         self.filepath, self.parsed = filepath, parsed
289
290     def render(self, context):
291         if not include_is_allowed(self.filepath):
292             if settings.DEBUG:
293                 return "[Didn't have permission to include file]"
294             else:
295                 return '' # Fail silently for invalid includes.
296         try:
297             fp = open(self.filepath, 'r')
298             output = fp.read()
299             fp.close()
300         except IOError:
301             output = ''
302         if self.parsed:
303             try:
304                 t = Template(output, name=self.filepath)
305                 return t.render(context)
306             except TemplateSyntaxError, e:
307                 if settings.DEBUG:
308                     return "[Included template had syntax error: %s]" % e
309                 else:
310                     return '' # Fail silently for invalid included templates.
311         return output
312
313 class LoadNode(Node):
314     def render(self, context):
315         return ''
316
317 class NowNode(Node):
318     def __init__(self, format_string):
319         self.format_string = format_string
320
321     def render(self, context):
322         from datetime import datetime
323         from django.utils.dateformat import DateFormat
324         df = DateFormat(datetime.now())
325         return df.format(self.format_string)
326
327 class SpacelessNode(Node):
328     def __init__(self, nodelist):
329         self.nodelist = nodelist
330
331     def render(self, context):
332         from django.utils.html import strip_spaces_between_tags
333         return strip_spaces_between_tags(self.nodelist.render(context).strip())
334
335 class TemplateTagNode(Node):
336     mapping = {'openblock': BLOCK_TAG_START,
337                'closeblock': BLOCK_TAG_END,
338                'openvariable': VARIABLE_TAG_START,
339                'closevariable': VARIABLE_TAG_END,
340                'openbrace': SINGLE_BRACE_START,
341                'closebrace': SINGLE_BRACE_END,
342                'opencomment': COMMENT_TAG_START,
343                'closecomment': COMMENT_TAG_END,
344                }
345
346     def __init__(self, tagtype):
347         self.tagtype = tagtype
348
349     def render(self, context):
350         return self.mapping.get(self.tagtype, '')
351
352 class URLNode(Node):
353     def __init__(self, view_name, args, kwargs):
354         self.view_name = view_name
355         self.args = args
356         self.kwargs = kwargs
357
358     def render(self, context):
359         from django.core.urlresolvers import reverse, NoReverseMatch
360         args = [arg.resolve(context) for arg in self.args]
361         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
362                        for k, v in self.kwargs.items()])
363         try:
364             return reverse(self.view_name, args=args, kwargs=kwargs)
365         except NoReverseMatch:
366             try:
367                 project_name = settings.SETTINGS_MODULE.split('.')[0]
368                 return reverse(project_name + '.' + self.view_name,
369                                args=args, kwargs=kwargs)
370             except NoReverseMatch:
371                 return ''
372
373 class WidthRatioNode(Node):
374     def __init__(self, val_expr, max_expr, max_width):
375         self.val_expr = val_expr
376         self.max_expr = max_expr
377         self.max_width = max_width
378
379     def render(self, context):
380         try:
381             value = self.val_expr.resolve(context)
382             maxvalue = self.max_expr.resolve(context)
383         except VariableDoesNotExist:
384             return ''
385         try:
386             value = float(value)
387             maxvalue = float(maxvalue)
388             ratio = (value / maxvalue) * int(self.max_width)
389         except (ValueError, ZeroDivisionError):
390             return ''
391         return str(int(round(ratio)))
392
393 class WithNode(Node):
394     def __init__(self, var, name, nodelist):
395         self.var = var
396         self.name = name
397         self.nodelist = nodelist
398
399     def __repr__(self):
400         return "<WithNode>"
401
402     def render(self, context):
403         val = self.var.resolve(context)
404         context.push()
405         context[self.name] = val
406         output = self.nodelist.render(context)
407         context.pop()
408         return output
409
410 #@register.tag
411 def autoescape(parser, token):
412     """
413     Force autoescape behaviour for this block.
414     """
415     args = token.contents.split()
416     if len(args) != 2:
417         raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
418     arg = args[1]
419     if arg not in (u'on', u'off'):
420         raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
421     nodelist = parser.parse(('endautoescape',))
422     parser.delete_first_token()
423     return AutoEscapeControlNode((arg == 'on'), nodelist)
424 autoescape = register.tag(autoescape)
425
426 #@register.tag
427 def comment(parser, token):
428     """
429     Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
430     """
431     parser.skip_past('endcomment')
432     return CommentNode()
433 comment = register.tag(comment)
434
435 #@register.tag
436 def cycle(parser, token):
437     """
438     Cycles among the given strings each time this tag is encountered.
439
440     Within a loop, cycles among the given strings each time through
441     the loop::
442
443         {% for o in some_list %}
444             <tr class="{% cycle 'row1' 'row2' %}">
445                 ...
446             </tr>
447         {% endfor %}
448
449     Outside of a loop, give the values a unique name the first time you call
450     it, then use that name each sucessive time through::
451
452             <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
453             <tr class="{% cycle rowcolors %}">...</tr>
454             <tr class="{% cycle rowcolors %}">...</tr>
455
456     You can use any number of values, separated by spaces. Commas can also
457     be used to separate values; if a comma is used, the cycle values are
458     interpreted as literal strings.
459     """
460
461     # Note: This returns the exact same node on each {% cycle name %} call;
462     # that is, the node object returned from {% cycle a b c as name %} and the
463     # one returned from {% cycle name %} are the exact same object. This
464     # shouldn't cause problems (heh), but if it does, now you know.
465     #
466     # Ugly hack warning: This stuffs the named template dict into parser so
467     # that names are only unique within each template (as opposed to using
468     # a global variable, which would make cycle names have to be unique across
469     # *all* templates.
470
471     args = token.split_contents()
472
473     if len(args) < 2:
474         raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
475
476     if ',' in args[1]:
477         # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
478         # case.
479         args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
480
481     if len(args) == 2:
482         # {% cycle foo %} case.
483         name = args[1]
484         if not hasattr(parser, '_namedCycleNodes'):
485             raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
486         if not name in parser._namedCycleNodes:
487             raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
488         return parser._namedCycleNodes[name]
489
490     if len(args) > 4 and args[-2] == 'as':
491         name = args[-1]
492         node = CycleNode(args[1:-2], name)
493         if not hasattr(parser, '_namedCycleNodes'):
494             parser._namedCycleNodes = {}
495         parser._namedCycleNodes[name] = node
496     else:
497         node = CycleNode(args[1:])
498     return node
499 cycle = register.tag(cycle)
500
501 def debug(parser, token):
502     """
503     Outputs a whole load of debugging information, including the current
504     context and imported modules.
505
506     Sample usage::
507
508         <pre>
509             {% debug %}
510         </pre>
511     """
512     return DebugNode()
513 debug = register.tag(debug)
514
515 #@register.tag(name="filter")
516 def do_filter(parser, token):
517     """
518     Filters the contents of the block through variable filters.
519
520     Filters can also be piped through each other, and they can have
521     arguments -- just like in variable syntax.
522
523     Sample usage::
524
525         {% filter force_escape|lower %}
526             This text will be HTML-escaped, and will appear in lowercase.
527         {% endfilter %}
528     """
529     _, rest = token.contents.split(None,