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_VARIABLE_TAG_START, SINGLE_VARIABLE_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):
|
---|
17 | self.cyclevars = cyclevars
|
---|
18 | self.cyclevars_len = len(cyclevars)
|
---|
19 | self.counter = -1
|
---|
20 |
|
---|
21 | def render(self, context):
|
---|
22 | self.counter += 1
|
---|
23 | return self.cyclevars[self.counter % self.cyclevars_len]
|
---|
24 |
|
---|
25 | class DebugNode(Node):
|
---|
26 | def render(self, context):
|
---|
27 | from pprint import pformat
|
---|
28 | output = [pformat(val) for val in context]
|
---|
29 | output.append('\n\n')
|
---|
30 | output.append(pformat(sys.modules))
|
---|
31 | return ''.join(output)
|
---|
32 |
|
---|
33 | class FilterNode(Node):
|
---|
34 | def __init__(self, filter_expr, nodelist):
|
---|
35 | self.filter_expr, self.nodelist = filter_expr, nodelist
|
---|
36 |
|
---|
37 | def render(self, context):
|
---|
38 | output = self.nodelist.render(context)
|
---|
39 | # apply filters
|
---|
40 | return self.filter_expr.resolve(Context({'var': output}))
|
---|
41 |
|
---|
42 | class FirstOfNode(Node):
|
---|
43 | def __init__(self, vars):
|
---|
44 | self.vars = vars
|
---|
45 |
|
---|
46 | def render(self, context):
|
---|
47 | for var in self.vars:
|
---|
48 | value = resolve_variable(var, context)
|
---|
49 | if value:
|
---|
50 | return str(value)
|
---|
51 | return ''
|
---|
52 |
|
---|
53 | class ForNode(Node):
|
---|
54 | def __init__(self, loopvar, sequence, reversed, nodelist_loop):
|
---|
55 | self.loopvar, self.sequence = loopvar, sequence
|
---|
56 | self.reversed = reversed
|
---|
57 | self.nodelist_loop = nodelist_loop
|
---|
58 |
|
---|
59 | def __repr__(self):
|
---|
60 | if self.reversed:
|
---|
61 | reversed = ' reversed'
|
---|
62 | else:
|
---|
63 | reversed = ''
|
---|
64 | return "<For Node: for %s in %s, tail_len: %d%s>" % \
|
---|
65 | (self.loopvar, self.sequence, len(self.nodelist_loop), reversed)
|
---|
66 |
|
---|
67 | def __iter__(self):
|
---|
68 | for node in self.nodelist_loop:
|
---|
69 | yield node
|
---|
70 |
|
---|
71 | def get_nodes_by_type(self, nodetype):
|
---|
72 | nodes = []
|
---|
73 | if isinstance(self, nodetype):
|
---|
74 | nodes.append(self)
|
---|
75 | nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
|
---|
76 | return nodes
|
---|
77 |
|
---|
78 | def render(self, context):
|
---|
79 | nodelist = NodeList()
|
---|
80 | if context.has_key('forloop'):
|
---|
81 | parentloop = context['forloop']
|
---|
82 | else:
|
---|
83 | parentloop = {}
|
---|
84 | context.push()
|
---|
85 | try:
|
---|
86 | values = self.sequence.resolve(context)
|
---|
87 | except VariableDoesNotExist:
|
---|
88 | values = []
|
---|
89 | if values is None:
|
---|
90 | values = []
|
---|
91 | len_values = len(values)
|
---|
92 | if self.reversed:
|
---|
93 | # From http://www.python.org/doc/current/tut/node11.html
|
---|
94 | def reverse(data):
|
---|
95 | for index in range(len(data)-1, -1, -1):
|
---|
96 | yield data[index]
|
---|
97 | values = reverse(values)
|
---|
98 | for i, item in enumerate(values):
|
---|
99 | context['forloop'] = {
|
---|
100 | # shortcuts for current loop iteration number
|
---|
101 | 'counter0': i,
|
---|
102 | 'counter': i+1,
|
---|
103 | # reverse counter iteration numbers
|
---|
104 | 'revcounter': len_values - i,
|
---|
105 | 'revcounter0': len_values - i - 1,
|
---|
106 | # boolean values designating first and last times through loop
|
---|
107 | 'first': (i == 0),
|
---|
108 | 'last': (i == len_values - 1),
|
---|
109 | 'parentloop': parentloop,
|
---|
110 | }
|
---|
111 | context[self.loopvar] = item
|
---|
112 | for node in self.nodelist_loop:
|
---|
113 | nodelist.append(node.render(context))
|
---|
114 | context.pop()
|
---|
115 | return nodelist.render(context)
|
---|
116 |
|
---|
117 | class IfChangedNode(Node):
|
---|
118 | def __init__(self, nodelist):
|
---|
119 | self.nodelist = nodelist
|
---|
120 | self._last_seen = None
|
---|
121 |
|
---|
122 | def render(self, context):
|
---|
123 | content = self.nodelist.render(context)
|
---|
124 | if content != self._last_seen:
|
---|
125 | firstloop = (self._last_seen == None)
|
---|
126 | self._last_seen = content
|
---|
127 | context.push()
|
---|
128 | context['ifchanged'] = {'firstloop': firstloop}
|
---|
129 | content = self.nodelist.render(context)
|
---|
130 | context.pop()
|
---|
131 | return content
|
---|
132 | else:
|
---|
133 | return ''
|
---|
134 |
|
---|
135 | class IfEqualNode(Node):
|
---|
136 | def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
|
---|
137 | self.var1, self.var2 = var1, var2
|
---|
138 | self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
|
---|
139 | self.negate = negate
|
---|
140 |
|
---|
141 | def __repr__(self):
|
---|
142 | return "<IfEqualNode>"
|
---|
143 |
|
---|
144 | def render(self, context):
|
---|
145 | val1 = resolve_variable(self.var1, context)
|
---|
146 | val2 = resolve_variable(self.var2, context)
|
---|
147 | if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
|
---|
148 | return self.nodelist_true.render(context)
|
---|
149 | return self.nodelist_false.render(context)
|
---|
150 |
|
---|
151 | class IfNode(Node):
|
---|
152 | def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
|
---|
153 | self.bool_exprs = bool_exprs
|
---|
154 | self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
|
---|
155 | self.link_type = link_type
|
---|
156 |
|
---|
157 | def __repr__(self):
|
---|
158 | return "<If node>"
|
---|
159 |
|
---|
160 | def __iter__(self):
|
---|
161 | for node in self.nodelist_true:
|
---|
162 | yield node
|
---|
163 | for node in self.nodelist_false:
|
---|
164 | yield node
|
---|
165 |
|
---|
166 | def get_nodes_by_type(self, nodetype):
|
---|
167 | nodes = []
|
---|
168 | if isinstance(self, nodetype):
|
---|
169 | nodes.append(self)
|
---|
170 | nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
|
---|
171 | nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
|
---|
172 | return nodes
|
---|
173 |
|
---|
174 | def render(self, context):
|
---|
175 | if self.link_type == IfNode.LinkTypes.or_:
|
---|
176 | for ifnot, bool_expr in self.bool_exprs:
|
---|
177 | try:
|
---|
178 | value = bool_expr.resolve(context)
|
---|
179 | except VariableDoesNotExist:
|
---|
180 | value = None
|
---|
181 | if (value and not ifnot) or (ifnot and not value):
|
---|
182 | return self.nodelist_true.render(context)
|
---|
183 | return self.nodelist_false.render(context)
|
---|
184 | else:
|
---|
185 | for ifnot, bool_expr in self.bool_exprs:
|
---|
186 | try:
|
---|
187 | value = bool_expr.resolve(context)
|
---|
188 | except VariableDoesNotExist:
|
---|
189 | value = None
|
---|
190 | if not ((value and not ifnot) or (ifnot and not value)):
|
---|
191 | return self.nodelist_false.render(context)
|
---|
192 | return self.nodelist_true.render(context)
|
---|
193 |
|
---|
194 | class LinkTypes:
|
---|
195 | and_ = 0,
|
---|
196 | or_ = 1
|
---|
197 |
|
---|
198 | class RegroupNode(Node):
|
---|
199 | def __init__(self, target, expression, var_name):
|
---|
200 | self.target, self.expression = target, expression
|
---|
201 | self.var_name = var_name
|
---|
202 |
|
---|
203 | def render(self, context):
|
---|
204 | obj_list = self.target.resolve(context)
|
---|
205 | if obj_list == '': # target_var wasn't found in context; fail silently
|
---|
206 | context[self.var_name] = []
|
---|
207 | return ''
|
---|
208 | output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
|
---|
209 | for obj in obj_list:
|
---|
210 | grouper = self.expression.resolve(Context({'var': obj}))
|
---|
211 | # TODO: Is this a sensible way to determine equality?
|
---|
212 | if output and repr(output[-1]['grouper']) == repr(grouper):
|
---|
213 | output[-1]['list'].append(obj)
|
---|
214 | else:
|
---|
215 | output.append({'grouper': grouper, 'list': [obj]})
|
---|
216 | context[self.var_name] = output
|
---|
217 | return ''
|
---|
218 |
|
---|
219 | def include_is_allowed(filepath):
|
---|
220 | for root in settings.ALLOWED_INCLUDE_ROOTS:
|
---|
221 | if filepath.startswith(root):
|
---|
222 | return True
|
---|
223 | return False
|
---|
224 |
|
---|
225 | class SsiNode(Node):
|
---|
226 | def __init__(self, filepath, parsed):
|
---|
227 | self.filepath, self.parsed = filepath, parsed
|
---|
228 |
|
---|
229 | def render(self, context):
|
---|
230 | if not include_is_allowed(self.filepath):
|
---|
231 | if settings.DEBUG:
|
---|
232 | return "[Didn't have permission to include file]"
|
---|
233 | else:
|
---|
234 | return '' # Fail silently for invalid includes.
|
---|
235 | try:
|
---|
236 | fp = open(self.filepath, 'r')
|
---|
237 | output = fp.read()
|
---|
238 | fp.close()
|
---|
239 | except IOError:
|
---|
240 | output = ''
|
---|
241 | if self.parsed:
|
---|
242 | try:
|
---|
243 | t = Template(output)
|
---|
244 | return t.render(context)
|
---|
245 | except TemplateSyntaxError, e:
|
---|
246 | if settings.DEBUG:
|
---|
247 | return "[Included template had syntax error: %s]" % e
|
---|
248 | else:
|
---|
249 | return '' # Fail silently for invalid included templates.
|
---|
250 | return output
|
---|
251 |
|
---|
252 | class LoadNode(Node):
|
---|
253 | def render(self, context):
|
---|
254 | return ''
|
---|
255 |
|
---|
256 | class NowNode(Node):
|
---|
257 | def __init__(self, format_string):
|
---|
258 | self.format_string = format_string
|
---|
259 |
|
---|
260 | def render(self, context):
|
---|
261 | from datetime import datetime
|
---|
262 | from django.utils.dateformat import DateFormat
|
---|
263 | df = DateFormat(datetime.now())
|
---|
264 | return df.format(self.format_string)
|
---|
265 |
|
---|
266 | class SpacelessNode(Node):
|
---|
267 | def __init__(self, nodelist):
|
---|
268 | self.nodelist = nodelist
|
---|
269 |
|
---|
270 | def render(self, context):
|
---|
271 | from django.utils.html import strip_spaces_between_tags
|
---|
272 | return strip_spaces_between_tags(self.nodelist.render(context).strip())
|
---|
273 |
|
---|
274 | class TemplateTagNode(Node):
|
---|
275 | mapping = {'openblock': BLOCK_TAG_START,
|
---|
276 | 'closeblock': BLOCK_TAG_END,
|
---|
277 | 'openvariable': VARIABLE_TAG_START,
|
---|
278 | 'closevariable': VARIABLE_TAG_END,
|
---|
279 | 'opensinglebrace': SINGLE_VARIABLE_TAG_START,
|
---|
280 | 'closesinglebrace': SINGLE_VARIABLE_TAG_END}
|
---|
281 |
|
---|
282 | def __init__(self, tagtype):
|
---|
283 | self.tagtype = tagtype
|
---|
284 |
|
---|
285 | def render(self, context):
|
---|
286 | return self.mapping.get(self.tagtype, '')
|
---|
287 |
|
---|
288 | class WidthRatioNode(Node):
|
---|
289 | def __init__(self, val_expr, max_expr, max_width):
|
---|
290 | self.val_expr = val_expr
|
---|
291 | self.max_expr = max_expr
|
---|
292 | self.max_width = max_width
|
---|
293 |
|
---|
294 | def render(self, context):
|
---|
295 | try:
|
---|
296 | value = self.val_expr.resolve(context)
|
---|
297 | maxvalue = self.max_expr.resolve(context)
|
---|
298 | except VariableDoesNotExist:
|
---|
299 | return ''
|
---|
300 | try:
|
---|
301 | value = float(value)
|
---|
302 | maxvalue = float(maxvalue)
|
---|
303 | ratio = (value / maxvalue) * int(self.max_width)
|
---|
304 | except (ValueError, ZeroDivisionError):
|
---|
305 | return ''
|
---|
306 | return str(int(round(ratio)))
|
---|
307 |
|
---|
308 | #@register.tag
|
---|
309 | def comment(parser, token):
|
---|
310 | """
|
---|
311 | Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
|
---|
312 | """
|
---|
313 | parser.skip_past('endcomment')
|
---|
314 | return CommentNode()
|
---|
315 | comment = register.tag(comment)
|
---|
316 |
|
---|
317 | #@register.tag
|
---|
318 | def cycle(parser, token):
|
---|
319 | """
|
---|
320 | Cycle among the given strings each time this tag is encountered
|
---|
321 |
|
---|
322 | Within a loop, cycles among the given strings each time through
|
---|
323 | the loop::
|
---|
324 |
|
---|
325 | {% for o in some_list %}
|
---|
326 | <tr class="{% cycle row1,row2 %}">
|
---|
327 | ...
|
---|
328 | </tr>
|
---|
329 | {% endfor %}
|
---|
330 |
|
---|
331 | Outside of a loop, give the values a unique name the first time you call
|
---|
332 | it, then use that name each sucessive time through::
|
---|
333 |
|
---|
334 | <tr class="{% cycle row1,row2,row3 as rowcolors %}">...</tr>
|
---|
335 | <tr class="{% cycle rowcolors %}">...</tr>
|
---|
336 | <tr class="{% cycle rowcolors %}">...</tr>
|
---|
337 |
|
---|
338 | You can use any number of values, seperated by commas. Make sure not to
|
---|
339 | put spaces between the values -- only commas.
|
---|
340 | """
|
---|
341 |
|
---|
342 | # Note: This returns the exact same node on each {% cycle name %} call; that
|
---|
343 | # is, the node object returned from {% cycle a,b,c as name %} and the one
|
---|
344 | # returned from {% cycle name %} are the exact same object. This shouldn't
|
---|
345 | # cause problems (heh), but if it does, now you know.
|
---|
346 | #
|
---|
347 | # Ugly hack warning: this stuffs the named template dict into parser so
|
---|
348 | # that names are only unique within each template (as opposed to using
|
---|
349 | # a global variable, which would make cycle names have to be unique across
|
---|
350 | # *all* templates.
|
---|
351 |
|
---|
352 | args = token.contents.split()
|
---|
353 | if len(args) < 2:
|
---|
354 | raise TemplateSyntaxError("'Cycle' statement requires at least two arguments")
|
---|
355 |
|
---|
356 | elif len(args) == 2 and "," in args[1]:
|
---|
357 | # {% cycle a,b,c %}
|
---|
358 | cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks
|
---|
359 | return CycleNode(cyclevars)
|
---|
360 | # {% cycle name %}
|
---|
361 |
|
---|
362 | elif len(args) == 2:
|
---|
363 | name = args[1]
|
---|
364 | if not hasattr(parser, '_namedCycleNodes'):
|
---|
365 | raise TemplateSyntaxError("No named cycles in template: '%s' is not defined" % name)
|
---|
366 | if not parser._namedCycleNodes.has_key(name):
|
---|
367 | raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
|
---|
368 | return parser._namedCycleNodes[name]
|
---|
369 |
|
---|
370 | elif len(args) == 4:
|
---|
371 | # {% cycle a,b,c as name %}
|
---|
372 | if args[2] != 'as':
|
---|
373 | raise TemplateSyntaxError("Second 'cycle' argument must be 'as'")
|
---|
374 | cyclevars = [v for v in args[1].split(",") if v] # split and kill blanks
|
---|
375 | name = args[3]
|
---|
376 | node = CycleNode(cyclevars)
|
---|
377 |
|
---|
378 | if not hasattr(parser, '_namedCycleNodes'):
|
---|
379 | parser._namedCycleNodes = {}
|
---|
380 |
|
---|
381 | parser._namedCycleNodes[name] = node
|
---|
382 | return node
|
---|
383 |
|
---|
384 | else:
|
---|
385 | raise TemplateSyntaxError("Invalid arguments to 'cycle': %s" % args)
|
---|
386 | cycle = register.tag(cycle)
|
---|
387 |
|
---|
388 | def debug(parser, token):
|
---|
389 | return DebugNode()
|
---|
390 | debug = register.tag(debug)
|
---|
391 |
|
---|
392 | #@register.tag(name="filter")
|
---|
393 | def do_filter(parser, token):
|
---|
394 | """
|
---|
395 | Filter the contents of the blog through variable filters.
|
---|
396 |
|
---|
397 | Filters can also be piped through each other, and they can have
|
---|
398 | arguments -- just like in variable syntax.
|
---|
399 |
|
---|
400 | Sample usage::
|
---|
401 |
|
---|
402 | {% filter escape|lower %}
|
---|
403 | This text will be HTML-escaped, and will appear in lowercase.
|
---|
404 | {% endfilter %}
|
---|
405 | """
|
---|
406 | _, rest = token.contents.split(None, 1)
|
---|
407 | filter_expr = parser.compile_filter("var|%s" % (rest))
|
---|
408 | nodelist = parser.parse(('endfilter',))
|
---|
409 | parser.delete_first_token()
|
---|
410 | return FilterNode(filter_expr, nodelist)
|
---|
411 | filter = register.tag("filter", do_filter)
|
---|
412 |
|
---|
413 | #@register.tag
|
---|
414 | def firstof(parser, token):
|
---|
415 | """
|
---|
416 | Outputs the first variable passed that is not False.
|
---|
417 |
|
---|
418 | Outputs nothing if all the passed variables are False.
|
---|
419 |
|
---|
420 | Sample usage::
|
---|
421 |
|
---|
422 | {% firstof var1 var2 var3 %}
|
---|
423 |
|
---|
424 | This is equivalent to::
|
---|
425 |
|
---|
426 | {% if var1 %}
|
---|
427 | {{ var1 }}
|
---|
428 | {% else %}{% if var2 %}
|
---|
429 | {{ var2 }}
|
---|
430 | {% else %}{% if var3 %}
|
---|
431 | {{ var3 }}
|
---|
432 | {% endif %}{% endif %}{% endif %}
|
---|
433 |
|
---|
434 | but obviously much cleaner!
|
---|
435 | """
|
---|
436 | bits = token.contents.split()[1:]
|
---|
437 | if len(bits) < 1:
|
---|
438 | raise TemplateSyntaxError, "'firstof' statement requires at least one argument"
|
---|
439 | return FirstOfNode(bits)
|
---|
440 | firstof = register.tag(firstof)
|
---|
441 |
|
---|
442 | #@register.tag(name="for")
|
---|
443 | def do_for(parser, token):
|
---|
444 | """
|
---|
445 | Loop over each item in an array.
|
---|
446 |
|
---|
447 | For example, to display a list of athletes given ``athlete_list``::
|
---|
448 |
|
---|
449 | <ul>
|
---|
450 | {% for athlete in athlete_list %}
|
---|
451 | <li>{{ athlete.name }}</li>
|
---|
452 | {% endfor %}
|
---|
453 | </ul>
|
---|
454 |
|
---|
455 | You can also loop over a list in reverse by using
|
---|
456 | ``{% for obj in list reversed %}``.
|
---|
457 |
|
---|
458 | The for loop sets a number of variables available within the loop:
|
---|
459 |
|
---|
460 | ========================== ================================================
|
---|
461 | Variable Description
|
---|
462 | ========================== ================================================
|
---|
463 | ``forloop.counter`` The current iteration of the loop (1-indexed)
|
---|
464 | ``forloop.counter0`` The current iteration of the loop (0-indexed)
|
---|
465 | ``forloop.revcounter`` The number of iterations from the end of the
|
---|
466 | loop (1-indexed)
|
---|
467 | ``forloop.revcounter0`` The number of iterations from the end of the
|
---|
468 | loop (0-indexed)
|
---|
469 | ``forloop.first`` True if this is the first time through the loop
|
---|
470 | ``forloop.last`` True if this is the last time through the loop
|
---|
471 | ``forloop.parentloop`` For nested loops, this is the loop "above" the
|
---|
472 | current one
|
---|
473 | ========================== ================================================
|
---|
474 |
|
---|
475 | """
|
---|
476 | bits = token.contents.split()
|
---|
477 | if len(bits) == 5 and bits[4] != 'reversed':
|
---|
478 | raise TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents
|
---|
479 | if len(bits) not in (4, 5):
|
---|
480 | raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents
|
---|
481 | if bits[2] != 'in':
|
---|
482 | raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents
|
---|
483 | loopvar = bits[1]
|
---|
484 | sequence = parser.compile_filter(bits[3])
|
---|
485 | reversed = (len(bits) == 5)
|
---|
486 | nodelist_loop = parser.parse(('endfor',))
|
---|
487 | parser.delete_first_token()
|
---|
488 | return ForNode(loopvar, sequence, reversed, nodelist_loop)
|
---|
489 | do_for = register.tag("for", do_for)
|
---|
490 |
|
---|
491 | def do_ifequal(parser, token, negate):
|
---|
492 | """
|
---|
493 | Output the contents of the block if the two arguments equal/don't equal each other.
|
---|
494 |
|
---|
495 | Examples::
|
---|
496 |
|
---|
497 | {% ifequal user.id comment.user_id %}
|
---|
498 | ...
|
---|
499 | {% endifequal %}
|
---|
500 |
|
---|
501 | {% ifnotequal user.id comment.user_id %}
|
---|
502 | ...
|
---|
503 | {% else %}
|
---|
504 | ...
|
---|
505 | {% endifnotequal %}
|
---|
506 | """
|
---|
507 | bits = list(token.split_contents())
|
---|
508 | if len(bits) != 3:
|
---|
509 | raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
|
---|
510 | end_tag = 'end' + bits[0]
|
---|
511 | nodelist_true = parser.parse(('else', end_tag))
|
---|
512 | token = parser.next_token()
|
---|
513 | if token.contents == 'else':
|
---|
514 | nodelist_false = parser.parse((end_tag,))
|
---|
515 | parser.delete_first_token()
|
---|
516 | else:
|
---|
517 | nodelist_false = NodeList()
|
---|
518 | return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
|
---|
519 |
|
---|
520 | #@register.tag
|
---|
521 | def ifequal(parser, token):
|
---|
522 | return do_ifequal(parser, token, False)
|
---|
523 | ifequal = register.tag(ifequal)
|
---|
524 |
|
---|
525 | #@register.tag
|
---|
526 | def ifnotequal(parser, token):
|
---|
527 | return do_ifequal(parser, token, True)
|
---|
528 | ifnotequal = register.tag(ifnotequal)
|
---|
529 |
|
---|
530 | #@register.tag(name="if")
|
---|
531 | def do_if(parser, token):
|
---|
532 | """
|
---|
533 | The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
|
---|
534 | (i.e. exists, is not empty, and is not a false boolean value) the contents
|
---|
535 | of the block are output:
|
---|
536 |
|
---|
537 | ::
|
---|
538 |
|
---|
539 | {% if althlete_list %}
|
---|
540 | Number of athletes: {{ althete_list|count }}
|
---|
541 | {% else %}
|
---|
542 | No athletes.
|
---|
543 | {% endif %}
|
---|
544 |
|
---|
545 | In the above, if ``athlete_list`` is not empty, the number of athletes will
|
---|
546 | be displayed by the ``{{ athlete_list|count }}`` variable.
|
---|
547 |
|
---|
548 | As you can see, the ``if`` tag can take an option ``{% else %}`` clause that
|
---|
549 | will be displayed if the test fails.
|
---|
550 |
|
---|
551 | ``if`` tags may use ``or`` or ``not`` to test a number of variables or to
|
---|
552 | negate a given variable::
|
---|
553 |
|
---|
554 | {% if not athlete_list %}
|
---|
555 | There are no athletes.
|
---|
556 | {% endif %}
|
---|
557 |
|
---|
558 | {% if athlete_list or coach_list %}
|
---|
559 | There are some athletes or some coaches.
|
---|
560 | {% endif %}
|
---|
561 |
|
---|
562 | {% if not athlete_list or coach_list %}
|
---|
563 | There are no athletes, or there are some coaches.
|
---|
564 | {% endif %}
|
---|
565 |
|
---|
566 | For simplicity, ``if`` tags do not allow ``and`` clauses. Use nested ``if``
|
---|
567 | tags instead::
|
---|
568 |
|
---|
569 | {% if athlete_list %}
|
---|
570 | {% if coach_list %}
|
---|
571 | Number of athletes: {{ athlete_list|count }}.
|
---|
572 | Number of coaches: {{ coach_list|count }}.
|
---|
573 | {% endif %}
|
---|
574 | {% endif %}
|
---|
575 | """
|
---|
576 | bits = token.contents.split()
|
---|
577 | del bits[0]
|
---|
578 | if not bits:
|
---|
579 | raise TemplateSyntaxError, "'if' statement requires at least one argument"
|
---|
580 | # bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
|
---|
581 | bitstr = ' '.join(bits)
|
---|
582 | boolpairs = bitstr.split(' and ')
|
---|
583 | boolvars = []
|
---|
584 | if len(boolpairs) == 1:
|
---|
585 | link_type = IfNode.LinkTypes.or_
|
---|
586 | boolpairs = bitstr.split(' or ')
|
---|
587 | else:
|
---|
588 | link_type = IfNode.LinkTypes.and_
|
---|
589 | if ' or ' in bitstr:
|
---|
590 | raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
|
---|
591 | for boolpair in boolpairs:
|
---|
592 | if ' ' in boolpair:
|
---|
593 | try:
|
---|
594 | not_, boolvar = boolpair.split()
|
---|
595 | except ValueError:
|
---|
596 | raise TemplateSyntaxError, "'if' statement improperly formatted"
|
---|
597 | if not_ != 'not':
|
---|
598 | raise TemplateSyntaxError, "Expected 'not' in if statement"
|
---|
599 | boolvars.append((True, parser.compile_filter(boolvar)))
|
---|
600 | else:
|
---|
601 | boolvars.append((False, parser.compile_filter(boolpair)))
|
---|
602 | nodelist_true = parser.parse(('else', 'endif'))
|
---|
603 | token = parser.next_token()
|
---|
604 | if token.contents == 'else':
|
---|
605 | nodelist_false = parser.parse(('endif',))
|
---|
606 | parser.delete_first_token()
|
---|
607 | else:
|
---|
608 | nodelist_false = NodeList()
|
---|
609 | return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
|
---|
610 | do_if = register.tag("if", do_if)
|
---|
611 |
|
---|
612 | #@register.tag
|
---|
613 | def ifchanged(parser, token):
|
---|
614 | """
|
---|
615 | Check if a value has changed from the last iteration of a loop.
|
---|
616 |
|
---|
617 | The 'ifchanged' block tag is used within a loop. It checks its own rendered
|
---|
618 | contents against its previous state and only displays its content if the
|
---|
619 | value has changed::
|
---|
620 |
|
---|
621 | <h1>Archive for {{ year }}</h1>
|
---|
622 |
|
---|
623 | {% for date in days %}
|
---|
624 | {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
|
---|
625 | <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
|
---|
626 | {% endfor %}
|
---|
627 | """
|
---|
628 | bits = token.contents.split()
|
---|
629 | if len(bits) != 1:
|
---|
630 | raise TemplateSyntaxError, "'ifchanged' tag takes no arguments"
|
---|
631 | nodelist = parser.parse(('endifchanged',))
|
---|
632 | parser.delete_first_token()
|
---|
633 | return IfChangedNode(nodelist)
|
---|
634 | ifchanged = register.tag(ifchanged)
|
---|
635 |
|
---|
636 | #@register.tag
|
---|
637 | def ssi(parser, token):
|
---|
638 | """
|
---|
639 | Output the contents of a given file into the page.
|
---|
640 |
|
---|
641 | Like a simple "include" tag, the ``ssi`` tag includes the contents
|
---|
642 | of another file -- which must be specified using an absolute page --
|
---|
643 | in the current page::
|
---|
644 |
|
---|
645 | {% ssi /home/html/ljworld.com/includes/right_generic.html %}
|
---|
646 |
|
---|
647 | If the optional "parsed" parameter is given, the contents of the included
|
---|
648 | file are evaluated as template code, with the current context::
|
---|
649 |
|
---|
650 | {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
|
---|
651 | """
|
---|
652 | bits = token.contents.split()
|
---|
653 | parsed = False
|
---|
654 | if len(bits) not in (2, 3):
|
---|
655 | raise TemplateSyntaxError, "'ssi' tag takes one argument: the path to the file to be included"
|
---|
656 | if len(bits) == 3:
|
---|
657 | if bits[2] == 'parsed':
|
---|
658 | parsed = True
|
---|
659 | else:
|
---|
660 | raise TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
|
---|
661 | return SsiNode(bits[1], parsed)
|
---|
662 | ssi = register.tag(ssi)
|
---|
663 |
|
---|
664 | #@register.tag
|
---|
665 | def load(parser, token):
|
---|
666 | """
|
---|
667 | Load a custom template tag set.
|
---|
668 |
|
---|
669 | For example, to load the template tags in ``django/templatetags/news/photos.py``::
|
---|
670 |
|
---|
671 | {% load news.photos %}
|
---|
672 | """
|
---|
673 | bits = token.contents.split()
|
---|
674 | for taglib in bits[1:]:
|
---|
675 | # add the library to the parser
|
---|
676 | try:
|
---|
677 | lib = get_library("django.templatetags.%s" % taglib.split('.')[-1])
|
---|
678 | parser.add_library(lib)
|
---|
679 | except InvalidTemplateLibrary, e:
|
---|
680 | raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e)
|
---|
681 | return LoadNode()
|
---|
682 | load = register.tag(load)
|
---|
683 |
|
---|
684 | #@register.tag
|
---|
685 | def now(parser, token):
|
---|
686 | """
|
---|
687 | Display the date, formatted according to the given string.
|
---|
688 |
|
---|
689 | Uses the same format as PHP's ``date()`` function; see http://php.net/date
|
---|
690 | for all the possible values.
|
---|
691 |
|
---|
692 | Sample usage::
|
---|
693 |
|
---|
694 | It is {% now "jS F Y H:i" %}
|
---|
695 | """
|
---|
696 | bits = token.contents.split('"')
|
---|
697 | if len(bits) != 3:
|
---|
698 | raise TemplateSyntaxError, "'now' statement takes one argument"
|
---|
699 | format_string = bits[1]
|
---|
700 | return NowNode(format_string)
|
---|
701 | now = register.tag(now)
|
---|
702 |
|
---|
703 | #@register.tag
|
---|
704 | def regroup(parser, token):
|
---|
705 | """
|
---|
706 | Regroup a list of alike objects by a common attribute.
|
---|
707 |
|
---|
708 | This complex tag is best illustrated by use of an example: say that
|
---|
709 | ``people`` is a list of ``Person`` objects that have ``first_name``,
|
---|
710 | ``last_name``, and ``gender`` attributes, and you'd like to display a list
|
---|
711 | that looks like:
|
---|
712 |
|
---|
713 | * Male:
|
---|
714 | * George Bush
|
---|
715 | * Bill Clinton
|
---|
716 | * Female:
|
---|
717 | * Margaret Thatcher
|
---|
718 | * Colendeeza Rice
|
---|
719 | * Unknown:
|
---|
720 | * Pat Smith
|
---|
721 |
|
---|
722 | The following snippet of template code would accomplish this dubious task::
|
---|
723 |
|
---|
724 | {% regroup people by gender as grouped %}
|
---|
725 | <ul>
|
---|
726 | {% for group in grouped %}
|
---|
727 | <li>{{ group.grouper }}
|
---|
728 | <ul>
|
---|
729 | {% for item in group.list %}
|
---|
730 | <li>{{ item }}</li>
|
---|
731 | {% endfor %}
|
---|
732 | </ul>
|
---|
733 | {% endfor %}
|
---|
734 | </ul>
|
---|
735 |
|
---|
736 | As you can see, ``{% regroup %}`` populates a variable with a list of
|
---|
737 | objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the
|
---|
738 | item that was grouped by; ``list`` contains the list of objects that share
|
---|
739 | that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female``
|
---|
740 | and ``Unknown``, and ``list`` is the list of people with those genders.
|
---|
741 |
|
---|
742 | Note that `{% regroup %}`` does not work when the list to be grouped is not
|
---|
743 | sorted by the key you are grouping by! This means that if your list of
|
---|
744 | people was not sorted by gender, you'd need to make sure it is sorted before
|
---|
745 | using it, i.e.::
|
---|
746 |
|
---|
747 | {% regroup people|dictsort:"gender" by gender as grouped %}
|
---|
748 |
|
---|
749 | """
|
---|
750 | firstbits = token.contents.split(None, 3)
|
---|
751 | if len(firstbits) != 4:
|
---|
752 | raise TemplateSyntaxError, "'regroup' tag takes five arguments"
|
---|
753 | target = parser.compile_filter(firstbits[1])
|
---|
754 | if firstbits[2] != 'by':
|
---|
755 | raise TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'"
|
---|
756 | lastbits_reversed = firstbits[3][::-1].split(None, 2)
|
---|
757 | if lastbits_reversed[1][::-1] != 'as':
|
---|
758 | raise TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'"
|
---|
759 |
|
---|
760 | expression = parser.compile_filter('var.%s' % lastbits_reversed[2][::-1])
|
---|
761 |
|
---|
762 | var_name = lastbits_reversed[0][::-1]
|
---|
763 | return RegroupNode(target, expression, var_name)
|
---|
764 | regroup = register.tag(regroup)
|
---|
765 |
|
---|
766 | def spaceless(parser, token):
|
---|
767 | """
|
---|
768 | Normalize whitespace between HTML tags to a single space. This includes tab
|
---|
769 | characters and newlines.
|
---|
770 |
|
---|
771 | Example usage::
|
---|
772 |
|
---|
773 | {% spaceless %}
|
---|
774 | <p>
|
---|
775 | <a href="foo/">Foo</a>
|
---|
776 | </p>
|
---|
777 | {% endspaceless %}
|
---|
778 |
|
---|
779 | This example would return this HTML::
|
---|
780 |
|
---|
781 | <p> <a href="foo/">Foo</a> </p>
|
---|
782 |
|
---|
783 | Only space between *tags* is normalized -- not space between tags and text. In
|
---|
784 | this example, the space around ``Hello`` won't be stripped::
|
---|
785 |
|
---|
786 | {% spaceless %}
|
---|
787 | <strong>
|
---|
788 | Hello
|
---|
789 | </strong>
|
---|
790 | {% endspaceless %}
|
---|
791 | """
|
---|
792 | nodelist = parser.parse(('endspaceless',))
|
---|
793 | parser.delete_first_token()
|
---|
794 | return SpacelessNode(nodelist)
|
---|
795 | spaceless = register.tag(spaceless)
|
---|
796 |
|
---|
797 | #@register.tag
|
---|
798 | def templatetag(parser, token):
|
---|
799 | """
|
---|
800 | Output one of the bits used to compose template tags.
|
---|
801 |
|
---|
802 | Since the template system has no concept of "escaping", to display one of
|
---|
803 | the bits used in template tags, you must use the ``{% templatetag %}`` tag.
|
---|
804 |
|
---|
805 | The argument tells which template bit to output:
|
---|
806 |
|
---|
807 | ================== =======
|
---|
808 | Argument Outputs
|
---|
809 | ================== =======
|
---|
810 | ``openblock`` ``{%``
|
---|
811 | ``closeblock`` ``%}``
|
---|
812 | ``openvariable`` ``{{``
|
---|
813 | ``closevariable`` ``}}``
|
---|
814 | ================== =======
|
---|
815 | """
|
---|
816 | bits = token.contents.split()
|
---|
817 | if len(bits) != 2:
|
---|
818 | raise TemplateSyntaxError, "'templatetag' statement takes one argument"
|
---|
819 | tag = bits[1]
|
---|
820 | if not TemplateTagNode.mapping.has_key(tag):
|
---|
821 | raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
|
---|
822 | (tag, TemplateTagNode.mapping.keys())
|
---|
823 | return TemplateTagNode(tag)
|
---|
824 | templatetag = register.tag(templatetag)
|
---|
825 |
|
---|
826 | #@register.tag
|
---|
827 | def widthratio(parser, token):
|
---|
828 | """
|
---|
829 | For creating bar charts and such, this tag calculates the ratio of a given
|
---|
830 | value to a maximum value, and then applies that ratio to a constant.
|
---|
831 |
|
---|
832 | For example::
|
---|
833 |
|
---|
834 | <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
|
---|
835 |
|
---|
836 | Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
|
---|
837 | the above example will be 88 pixels wide (because 175/200 = .875; .875 *
|
---|
838 | 100 = 87.5 which is rounded up to 88).
|
---|
839 | """
|
---|
840 | bits = token.contents.split()
|
---|
841 | if len(bits) != 4:
|
---|
842 | raise TemplateSyntaxError("widthratio takes three arguments")
|
---|
843 | tag, this_value_expr, max_value_expr, max_width = bits
|
---|
844 | try:
|
---|
845 | max_width = int(max_width)
|
---|
846 | except ValueError:
|
---|
847 | raise TemplateSyntaxError("widthratio final argument must be an integer")
|
---|
848 | return WidthRatioNode(parser.compile_filter(this_value_expr),
|
---|
849 | parser.compile_filter(max_value_expr), max_width)
|
---|
850 | widthratio = register.tag(widthratio)
|
---|