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':
540         raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents
541     loopvar = bits[1]
542     sequence = parser.compile_filter(bits[3])
543     reversed = (len(bits) == 5)
544     nodelist_loop = parser.parse(('endfor',))
545     parser.delete_first_token()
546     return ForNode(loopvar, sequence, reversed, nodelist_loop)
547 do_for = register.tag("for", do_for)
548
549 def do_ifequal(parser, token, negate):
550     bits = list(token.split_contents())
551     if len(bits) != 3:
552         raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
553     end_tag = 'end' + bits[0]
554     nodelist_true = parser.parse(('else', end_tag))
555     token = parser.next_token()
556     if token.contents == 'else':
557         nodelist_false = parser.parse((end_tag,))
558         parser.delete_first_token()
559     else:
560         nodelist_false = NodeList()
561     return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
562
563 #@register.tag
564 def ifequal(parser, token):
565     """
566     Output the contents of the block if the two arguments equal each other.
567
568     Examples::
569
570         {% ifequal user.id comment.user_id %}
571             ...
572         {% endifequal %}
573
574         {% ifnotequal user.id comment.user_id %}
575             ...
576         {% else %}
577             ...
578         {% endifnotequal %}
579     """
580     return do_ifequal(parser, token, False)
581 ifequal = register.tag(ifequal)
582
583 #@register.tag
584 def ifnotequal(parser, token):
585     """Output the contents of the block if the two arguments are not equal. See ifequal."""
586     return do_ifequal(parser, token, True)
587 ifnotequal = register.tag(ifnotequal)
588
589 #@register.tag(name="if")
590 def do_if(parser, token):
591     """
592     The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
593     (i.e. exists, is not empty, and is not a false boolean value) the contents
594     of the block are output:
595
596     ::
597
598         {% if althlete_list %}
599             Number of athletes: {{ althete_list|count }}
600         {% else %}
601             No athletes.
602         {% endif %}
603
604     In the above, if ``athlete_list`` is not empty, the number of athletes will
605     be displayed by the ``{{ athlete_list|count }}`` variable.
606
607     As you can see, the ``if`` tag can take an option ``{% else %}`` clause that
608     will be displayed if the test fails.
609
610     ``if`` tags may use ``or`` or ``not`` to test a number of variables or to
611     negate a given variable::
612
613         {% if not athlete_list %}
614             There are no athletes.
615         {% endif %}
616
617         {% if athlete_list or coach_list %}
618             There are some athletes or some coaches.
619         {% endif %}
620
621         {% if not athlete_list or coach_list %}
622             There are no athletes, or there are some coaches.
623         {% endif %}
624
625     For simplicity, ``if`` tags do not allow ``and`` clauses. Use nested ``if``
626     tags instead::
627
628         {% if athlete_list %}
629             {% if coach_list %}
630                 Number of athletes: {{ athlete_list|count }}.
631                 Number of coaches: {{ coach_list|count }}.
632             {% endif %}
633         {% endif %}
634     """
635     bits = token.contents.split()
636     del bits[0]
637     if not bits:
638         raise TemplateSyntaxError, "'if' statement requires at least one argument"
639     # bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
640     bitstr = ' '.join(bits)
641     boolpairs = bitstr.split(' and ')
642     boolvars = []
643     if len(boolpairs) == 1:
644         link_type = IfNode.LinkTypes.or_
645         boolpairs = bitstr.split(' or ')
646     else:
647         link_type = IfNode.LinkTypes.and_
648         if ' or ' in bitstr:
649             raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
650     for boolpair in boolpairs:
651         if ' ' in boolpair:
652             try:
653                 not_, boolvar = boolpair.split()
654             except ValueError:
655                 raise TemplateSyntaxError, "'if' statement improperly formatted"
656             if not_ != 'not':
657                 raise TemplateSyntaxError, "Expected 'not' in if statement"
658             boolvars.append((True, parser.compile_filter(boolvar)))
659         else:
660             boolvars.append((False, parser.compile_filter(boolpair)))
661     nodelist_true = parser.parse(('else', 'endif'))
662     token = parser.next_token()
663     if token.contents == 'else':
664         nodelist_false = parser.parse(('endif',))
665         parser.delete_first_token()
666     else:
667         nodelist_false = NodeList()
668     return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
669 do_if = register.tag("if", do_if)
670
671 #@register.tag
672 def ifchanged(parser, token):
673     """
674     Check if a value has changed from the last iteration of a loop.
675
676     The 'ifchanged' block tag is used within a loop. It has two possible uses.
677
678     1. Checks its own rendered contents against its previous state and only
679        displays the content if it has changed. For example, this displays a list of
680        days, only displaying the month if it changes::
681
682             <h1>Archive for {{ year }}</h1>
683
684             {% for date in days %}
685                 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
686                 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
687             {% endfor %}
688
689     2. If given a variable, check whether that variable has changed. For example, the
690        following shows the date every time it changes, but only shows the hour if both
691        the hour and the date have changed::
692
693             {% for date in days %}
694                 {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
695                 {% ifchanged date.hour date.date %}
696                     {{ date.hour }}
697                 {% endifchanged %}
698             {% endfor %}
699     """
700     bits = token.contents.split()
701     nodelist = parser.parse(('endifchanged',))
702     parser.delete_first_token()
703     return IfChangedNode(nodelist, *bits[1:])
704 ifchanged = register.tag(ifchanged)
705
706 #@register.tag
707 def ssi(parser, token):
708     """
709     Output the contents of a given file into the page.
710
711     Like a simple "include" tag, the ``ssi`` tag includes the contents
712     of another file -- which must be specified using an absolute page --
713     in the current page::
714
715         {% ssi /home/html/ljworld.com/includes/right_generic.html %}
716
717     If the optional "parsed" parameter is given, the contents of the included
718     file are evaluated as template code, with the current context::
719
720         {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
721     """
722     bits = token.contents.split()
723     parsed = False
724     if len(bits) not in (2, 3):
725         raise TemplateSyntaxError, "'ssi' tag takes one argument: the path to the file to be included"
726     if len(bits) == 3:
727         if bits[2] == 'parsed':
728             parsed = True
729         else:
730             raise TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
731     return SsiNode(bits[1], parsed)
732 ssi = register.tag(ssi)
733
734 #@register.tag
735 def load(parser, token):
736     """
737     Load a custom template tag set.
738
739     For example, to load the template tags in ``django/templatetags/news/photos.py``::
740
741         {% load news.photos %}
742     """
743     bits = token.contents.split()
744     for taglib in bits[1:]:
745         # add the library to the parser
746         try:
747             lib = get_library("django.templatetags.%s" % taglib.split('.')[-1])
748             parser.add_library(lib)
749         except InvalidTemplateLibrary, e:
750             raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e)
751     return LoadNode()
752 load = register.tag(load)
753
754 #@register.tag
755 def now(parser, token):
756     """
757     Display the date, formatted according to the given string.
758
759     Uses the same format as PHP's ``date()`` function; see http://php.net/date
760     for all the possible values.
761
762     Sample usage::
763
764         It is {% now "jS F Y H:i" %}
765     """
766     bits = token.contents.split('"')
767     if len(bits) != 3:
768         raise TemplateSyntaxError, "'now' statement takes one argument"
769     format_string = bits[1]
770     return NowNode(format_string)
771 now = register.tag(now)
772
773 #@register.tag
774 def regroup(parser, token):
775     """
776     Regroup a list of alike objects by a common attribute.
777
778     This complex tag is best illustrated by use of an example:  say that
779     ``people`` is a list of ``Person`` objects that have ``first_name``,
780     ``last_name``, and ``gender`` attributes, and you'd like to display a list
781     that looks like:
782
783         * Male:
784             * George Bush
785             * Bill Clinton
786         * Female:
787             * Margaret Thatcher
788             * Colendeeza Rice
789         * Unknown:
790             * Pat Smith
791
792     The following snippet of template code would accomplish this dubious task::
793
794         {% regroup people by gender as grouped %}
795         <ul>
796         {% for group in grouped %}
797             <li>{{ group.grouper }}
798             <ul>
799                 {% for item in group.list %}
800                 <li>{{ item }}</li>
801                 {% endfor %}
802             </ul>
803         {% endfor %}
804         </ul>
805
806     As you can see, ``{% regroup %}`` populates a variable with a list of
807     objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
808     item that was grouped by; ``list`` contains the list of objects that share
809     that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
810     and ``Unknown``, and ``list`` is the list of people with those genders.
811
812     Note that `{% regroup %}`` does not work when the list to be grouped is not
813     sorted by the key you are grouping by!  This means that if your list of
814     people was not sorted by gender, you'd need to make sure it is sorted before
815     using it, i.e.::
816
817         {% regroup people|dictsort:"gender" by gender as grouped %}
818
819     """
820     firstbits = token.contents.split(None, 3)
821     if len(firstbits) != 4:
822         raise TemplateSyntaxError, "'regroup' tag takes five arguments"
823     target = parser.compile_filter(firstbits[1])
824     if firstbits[2] != 'by':
825         raise TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'"
826     lastbits_reversed = firstbits[3][::-1].split(None, 2)
827     if lastbits_reversed[1][::-1] != 'as':
828         raise TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'"
829
830     expression = parser.compile_filter('var.%s' % lastbits_reversed[2][::-1])
831
832     var_name = lastbits_reversed[0][::-1]
833     return RegroupNode(target, expression, var_name)
834 regroup = register.tag(regroup)
835
836 def spaceless(parser, token):
837     """
838     Normalize whitespace between HTML tags to a single space. This includes tab
839     characters and newlines.
840
841     Example usage::
842
843         {% spaceless %}
844             <p>
845                 <a href="foo/">Foo</a>
846             </p>
847         {% endspaceless %}
848
849     This example would return this HTML::
850
851         <p> <a href="foo/">Foo</a> </p>
852
853     Only space between *tags* is normalized -- not space between tags and text. In
854     this example, the space around ``Hello`` won't be stripped::
855
856         {% spaceless %}
857             <strong>
858                 Hello
859             </strong>
860         {% endspaceless %}
861     """
862     nodelist = parser.parse(('endspaceless',))
863     parser.delete_first_token()
864     return SpacelessNode(nodelist)
865 spaceless = register.tag(spaceless)
866
867 #@register.tag
868 def templatetag(parser, token):
869     """
870     Output one of the bits used to compose template tags.
871
872     Since the template system has no concept of "escaping", to display one of
873     the bits used in template tags, you must use the ``{% templatetag %}`` tag.
874
875     The argument tells which template bit to output:
876
877         ==================  =======
878         Argument            Outputs
879         ==================  =======
880         ``openblock``       ``{%``
881         ``closeblock``      ``%}``
882         ``openvariable``    ``{{``
883         ``closevariable``   ``}}``
884         ``openbrace``       ``{``
885         ``closebrace``      ``}``
886         ``opencomment``     ``{#``
887         ``closecomment``    ``#}``
888         ==================  =======
889     """
890     bits = token.contents.split()
891     if len(bits) != 2:
892         raise TemplateSyntaxError, "'templatetag' statement takes one argument"
893     tag = bits[1]
894     if not TemplateTagNode.mapping.has_key(tag):
895         raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
896             (tag, TemplateTagNode.mapping.keys())
897     return TemplateTagNode(tag)
898 templatetag = register.tag(templatetag)
899
900 def url(parser, token):
901     """
902     Returns an absolute URL matching given view with its parameters.
903     
904     This is a way to define links that aren't tied to a particular URL configuration::
905     
906         {% url path.to.some_view arg1,arg2,name1=value1 %}
907     
908     The first argument is a path to a view. It can be an absolute python path
909     or just ``app_name.view_name`` without the project name if the view is
910     located inside the project.  Other arguments are comma-separated values
911     that will be filled in place of positional and keyword arguments in the
912     URL. All arguments for the URL should be present.
913
914     For example if you have a view ``app_name.client`` taking client's id and
915     the corresponding line in a URLconf looks like this::
916     
917         ('^client/(\d+)/$', 'app_name.client')
918     
919     and this app's URLconf is included into the project's URLconf under some
920     path::
921     
922         ('^clients/', include('project_name.app_name.urls'))
923     
924     then in a template you can create a link for a certain client like this::
925     
926         {% url app_name.client client.id %}
927     
928     The URL will look like ``/clients/client/123/``.
929     """
930     bits = token.contents.split(' ', 2)
931     if len(bits) < 2:
932         raise TemplateSyntaxError, "'%s' takes at least one argument (path to a view)" % bits[0]
933     args = []
934     kwargs = {}
935     if len(bits) > 2:
936         for arg in bits[2].split(','):
937             if '=' in arg:
938                 k, v = arg.split('=', 1)
939                 kwargs[k] = parser.compile_filter(v)
940             else:
941                 args.append(parser.compile_filter(arg))
942     return URLNode(bits[1], args, kwargs)
943 url = register.tag(url)
944
945 #@register.tag
946 def widthratio(parser, token):
947     """
948     For creating bar charts and such, this tag calculates the ratio of a given
949     value to a maximum value, and then applies that ratio to a constant.
950
951     For example::
952
953         <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
954
955     Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
956     the above example will be 88 pixels wide (because 175/200 = .875; .875 *
957     100 = 87.5 which is rounded up to 88).
958     """
959     bits = token.contents.split()
960     if len(bits) != 4:
961         raise TemplateSyntaxError("widthratio takes three arguments")
962     tag, this_value_expr, max_value_expr, max_width = bits
963     try:
964         max_width = int(max_width)
965     except ValueError:
966         raise TemplateSyntaxError("widthratio final argument must be an integer")
967     return WidthRatioNode(parser.compile_filter(this_value_expr),
968                           parser.compile_filter(max_value_expr), max_width)
969 widthratio = register.tag(widthratio)
Note: See TracBrowser for help on using the browser.