OLD | NEW |
(Empty) | |
| 1 """Default tags used by the template system, available to all templates.""" |
| 2 from __future__ import unicode_literals |
| 3 |
| 4 import re |
| 5 import sys |
| 6 import warnings |
| 7 from collections import namedtuple |
| 8 from datetime import datetime |
| 9 from itertools import cycle as itertools_cycle, groupby |
| 10 |
| 11 from django.conf import settings |
| 12 from django.utils import six, timezone |
| 13 from django.utils.encoding import force_text |
| 14 from django.utils.html import conditional_escape, format_html |
| 15 from django.utils.lorem_ipsum import paragraphs, words |
| 16 from django.utils.safestring import mark_safe |
| 17 |
| 18 from .base import ( |
| 19 BLOCK_TAG_END, BLOCK_TAG_START, COMMENT_TAG_END, COMMENT_TAG_START, |
| 20 FILTER_SEPARATOR, SINGLE_BRACE_END, SINGLE_BRACE_START, |
| 21 VARIABLE_ATTRIBUTE_SEPARATOR, VARIABLE_TAG_END, VARIABLE_TAG_START, |
| 22 Context, Node, NodeList, TemplateSyntaxError, VariableDoesNotExist, |
| 23 kwarg_re, render_value_in_context, token_kwargs, |
| 24 ) |
| 25 from .defaultfilters import date |
| 26 from .library import Library |
| 27 from .smartif import IfParser, Literal |
| 28 |
| 29 register = Library() |
| 30 |
| 31 |
| 32 class AutoEscapeControlNode(Node): |
| 33 """Implements the actions of the autoescape tag.""" |
| 34 def __init__(self, setting, nodelist): |
| 35 self.setting, self.nodelist = setting, nodelist |
| 36 |
| 37 def render(self, context): |
| 38 old_setting = context.autoescape |
| 39 context.autoescape = self.setting |
| 40 output = self.nodelist.render(context) |
| 41 context.autoescape = old_setting |
| 42 if self.setting: |
| 43 return mark_safe(output) |
| 44 else: |
| 45 return output |
| 46 |
| 47 |
| 48 class CommentNode(Node): |
| 49 def render(self, context): |
| 50 return '' |
| 51 |
| 52 |
| 53 class CsrfTokenNode(Node): |
| 54 def render(self, context): |
| 55 csrf_token = context.get('csrf_token') |
| 56 if csrf_token: |
| 57 if csrf_token == 'NOTPROVIDED': |
| 58 return format_html("") |
| 59 else: |
| 60 return format_html("<input type='hidden' name='csrfmiddlewaretok
en' value='{}' />", csrf_token) |
| 61 else: |
| 62 # It's very probable that the token is missing because of |
| 63 # misconfiguration, so we raise a warning |
| 64 if settings.DEBUG: |
| 65 warnings.warn( |
| 66 "A {% csrf_token %} was used in a template, but the context
" |
| 67 "did not provide the value. This is usually caused by not " |
| 68 "using RequestContext." |
| 69 ) |
| 70 return '' |
| 71 |
| 72 |
| 73 class CycleNode(Node): |
| 74 def __init__(self, cyclevars, variable_name=None, silent=False): |
| 75 self.cyclevars = cyclevars |
| 76 self.variable_name = variable_name |
| 77 self.silent = silent |
| 78 |
| 79 def render(self, context): |
| 80 if self not in context.render_context: |
| 81 # First time the node is rendered in template |
| 82 context.render_context[self] = itertools_cycle(self.cyclevars) |
| 83 cycle_iter = context.render_context[self] |
| 84 value = next(cycle_iter).resolve(context) |
| 85 if self.variable_name: |
| 86 context.set_upward(self.variable_name, value) |
| 87 if self.silent: |
| 88 return '' |
| 89 return render_value_in_context(value, context) |
| 90 |
| 91 def reset(self, context): |
| 92 """ |
| 93 Reset the cycle iteration back to the beginning. |
| 94 """ |
| 95 context.render_context[self] = itertools_cycle(self.cyclevars) |
| 96 |
| 97 |
| 98 class DebugNode(Node): |
| 99 def render(self, context): |
| 100 from pprint import pformat |
| 101 output = [force_text(pformat(val)) for val in context] |
| 102 output.append('\n\n') |
| 103 output.append(force_text(pformat(sys.modules))) |
| 104 return ''.join(output) |
| 105 |
| 106 |
| 107 class FilterNode(Node): |
| 108 def __init__(self, filter_expr, nodelist): |
| 109 self.filter_expr, self.nodelist = filter_expr, nodelist |
| 110 |
| 111 def render(self, context): |
| 112 output = self.nodelist.render(context) |
| 113 # Apply filters. |
| 114 with context.push(var=output): |
| 115 return self.filter_expr.resolve(context) |
| 116 |
| 117 |
| 118 class FirstOfNode(Node): |
| 119 def __init__(self, variables, asvar=None): |
| 120 self.vars = variables |
| 121 self.asvar = asvar |
| 122 |
| 123 def render(self, context): |
| 124 for var in self.vars: |
| 125 value = var.resolve(context, True) |
| 126 if value: |
| 127 first = render_value_in_context(value, context) |
| 128 if self.asvar: |
| 129 context[self.asvar] = first |
| 130 return '' |
| 131 return first |
| 132 return '' |
| 133 |
| 134 |
| 135 class ForNode(Node): |
| 136 child_nodelists = ('nodelist_loop', 'nodelist_empty') |
| 137 |
| 138 def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_
empty=None): |
| 139 self.loopvars, self.sequence = loopvars, sequence |
| 140 self.is_reversed = is_reversed |
| 141 self.nodelist_loop = nodelist_loop |
| 142 if nodelist_empty is None: |
| 143 self.nodelist_empty = NodeList() |
| 144 else: |
| 145 self.nodelist_empty = nodelist_empty |
| 146 |
| 147 def __repr__(self): |
| 148 reversed_text = ' reversed' if self.is_reversed else '' |
| 149 return "<For Node: for %s in %s, tail_len: %d%s>" % \ |
| 150 (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop), |
| 151 reversed_text) |
| 152 |
| 153 def __iter__(self): |
| 154 for node in self.nodelist_loop: |
| 155 yield node |
| 156 for node in self.nodelist_empty: |
| 157 yield node |
| 158 |
| 159 def render(self, context): |
| 160 if 'forloop' in context: |
| 161 parentloop = context['forloop'] |
| 162 else: |
| 163 parentloop = {} |
| 164 with context.push(): |
| 165 try: |
| 166 values = self.sequence.resolve(context, True) |
| 167 except VariableDoesNotExist: |
| 168 values = [] |
| 169 if values is None: |
| 170 values = [] |
| 171 if not hasattr(values, '__len__'): |
| 172 values = list(values) |
| 173 len_values = len(values) |
| 174 if len_values < 1: |
| 175 return self.nodelist_empty.render(context) |
| 176 nodelist = [] |
| 177 if self.is_reversed: |
| 178 values = reversed(values) |
| 179 num_loopvars = len(self.loopvars) |
| 180 unpack = num_loopvars > 1 |
| 181 # Create a forloop value in the context. We'll update counters on e
ach |
| 182 # iteration just below. |
| 183 loop_dict = context['forloop'] = {'parentloop': parentloop} |
| 184 for i, item in enumerate(values): |
| 185 # Shortcuts for current loop iteration number. |
| 186 loop_dict['counter0'] = i |
| 187 loop_dict['counter'] = i + 1 |
| 188 # Reverse counter iteration numbers. |
| 189 loop_dict['revcounter'] = len_values - i |
| 190 loop_dict['revcounter0'] = len_values - i - 1 |
| 191 # Boolean values designating first and last times through loop. |
| 192 loop_dict['first'] = (i == 0) |
| 193 loop_dict['last'] = (i == len_values - 1) |
| 194 |
| 195 pop_context = False |
| 196 if unpack: |
| 197 # If there are multiple loop variables, unpack the item into |
| 198 # them. |
| 199 try: |
| 200 len_item = len(item) |
| 201 except TypeError: # not an iterable |
| 202 len_item = 1 |
| 203 # Check loop variable count before unpacking |
| 204 if num_loopvars != len_item: |
| 205 raise ValueError( |
| 206 "Need {} values to unpack in for loop; got {}. " |
| 207 .format(num_loopvars, len_item), |
| 208 ) |
| 209 unpacked_vars = dict(zip(self.loopvars, item)) |
| 210 pop_context = True |
| 211 context.update(unpacked_vars) |
| 212 else: |
| 213 context[self.loopvars[0]] = item |
| 214 |
| 215 for node in self.nodelist_loop: |
| 216 nodelist.append(node.render_annotated(context)) |
| 217 |
| 218 if pop_context: |
| 219 # The loop variables were pushed on to the context so pop th
em |
| 220 # off again. This is necessary because the tag lets the leng
th |
| 221 # of loopvars differ to the length of each set of items and
we |
| 222 # don't want to leave any vars from the previous loop on the |
| 223 # context. |
| 224 context.pop() |
| 225 return mark_safe(''.join(force_text(n) for n in nodelist)) |
| 226 |
| 227 |
| 228 class IfChangedNode(Node): |
| 229 child_nodelists = ('nodelist_true', 'nodelist_false') |
| 230 |
| 231 def __init__(self, nodelist_true, nodelist_false, *varlist): |
| 232 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false |
| 233 self._varlist = varlist |
| 234 |
| 235 def render(self, context): |
| 236 # Init state storage |
| 237 state_frame = self._get_context_stack_frame(context) |
| 238 if self not in state_frame: |
| 239 state_frame[self] = None |
| 240 |
| 241 nodelist_true_output = None |
| 242 try: |
| 243 if self._varlist: |
| 244 # Consider multiple parameters. This automatically behaves |
| 245 # like an OR evaluation of the multiple variables. |
| 246 compare_to = [var.resolve(context, True) for var in self._varlis
t] |
| 247 else: |
| 248 # The "{% ifchanged %}" syntax (without any variables) compares
the rendered output. |
| 249 compare_to = nodelist_true_output = self.nodelist_true.render(co
ntext) |
| 250 except VariableDoesNotExist: |
| 251 compare_to = None |
| 252 |
| 253 if compare_to != state_frame[self]: |
| 254 state_frame[self] = compare_to |
| 255 # render true block if not already rendered |
| 256 return nodelist_true_output or self.nodelist_true.render(context) |
| 257 elif self.nodelist_false: |
| 258 return self.nodelist_false.render(context) |
| 259 return '' |
| 260 |
| 261 def _get_context_stack_frame(self, context): |
| 262 # The Context object behaves like a stack where each template tag can cr
eate a new scope. |
| 263 # Find the place where to store the state to detect changes. |
| 264 if 'forloop' in context: |
| 265 # Ifchanged is bound to the local for loop. |
| 266 # When there is a loop-in-loop, the state is bound to the inner loop
, |
| 267 # so it resets when the outer loop continues. |
| 268 return context['forloop'] |
| 269 else: |
| 270 # Using ifchanged outside loops. Effectively this is a no-op because
the state is associated with 'self'. |
| 271 return context.render_context |
| 272 |
| 273 |
| 274 class IfEqualNode(Node): |
| 275 child_nodelists = ('nodelist_true', 'nodelist_false') |
| 276 |
| 277 def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): |
| 278 self.var1, self.var2 = var1, var2 |
| 279 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false |
| 280 self.negate = negate |
| 281 |
| 282 def __repr__(self): |
| 283 return "<IfEqualNode>" |
| 284 |
| 285 def render(self, context): |
| 286 val1 = self.var1.resolve(context, True) |
| 287 val2 = self.var2.resolve(context, True) |
| 288 if (self.negate and val1 != val2) or (not self.negate and val1 == val2): |
| 289 return self.nodelist_true.render(context) |
| 290 return self.nodelist_false.render(context) |
| 291 |
| 292 |
| 293 class IfNode(Node): |
| 294 |
| 295 def __init__(self, conditions_nodelists): |
| 296 self.conditions_nodelists = conditions_nodelists |
| 297 |
| 298 def __repr__(self): |
| 299 return "<IfNode>" |
| 300 |
| 301 def __iter__(self): |
| 302 for _, nodelist in self.conditions_nodelists: |
| 303 for node in nodelist: |
| 304 yield node |
| 305 |
| 306 @property |
| 307 def nodelist(self): |
| 308 return NodeList(node for _, nodelist in self.conditions_nodelists for no
de in nodelist) |
| 309 |
| 310 def render(self, context): |
| 311 for condition, nodelist in self.conditions_nodelists: |
| 312 |
| 313 if condition is not None: # if / elif clause |
| 314 try: |
| 315 match = condition.eval(context) |
| 316 except VariableDoesNotExist: |
| 317 match = None |
| 318 else: # else clause |
| 319 match = True |
| 320 |
| 321 if match: |
| 322 return nodelist.render(context) |
| 323 |
| 324 return '' |
| 325 |
| 326 |
| 327 class LoremNode(Node): |
| 328 def __init__(self, count, method, common): |
| 329 self.count, self.method, self.common = count, method, common |
| 330 |
| 331 def render(self, context): |
| 332 try: |
| 333 count = int(self.count.resolve(context)) |
| 334 except (ValueError, TypeError): |
| 335 count = 1 |
| 336 if self.method == 'w': |
| 337 return words(count, common=self.common) |
| 338 else: |
| 339 paras = paragraphs(count, common=self.common) |
| 340 if self.method == 'p': |
| 341 paras = ['<p>%s</p>' % p for p in paras] |
| 342 return '\n\n'.join(paras) |
| 343 |
| 344 |
| 345 GroupedResult = namedtuple('GroupedResult', ['grouper', 'list']) |
| 346 |
| 347 |
| 348 class RegroupNode(Node): |
| 349 def __init__(self, target, expression, var_name): |
| 350 self.target, self.expression = target, expression |
| 351 self.var_name = var_name |
| 352 |
| 353 def resolve_expression(self, obj, context): |
| 354 # This method is called for each object in self.target. See regroup() |
| 355 # for the reason why we temporarily put the object in the context. |
| 356 context[self.var_name] = obj |
| 357 return self.expression.resolve(context, True) |
| 358 |
| 359 def render(self, context): |
| 360 obj_list = self.target.resolve(context, True) |
| 361 if obj_list is None: |
| 362 # target variable wasn't found in context; fail silently. |
| 363 context[self.var_name] = [] |
| 364 return '' |
| 365 # List of dictionaries in the format: |
| 366 # {'grouper': 'key', 'list': [list of contents]}. |
| 367 context[self.var_name] = [ |
| 368 GroupedResult(grouper=key, list=list(val)) |
| 369 for key, val in |
| 370 groupby(obj_list, lambda obj: self.resolve_expression(obj, context)) |
| 371 ] |
| 372 return '' |
| 373 |
| 374 |
| 375 class LoadNode(Node): |
| 376 def render(self, context): |
| 377 return '' |
| 378 |
| 379 |
| 380 class NowNode(Node): |
| 381 def __init__(self, format_string, asvar=None): |
| 382 self.format_string = format_string |
| 383 self.asvar = asvar |
| 384 |
| 385 def render(self, context): |
| 386 tzinfo = timezone.get_current_timezone() if settings.USE_TZ else None |
| 387 formatted = date(datetime.now(tz=tzinfo), self.format_string) |
| 388 |
| 389 if self.asvar: |
| 390 context[self.asvar] = formatted |
| 391 return '' |
| 392 else: |
| 393 return formatted |
| 394 |
| 395 |
| 396 class ResetCycleNode(Node): |
| 397 def __init__(self, node): |
| 398 self.node = node |
| 399 |
| 400 def render(self, context): |
| 401 self.node.reset(context) |
| 402 return '' |
| 403 |
| 404 |
| 405 class SpacelessNode(Node): |
| 406 def __init__(self, nodelist): |
| 407 self.nodelist = nodelist |
| 408 |
| 409 def render(self, context): |
| 410 from django.utils.html import strip_spaces_between_tags |
| 411 return strip_spaces_between_tags(self.nodelist.render(context).strip()) |
| 412 |
| 413 |
| 414 class TemplateTagNode(Node): |
| 415 mapping = {'openblock': BLOCK_TAG_START, |
| 416 'closeblock': BLOCK_TAG_END, |
| 417 'openvariable': VARIABLE_TAG_START, |
| 418 'closevariable': VARIABLE_TAG_END, |
| 419 'openbrace': SINGLE_BRACE_START, |
| 420 'closebrace': SINGLE_BRACE_END, |
| 421 'opencomment': COMMENT_TAG_START, |
| 422 'closecomment': COMMENT_TAG_END, |
| 423 } |
| 424 |
| 425 def __init__(self, tagtype): |
| 426 self.tagtype = tagtype |
| 427 |
| 428 def render(self, context): |
| 429 return self.mapping.get(self.tagtype, '') |
| 430 |
| 431 |
| 432 class URLNode(Node): |
| 433 def __init__(self, view_name, args, kwargs, asvar): |
| 434 self.view_name = view_name |
| 435 self.args = args |
| 436 self.kwargs = kwargs |
| 437 self.asvar = asvar |
| 438 |
| 439 def render(self, context): |
| 440 from django.urls import reverse, NoReverseMatch |
| 441 args = [arg.resolve(context) for arg in self.args] |
| 442 kwargs = { |
| 443 force_text(k, 'ascii'): v.resolve(context) |
| 444 for k, v in self.kwargs.items() |
| 445 } |
| 446 view_name = self.view_name.resolve(context) |
| 447 try: |
| 448 current_app = context.request.current_app |
| 449 except AttributeError: |
| 450 try: |
| 451 current_app = context.request.resolver_match.namespace |
| 452 except AttributeError: |
| 453 current_app = None |
| 454 # Try to look up the URL. If it fails, raise NoReverseMatch unless the |
| 455 # {% url ... as var %} construct is used, in which case return nothing. |
| 456 url = '' |
| 457 try: |
| 458 url = reverse(view_name, args=args, kwargs=kwargs, current_app=curre
nt_app) |
| 459 except NoReverseMatch: |
| 460 if self.asvar is None: |
| 461 raise |
| 462 |
| 463 if self.asvar: |
| 464 context[self.asvar] = url |
| 465 return '' |
| 466 else: |
| 467 if context.autoescape: |
| 468 url = conditional_escape(url) |
| 469 return url |
| 470 |
| 471 |
| 472 class VerbatimNode(Node): |
| 473 def __init__(self, content): |
| 474 self.content = content |
| 475 |
| 476 def render(self, context): |
| 477 return self.content |
| 478 |
| 479 |
| 480 class WidthRatioNode(Node): |
| 481 def __init__(self, val_expr, max_expr, max_width, asvar=None): |
| 482 self.val_expr = val_expr |
| 483 self.max_expr = max_expr |
| 484 self.max_width = max_width |
| 485 self.asvar = asvar |
| 486 |
| 487 def render(self, context): |
| 488 try: |
| 489 value = self.val_expr.resolve(context) |
| 490 max_value = self.max_expr.resolve(context) |
| 491 max_width = int(self.max_width.resolve(context)) |
| 492 except VariableDoesNotExist: |
| 493 return '' |
| 494 except (ValueError, TypeError): |
| 495 raise TemplateSyntaxError("widthratio final argument must be a numbe
r") |
| 496 try: |
| 497 value = float(value) |
| 498 max_value = float(max_value) |
| 499 ratio = (value / max_value) * max_width |
| 500 result = str(int(round(ratio))) |
| 501 except ZeroDivisionError: |
| 502 return '0' |
| 503 except (ValueError, TypeError, OverflowError): |
| 504 return '' |
| 505 |
| 506 if self.asvar: |
| 507 context[self.asvar] = result |
| 508 return '' |
| 509 else: |
| 510 return result |
| 511 |
| 512 |
| 513 class WithNode(Node): |
| 514 def __init__(self, var, name, nodelist, extra_context=None): |
| 515 self.nodelist = nodelist |
| 516 # var and name are legacy attributes, being left in case they are used |
| 517 # by third-party subclasses of this Node. |
| 518 self.extra_context = extra_context or {} |
| 519 if name: |
| 520 self.extra_context[name] = var |
| 521 |
| 522 def __repr__(self): |
| 523 return "<WithNode>" |
| 524 |
| 525 def render(self, context): |
| 526 values = {key: val.resolve(context) for key, val in |
| 527 six.iteritems(self.extra_context)} |
| 528 with context.push(**values): |
| 529 return self.nodelist.render(context) |
| 530 |
| 531 |
| 532 @register.tag |
| 533 def autoescape(parser, token): |
| 534 """ |
| 535 Force autoescape behavior for this block. |
| 536 """ |
| 537 # token.split_contents() isn't useful here because this tag doesn't accept v
ariable as arguments |
| 538 args = token.contents.split() |
| 539 if len(args) != 2: |
| 540 raise TemplateSyntaxError("'autoescape' tag requires exactly one argumen
t.") |
| 541 arg = args[1] |
| 542 if arg not in ('on', 'off'): |
| 543 raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'
") |
| 544 nodelist = parser.parse(('endautoescape',)) |
| 545 parser.delete_first_token() |
| 546 return AutoEscapeControlNode((arg == 'on'), nodelist) |
| 547 |
| 548 |
| 549 @register.tag |
| 550 def comment(parser, token): |
| 551 """ |
| 552 Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. |
| 553 """ |
| 554 parser.skip_past('endcomment') |
| 555 return CommentNode() |
| 556 |
| 557 |
| 558 @register.tag |
| 559 def cycle(parser, token): |
| 560 """ |
| 561 Cycles among the given strings each time this tag is encountered. |
| 562 |
| 563 Within a loop, cycles among the given strings each time through |
| 564 the loop:: |
| 565 |
| 566 {% for o in some_list %} |
| 567 <tr class="{% cycle 'row1' 'row2' %}"> |
| 568 ... |
| 569 </tr> |
| 570 {% endfor %} |
| 571 |
| 572 Outside of a loop, give the values a unique name the first time you call |
| 573 it, then use that name each successive time through:: |
| 574 |
| 575 <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr> |
| 576 <tr class="{% cycle rowcolors %}">...</tr> |
| 577 <tr class="{% cycle rowcolors %}">...</tr> |
| 578 |
| 579 You can use any number of values, separated by spaces. Commas can also |
| 580 be used to separate values; if a comma is used, the cycle values are |
| 581 interpreted as literal strings. |
| 582 |
| 583 The optional flag "silent" can be used to prevent the cycle declaration |
| 584 from returning any value:: |
| 585 |
| 586 {% for o in some_list %} |
| 587 {% cycle 'row1' 'row2' as rowcolors silent %} |
| 588 <tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr> |
| 589 {% endfor %} |
| 590 """ |
| 591 # Note: This returns the exact same node on each {% cycle name %} call; |
| 592 # that is, the node object returned from {% cycle a b c as name %} and the |
| 593 # one returned from {% cycle name %} are the exact same object. This |
| 594 # shouldn't cause problems (heh), but if it does, now you know. |
| 595 # |
| 596 # Ugly hack warning: This stuffs the named template dict into parser so |
| 597 # that names are only unique within each template (as opposed to using |
| 598 # a global variable, which would make cycle names have to be unique across |
| 599 # *all* templates. |
| 600 # |
| 601 # It keeps the last node in the parser to be able to reset it with |
| 602 # {% resetcycle %}. |
| 603 |
| 604 args = token.split_contents() |
| 605 |
| 606 if len(args) < 2: |
| 607 raise TemplateSyntaxError("'cycle' tag requires at least two arguments") |
| 608 |
| 609 if len(args) == 2: |
| 610 # {% cycle foo %} case. |
| 611 name = args[1] |
| 612 if not hasattr(parser, '_named_cycle_nodes'): |
| 613 raise TemplateSyntaxError("No named cycles in template. '%s' is not
defined" % name) |
| 614 if name not in parser._named_cycle_nodes: |
| 615 raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) |
| 616 return parser._named_cycle_nodes[name] |
| 617 |
| 618 as_form = False |
| 619 |
| 620 if len(args) > 4: |
| 621 # {% cycle ... as foo [silent] %} case. |
| 622 if args[-3] == "as": |
| 623 if args[-1] != "silent": |
| 624 raise TemplateSyntaxError("Only 'silent' flag is allowed after c
ycle's name, not '%s'." % args[-1]) |
| 625 as_form = True |
| 626 silent = True |
| 627 args = args[:-1] |
| 628 elif args[-2] == "as": |
| 629 as_form = True |
| 630 silent = False |
| 631 |
| 632 if as_form: |
| 633 name = args[-1] |
| 634 values = [parser.compile_filter(arg) for arg in args[1:-2]] |
| 635 node = CycleNode(values, name, silent=silent) |
| 636 if not hasattr(parser, '_named_cycle_nodes'): |
| 637 parser._named_cycle_nodes = {} |
| 638 parser._named_cycle_nodes[name] = node |
| 639 else: |
| 640 values = [parser.compile_filter(arg) for arg in args[1:]] |
| 641 node = CycleNode(values) |
| 642 parser._last_cycle_node = node |
| 643 return node |
| 644 |
| 645 |
| 646 @register.tag |
| 647 def csrf_token(parser, token): |
| 648 return CsrfTokenNode() |
| 649 |
| 650 |
| 651 @register.tag |
| 652 def debug(parser, token): |
| 653 """ |
| 654 Outputs a whole load of debugging information, including the current |
| 655 context and imported modules. |
| 656 |
| 657 Sample usage:: |
| 658 |
| 659 <pre> |
| 660 {% debug %} |
| 661 </pre> |
| 662 """ |
| 663 return DebugNode() |
| 664 |
| 665 |
| 666 @register.tag('filter') |
| 667 def do_filter(parser, token): |
| 668 """ |
| 669 Filters the contents of the block through variable filters. |
| 670 |
| 671 Filters can also be piped through each other, and they can have |
| 672 arguments -- just like in variable syntax. |
| 673 |
| 674 Sample usage:: |
| 675 |
| 676 {% filter force_escape|lower %} |
| 677 This text will be HTML-escaped, and will appear in lowercase. |
| 678 {% endfilter %} |
| 679 |
| 680 Note that the ``escape`` and ``safe`` filters are not acceptable arguments. |
| 681 Instead, use the ``autoescape`` tag to manage autoescaping for blocks of |
| 682 template code. |
| 683 """ |
| 684 # token.split_contents() isn't useful here because this tag doesn't accept v
ariable as arguments |
| 685 _, rest = token.contents.split(None, 1) |
| 686 filter_expr = parser.compile_filter("var|%s" % (rest)) |
| 687 for func, unused in filter_expr.filters: |
| 688 filter_name = getattr(func, '_filter_name', None) |
| 689 if filter_name in ('escape', 'safe'): |
| 690 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "a
utoescape" tag instead.' % filter_name) |
| 691 nodelist = parser.parse(('endfilter',)) |
| 692 parser.delete_first_token() |
| 693 return FilterNode(filter_expr, nodelist) |
| 694 |
| 695 |
| 696 @register.tag |
| 697 def firstof(parser, token): |
| 698 """ |
| 699 Outputs the first variable passed that is not False. |
| 700 |
| 701 Outputs nothing if all the passed variables are False. |
| 702 |
| 703 Sample usage:: |
| 704 |
| 705 {% firstof var1 var2 var3 as myvar %} |
| 706 |
| 707 This is equivalent to:: |
| 708 |
| 709 {% if var1 %} |
| 710 {{ var1 }} |
| 711 {% elif var2 %} |
| 712 {{ var2 }} |
| 713 {% elif var3 %} |
| 714 {{ var3 }} |
| 715 {% endif %} |
| 716 |
| 717 but obviously much cleaner! |
| 718 |
| 719 You can also use a literal string as a fallback value in case all |
| 720 passed variables are False:: |
| 721 |
| 722 {% firstof var1 var2 var3 "fallback value" %} |
| 723 |
| 724 If you want to disable auto-escaping of variables you can use:: |
| 725 |
| 726 {% autoescape off %} |
| 727 {% firstof var1 var2 var3 "<strong>fallback value</strong>" %} |
| 728 {% autoescape %} |
| 729 |
| 730 Or if only some variables should be escaped, you can use:: |
| 731 |
| 732 {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %} |
| 733 """ |
| 734 bits = token.split_contents()[1:] |
| 735 asvar = None |
| 736 if len(bits) < 1: |
| 737 raise TemplateSyntaxError("'firstof' statement requires at least one arg
ument") |
| 738 |
| 739 if len(bits) >= 2 and bits[-2] == 'as': |
| 740 asvar = bits[-1] |
| 741 bits = bits[:-2] |
| 742 return FirstOfNode([parser.compile_filter(bit) for bit in bits], asvar) |
| 743 |
| 744 |
| 745 @register.tag('for') |
| 746 def do_for(parser, token): |
| 747 """ |
| 748 Loops over each item in an array. |
| 749 |
| 750 For example, to display a list of athletes given ``athlete_list``:: |
| 751 |
| 752 <ul> |
| 753 {% for athlete in athlete_list %} |
| 754 <li>{{ athlete.name }}</li> |
| 755 {% endfor %} |
| 756 </ul> |
| 757 |
| 758 You can loop over a list in reverse by using |
| 759 ``{% for obj in list reversed %}``. |
| 760 |
| 761 You can also unpack multiple values from a two-dimensional array:: |
| 762 |
| 763 {% for key,value in dict.items %} |
| 764 {{ key }}: {{ value }} |
| 765 {% endfor %} |
| 766 |
| 767 The ``for`` tag can take an optional ``{% empty %}`` clause that will |
| 768 be displayed if the given array is empty or could not be found:: |
| 769 |
| 770 <ul> |
| 771 {% for athlete in athlete_list %} |
| 772 <li>{{ athlete.name }}</li> |
| 773 {% empty %} |
| 774 <li>Sorry, no athletes in this list.</li> |
| 775 {% endfor %} |
| 776 <ul> |
| 777 |
| 778 The above is equivalent to -- but shorter, cleaner, and possibly faster |
| 779 than -- the following:: |
| 780 |
| 781 <ul> |
| 782 {% if athlete_list %} |
| 783 {% for athlete in athlete_list %} |
| 784 <li>{{ athlete.name }}</li> |
| 785 {% endfor %} |
| 786 {% else %} |
| 787 <li>Sorry, no athletes in this list.</li> |
| 788 {% endif %} |
| 789 </ul> |
| 790 |
| 791 The for loop sets a number of variables available within the loop: |
| 792 |
| 793 ========================== ============================================
==== |
| 794 Variable Description |
| 795 ========================== ============================================
==== |
| 796 ``forloop.counter`` The current iteration of the loop (1-indexed
) |
| 797 ``forloop.counter0`` The current iteration of the loop (0-indexed
) |
| 798 ``forloop.revcounter`` The number of iterations from the end of the |
| 799 loop (1-indexed) |
| 800 ``forloop.revcounter0`` The number of iterations from the end of the |
| 801 loop (0-indexed) |
| 802 ``forloop.first`` True if this is the first time through the l
oop |
| 803 ``forloop.last`` True if this is the last time through the lo
op |
| 804 ``forloop.parentloop`` For nested loops, this is the loop "above" t
he |
| 805 current one |
| 806 ========================== ============================================
==== |
| 807 """ |
| 808 bits = token.split_contents() |
| 809 if len(bits) < 4: |
| 810 raise TemplateSyntaxError("'for' statements should have at least four" |
| 811 " words: %s" % token.contents) |
| 812 |
| 813 is_reversed = bits[-1] == 'reversed' |
| 814 in_index = -3 if is_reversed else -2 |
| 815 if bits[in_index] != 'in': |
| 816 raise TemplateSyntaxError("'for' statements should use the format" |
| 817 " 'for x in y': %s" % token.contents) |
| 818 |
| 819 invalid_chars = frozenset((' ', '"', "'", FILTER_SEPARATOR)) |
| 820 loopvars = re.split(r' *, *', ' '.join(bits[1:in_index])) |
| 821 for var in loopvars: |
| 822 if not var or not invalid_chars.isdisjoint(var): |
| 823 raise TemplateSyntaxError("'for' tag received an invalid argument:" |
| 824 " %s" % token.contents) |
| 825 |
| 826 sequence = parser.compile_filter(bits[in_index + 1]) |
| 827 nodelist_loop = parser.parse(('empty', 'endfor',)) |
| 828 token = parser.next_token() |
| 829 if token.contents == 'empty': |
| 830 nodelist_empty = parser.parse(('endfor',)) |
| 831 parser.delete_first_token() |
| 832 else: |
| 833 nodelist_empty = None |
| 834 return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empt
y) |
| 835 |
| 836 |
| 837 def do_ifequal(parser, token, negate): |
| 838 bits = list(token.split_contents()) |
| 839 if len(bits) != 3: |
| 840 raise TemplateSyntaxError("%r takes two arguments" % bits[0]) |
| 841 end_tag = 'end' + bits[0] |
| 842 nodelist_true = parser.parse(('else', end_tag)) |
| 843 token = parser.next_token() |
| 844 if token.contents == 'else': |
| 845 nodelist_false = parser.parse((end_tag,)) |
| 846 parser.delete_first_token() |
| 847 else: |
| 848 nodelist_false = NodeList() |
| 849 val1 = parser.compile_filter(bits[1]) |
| 850 val2 = parser.compile_filter(bits[2]) |
| 851 return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate) |
| 852 |
| 853 |
| 854 @register.tag |
| 855 def ifequal(parser, token): |
| 856 """ |
| 857 Outputs the contents of the block if the two arguments equal each other. |
| 858 |
| 859 Examples:: |
| 860 |
| 861 {% ifequal user.id comment.user_id %} |
| 862 ... |
| 863 {% endifequal %} |
| 864 |
| 865 {% ifnotequal user.id comment.user_id %} |
| 866 ... |
| 867 {% else %} |
| 868 ... |
| 869 {% endifnotequal %} |
| 870 """ |
| 871 return do_ifequal(parser, token, False) |
| 872 |
| 873 |
| 874 @register.tag |
| 875 def ifnotequal(parser, token): |
| 876 """ |
| 877 Outputs the contents of the block if the two arguments are not equal. |
| 878 See ifequal. |
| 879 """ |
| 880 return do_ifequal(parser, token, True) |
| 881 |
| 882 |
| 883 class TemplateLiteral(Literal): |
| 884 def __init__(self, value, text): |
| 885 self.value = value |
| 886 self.text = text # for better error messages |
| 887 |
| 888 def display(self): |
| 889 return self.text |
| 890 |
| 891 def eval(self, context): |
| 892 return self.value.resolve(context, ignore_failures=True) |
| 893 |
| 894 |
| 895 class TemplateIfParser(IfParser): |
| 896 error_class = TemplateSyntaxError |
| 897 |
| 898 def __init__(self, parser, *args, **kwargs): |
| 899 self.template_parser = parser |
| 900 super(TemplateIfParser, self).__init__(*args, **kwargs) |
| 901 |
| 902 def create_var(self, value): |
| 903 return TemplateLiteral(self.template_parser.compile_filter(value), value
) |
| 904 |
| 905 |
| 906 @register.tag('if') |
| 907 def do_if(parser, token): |
| 908 """ |
| 909 The ``{% if %}`` tag evaluates a variable, and if that variable is "true" |
| 910 (i.e., exists, is not empty, and is not a false boolean value), the |
| 911 contents of the block are output: |
| 912 |
| 913 :: |
| 914 |
| 915 {% if athlete_list %} |
| 916 Number of athletes: {{ athlete_list|count }} |
| 917 {% elif athlete_in_locker_room_list %} |
| 918 Athletes should be out of the locker room soon! |
| 919 {% else %} |
| 920 No athletes. |
| 921 {% endif %} |
| 922 |
| 923 In the above, if ``athlete_list`` is not empty, the number of athletes will |
| 924 be displayed by the ``{{ athlete_list|count }}`` variable. |
| 925 |
| 926 As you can see, the ``if`` tag may take one or several `` {% elif %}`` |
| 927 clauses, as well as an ``{% else %}`` clause that will be displayed if all |
| 928 previous conditions fail. These clauses are optional. |
| 929 |
| 930 ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of |
| 931 variables or to negate a given variable:: |
| 932 |
| 933 {% if not athlete_list %} |
| 934 There are no athletes. |
| 935 {% endif %} |
| 936 |
| 937 {% if athlete_list or coach_list %} |
| 938 There are some athletes or some coaches. |
| 939 {% endif %} |
| 940 |
| 941 {% if athlete_list and coach_list %} |
| 942 Both athletes and coaches are available. |
| 943 {% endif %} |
| 944 |
| 945 {% if not athlete_list or coach_list %} |
| 946 There are no athletes, or there are some coaches. |
| 947 {% endif %} |
| 948 |
| 949 {% if athlete_list and not coach_list %} |
| 950 There are some athletes and absolutely no coaches. |
| 951 {% endif %} |
| 952 |
| 953 Comparison operators are also available, and the use of filters is also |
| 954 allowed, for example:: |
| 955 |
| 956 {% if articles|length >= 5 %}...{% endif %} |
| 957 |
| 958 Arguments and operators _must_ have a space between them, so |
| 959 ``{% if 1>2 %}`` is not a valid if tag. |
| 960 |
| 961 All supported operators are: ``or``, ``and``, ``in``, ``not in`` |
| 962 ``==``, ``!=``, ``>``, ``>=``, ``<`` and ``<=``. |
| 963 |
| 964 Operator precedence follows Python. |
| 965 """ |
| 966 # {% if ... %} |
| 967 bits = token.split_contents()[1:] |
| 968 condition = TemplateIfParser(parser, bits).parse() |
| 969 nodelist = parser.parse(('elif', 'else', 'endif')) |
| 970 conditions_nodelists = [(condition, nodelist)] |
| 971 token = parser.next_token() |
| 972 |
| 973 # {% elif ... %} (repeatable) |
| 974 while token.contents.startswith('elif'): |
| 975 bits = token.split_contents()[1:] |
| 976 condition = TemplateIfParser(parser, bits).parse() |
| 977 nodelist = parser.parse(('elif', 'else', 'endif')) |
| 978 conditions_nodelists.append((condition, nodelist)) |
| 979 token = parser.next_token() |
| 980 |
| 981 # {% else %} (optional) |
| 982 if token.contents == 'else': |
| 983 nodelist = parser.parse(('endif',)) |
| 984 conditions_nodelists.append((None, nodelist)) |
| 985 token = parser.next_token() |
| 986 |
| 987 # {% endif %} |
| 988 if token.contents != 'endif': |
| 989 raise TemplateSyntaxError('Malformed template tag at line {0}: "{1}"'.fo
rmat(token.lineno, token.contents)) |
| 990 |
| 991 return IfNode(conditions_nodelists) |
| 992 |
| 993 |
| 994 @register.tag |
| 995 def ifchanged(parser, token): |
| 996 """ |
| 997 Checks if a value has changed from the last iteration of a loop. |
| 998 |
| 999 The ``{% ifchanged %}`` block tag is used within a loop. It has two |
| 1000 possible uses. |
| 1001 |
| 1002 1. Checks its own rendered contents against its previous state and only |
| 1003 displays the content if it has changed. For example, this displays a |
| 1004 list of days, only displaying the month if it changes:: |
| 1005 |
| 1006 <h1>Archive for {{ year }}</h1> |
| 1007 |
| 1008 {% for date in days %} |
| 1009 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %} |
| 1010 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> |
| 1011 {% endfor %} |
| 1012 |
| 1013 2. If given one or more variables, check whether any variable has changed. |
| 1014 For example, the following shows the date every time it changes, while |
| 1015 showing the hour if either the hour or the date has changed:: |
| 1016 |
| 1017 {% for date in days %} |
| 1018 {% ifchanged date.date %} {{ date.date }} {% endifchanged %} |
| 1019 {% ifchanged date.hour date.date %} |
| 1020 {{ date.hour }} |
| 1021 {% endifchanged %} |
| 1022 {% endfor %} |
| 1023 """ |
| 1024 bits = token.split_contents() |
| 1025 nodelist_true = parser.parse(('else', 'endifchanged')) |
| 1026 token = parser.next_token() |
| 1027 if token.contents == 'else': |
| 1028 nodelist_false = parser.parse(('endifchanged',)) |
| 1029 parser.delete_first_token() |
| 1030 else: |
| 1031 nodelist_false = NodeList() |
| 1032 values = [parser.compile_filter(bit) for bit in bits[1:]] |
| 1033 return IfChangedNode(nodelist_true, nodelist_false, *values) |
| 1034 |
| 1035 |
| 1036 def find_library(parser, name): |
| 1037 try: |
| 1038 return parser.libraries[name] |
| 1039 except KeyError: |
| 1040 raise TemplateSyntaxError( |
| 1041 "'%s' is not a registered tag library. Must be one of:\n%s" % ( |
| 1042 name, "\n".join(sorted(parser.libraries.keys())), |
| 1043 ), |
| 1044 ) |
| 1045 |
| 1046 |
| 1047 def load_from_library(library, label, names): |
| 1048 """ |
| 1049 Return a subset of tags and filters from a library. |
| 1050 """ |
| 1051 subset = Library() |
| 1052 for name in names: |
| 1053 found = False |
| 1054 if name in library.tags: |
| 1055 found = True |
| 1056 subset.tags[name] = library.tags[name] |
| 1057 if name in library.filters: |
| 1058 found = True |
| 1059 subset.filters[name] = library.filters[name] |
| 1060 if found is False: |
| 1061 raise TemplateSyntaxError( |
| 1062 "'%s' is not a valid tag or filter in tag library '%s'" % ( |
| 1063 name, label, |
| 1064 ), |
| 1065 ) |
| 1066 return subset |
| 1067 |
| 1068 |
| 1069 @register.tag |
| 1070 def load(parser, token): |
| 1071 """ |
| 1072 Loads a custom template tag library into the parser. |
| 1073 |
| 1074 For example, to load the template tags in |
| 1075 ``django/templatetags/news/photos.py``:: |
| 1076 |
| 1077 {% load news.photos %} |
| 1078 |
| 1079 Can also be used to load an individual tag/filter from |
| 1080 a library:: |
| 1081 |
| 1082 {% load byline from news %} |
| 1083 """ |
| 1084 # token.split_contents() isn't useful here because this tag doesn't accept v
ariable as arguments |
| 1085 bits = token.contents.split() |
| 1086 if len(bits) >= 4 and bits[-2] == "from": |
| 1087 # from syntax is used; load individual tags from the library |
| 1088 name = bits[-1] |
| 1089 lib = find_library(parser, name) |
| 1090 subset = load_from_library(lib, name, bits[1:-2]) |
| 1091 parser.add_library(subset) |
| 1092 else: |
| 1093 # one or more libraries are specified; load and add them to the parser |
| 1094 for name in bits[1:]: |
| 1095 lib = find_library(parser, name) |
| 1096 parser.add_library(lib) |
| 1097 return LoadNode() |
| 1098 |
| 1099 |
| 1100 @register.tag |
| 1101 def lorem(parser, token): |
| 1102 """ |
| 1103 Creates random Latin text useful for providing test data in templates. |
| 1104 |
| 1105 Usage format:: |
| 1106 |
| 1107 {% lorem [count] [method] [random] %} |
| 1108 |
| 1109 ``count`` is a number (or variable) containing the number of paragraphs or |
| 1110 words to generate (default is 1). |
| 1111 |
| 1112 ``method`` is either ``w`` for words, ``p`` for HTML paragraphs, ``b`` for |
| 1113 plain-text paragraph blocks (default is ``b``). |
| 1114 |
| 1115 ``random`` is the word ``random``, which if given, does not use the common |
| 1116 paragraph (starting "Lorem ipsum dolor sit amet, consectetuer..."). |
| 1117 |
| 1118 Examples: |
| 1119 |
| 1120 * ``{% lorem %}`` will output the common "lorem ipsum" paragraph |
| 1121 * ``{% lorem 3 p %}`` will output the common "lorem ipsum" paragraph |
| 1122 and two random paragraphs each wrapped in HTML ``<p>`` tags |
| 1123 * ``{% lorem 2 w random %}`` will output two random latin words |
| 1124 """ |
| 1125 bits = list(token.split_contents()) |
| 1126 tagname = bits[0] |
| 1127 # Random bit |
| 1128 common = bits[-1] != 'random' |
| 1129 if not common: |
| 1130 bits.pop() |
| 1131 # Method bit |
| 1132 if bits[-1] in ('w', 'p', 'b'): |
| 1133 method = bits.pop() |
| 1134 else: |
| 1135 method = 'b' |
| 1136 # Count bit |
| 1137 if len(bits) > 1: |
| 1138 count = bits.pop() |
| 1139 else: |
| 1140 count = '1' |
| 1141 count = parser.compile_filter(count) |
| 1142 if len(bits) != 1: |
| 1143 raise TemplateSyntaxError("Incorrect format for %r tag" % tagname) |
| 1144 return LoremNode(count, method, common) |
| 1145 |
| 1146 |
| 1147 @register.tag |
| 1148 def now(parser, token): |
| 1149 """ |
| 1150 Displays the date, formatted according to the given string. |
| 1151 |
| 1152 Uses the same format as PHP's ``date()`` function; see http://php.net/date |
| 1153 for all the possible values. |
| 1154 |
| 1155 Sample usage:: |
| 1156 |
| 1157 It is {% now "jS F Y H:i" %} |
| 1158 """ |
| 1159 bits = token.split_contents() |
| 1160 asvar = None |
| 1161 if len(bits) == 4 and bits[-2] == 'as': |
| 1162 asvar = bits[-1] |
| 1163 bits = bits[:-2] |
| 1164 if len(bits) != 2: |
| 1165 raise TemplateSyntaxError("'now' statement takes one argument") |
| 1166 format_string = bits[1][1:-1] |
| 1167 return NowNode(format_string, asvar) |
| 1168 |
| 1169 |
| 1170 @register.tag |
| 1171 def regroup(parser, token): |
| 1172 """ |
| 1173 Regroups a list of alike objects by a common attribute. |
| 1174 |
| 1175 This complex tag is best illustrated by use of an example: say that |
| 1176 ``musicians`` is a list of ``Musician`` objects that have ``name`` and |
| 1177 ``instrument`` attributes, and you'd like to display a list that |
| 1178 looks like: |
| 1179 |
| 1180 * Guitar: |
| 1181 * Django Reinhardt |
| 1182 * Emily Remler |
| 1183 * Piano: |
| 1184 * Lovie Austin |
| 1185 * Bud Powell |
| 1186 * Trumpet: |
| 1187 * Duke Ellington |
| 1188 |
| 1189 The following snippet of template code would accomplish this dubious task:: |
| 1190 |
| 1191 {% regroup musicians by instrument as grouped %} |
| 1192 <ul> |
| 1193 {% for group in grouped %} |
| 1194 <li>{{ group.grouper }} |
| 1195 <ul> |
| 1196 {% for musician in group.list %} |
| 1197 <li>{{ musician.name }}</li> |
| 1198 {% endfor %} |
| 1199 </ul> |
| 1200 {% endfor %} |
| 1201 </ul> |
| 1202 |
| 1203 As you can see, ``{% regroup %}`` populates a variable with a list of |
| 1204 objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the |
| 1205 item that was grouped by; ``list`` contains the list of objects that share |
| 1206 that ``grouper``. In this case, ``grouper`` would be ``Guitar``, ``Piano`` |
| 1207 and ``Trumpet``, and ``list`` is the list of musicians who play this |
| 1208 instrument. |
| 1209 |
| 1210 Note that ``{% regroup %}`` does not work when the list to be grouped is not |
| 1211 sorted by the key you are grouping by! This means that if your list of |
| 1212 musicians was not sorted by instrument, you'd need to make sure it is sorted |
| 1213 before using it, i.e.:: |
| 1214 |
| 1215 {% regroup musicians|dictsort:"instrument" by instrument as grouped %} |
| 1216 """ |
| 1217 bits = token.split_contents() |
| 1218 if len(bits) != 6: |
| 1219 raise TemplateSyntaxError("'regroup' tag takes five arguments") |
| 1220 target = parser.compile_filter(bits[1]) |
| 1221 if bits[2] != 'by': |
| 1222 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'
") |
| 1223 if bits[4] != 'as': |
| 1224 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" |
| 1225 " be 'as'") |
| 1226 var_name = bits[5] |
| 1227 # RegroupNode will take each item in 'target', put it in the context under |
| 1228 # 'var_name', evaluate 'var_name'.'expression' in the current context, and |
| 1229 # group by the resulting value. After all items are processed, it will |
| 1230 # save the final result in the context under 'var_name', thus clearing the |
| 1231 # temporary values. This hack is necessary because the template engine |
| 1232 # doesn't provide a context-aware equivalent of Python's getattr. |
| 1233 expression = parser.compile_filter(var_name + |
| 1234 VARIABLE_ATTRIBUTE_SEPARATOR + |
| 1235 bits[3]) |
| 1236 return RegroupNode(target, expression, var_name) |
| 1237 |
| 1238 |
| 1239 @register.tag |
| 1240 def resetcycle(parser, token): |
| 1241 """ |
| 1242 Resets a cycle tag. |
| 1243 |
| 1244 If an argument is given, resets the last rendered cycle tag whose name |
| 1245 matches the argument, else resets the last rendered cycle tag (named or |
| 1246 unnamed). |
| 1247 """ |
| 1248 args = token.split_contents() |
| 1249 |
| 1250 if len(args) > 2: |
| 1251 raise TemplateSyntaxError("%r tag accepts at most one argument." % args[
0]) |
| 1252 |
| 1253 if len(args) == 2: |
| 1254 name = args[1] |
| 1255 try: |
| 1256 return ResetCycleNode(parser._named_cycle_nodes[name]) |
| 1257 except (AttributeError, KeyError): |
| 1258 raise TemplateSyntaxError("Named cycle '%s' does not exist." % name) |
| 1259 try: |
| 1260 return ResetCycleNode(parser._last_cycle_node) |
| 1261 except AttributeError: |
| 1262 raise TemplateSyntaxError("No cycles in template.") |
| 1263 |
| 1264 |
| 1265 @register.tag |
| 1266 def spaceless(parser, token): |
| 1267 """ |
| 1268 Removes whitespace between HTML tags, including tab and newline characters. |
| 1269 |
| 1270 Example usage:: |
| 1271 |
| 1272 {% spaceless %} |
| 1273 <p> |
| 1274 <a href="foo/">Foo</a> |
| 1275 </p> |
| 1276 {% endspaceless %} |
| 1277 |
| 1278 This example would return this HTML:: |
| 1279 |
| 1280 <p><a href="foo/">Foo</a></p> |
| 1281 |
| 1282 Only space between *tags* is normalized -- not space between tags and text. |
| 1283 In this example, the space around ``Hello`` won't be stripped:: |
| 1284 |
| 1285 {% spaceless %} |
| 1286 <strong> |
| 1287 Hello |
| 1288 </strong> |
| 1289 {% endspaceless %} |
| 1290 """ |
| 1291 nodelist = parser.parse(('endspaceless',)) |
| 1292 parser.delete_first_token() |
| 1293 return SpacelessNode(nodelist) |
| 1294 |
| 1295 |
| 1296 @register.tag |
| 1297 def templatetag(parser, token): |
| 1298 """ |
| 1299 Outputs one of the bits used to compose template tags. |
| 1300 |
| 1301 Since the template system has no concept of "escaping", to display one of |
| 1302 the bits used in template tags, you must use the ``{% templatetag %}`` tag. |
| 1303 |
| 1304 The argument tells which template bit to output: |
| 1305 |
| 1306 ================== ======= |
| 1307 Argument Outputs |
| 1308 ================== ======= |
| 1309 ``openblock`` ``{%`` |
| 1310 ``closeblock`` ``%}`` |
| 1311 ``openvariable`` ``{{`` |
| 1312 ``closevariable`` ``}}`` |
| 1313 ``openbrace`` ``{`` |
| 1314 ``closebrace`` ``}`` |
| 1315 ``opencomment`` ``{#`` |
| 1316 ``closecomment`` ``#}`` |
| 1317 ================== ======= |
| 1318 """ |
| 1319 # token.split_contents() isn't useful here because this tag doesn't accept v
ariable as arguments |
| 1320 bits = token.contents.split() |
| 1321 if len(bits) != 2: |
| 1322 raise TemplateSyntaxError("'templatetag' statement takes one argument") |
| 1323 tag = bits[1] |
| 1324 if tag not in TemplateTagNode.mapping: |
| 1325 raise TemplateSyntaxError("Invalid templatetag argument: '%s'." |
| 1326 " Must be one of: %s" % |
| 1327 (tag, list(TemplateTagNode.mapping))) |
| 1328 return TemplateTagNode(tag) |
| 1329 |
| 1330 |
| 1331 @register.tag |
| 1332 def url(parser, token): |
| 1333 r""" |
| 1334 Return an absolute URL matching the given view with its parameters. |
| 1335 |
| 1336 This is a way to define links that aren't tied to a particular URL |
| 1337 configuration:: |
| 1338 |
| 1339 {% url "url_name" arg1 arg2 %} |
| 1340 |
| 1341 or |
| 1342 |
| 1343 {% url "url_name" name1=value1 name2=value2 %} |
| 1344 |
| 1345 The first argument is a django.conf.urls.url() name. Other arguments are |
| 1346 space-separated values that will be filled in place of positional and |
| 1347 keyword arguments in the URL. Don't mix positional and keyword arguments. |
| 1348 All arguments for the URL must be present. |
| 1349 |
| 1350 For example, if you have a view ``app_name.views.client_details`` taking |
| 1351 the client's id and the corresponding line in a URLconf looks like this:: |
| 1352 |
| 1353 url('^client/(\d+)/$', views.client_details, name='client-detail-view') |
| 1354 |
| 1355 and this app's URLconf is included into the project's URLconf under some |
| 1356 path:: |
| 1357 |
| 1358 url('^clients/', include('app_name.urls')) |
| 1359 |
| 1360 then in a template you can create a link for a certain client like this:: |
| 1361 |
| 1362 {% url "client-detail-view" client.id %} |
| 1363 |
| 1364 The URL will look like ``/clients/client/123/``. |
| 1365 |
| 1366 The first argument may also be the name of a template variable that will be |
| 1367 evaluated to obtain the view name or the URL name, e.g.:: |
| 1368 |
| 1369 {% with url_name="client-detail-view" %} |
| 1370 {% url url_name client.id %} |
| 1371 {% endwith %} |
| 1372 """ |
| 1373 bits = token.split_contents() |
| 1374 if len(bits) < 2: |
| 1375 raise TemplateSyntaxError("'%s' takes at least one argument, the name of
a url()." % bits[0]) |
| 1376 viewname = parser.compile_filter(bits[1]) |
| 1377 args = [] |
| 1378 kwargs = {} |
| 1379 asvar = None |
| 1380 bits = bits[2:] |
| 1381 if len(bits) >= 2 and bits[-2] == 'as': |
| 1382 asvar = bits[-1] |
| 1383 bits = bits[:-2] |
| 1384 |
| 1385 if len(bits): |
| 1386 for bit in bits: |
| 1387 match = kwarg_re.match(bit) |
| 1388 if not match: |
| 1389 raise TemplateSyntaxError("Malformed arguments to url tag") |
| 1390 name, value = match.groups() |
| 1391 if name: |
| 1392 kwargs[name] = parser.compile_filter(value) |
| 1393 else: |
| 1394 args.append(parser.compile_filter(value)) |
| 1395 |
| 1396 return URLNode(viewname, args, kwargs, asvar) |
| 1397 |
| 1398 |
| 1399 @register.tag |
| 1400 def verbatim(parser, token): |
| 1401 """ |
| 1402 Stops the template engine from rendering the contents of this block tag. |
| 1403 |
| 1404 Usage:: |
| 1405 |
| 1406 {% verbatim %} |
| 1407 {% don't process this %} |
| 1408 {% endverbatim %} |
| 1409 |
| 1410 You can also designate a specific closing tag block (allowing the |
| 1411 unrendered use of ``{% endverbatim %}``):: |
| 1412 |
| 1413 {% verbatim myblock %} |
| 1414 ... |
| 1415 {% endverbatim myblock %} |
| 1416 """ |
| 1417 nodelist = parser.parse(('endverbatim',)) |
| 1418 parser.delete_first_token() |
| 1419 return VerbatimNode(nodelist.render(Context())) |
| 1420 |
| 1421 |
| 1422 @register.tag |
| 1423 def widthratio(parser, token): |
| 1424 """ |
| 1425 For creating bar charts and such, this tag calculates the ratio of a given |
| 1426 value to a maximum value, and then applies that ratio to a constant. |
| 1427 |
| 1428 For example:: |
| 1429 |
| 1430 <img src="bar.png" alt="Bar" |
| 1431 height="10" width="{% widthratio this_value max_value max_width %}"
/> |
| 1432 |
| 1433 If ``this_value`` is 175, ``max_value`` is 200, and ``max_width`` is 100, |
| 1434 the image in the above example will be 88 pixels wide |
| 1435 (because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88). |
| 1436 |
| 1437 In some cases you might want to capture the result of widthratio in a |
| 1438 variable. It can be useful for instance in a blocktrans like this:: |
| 1439 |
| 1440 {% widthratio this_value max_value max_width as width %} |
| 1441 {% blocktrans %}The width is: {{ width }}{% endblocktrans %} |
| 1442 """ |
| 1443 bits = token.split_contents() |
| 1444 if len(bits) == 4: |
| 1445 tag, this_value_expr, max_value_expr, max_width = bits |
| 1446 asvar = None |
| 1447 elif len(bits) == 6: |
| 1448 tag, this_value_expr, max_value_expr, max_width, as_, asvar = bits |
| 1449 if as_ != 'as': |
| 1450 raise TemplateSyntaxError("Invalid syntax in widthratio tag. Expecti
ng 'as' keyword") |
| 1451 else: |
| 1452 raise TemplateSyntaxError("widthratio takes at least three arguments") |
| 1453 |
| 1454 return WidthRatioNode(parser.compile_filter(this_value_expr), |
| 1455 parser.compile_filter(max_value_expr), |
| 1456 parser.compile_filter(max_width), |
| 1457 asvar=asvar) |
| 1458 |
| 1459 |
| 1460 @register.tag('with') |
| 1461 def do_with(parser, token): |
| 1462 """ |
| 1463 Adds one or more values to the context (inside of this block) for caching |
| 1464 and easy access. |
| 1465 |
| 1466 For example:: |
| 1467 |
| 1468 {% with total=person.some_sql_method %} |
| 1469 {{ total }} object{{ total|pluralize }} |
| 1470 {% endwith %} |
| 1471 |
| 1472 Multiple values can be added to the context:: |
| 1473 |
| 1474 {% with foo=1 bar=2 %} |
| 1475 ... |
| 1476 {% endwith %} |
| 1477 |
| 1478 The legacy format of ``{% with person.some_sql_method as total %}`` is |
| 1479 still accepted. |
| 1480 """ |
| 1481 bits = token.split_contents() |
| 1482 remaining_bits = bits[1:] |
| 1483 extra_context = token_kwargs(remaining_bits, parser, support_legacy=True) |
| 1484 if not extra_context: |
| 1485 raise TemplateSyntaxError("%r expected at least one variable " |
| 1486 "assignment" % bits[0]) |
| 1487 if remaining_bits: |
| 1488 raise TemplateSyntaxError("%r received an invalid token: %r" % |
| 1489 (bits[0], remaining_bits[0])) |
| 1490 nodelist = parser.parse(('endwith',)) |
| 1491 parser.delete_first_token() |
| 1492 return WithNode(None, None, nodelist, extra_context=extra_context) |
OLD | NEW |