Django

Code

root/django/tags/releases/0.96/django/template/defaulttags.py

Revision 4747, 33.3 kB (checked in by adrian, 2 years ago)

Small tweaks to docstrings from [4700]

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