Django

Code

root/django/branches/gis/django/template/defaulttags.py

Revision 8215, 37.8 kB (checked in by jbronn, 4 months ago)

gis: Merged revisions 7981-8001,8003-8011,8013-8033,8035-8036,8038-8039,8041-8063,8065-8076,8078-8139,8141-8154,8156-8214 via svnmerge from trunk.

  • 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):
355         self.view_name = view_name
356         self.args = args
357         self.kwargs = kwargs
358
359     def render(self, context):
360         from django.core.urlresolvers import reverse, NoReverseMatch
361         args = [arg.resolve(context) for arg in self.args]
362         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
363                        for k, v in self.kwargs.items()])
364         try:
365             return reverse(self.view_name, args=args, kwargs=kwargs)
366         except NoReverseMatch:
367             project_name = settings.SETTINGS_MODULE.split('.')[0]
368             return reverse(project_name + '.' + self.view_name,
369                            args=args, kwargs=kwargs)
370
371 class WidthRatioNode(Node):
372     def __init__(self, val_expr, max_expr, max_width):
373         self.val_expr = val_expr
374         self.max_expr = max_expr
375         self.max_width = max_width
376
377     def render(self, context):
378         try:
379             value = self.val_expr.resolve(context)
380             maxvalue = self.max_expr.resolve(context)
381         except VariableDoesNotExist:
382             return ''
383         try:
384             value = float(value)
385             maxvalue = float(maxvalue)
386             ratio = (value / maxvalue) * int(self.max_width)
387         except (ValueError, ZeroDivisionError):
388             return ''
389         return str(int(round(ratio)))
390
391 class WithNode(Node):
392     def __init__(self, var, name, nodelist):
393         self.var = var
394         self.name = name
395         self.nodelist = nodelist
396
397     def __repr__(self):
398         return "<WithNode>"
399
400     def render(self, context):
401         val = self.var.resolve(context)
402         context.push()
403         context[self.name] = val
404         output = self.nodelist.render(context)
405         context.pop()
406         return output
407
408 #@register.tag
409 def autoescape(parser, token):
410     """
411     Force autoescape behaviour for this block.
412     """
413     args = token.contents.split()
414     if len(args) != 2:
415         raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
416     arg = args[1]
417     if arg not in (u'on', u'off'):
418         raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
419     nodelist = parser.parse(('endautoescape',))
420     parser.delete_first_token()
421     return AutoEscapeControlNode((arg == 'on'), nodelist)
422 autoescape = register.tag(autoescape)
423
424 #@register.tag
425 def comment(parser, token):
426     """
427     Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
428     """
429     parser.skip_past('endcomment')
430     return CommentNode()
431 comment = register.tag(comment)
432
433 #@register.tag
434 def cycle(parser, token):
435     """
436     Cycles among the given strings each time this tag is encountered.
437
438     Within a loop, cycles among the given strings each time through
439     the loop::
440
441         {% for o in some_list %}
442             <tr class="{% cycle 'row1' 'row2' %}">
443                 ...
444             </tr>
445         {% endfor %}
446
447     Outside of a loop, give the values a unique name the first time you call
448     it, then use that name each sucessive time through::
449
450             <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
451             <tr class="{% cycle rowcolors %}">...</tr>
452             <tr class="{% cycle rowcolors %}">...</tr>
453
454     You can use any number of values, separated by spaces. Commas can also
455     be used to separate values; if a comma is used, the cycle values are
456     interpreted as literal strings.
457     """
458
459     # Note: This returns the exact same node on each {% cycle name %} call;
460     # that is, the node object returned from {% cycle a b c as name %} and the
461     # one returned from {% cycle name %} are the exact same object. This
462     # shouldn't cause problems (heh), but if it does, now you know.
463     #
464     # Ugly hack warning: This stuffs the named template dict into parser so
465     # that names are only unique within each template (as opposed to using
466     # a global variable, which would make cycle names have to be unique across
467     # *all* templates.
468
469     args = token.split_contents()
470
471     if len(args) < 2:
472         raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
473
474     if ',' in args[1]:
475         # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
476         # case.
477         args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
478
479     if len(args) == 2:
480         # {% cycle foo %} case.
481         name = args[1]
482         if not hasattr(parser, '_namedCycleNodes'):
483             raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
484         if not name in parser._namedCycleNodes:
485             raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
486         return parser._namedCycleNodes[name]
487
488     if len(args) > 4 and args[-2] == 'as':
489         name = args[-1]
490         node = CycleNode(args[1:-2], name)
491         if not hasattr(parser, '_namedCycleNodes'):
492             parser._namedCycleNodes = {}
493         parser._namedCycleNodes[name] = node
494     else:
495         node = CycleNode(args[1:])
496     return node
497 cycle = register.tag(cycle)
498
499 def debug(parser, token):
500     """
501     Outputs a whole load of debugging information, including the current
502     context and imported modules.
503
504     Sample usage::
505
506         <pre>
507             {% debug %}
508         </pre>
509     """
510     return DebugNode()
511 debug = register.tag(debug)
512
513 #@register.tag(name="filter")
514 def do_filter(parser, token):
515     """
516     Filters the contents of the block through variable filters.
517
518     Filters can also be piped through each other, and they can have
519     arguments -- just like in variable syntax.
520
521     Sample usage::
522
523         {% filter force_escape|lower %}
524             This text will be HTML-escaped, and will appear in lowercase.
525         {% endfilter %}
526     """
527     _, rest = token.contents.split(None, 1)<