Ticket #20434: patch2.2.patch
File patch2.2.patch, 105.9 KB (added by , 11 years ago) |
---|
-
django/template/defaulttags.py
commit c2144eddd05d8b15cbc4abcea8106bb09974ab6a Author: Jonathan Slenders <jonathan@slenders.be> Date: Wed Jun 5 22:46:34 2013 +0200 refactoring of the built-in tags to use the TemplateTag class. (and become introspectable.) diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 04e7a37..0d3480d 100644
a b from django.template.base import (Node, NodeList, Template, Context, Library, 14 14 SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END, 15 15 VARIABLE_ATTRIBUTE_SEPARATOR, get_library, token_kwargs, kwarg_re, 16 16 render_value_in_context) 17 from django.template.generic import TemplateTag, Grammar 17 18 from django.template.smartif import IfParser, Literal 18 19 from django.template.defaultfilters import date 19 20 from django.utils.encoding import smart_text … … from django.utils import timezone 24 25 25 26 register = Library() 26 27 27 class AutoEscapeControlNode(Node): 28 """Implements the actions of the autoescape tag.""" 29 def __init__(self, setting, nodelist): 30 self.setting, self.nodelist = setting, nodelist 28 @register.tag 29 class AutoEscapeControlNode(TemplateTag): 30 """ 31 Force autoescape behavior for this block. 32 """ 33 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 34 grammar = Grammar('autoescape endautoescape', _split_contents=False) 35 36 def __init__(self, parser, parse_result): 37 args = parse_result.arguments.split() 38 39 if len(args) != 1: 40 raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.") 41 arg = args[0] 42 if arg not in ('on', 'off'): 43 raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'") 44 45 self.setting = (arg == 'on') 31 46 32 47 def render(self, context): 33 48 old_setting = context.autoescape … … class CommentNode(Node): 43 58 def render(self, context): 44 59 return '' 45 60 46 class CsrfTokenNode(Node): 61 @register.tag 62 class CsrfTokenNode(TemplateTag): 63 grammar = Grammar('csrf_token') 64 47 65 def render(self, context): 48 66 csrf_token = context.get('csrf_token', None) 49 67 if csrf_token: … … class CsrfTokenNode(Node): 59 77 warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.") 60 78 return '' 61 79 62 class CycleNode(Node): 63 def __init__(self, cyclevars, variable_name=None, silent=False, escape=False): 64 self.cyclevars = cyclevars 65 self.variable_name = variable_name 66 self.silent = silent 67 self.escape = escape # only while the "future" version exists 80 @register.tag 81 class DebugNode(TemplateTag): 82 """ 83 Outputs a whole load of debugging information, including the current 84 context and imported modules. 68 85 69 def render(self, context): 70 if self not in context.render_context: 71 # First time the node is rendered in template 72 context.render_context[self] = itertools_cycle(self.cyclevars) 73 cycle_iter = context.render_context[self] 74 value = next(cycle_iter).resolve(context) 75 if self.variable_name: 76 context[self.variable_name] = value 77 if self.silent: 78 return '' 79 if not self.escape: 80 value = mark_safe(value) 81 return render_value_in_context(value, context) 86 Sample usage:: 87 88 <pre> 89 {% debug %} 90 </pre> 91 """ 92 grammar = Grammar('debug') 82 93 83 class DebugNode(Node):84 94 def render(self, context): 85 95 from pprint import pformat 86 96 output = [pformat(val) for val in context] … … class DebugNode(Node): 88 98 output.append(pformat(sys.modules)) 89 99 return ''.join(output) 90 100 91 class FilterNode(Node): 92 def __init__(self, filter_expr, nodelist): 93 self.filter_expr, self.nodelist = filter_expr, nodelist 101 @register.tag 102 class FilterNode(TemplateTag): 103 """ 104 Filters the contents of the block through variable filters. 105 106 Filters can also be piped through each other, and they can have 107 arguments -- just like in variable syntax. 108 109 Sample usage:: 110 111 {% filter force_escape|lower %} 112 This text will be HTML-escaped, and will appear in lowercase. 113 {% endfilter %} 114 115 Note that the ``escape`` and ``safe`` filters are not acceptable arguments. 116 Instead, use the ``autoescape`` tag to manage autoescaping for blocks of 117 template code. 118 """ 119 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 120 grammar = Grammar('filter endfilter', _split_contents=False) 121 122 def __init__(self, parser, parse_result): 123 filter_expr = parser.compile_filter("var|%s" % parse_result.arguments) 124 for func, unused in filter_expr.filters: 125 if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): 126 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) 127 128 self.filter_expr = filter_expr 94 129 95 130 def render(self, context): 96 131 output = self.nodelist.render(context) … … class FilterNode(Node): 100 135 context.pop() 101 136 return filtered 102 137 103 class FirstOfNode(Node): 104 def __init__(self, variables, escape=False): 105 self.vars = variables 106 self.escape = escape # only while the "future" version exists 138 139 @register.tag 140 class FirstOfNode(TemplateTag): 141 """ 142 Outputs the first variable passed that is not False, without escaping. 143 144 Outputs nothing if all the passed variables are False. 145 146 Sample usage:: 147 148 {% firstof var1 var2 var3 %} 149 150 This is equivalent to:: 151 152 {% if var1 %} 153 {{ var1|safe }} 154 {% elif var2 %} 155 {{ var2|safe }} 156 {% elif var3 %} 157 {{ var3|safe }} 158 {% endif %} 159 160 but obviously much cleaner! 161 162 You can also use a literal string as a fallback value in case all 163 passed variables are False:: 164 165 {% firstof var1 var2 var3 "fallback value" %} 166 167 If you want to escape the output, use a filter tag:: 168 169 {% filter force_escape %} 170 {% firstof var1 var2 var3 "fallback value" %} 171 {% endfilter %} 172 173 """ 174 grammar = Grammar('firstof') 175 escape = False # Only while the "future" version exists 176 177 def __init__(self, parser, parse_result): 178 if not self.escape: 179 warnings.warn( 180 "'The `firstof` template tag is changing to escape its arguments; " 181 "the non-autoescaping version is deprecated. Load it " 182 "from the `future` tag library to start using the new behavior.", 183 PendingDeprecationWarning, stacklevel=2) 184 185 args = parse_result.arguments 186 if len(args) < 1: 187 raise TemplateSyntaxError("'firstof' statement requires at least one argument") 188 self.vars = [ parser.compile_filter(a) for a in args] 107 189 108 190 def render(self, context): 109 191 for var in self.vars: … … class FirstOfNode(Node): 114 196 return render_value_in_context(value, context) 115 197 return '' 116 198 117 class ForNode(Node): 199 @register.tag 200 class ForNode(TemplateTag): 201 """ 202 Loops over each item in an array. 203 204 For example, to display a list of athletes given ``athlete_list``:: 205 206 <ul> 207 {% for athlete in athlete_list %} 208 <li>{{ athlete.name }}</li> 209 {% endfor %} 210 </ul> 211 212 You can loop over a list in reverse by using 213 ``{% for obj in list reversed %}``. 214 215 You can also unpack multiple values from a two-dimensional array:: 216 217 {% for key,value in dict.items %} 218 {{ key }}: {{ value }} 219 {% endfor %} 220 221 The ``for`` tag can take an optional ``{% empty %}`` clause that will 222 be displayed if the given array is empty or could not be found:: 223 224 <ul> 225 {% for athlete in athlete_list %} 226 <li>{{ athlete.name }}</li> 227 {% empty %} 228 <li>Sorry, no athletes in this list.</li> 229 {% endfor %} 230 <ul> 231 232 The above is equivalent to -- but shorter, cleaner, and possibly faster 233 than -- the following:: 234 235 <ul> 236 {% if althete_list %} 237 {% for athlete in athlete_list %} 238 <li>{{ athlete.name }}</li> 239 {% endfor %} 240 {% else %} 241 <li>Sorry, no athletes in this list.</li> 242 {% endif %} 243 </ul> 244 245 The for loop sets a number of variables available within the loop: 246 247 ========================== ================================================ 248 Variable Description 249 ========================== ================================================ 250 ``forloop.counter`` The current iteration of the loop (1-indexed) 251 ``forloop.counter0`` The current iteration of the loop (0-indexed) 252 ``forloop.revcounter`` The number of iterations from the end of the 253 loop (1-indexed) 254 ``forloop.revcounter0`` The number of iterations from the end of the 255 loop (0-indexed) 256 ``forloop.first`` True if this is the first time through the loop 257 ``forloop.last`` True if this is the last time through the loop 258 ``forloop.parentloop`` For nested loops, this is the loop "above" the 259 current one 260 ========================== ================================================ 261 262 """ 263 grammar = Grammar('for empty? endfor') 118 264 child_nodelists = ('nodelist_loop', 'nodelist_empty') 119 265 120 def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None): 121 self.loopvars, self.sequence = loopvars, sequence 122 self.is_reversed = is_reversed 123 self.nodelist_loop = nodelist_loop 124 if nodelist_empty is None: 125 self.nodelist_empty = NodeList() 126 else: 127 self.nodelist_empty = nodelist_empty 266 def __init__(self, parser, parse_result): 267 self.nodelist_empty = NodeList() 268 269 for p in parse_result.parts: 270 if p.name == 'for': 271 bits = p.arguments 272 bit_contents = ' '.join(bits) 273 274 if len(bits) < 3: 275 raise TemplateSyntaxError("'for' statements should have at least four" 276 " words: %s" % bit_contents) 277 278 self.is_reversed = bits[-1] == 'reversed' 279 in_index = -3 if self.is_reversed else -2 280 if bits[in_index] != 'in': 281 raise TemplateSyntaxError("'for' statements should use the format" 282 " 'for x in y': %s" % bit_contents) 283 284 self.loopvars = re.split(r' *, *', ' '.join(bits[:in_index])) 285 for var in self.loopvars: 286 if not var or ' ' in var: 287 raise TemplateSyntaxError("'for' tag received an invalid argument:" 288 " %s" % bit_contents) 289 290 self.sequence = parser.compile_filter(bits[in_index+1]) 291 self.nodelist_loop = p.nodelist 292 293 elif p.name == 'empty': 294 self.nodelist_empty = p.nodelist 128 295 129 296 def __repr__(self): 130 297 reversed_text = ' reversed' if self.is_reversed else '' … … class ForNode(Node): 210 377 context.pop() 211 378 return nodelist.render(context) 212 379 213 class IfChangedNode(Node): 380 @register.tag 381 class IfChangedTag(TemplateTag): 382 """ 383 Checks if a value has changed from the last iteration of a loop. 384 385 The ``{% ifchanged %}`` block tag is used within a loop. It has two 386 possible uses. 387 388 1. Checks its own rendered contents against its previous state and only 389 displays the content if it has changed. For example, this displays a 390 list of days, only displaying the month if it changes:: 391 392 <h1>Archive for {{ year }}</h1> 393 394 {% for date in days %} 395 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %} 396 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> 397 {% endfor %} 398 399 2. If given one or more variables, check whether any variable has changed. 400 For example, the following shows the date every time it changes, while 401 showing the hour if either the hour or the date has changed:: 402 403 {% for date in days %} 404 {% ifchanged date.date %} {{ date.date }} {% endifchanged %} 405 {% ifchanged date.hour date.date %} 406 {{ date.hour }} 407 {% endifchanged %} 408 {% endfor %} 409 """ 410 grammar = Grammar('ifchanged else? endifchanged') 214 411 child_nodelists = ('nodelist_true', 'nodelist_false') 215 412 216 def __init__(self, nodelist_true, nodelist_false, *varlist): 217 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 218 self._varlist = varlist 413 def __init__(self, parser, parse_result): 414 self.nodelist_true = NodeList() 415 self.nodelist_false = NodeList() 416 417 for p in parse_result.parts: 418 if p.name == 'ifchanged': 419 self.nodelist_true = p.nodelist 420 self._varlist = [parser.compile_filter(a) for a in p.arguments] 421 elif p.name == 'else': 422 self.nodelist_false = p.nodelist 219 423 220 424 def render(self, context): 221 425 # Init state storage … … class IfChangedNode(Node): 254 458 # Using ifchanged outside loops. Effectively this is a no-op because the state is associated with 'self'. 255 459 return context.render_context 256 460 257 class IfEqualNode(Node): 461 462 @register.tag 463 class IfEqualNode(TemplateTag): 464 """ 465 Outputs the contents of the block if the two arguments equal each other. 466 467 Examples:: 468 469 {% ifequal user.id comment.user_id %} 470 ... 471 {% endifequal %} 472 473 {% ifnotequal user.id comment.user_id %} 474 ... 475 {% else %} 476 ... 477 {% endifnotequal %} 478 """ 479 grammar = Grammar('ifequal else? endifequal') 258 480 child_nodelists = ('nodelist_true', 'nodelist_false') 481 negate = False 482 483 def __init__(self, parser, parse_result): 484 self.nodelist_true = NodeList() 485 self.nodelist_false = NodeList() 486 self.var1 = self.val2 = None 259 487 260 def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): 261 self.var1, self.var2 = var1, var2 262 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 263 self.negate = negate 488 for p in parse_result.parts: 489 if p.name in ('ifequal', 'ifnotequal'): 490 if len(p.arguments) != 2: 491 raise TemplateSyntaxError("%r takes two arguments" % p.name) 492 493 self.nodelist_true = p.nodelist 494 self.var1 = parser.compile_filter(p.arguments[0]) 495 self.var2 = parser.compile_filter(p.arguments[1]) 496 elif p.name == 'else': 497 self.nodelist_false = p.nodelist 264 498 265 499 def __repr__(self): 266 500 return "<IfEqualNode>" … … class IfEqualNode(Node): 272 506 return self.nodelist_true.render(context) 273 507 return self.nodelist_false.render(context) 274 508 275 class IfNode(Node): 509 @register.tag 510 class IfNotEqualNode(IfEqualNode): 511 """ 512 Outputs the contents of the block if the two arguments are not equal. 513 See ifequal. 514 """ 515 grammar = Grammar('ifnotequal else? endifnotequal') 516 negate = True 276 517 277 def __init__(self, conditions_nodelists): 278 self.conditions_nodelists = conditions_nodelists 518 @register.tag 519 class IfNode(TemplateTag): 520 """ 521 The ``{% if %}`` tag evaluates a variable, and if that variable is "true" 522 (i.e., exists, is not empty, and is not a false boolean value), the 523 contents of the block are output: 279 524 280 def __repr__(self): 281 return "<IfNode>" 525 :: 282 526 283 def __iter__(self): 284 for _, nodelist in self.conditions_nodelists: 285 for node in nodelist: 286 yield node 527 {% if athlete_list %} 528 Number of athletes: {{ athlete_list|count }} 529 {% elif athlete_in_locker_room_list %} 530 Athletes should be out of the locker room soon! 531 {% else %} 532 No athletes. 533 {% endif %} 287 534 288 @property 289 def nodelist(self): 290 return NodeList(node for _, nodelist in self.conditions_nodelists for node in nodelist) 535 In the above, if ``athlete_list`` is not empty, the number of athletes will 536 be displayed by the ``{{ athlete_list|count }}`` variable. 291 537 292 def render(self, context): 293 for condition, nodelist in self.conditions_nodelists: 538 As you can see, the ``if`` tag may take one or several `` {% elif %}`` 539 clauses, as well as an ``{% else %}`` clause that will be displayed if all 540 previous conditions fail. These clauses are optional. 294 541 295 if condition is not None: # if / elif clause 296 try: 297 match = condition.eval(context) 298 except VariableDoesNotExist: 299 match = None 300 else: # else clause 301 match = True 542 ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of 543 variables or to negate a given variable:: 302 544 303 if match: 304 return nodelist.render(context) 305 306 return '' 307 308 class RegroupNode(Node): 309 def __init__(self, target, expression, var_name): 310 self.target, self.expression = target, expression 311 self.var_name = var_name 312 313 def resolve_expression(self, obj, context): 314 # This method is called for each object in self.target. See regroup() 315 # for the reason why we temporarily put the object in the context. 316 context[self.var_name] = obj 317 return self.expression.resolve(context, True) 318 319 def render(self, context): 320 obj_list = self.target.resolve(context, True) 321 if obj_list == None: 322 # target variable wasn't found in context; fail silently. 323 context[self.var_name] = [] 324 return '' 325 # List of dictionaries in the format: 326 # {'grouper': 'key', 'list': [list of contents]}. 327 context[self.var_name] = [ 328 {'grouper': key, 'list': list(val)} 329 for key, val in 330 groupby(obj_list, lambda obj: self.resolve_expression(obj, context)) 331 ] 332 return '' 333 334 def include_is_allowed(filepath): 335 for root in settings.ALLOWED_INCLUDE_ROOTS: 336 if filepath.startswith(root): 337 return True 338 return False 339 340 class SsiNode(Node): 341 def __init__(self, filepath, parsed): 342 self.filepath = filepath 343 self.parsed = parsed 344 345 def render(self, context): 346 filepath = self.filepath.resolve(context) 347 348 if not include_is_allowed(filepath): 349 if settings.DEBUG: 350 return "[Didn't have permission to include file]" 351 else: 352 return '' # Fail silently for invalid includes. 353 try: 354 with open(filepath, 'r') as fp: 355 output = fp.read() 356 except IOError: 357 output = '' 358 if self.parsed: 359 try: 360 t = Template(output, name=filepath) 361 return t.render(context) 362 except TemplateSyntaxError as e: 363 if settings.DEBUG: 364 return "[Included template had syntax error: %s]" % e 365 else: 366 return '' # Fail silently for invalid included templates. 367 return output 368 369 class LoadNode(Node): 370 def render(self, context): 371 return '' 372 373 class NowNode(Node): 374 def __init__(self, format_string): 375 self.format_string = format_string 376 377 def render(self, context): 378 tzinfo = timezone.get_current_timezone() if settings.USE_TZ else None 379 return date(datetime.now(tz=tzinfo), self.format_string) 380 381 class SpacelessNode(Node): 382 def __init__(self, nodelist): 383 self.nodelist = nodelist 384 385 def render(self, context): 386 from django.utils.html import strip_spaces_between_tags 387 return strip_spaces_between_tags(self.nodelist.render(context).strip()) 388 389 class TemplateTagNode(Node): 390 mapping = {'openblock': BLOCK_TAG_START, 391 'closeblock': BLOCK_TAG_END, 392 'openvariable': VARIABLE_TAG_START, 393 'closevariable': VARIABLE_TAG_END, 394 'openbrace': SINGLE_BRACE_START, 395 'closebrace': SINGLE_BRACE_END, 396 'opencomment': COMMENT_TAG_START, 397 'closecomment': COMMENT_TAG_END, 398 } 399 400 def __init__(self, tagtype): 401 self.tagtype = tagtype 402 403 def render(self, context): 404 return self.mapping.get(self.tagtype, '') 405 406 class URLNode(Node): 407 def __init__(self, view_name, args, kwargs, asvar): 408 self.view_name = view_name 409 self.args = args 410 self.kwargs = kwargs 411 self.asvar = asvar 412 413 def render(self, context): 414 from django.core.urlresolvers import reverse, NoReverseMatch 415 args = [arg.resolve(context) for arg in self.args] 416 kwargs = dict([(smart_text(k, 'ascii'), v.resolve(context)) 417 for k, v in self.kwargs.items()]) 418 419 view_name = self.view_name.resolve(context) 420 421 if not view_name: 422 raise NoReverseMatch("'url' requires a non-empty first argument. " 423 "The syntax changed in Django 1.5, see the docs.") 424 425 # Try to look up the URL twice: once given the view name, and again 426 # relative to what we guess is the "main" app. If they both fail, 427 # re-raise the NoReverseMatch unless we're using the 428 # {% url ... as var %} construct in which case return nothing. 429 url = '' 430 try: 431 url = reverse(view_name, args=args, kwargs=kwargs, current_app=context.current_app) 432 except NoReverseMatch: 433 exc_info = sys.exc_info() 434 if settings.SETTINGS_MODULE: 435 project_name = settings.SETTINGS_MODULE.split('.')[0] 436 try: 437 url = reverse(project_name + '.' + view_name, 438 args=args, kwargs=kwargs, 439 current_app=context.current_app) 440 except NoReverseMatch: 441 if self.asvar is None: 442 # Re-raise the original exception, not the one with 443 # the path relative to the project. This makes a 444 # better error message. 445 six.reraise(*exc_info) 446 else: 447 if self.asvar is None: 448 raise 449 450 if self.asvar: 451 context[self.asvar] = url 452 return '' 453 else: 454 return url 455 456 class VerbatimNode(Node): 457 def __init__(self, content): 458 self.content = content 459 460 def render(self, context): 461 return self.content 462 463 class WidthRatioNode(Node): 464 def __init__(self, val_expr, max_expr, max_width): 465 self.val_expr = val_expr 466 self.max_expr = max_expr 467 self.max_width = max_width 468 469 def render(self, context): 470 try: 471 value = self.val_expr.resolve(context) 472 max_value = self.max_expr.resolve(context) 473 max_width = int(self.max_width.resolve(context)) 474 except VariableDoesNotExist: 475 return '' 476 except (ValueError, TypeError): 477 raise TemplateSyntaxError("widthratio final argument must be a number") 478 try: 479 value = float(value) 480 max_value = float(max_value) 481 ratio = (value / max_value) * max_width 482 except ZeroDivisionError: 483 return '0' 484 except (ValueError, TypeError): 485 return '' 486 return str(int(round(ratio))) 487 488 class WithNode(Node): 489 def __init__(self, var, name, nodelist, extra_context=None): 490 self.nodelist = nodelist 491 # var and name are legacy attributes, being left in case they are used 492 # by third-party subclasses of this Node. 493 self.extra_context = extra_context or {} 494 if name: 495 self.extra_context[name] = var 496 497 def __repr__(self): 498 return "<WithNode>" 499 500 def render(self, context): 501 values = dict([(key, val.resolve(context)) for key, val in 502 six.iteritems(self.extra_context)]) 503 context.update(values) 504 output = self.nodelist.render(context) 505 context.pop() 506 return output 507 508 @register.tag 509 def autoescape(parser, token): 510 """ 511 Force autoescape behavior for this block. 512 """ 513 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 514 args = token.contents.split() 515 if len(args) != 2: 516 raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.") 517 arg = args[1] 518 if arg not in ('on', 'off'): 519 raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'") 520 nodelist = parser.parse(('endautoescape',)) 521 parser.delete_first_token() 522 return AutoEscapeControlNode((arg == 'on'), nodelist) 523 524 @register.tag 525 def comment(parser, token): 526 """ 527 Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. 528 """ 529 parser.skip_past('endcomment') 530 return CommentNode() 531 532 @register.tag 533 def cycle(parser, token, escape=False): 534 """ 535 Cycles among the given strings each time this tag is encountered. 536 537 Within a loop, cycles among the given strings each time through 538 the loop:: 539 540 {% for o in some_list %} 541 <tr class="{% cycle 'row1' 'row2' %}"> 542 ... 543 </tr> 544 {% endfor %} 545 546 Outside of a loop, give the values a unique name the first time you call 547 it, then use that name each sucessive time through:: 548 549 <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr> 550 <tr class="{% cycle rowcolors %}">...</tr> 551 <tr class="{% cycle rowcolors %}">...</tr> 552 553 You can use any number of values, separated by spaces. Commas can also 554 be used to separate values; if a comma is used, the cycle values are 555 interpreted as literal strings. 556 557 The optional flag "silent" can be used to prevent the cycle declaration 558 from returning any value:: 559 560 {% for o in some_list %} 561 {% cycle 'row1' 'row2' as rowcolors silent %} 562 <tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr> 563 {% endfor %} 564 565 """ 566 if not escape: 567 warnings.warn( 568 "'The `cycle` template tag is changing to escape its arguments; " 569 "the non-autoescaping version is deprecated. Load it " 570 "from the `future` tag library to start using the new behavior.", 571 PendingDeprecationWarning, stacklevel=2) 572 573 # Note: This returns the exact same node on each {% cycle name %} call; 574 # that is, the node object returned from {% cycle a b c as name %} and the 575 # one returned from {% cycle name %} are the exact same object. This 576 # shouldn't cause problems (heh), but if it does, now you know. 577 # 578 # Ugly hack warning: This stuffs the named template dict into parser so 579 # that names are only unique within each template (as opposed to using 580 # a global variable, which would make cycle names have to be unique across 581 # *all* templates. 582 583 args = token.split_contents() 584 585 if len(args) < 2: 586 raise TemplateSyntaxError("'cycle' tag requires at least two arguments") 587 588 if ',' in args[1]: 589 # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %} 590 # case. 591 args[1:2] = ['"%s"' % arg for arg in args[1].split(",")] 592 593 if len(args) == 2: 594 # {% cycle foo %} case. 595 name = args[1] 596 if not hasattr(parser, '_namedCycleNodes'): 597 raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name) 598 if not name in parser._namedCycleNodes: 599 raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) 600 return parser._namedCycleNodes[name] 601 602 as_form = False 603 604 if len(args) > 4: 605 # {% cycle ... as foo [silent] %} case. 606 if args[-3] == "as": 607 if args[-1] != "silent": 608 raise TemplateSyntaxError("Only 'silent' flag is allowed after cycle's name, not '%s'." % args[-1]) 609 as_form = True 610 silent = True 611 args = args[:-1] 612 elif args[-2] == "as": 613 as_form = True 614 silent = False 615 616 if as_form: 617 name = args[-1] 618 values = [parser.compile_filter(arg) for arg in args[1:-2]] 619 node = CycleNode(values, name, silent=silent, escape=escape) 620 if not hasattr(parser, '_namedCycleNodes'): 621 parser._namedCycleNodes = {} 622 parser._namedCycleNodes[name] = node 623 else: 624 values = [parser.compile_filter(arg) for arg in args[1:]] 625 node = CycleNode(values, escape=escape) 626 return node 627 628 @register.tag 629 def csrf_token(parser, token): 630 return CsrfTokenNode() 631 632 @register.tag 633 def debug(parser, token): 634 """ 635 Outputs a whole load of debugging information, including the current 636 context and imported modules. 637 638 Sample usage:: 639 640 <pre> 641 {% debug %} 642 </pre> 643 """ 644 return DebugNode() 645 646 @register.tag('filter') 647 def do_filter(parser, token): 648 """ 649 Filters the contents of the block through variable filters. 650 651 Filters can also be piped through each other, and they can have 652 arguments -- just like in variable syntax. 653 654 Sample usage:: 655 656 {% filter force_escape|lower %} 657 This text will be HTML-escaped, and will appear in lowercase. 658 {% endfilter %} 659 660 Note that the ``escape`` and ``safe`` filters are not acceptable arguments. 661 Instead, use the ``autoescape`` tag to manage autoescaping for blocks of 662 template code. 663 """ 664 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 665 _, rest = token.contents.split(None, 1) 666 filter_expr = parser.compile_filter("var|%s" % (rest)) 667 for func, unused in filter_expr.filters: 668 if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): 669 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) 670 nodelist = parser.parse(('endfilter',)) 671 parser.delete_first_token() 672 return FilterNode(filter_expr, nodelist) 673 674 @register.tag 675 def firstof(parser, token, escape=False): 676 """ 677 Outputs the first variable passed that is not False, without escaping. 678 679 Outputs nothing if all the passed variables are False. 680 681 Sample usage:: 682 683 {% firstof var1 var2 var3 %} 684 685 This is equivalent to:: 686 687 {% if var1 %} 688 {{ var1|safe }} 689 {% elif var2 %} 690 {{ var2|safe }} 691 {% elif var3 %} 692 {{ var3|safe }} 693 {% endif %} 694 695 but obviously much cleaner! 696 697 You can also use a literal string as a fallback value in case all 698 passed variables are False:: 699 700 {% firstof var1 var2 var3 "fallback value" %} 701 702 If you want to escape the output, use a filter tag:: 703 704 {% filter force_escape %} 705 {% firstof var1 var2 var3 "fallback value" %} 706 {% endfilter %} 707 708 """ 709 if not escape: 710 warnings.warn( 711 "'The `firstof` template tag is changing to escape its arguments; " 712 "the non-autoescaping version is deprecated. Load it " 713 "from the `future` tag library to start using the new behavior.", 714 PendingDeprecationWarning, stacklevel=2) 715 716 bits = token.split_contents()[1:] 717 if len(bits) < 1: 718 raise TemplateSyntaxError("'firstof' statement requires at least one argument") 719 return FirstOfNode([parser.compile_filter(bit) for bit in bits], escape=escape) 720 721 @register.tag('for') 722 def do_for(parser, token): 723 """ 724 Loops over each item in an array. 725 726 For example, to display a list of athletes given ``athlete_list``:: 727 728 <ul> 729 {% for athlete in athlete_list %} 730 <li>{{ athlete.name }}</li> 731 {% endfor %} 732 </ul> 733 734 You can loop over a list in reverse by using 735 ``{% for obj in list reversed %}``. 736 737 You can also unpack multiple values from a two-dimensional array:: 738 739 {% for key,value in dict.items %} 740 {{ key }}: {{ value }} 741 {% endfor %} 742 743 The ``for`` tag can take an optional ``{% empty %}`` clause that will 744 be displayed if the given array is empty or could not be found:: 745 746 <ul> 747 {% for athlete in athlete_list %} 748 <li>{{ athlete.name }}</li> 749 {% empty %} 750 <li>Sorry, no athletes in this list.</li> 751 {% endfor %} 752 <ul> 753 754 The above is equivalent to -- but shorter, cleaner, and possibly faster 755 than -- the following:: 756 757 <ul> 758 {% if althete_list %} 759 {% for athlete in athlete_list %} 760 <li>{{ athlete.name }}</li> 761 {% endfor %} 762 {% else %} 763 <li>Sorry, no athletes in this list.</li> 764 {% endif %} 765 </ul> 766 767 The for loop sets a number of variables available within the loop: 768 769 ========================== ================================================ 770 Variable Description 771 ========================== ================================================ 772 ``forloop.counter`` The current iteration of the loop (1-indexed) 773 ``forloop.counter0`` The current iteration of the loop (0-indexed) 774 ``forloop.revcounter`` The number of iterations from the end of the 775 loop (1-indexed) 776 ``forloop.revcounter0`` The number of iterations from the end of the 777 loop (0-indexed) 778 ``forloop.first`` True if this is the first time through the loop 779 ``forloop.last`` True if this is the last time through the loop 780 ``forloop.parentloop`` For nested loops, this is the loop "above" the 781 current one 782 ========================== ================================================ 783 784 """ 785 bits = token.split_contents() 786 if len(bits) < 4: 787 raise TemplateSyntaxError("'for' statements should have at least four" 788 " words: %s" % token.contents) 789 790 is_reversed = bits[-1] == 'reversed' 791 in_index = -3 if is_reversed else -2 792 if bits[in_index] != 'in': 793 raise TemplateSyntaxError("'for' statements should use the format" 794 " 'for x in y': %s" % token.contents) 795 796 loopvars = re.split(r' *, *', ' '.join(bits[1:in_index])) 797 for var in loopvars: 798 if not var or ' ' in var: 799 raise TemplateSyntaxError("'for' tag received an invalid argument:" 800 " %s" % token.contents) 801 802 sequence = parser.compile_filter(bits[in_index+1]) 803 nodelist_loop = parser.parse(('empty', 'endfor',)) 804 token = parser.next_token() 805 if token.contents == 'empty': 806 nodelist_empty = parser.parse(('endfor',)) 807 parser.delete_first_token() 808 else: 809 nodelist_empty = None 810 return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty) 811 812 def do_ifequal(parser, token, negate): 813 bits = list(token.split_contents()) 814 if len(bits) != 3: 815 raise TemplateSyntaxError("%r takes two arguments" % bits[0]) 816 end_tag = 'end' + bits[0] 817 nodelist_true = parser.parse(('else', end_tag)) 818 token = parser.next_token() 819 if token.contents == 'else': 820 nodelist_false = parser.parse((end_tag,)) 821 parser.delete_first_token() 822 else: 823 nodelist_false = NodeList() 824 val1 = parser.compile_filter(bits[1]) 825 val2 = parser.compile_filter(bits[2]) 826 return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate) 827 828 @register.tag 829 def ifequal(parser, token): 830 """ 831 Outputs the contents of the block if the two arguments equal each other. 832 833 Examples:: 834 835 {% ifequal user.id comment.user_id %} 836 ... 837 {% endifequal %} 838 839 {% ifnotequal user.id comment.user_id %} 840 ... 841 {% else %} 842 ... 843 {% endifnotequal %} 844 """ 845 return do_ifequal(parser, token, False) 846 847 @register.tag 848 def ifnotequal(parser, token): 849 """ 850 Outputs the contents of the block if the two arguments are not equal. 851 See ifequal. 852 """ 853 return do_ifequal(parser, token, True) 854 855 class TemplateLiteral(Literal): 856 def __init__(self, value, text): 857 self.value = value 858 self.text = text # for better error messages 859 860 def display(self): 861 return self.text 862 863 def eval(self, context): 864 return self.value.resolve(context, ignore_failures=True) 865 866 class TemplateIfParser(IfParser): 867 error_class = TemplateSyntaxError 868 869 def __init__(self, parser, *args, **kwargs): 870 self.template_parser = parser 871 super(TemplateIfParser, self).__init__(*args, **kwargs) 872 873 def create_var(self, value): 874 return TemplateLiteral(self.template_parser.compile_filter(value), value) 875 876 @register.tag('if') 877 def do_if(parser, token): 878 """ 879 The ``{% if %}`` tag evaluates a variable, and if that variable is "true" 880 (i.e., exists, is not empty, and is not a false boolean value), the 881 contents of the block are output: 882 883 :: 884 885 {% if athlete_list %} 886 Number of athletes: {{ athlete_list|count }} 887 {% elif athlete_in_locker_room_list %} 888 Athletes should be out of the locker room soon! 889 {% else %} 890 No athletes. 891 {% endif %} 892 893 In the above, if ``athlete_list`` is not empty, the number of athletes will 894 be displayed by the ``{{ athlete_list|count }}`` variable. 895 896 As you can see, the ``if`` tag may take one or several `` {% elif %}`` 897 clauses, as well as an ``{% else %}`` clause that will be displayed if all 898 previous conditions fail. These clauses are optional. 899 900 ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of 901 variables or to negate a given variable:: 902 903 {% if not athlete_list %} 904 There are no athletes. 905 {% endif %} 545 {% if not athlete_list %} 546 There are no athletes. 547 {% endif %} 906 548 907 549 {% if athlete_list or coach_list %} 908 550 There are some athletes or some coaches. … … def do_if(parser, token): 933 575 934 576 Operator precedence follows Python. 935 577 """ 936 # {% if ... %} 937 bits = token.split_contents()[1:] 938 condition = TemplateIfParser(parser, bits).parse() 939 nodelist = parser.parse(('elif', 'else', 'endif')) 940 conditions_nodelists = [(condition, nodelist)] 941 token = parser.next_token() 578 grammar = Grammar('if elif* else? endif') 942 579 943 # {% elif ... %} (repeatable) 944 while token.contents.startswith('elif'): 945 bits = token.split_contents()[1:] 946 condition = TemplateIfParser(parser, bits).parse() 947 nodelist = parser.parse(('elif', 'else', 'endif')) 948 conditions_nodelists.append((condition, nodelist)) 949 token = parser.next_token() 580 def __init__(self, parser, parse_result): 581 self.conditions_nodelists = [] 582 for p in parse_result.parts: 583 if p.name in ('if', 'elif'): 584 condition = TemplateIfParser(parser, p.arguments).parse() 585 self.conditions_nodelists.append( (condition, p.nodelist) ) 586 elif p.name == 'else': 587 self.conditions_nodelists.append( (None, p.nodelist) ) 588 589 def __repr__(self): 590 return "<IfNode>" 950 591 951 # {% else %} (optional) 952 if token.contents == 'else': 953 nodelist = parser.parse(('endif',)) 954 conditions_nodelists.append((None, nodelist)) 955 token = parser.next_token() 592 def render(self, context): 593 for condition, nodelist in self.conditions_nodelists: 956 594 957 # {% endif %} 958 assert token.contents == 'endif' 595 if condition is not None: # if / elif clause 596 try: 597 match = condition.eval(context) 598 except VariableDoesNotExist: 599 match = None 600 else: # else clause 601 match = True 959 602 960 return IfNode(conditions_nodelists) 603 if match: 604 return nodelist.render(context) 961 605 606 return '' 962 607 963 608 @register.tag 964 def ifchanged(parser, token):609 class RegroupNode(TemplateTag): 965 610 """ 966 Checks if a value has changed from the last iteration of a loop.611 Regroups a list of alike objects by a common attribute. 967 612 968 The ``{% ifchanged %}`` block tag is used within a loop. It has two 969 possible uses. 613 This complex tag is best illustrated by use of an example: say that 614 ``people`` is a list of ``Person`` objects that have ``first_name``, 615 ``last_name``, and ``gender`` attributes, and you'd like to display a list 616 that looks like: 970 617 971 1. Checks its own rendered contents against its previous state and only 972 displays the content if it has changed. For example, this displays a 973 list of days, only displaying the month if it changes:: 618 * Male: 619 * George Bush 620 * Bill Clinton 621 * Female: 622 * Margaret Thatcher 623 * Colendeeza Rice 624 * Unknown: 625 * Pat Smith 974 626 975 <h1>Archive for {{ year }}</h1>627 The following snippet of template code would accomplish this dubious task:: 976 628 977 {% for date in days %} 978 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %} 979 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> 980 {% endfor %} 629 {% regroup people by gender as grouped %} 630 <ul> 631 {% for group in grouped %} 632 <li>{{ group.grouper }} 633 <ul> 634 {% for item in group.list %} 635 <li>{{ item }}</li> 636 {% endfor %} 637 </ul> 638 {% endfor %} 639 </ul> 981 640 982 2. If given one or more variables, check whether any variable has changed. 983 For example, the following shows the date every time it changes, while 984 showing the hour if either the hour or the date has changed:: 641 As you can see, ``{% regroup %}`` populates a variable with a list of 642 objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the 643 item that was grouped by; ``list`` contains the list of objects that share 644 that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female`` 645 and ``Unknown``, and ``list`` is the list of people with those genders. 646 647 Note that ``{% regroup %}`` does not work when the list to be grouped is not 648 sorted by the key you are grouping by! This means that if your list of 649 people was not sorted by gender, you'd need to make sure it is sorted 650 before using it, i.e.:: 651 652 {% regroup people|dictsort:"gender" by gender as grouped %} 985 653 986 {% for date in days %}987 {% ifchanged date.date %} {{ date.date }} {% endifchanged %}988 {% ifchanged date.hour date.date %}989 {{ date.hour }}990 {% endifchanged %}991 {% endfor %}992 654 """ 993 bits = token.split_contents() 994 nodelist_true = parser.parse(('else', 'endifchanged')) 995 token = parser.next_token() 996 if token.contents == 'else': 997 nodelist_false = parser.parse(('endifchanged',)) 998 parser.delete_first_token() 999 else: 1000 nodelist_false = NodeList() 1001 values = [parser.compile_filter(bit) for bit in bits[1:]] 1002 return IfChangedNode(nodelist_true, nodelist_false, *values) 655 grammar = Grammar('regroup') 656 657 def __init__(self, parser, parse_result): 658 bits = parse_result.arguments 659 660 if len(bits) != 5: 661 raise TemplateSyntaxError("'regroup' tag takes five arguments") 662 self.target = parser.compile_filter(bits[0]) 663 if bits[1] != 'by': 664 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") 665 if bits[3] != 'as': 666 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" 667 " be 'as'") 668 self.var_name = bits[4] 669 # RegroupNode will take each item in 'target', put it in the context under 670 # 'var_name', evaluate 'var_name'.'expression' in the current context, and 671 # group by the resulting value. After all items are processed, it will 672 # save the final result in the context under 'var_name', thus clearing the 673 # temporary values. This hack is necessary because the template engine 674 # doesn't provide a context-aware equivalent of Python's getattr. 675 self.expression = parser.compile_filter(self.var_name + 676 VARIABLE_ATTRIBUTE_SEPARATOR + 677 bits[2]) 678 679 def resolve_expression(self, obj, context): 680 # This method is called for each object in self.target. See regroup() 681 # for the reason why we temporarily put the object in the context. 682 context[self.var_name] = obj 683 return self.expression.resolve(context, True) 684 685 def render(self, context): 686 obj_list = self.target.resolve(context, True) 687 if obj_list == None: 688 # target variable wasn't found in context; fail silently. 689 context[self.var_name] = [] 690 return '' 691 # List of dictionaries in the format: 692 # {'grouper': 'key', 'list': [list of contents]}. 693 context[self.var_name] = [ 694 {'grouper': key, 'list': list(val)} 695 for key, val in 696 groupby(obj_list, lambda obj: self.resolve_expression(obj, context)) 697 ] 698 return '' 699 700 def include_is_allowed(filepath): 701 for root in settings.ALLOWED_INCLUDE_ROOTS: 702 if filepath.startswith(root): 703 return True 704 return False 1003 705 1004 706 @register.tag 1005 def ssi(parser, token):707 class SsiNode(TemplateTag): 1006 708 """ 1007 709 Outputs the contents of a given file into the page. 1008 710 … … def ssi(parser, token): 1017 719 1018 720 {% ssi "/home/html/ljworld.com/includes/right_generic.html" parsed %} 1019 721 """ 1020 bits = token.split_contents() 1021 parsed = False 1022 if len(bits) not in (2, 3): 1023 raise TemplateSyntaxError("'ssi' tag takes one argument: the path to" 1024 " the file to be included") 1025 if len(bits) == 3: 1026 if bits[2] == 'parsed': 1027 parsed = True 1028 else: 1029 raise TemplateSyntaxError("Second (optional) argument to %s tag" 1030 " must be 'parsed'" % bits[0]) 1031 filepath = parser.compile_filter(bits[1]) 1032 return SsiNode(filepath, parsed) 722 grammar = Grammar('ssi') 723 724 def __init__(self, parser, parse_result): 725 bits = parse_result.arguments 726 727 self.parsed = False 728 if len(bits) not in (1, 2): 729 raise TemplateSyntaxError("'ssi' tag takes one argument: the path to" 730 " the file to be included") 731 if len(bits) == 2: 732 if bits[1] == 'parsed': 733 self.parsed = True 734 else: 735 raise TemplateSyntaxError("Second (optional) argument to %s tag" 736 " must be 'parsed'" % name) 737 self.filepath = parser.compile_filter(bits[0]) 738 739 def render(self, context): 740 filepath = self.filepath.resolve(context) 741 742 if not include_is_allowed(filepath): 743 if settings.DEBUG: 744 return "[Didn't have permission to include file]" 745 else: 746 return '' # Fail silently for invalid includes. 747 try: 748 with open(filepath, 'r') as fp: 749 output = fp.read() 750 except IOError: 751 output = '' 752 if self.parsed: 753 try: 754 t = Template(output, name=filepath) 755 return t.render(context) 756 except TemplateSyntaxError as e: 757 if settings.DEBUG: 758 return "[Included template had syntax error: %s]" % e 759 else: 760 return '' # Fail silently for invalid included templates. 761 return output 1033 762 1034 763 @register.tag 1035 def load(parser, token):764 class LoadNode(TemplateTag): 1036 765 """ 1037 766 Loads a custom template tag set. 1038 767 … … def load(parser, token): 1048 777 1049 778 """ 1050 779 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 1051 bits = token.contents.split() 1052 if len(bits) >= 4 and bits[-2] == "from": 1053 try: 1054 taglib = bits[-1] 1055 lib = get_library(taglib) 1056 except InvalidTemplateLibrary as e: 1057 raise TemplateSyntaxError("'%s' is not a valid tag library: %s" % 1058 (taglib, e)) 1059 else: 1060 temp_lib = Library() 1061 for name in bits[1:-2]: 1062 if name in lib.tags: 1063 temp_lib.tags[name] = lib.tags[name] 1064 # a name could be a tag *and* a filter, so check for both 1065 if name in lib.filters: 1066 temp_lib.filters[name] = lib.filters[name] 1067 elif name in lib.filters: 1068 temp_lib.filters[name] = lib.filters[name] 1069 else: 1070 raise TemplateSyntaxError("'%s' is not a valid tag or filter in tag library '%s'" % 1071 (name, taglib)) 1072 parser.add_library(temp_lib) 1073 else: 1074 for taglib in bits[1:]: 1075 # add the library to the parser 780 grammar = Grammar('load', _split_contents=False) 781 782 def __init__(self, parser, parse_result): 783 bits = parse_result.arguments.split() 784 785 if len(bits) >= 3 and bits[-2] == "from": 1076 786 try: 787 taglib = bits[-1] 1077 788 lib = get_library(taglib) 1078 parser.add_library(lib)1079 789 except InvalidTemplateLibrary as e: 1080 790 raise TemplateSyntaxError("'%s' is not a valid tag library: %s" % 1081 791 (taglib, e)) 1082 return LoadNode() 792 else: 793 temp_lib = Library() 794 for name in bits[0:-2]: 795 if name in lib.tags: 796 temp_lib.tags[name] = lib.tags[name] 797 # a name could be a tag *and* a filter, so check for both 798 if name in lib.filters: 799 temp_lib.filters[name] = lib.filters[name] 800 elif name in lib.filters: 801 temp_lib.filters[name] = lib.filters[name] 802 else: 803 raise TemplateSyntaxError("'%s' is not a valid tag or filter in tag library '%s'" % 804 (name, taglib)) 805 parser.add_library(temp_lib) 806 else: 807 for taglib in bits: 808 # add the library to the parser 809 try: 810 lib = get_library(taglib) 811 parser.add_library(lib) 812 except InvalidTemplateLibrary as e: 813 raise TemplateSyntaxError("'%s' is not a valid tag library: %s" % 814 (taglib, e)) 815 816 def render(self, context): 817 return '' 1083 818 1084 819 @register.tag 1085 def now(parser, token):820 class NowNode(TemplateTag): 1086 821 """ 1087 822 Displays the date, formatted according to the given string. 1088 823 … … def now(parser, token): 1093 828 1094 829 It is {% now "jS F Y H:i" %} 1095 830 """ 1096 bits = token.split_contents() 1097 if len(bits) != 2: 1098 raise TemplateSyntaxError("'now' statement takes one argument") 1099 format_string = bits[1][1:-1] 1100 return NowNode(format_string) 1101 1102 @register.tag 1103 def regroup(parser, token): 1104 """ 1105 Regroups a list of alike objects by a common attribute. 1106 1107 This complex tag is best illustrated by use of an example: say that 1108 ``people`` is a list of ``Person`` objects that have ``first_name``, 1109 ``last_name``, and ``gender`` attributes, and you'd like to display a list 1110 that looks like: 1111 1112 * Male: 1113 * George Bush 1114 * Bill Clinton 1115 * Female: 1116 * Margaret Thatcher 1117 * Colendeeza Rice 1118 * Unknown: 1119 * Pat Smith 1120 1121 The following snippet of template code would accomplish this dubious task:: 1122 1123 {% regroup people by gender as grouped %} 1124 <ul> 1125 {% for group in grouped %} 1126 <li>{{ group.grouper }} 1127 <ul> 1128 {% for item in group.list %} 1129 <li>{{ item }}</li> 1130 {% endfor %} 1131 </ul> 1132 {% endfor %} 1133 </ul> 1134 1135 As you can see, ``{% regroup %}`` populates a variable with a list of 1136 objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the 1137 item that was grouped by; ``list`` contains the list of objects that share 1138 that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female`` 1139 and ``Unknown``, and ``list`` is the list of people with those genders. 1140 1141 Note that ``{% regroup %}`` does not work when the list to be grouped is not 1142 sorted by the key you are grouping by! This means that if your list of 1143 people was not sorted by gender, you'd need to make sure it is sorted 1144 before using it, i.e.:: 831 grammar = Grammar('now') 1145 832 1146 {% regroup people|dictsort:"gender" by gender as grouped %} 833 def __init__(self, parser, parse_result): 834 bits = parse_result.arguments 835 if len(bits) != 1: 836 raise TemplateSyntaxError("'now' statement takes one argument") 837 self.format_string = bits[0][1:-1] 1147 838 1148 """ 1149 bits = token.split_contents() 1150 if len(bits) != 6: 1151 raise TemplateSyntaxError("'regroup' tag takes five arguments") 1152 target = parser.compile_filter(bits[1]) 1153 if bits[2] != 'by': 1154 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") 1155 if bits[4] != 'as': 1156 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" 1157 " be 'as'") 1158 var_name = bits[5] 1159 # RegroupNode will take each item in 'target', put it in the context under 1160 # 'var_name', evaluate 'var_name'.'expression' in the current context, and 1161 # group by the resulting value. After all items are processed, it will 1162 # save the final result in the context under 'var_name', thus clearing the 1163 # temporary values. This hack is necessary because the template engine 1164 # doesn't provide a context-aware equivalent of Python's getattr. 1165 expression = parser.compile_filter(var_name + 1166 VARIABLE_ATTRIBUTE_SEPARATOR + 1167 bits[3]) 1168 return RegroupNode(target, expression, var_name) 839 def render(self, context): 840 tzinfo = timezone.get_current_timezone() if settings.USE_TZ else None 841 return date(datetime.now(tz=tzinfo), self.format_string) 1169 842 1170 843 @register.tag 1171 def spaceless(parser, token):844 class SpacelessNode(TemplateTag): 1172 845 """ 1173 846 Removes whitespace between HTML tags, including tab and newline characters. 1174 847 … … def spaceless(parser, token): 1193 866 </strong> 1194 867 {% endspaceless %} 1195 868 """ 1196 nodelist = parser.parse(('endspaceless',)) 1197 parser.delete_first_token() 1198 return SpacelessNode(nodelist) 869 grammar = Grammar('spaceless endspaceless') 870 871 def render(self, context): 872 from django.utils.html import strip_spaces_between_tags 873 return strip_spaces_between_tags(self.nodelist.render(context).strip()) 874 1199 875 1200 876 @register.tag 1201 def templatetag(parser, token):877 class TemplateTagNode(TemplateTag): 1202 878 """ 1203 879 Outputs one of the bits used to compose template tags. 1204 880 … … def templatetag(parser, token): 1220 896 ``closecomment`` ``#}`` 1221 897 ================== ======= 1222 898 """ 1223 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 1224 bits = token.contents.split() 1225 if len(bits) != 2: 1226 raise TemplateSyntaxError("'templatetag' statement takes one argument") 1227 tag = bits[1] 1228 if tag not in TemplateTagNode.mapping: 1229 raise TemplateSyntaxError("Invalid templatetag argument: '%s'." 1230 " Must be one of: %s" % 1231 (tag, list(TemplateTagNode.mapping))) 1232 return TemplateTagNode(tag) 899 grammar = Grammar('templatetag') 900 mapping = {'openblock': BLOCK_TAG_START, 901 'closeblock': BLOCK_TAG_END, 902 'openvariable': VARIABLE_TAG_START, 903 'closevariable': VARIABLE_TAG_END, 904 'openbrace': SINGLE_BRACE_START, 905 'closebrace': SINGLE_BRACE_END, 906 'opencomment': COMMENT_TAG_START, 907 'closecomment': COMMENT_TAG_END, 908 } 909 910 def __init__(self, parser, parse_result): 911 bits = parse_result.arguments 912 if len(bits) != 1: 913 raise TemplateSyntaxError("'templatetag' statement takes one argument") 914 self.tagtype = bits[0] 915 if self.tagtype not in TemplateTagNode.mapping: 916 raise TemplateSyntaxError("Invalid templatetag argument: '%s'." 917 " Must be one of: %s" % 918 (self.tagtype, list(TemplateTagNode.mapping))) 919 920 def render(self, context): 921 return self.mapping.get(self.tagtype, '') 1233 922 1234 923 @register.tag 1235 def url(parser, token):924 class URLNode(TemplateTag): 1236 925 """ 1237 926 Returns an absolute URL matching given view with its parameters. 1238 927 … … def url(parser, token): 1295 984 {% endwith %} 1296 985 1297 986 """ 1298 bits = token.split_contents() 1299 if len(bits) < 2: 1300 raise TemplateSyntaxError("'%s' takes at least one argument" 1301 " (path to a view)" % bits[0]) 1302 try: 1303 viewname = parser.compile_filter(bits[1]) 1304 except TemplateSyntaxError as exc: 1305 exc.args = (exc.args[0] + ". " 1306 "The syntax of 'url' changed in Django 1.5, see the docs."), 1307 raise 1308 args = [] 1309 kwargs = {} 1310 asvar = None 1311 bits = bits[2:] 1312 if len(bits) >= 2 and bits[-2] == 'as': 1313 asvar = bits[-1] 1314 bits = bits[:-2] 1315 1316 if len(bits): 1317 for bit in bits: 1318 match = kwarg_re.match(bit) 1319 if not match: 1320 raise TemplateSyntaxError("Malformed arguments to url tag") 1321 name, value = match.groups() 1322 if name: 1323 kwargs[name] = parser.compile_filter(value) 987 grammar = Grammar('url') 988 989 def __init__(self, parser, parse_result): 990 bits = parse_result.arguments 991 if len(bits) < 1: 992 raise TemplateSyntaxError("'%s' takes at least one argument" 993 " (path to a view)" % parse_result.tagname) 994 try: 995 view_name = parser.compile_filter(bits[0]) 996 except TemplateSyntaxError as exc: 997 exc.args = (exc.args[0] + ". " 998 "The syntax of 'url' changed in Django 1.5, see the docs."), 999 raise 1000 args = [] 1001 kwargs = {} 1002 asvar = None 1003 bits = bits[1:] 1004 if len(bits) >= 2 and bits[-2] == 'as': 1005 asvar = bits[-1] 1006 bits = bits[:-2] 1007 1008 if len(bits): 1009 for bit in bits: 1010 match = kwarg_re.match(bit) 1011 if not match: 1012 raise TemplateSyntaxError("Malformed arguments to url tag") 1013 name, value = match.groups() 1014 if name: 1015 kwargs[name] = parser.compile_filter(value) 1016 else: 1017 args.append(parser.compile_filter(value)) 1018 1019 self.view_name = view_name 1020 self.args, self.kwargs = args, kwargs 1021 self.asvar = asvar 1022 1023 def render(self, context): 1024 from django.core.urlresolvers import reverse, NoReverseMatch 1025 args = [arg.resolve(context) for arg in self.args] 1026 kwargs = dict([(smart_text(k, 'ascii'), v.resolve(context)) 1027 for k, v in self.kwargs.items()]) 1028 1029 view_name = self.view_name.resolve(context) 1030 1031 if not view_name: 1032 raise NoReverseMatch("'url' requires a non-empty first argument. " 1033 "The syntax changed in Django 1.5, see the docs.") 1034 1035 # Try to look up the URL twice: once given the view name, and again 1036 # relative to what we guess is the "main" app. If they both fail, 1037 # re-raise the NoReverseMatch unless we're using the 1038 # {% url ... as var %} construct in which case return nothing. 1039 url = '' 1040 try: 1041 url = reverse(view_name, args=args, kwargs=kwargs, current_app=context.current_app) 1042 except NoReverseMatch: 1043 exc_info = sys.exc_info() 1044 if settings.SETTINGS_MODULE: 1045 project_name = settings.SETTINGS_MODULE.split('.')[0] 1046 try: 1047 url = reverse(project_name + '.' + view_name, 1048 args=args, kwargs=kwargs, 1049 current_app=context.current_app) 1050 except NoReverseMatch: 1051 if self.asvar is None: 1052 # Re-raise the original exception, not the one with 1053 # the path relative to the project. This makes a 1054 # better error message. 1055 six.reraise(*exc_info) 1324 1056 else: 1325 args.append(parser.compile_filter(value)) 1057 if self.asvar is None: 1058 raise 1326 1059 1327 return URLNode(viewname, args, kwargs, asvar) 1060 if self.asvar: 1061 context[self.asvar] = url 1062 return '' 1063 else: 1064 return url 1328 1065 1329 1066 @register.tag 1330 def verbatim(parser, token):1067 class VerbatimNode(TemplateTag): 1331 1068 """ 1332 1069 Stops the template engine from rendering the contents of this block tag. 1333 1070 … … def verbatim(parser, token): 1344 1081 ... 1345 1082 {% endverbatim myblock %} 1346 1083 """ 1347 nodelist = parser.parse(('endverbatim',)) 1348 parser.delete_first_token() 1349 return VerbatimNode(nodelist.render(Context())) 1084 grammar = Grammar('verbatim endverbatim') 1085 1086 def __init__(self, parser, parse_result): 1087 self.content = parse_result.nodelist.render(Context()) 1088 1089 def render(self, context): 1090 return self.content 1350 1091 1351 1092 @register.tag 1352 def widthratio(parser, token):1093 class WidthRatioNode(TemplateTag): 1353 1094 """ 1354 1095 For creating bar charts and such, this tag calculates the ratio of a given 1355 1096 value to a maximum value, and then applies that ratio to a constant. … … def widthratio(parser, token): 1362 1103 the image in the above example will be 88 pixels wide 1363 1104 (because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88). 1364 1105 """ 1365 bits = token.split_contents() 1366 if len(bits) != 4: 1367 raise TemplateSyntaxError("widthratio takes three arguments") 1368 tag, this_value_expr, max_value_expr, max_width = bits 1106 grammar = Grammar('widthratio') 1107 1108 def __init__(self, parser, parse_result): 1109 bits = parse_result.arguments 1110 1111 if len(bits) != 3: 1112 raise TemplateSyntaxError("widthratio takes three arguments") 1369 1113 1370 return WidthRatioNode(parser.compile_filter(this_value_expr), 1371 parser.compile_filter(max_value_expr), 1372 parser.compile_filter(max_width)) 1114 self.val_expr = parser.compile_filter(bits[0]) 1115 self.max_expr = parser.compile_filter(bits[1]) 1116 self.max_width = parser.compile_filter(bits[2]) 1117 1118 def render(self, context): 1119 try: 1120 value = self.val_expr.resolve(context) 1121 max_value = self.max_expr.resolve(context) 1122 max_width = int(self.max_width.resolve(context)) 1123 except VariableDoesNotExist: 1124 return '' 1125 except (ValueError, TypeError): 1126 raise TemplateSyntaxError("widthratio final argument must be a number") 1127 try: 1128 value = float(value) 1129 max_value = float(max_value) 1130 ratio = (value / max_value) * max_width 1131 except ZeroDivisionError: 1132 return '0' 1133 except (ValueError, TypeError): 1134 return '' 1135 return str(int(round(ratio))) 1373 1136 1374 @register.tag ('with')1375 def do_with(parser, token):1137 @register.tag 1138 class WithNode(TemplateTag): 1376 1139 """ 1377 1140 Adds one or more values to the context (inside of this block) for caching 1378 1141 and easy access. … … def do_with(parser, token): 1392 1155 The legacy format of ``{% with person.some_sql_method as total %}`` is 1393 1156 still accepted. 1394 1157 """ 1395 bits = token.split_contents() 1396 remaining_bits = bits[1:] 1397 extra_context = token_kwargs(remaining_bits, parser, support_legacy=True) 1398 if not extra_context: 1399 raise TemplateSyntaxError("%r expected at least one variable " 1400 "assignment" % bits[0]) 1401 if remaining_bits: 1402 raise TemplateSyntaxError("%r received an invalid token: %r" % 1403 (bits[0], remaining_bits[0])) 1404 nodelist = parser.parse(('endwith',)) 1405 parser.delete_first_token() 1406 return WithNode(None, None, nodelist, extra_context=extra_context) 1158 grammar = Grammar('with endwith') 1159 1160 def __init__(self, parser, parse_result): 1161 bits = parse_result.arguments 1162 1163 self.extra_context = token_kwargs(bits, parser, support_legacy=True) 1164 if not self.extra_context: 1165 raise TemplateSyntaxError("with expected at least one variable " 1166 "assignment") 1167 if bits: 1168 raise TemplateSyntaxError("with received an invalid token: %r" % 1169 (remaining_bits[0])) # XXX: check this. remaining_bits doesn't exist 1170 1171 def __repr__(self): 1172 return "<WithNode>" 1173 1174 def render(self, context): 1175 values = dict([(key, val.resolve(context)) for key, val in 1176 six.iteritems(self.extra_context)]) 1177 context.update(values) 1178 output = self.nodelist.render(context) 1179 context.pop() 1180 return output 1181 1182 @register.tag 1183 def comment(parser, token): 1184 """ 1185 Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. 1186 """ 1187 parser.skip_past('endcomment') 1188 return CommentNode() 1189 comment.grammar = Grammar('comment endcomment') 1190 1191 @register.tag 1192 class CycleNode(TemplateTag): 1193 """ 1194 Cycles among the given strings each time this tag is encountered. 1195 1196 Within a loop, cycles among the given strings each time through 1197 the loop:: 1198 1199 {% for o in some_list %} 1200 <tr class="{% cycle 'row1' 'row2' %}"> 1201 ... 1202 </tr> 1203 {% endfor %} 1204 1205 Outside of a loop, give the values a unique name the first time you call 1206 it, then use that name each sucessive time through:: 1207 1208 <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr> 1209 <tr class="{% cycle rowcolors %}">...</tr> 1210 <tr class="{% cycle rowcolors %}">...</tr> 1211 1212 You can use any number of values, separated by spaces. Commas can also 1213 be used to separate values; if a comma is used, the cycle values are 1214 interpreted as literal strings. 1215 1216 The optional flag "silent" can be used to prevent the cycle declaration 1217 from returning any value:: 1218 1219 {% for o in some_list %} 1220 {% cycle 'row1' 'row2' as rowcolors silent %} 1221 <tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr> 1222 {% endfor %} 1223 1224 """ 1225 grammar = Grammar('cycle') 1226 escape = False # only while the "future" version exists 1227 1228 @classmethod 1229 def handle_parse_result(cls, parser, parse_result): 1230 if not cls.escape: 1231 warnings.warn( 1232 "'The `cycle` template tag is changing to escape its arguments; " 1233 "the non-autoescaping version is deprecated. Load it " 1234 "from the `future` tag library to start using the new behavior.", 1235 PendingDeprecationWarning, stacklevel=2) 1236 1237 # Note: This returns the exact same node on each {% cycle name %} call; 1238 # that is, the node object returned from {% cycle a b c as name %} and the 1239 # one returned from {% cycle name %} are the exact same object. This 1240 # shouldn't cause problems (heh), but if it does, now you know. 1241 # 1242 # Ugly hack warning: This stuffs the named template dict into parser so 1243 # that names are only unique within each template (as opposed to using 1244 # a global variable, which would make cycle names have to be unique across 1245 # *all* templates. 1246 1247 args = parse_result.arguments 1248 1249 if len(args) < 1: 1250 raise TemplateSyntaxError("'cycle' tag requires at least one argument") 1251 1252 if ',' in args[0]: 1253 # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %} 1254 # case. 1255 args[0:1] = ['"%s"' % arg for arg in args[0].split(",")] 1256 1257 if len(args) == 1: 1258 # {% cycle foo %} case. 1259 name = args[0] 1260 if not hasattr(parser, '_namedCycleNodes'): 1261 raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name) 1262 if not name in parser._namedCycleNodes: 1263 raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) 1264 return parser._namedCycleNodes[name] 1265 1266 as_form = False 1267 1268 if len(args) > 3: 1269 # {% cycle ... as foo [silent] %} case. 1270 if args[-3] == "as": 1271 if args[-1] != "silent": 1272 raise TemplateSyntaxError("Only 'silent' flag is allowed after cycle's name, not '%s'." % args[-1]) 1273 as_form = True 1274 silent = True 1275 args = args[:-1] 1276 elif args[-2] == "as": 1277 as_form = True 1278 silent = False 1279 1280 if as_form: 1281 name = args[-1] 1282 values = [parser.compile_filter(arg) for arg in args[0:-2]] 1283 node = cls(values, name, silent=silent) 1284 if not hasattr(parser, '_namedCycleNodes'): 1285 parser._namedCycleNodes = {} 1286 parser._namedCycleNodes[name] = node 1287 else: 1288 values = [parser.compile_filter(arg) for arg in args] 1289 node = cls(values) 1290 return node 1291 1292 def __init__(self, cyclevars, variable_name=None, silent=False): 1293 self.cyclevars = cyclevars 1294 self.variable_name = variable_name 1295 self.silent = silent 1296 1297 def render(self, context): 1298 if self not in context.render_context: 1299 # First time the node is rendered in template 1300 context.render_context[self] = itertools_cycle(self.cyclevars) 1301 cycle_iter = context.render_context[self] 1302 value = next(cycle_iter).resolve(context) 1303 if self.variable_name: 1304 context[self.variable_name] = value 1305 if self.silent: 1306 return '' 1307 if not self.escape: 1308 value = mark_safe(value) 1309 return render_value_in_context(value, context) 1310 1311 1312 class TemplateLiteral(Literal): 1313 def __init__(self, value, text): 1314 self.value = value 1315 self.text = text # for better error messages 1316 1317 def display(self): 1318 return self.text 1319 1320 def eval(self, context): 1321 return self.value.resolve(context, ignore_failures=True) 1322 1323 class TemplateIfParser(IfParser): 1324 error_class = TemplateSyntaxError 1325 1326 def __init__(self, parser, *args, **kwargs): 1327 self.template_parser = parser 1328 super(TemplateIfParser, self).__init__(*args, **kwargs) 1329 1330 def create_var(self, value): 1331 return TemplateLiteral(self.template_parser.compile_filter(value), value) -
django/template/loader_tags.py
diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py index 767f0e5..16b5d60 100644
a b from django.conf import settings 4 4 from django.template.base import TemplateSyntaxError, Library, Node, TextNode,\ 5 5 token_kwargs, Variable 6 6 from django.template.loader import get_template 7 from django.template.generic import Grammar 7 8 from django.utils.safestring import mark_safe 8 9 from django.utils import six 9 10 … … def do_block(parser, token): 174 175 """ 175 176 Define a block that can be overridden by child templates. 176 177 """ 177 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 178 bits = token.contents.split() 179 if len(bits) != 2: 180 raise TemplateSyntaxError("'%s' tag takes only one argument" % bits[0]) 181 block_name = bits[1] 178 parse_result = do_block.grammar.parse(parser, token) 179 bits = parse_result.arguments.split() 180 tagname = parse_result.tagname 181 182 if len(bits) != 1: 183 raise TemplateSyntaxError("'%s' tag takes only one argument" % tagname) 184 block_name = bits[0] 182 185 # Keep track of the names of BlockNodes found in this template, so we can 183 186 # check for duplication. 184 187 try: 185 188 if block_name in parser.__loaded_blocks: 186 raise TemplateSyntaxError("'%s' tag with name '%s' appears more than once" % ( bits[0], block_name))189 raise TemplateSyntaxError("'%s' tag with name '%s' appears more than once" % (tagname, block_name)) 187 190 parser.__loaded_blocks.append(block_name) 188 191 except AttributeError: # parser.__loaded_blocks isn't a list yet 189 192 parser.__loaded_blocks = [block_name] 190 nodelist = parser.parse(('endblock',))191 193 192 194 # This check is kept for backwards-compatibility. See #3100. 193 endblock = parse r.next_token()195 endblock = parse_result.parts[1].token 194 196 acceptable_endblocks = ('endblock', 'endblock %s' % block_name) 195 197 if endblock.contents not in acceptable_endblocks: 196 198 parser.invalid_block_tag(endblock, 'endblock', acceptable_endblocks) 197 199 198 return BlockNode(block_name, nodelist) 200 return BlockNode(block_name, parse_result.nodelist) 201 202 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 203 do_block.grammar = Grammar('block endblock', _split_contents=False) 199 204 200 205 @register.tag('extends') 201 206 def do_extends(parser, token): … … def do_extends(parser, token): 208 213 name of the parent template to extend (if it evaluates to a string) or as 209 214 the parent tempate itelf (if it evaluates to a Template object). 210 215 """ 211 bits = token.split_contents() 212 if len(bits) != 2: 213 raise TemplateSyntaxError("'%s' takes one argument" % bits[0]) 214 parent_name = parser.compile_filter(bits[1]) 216 parse_result = do_extends.grammar.parse(parser, token) 217 bits = parse_result.arguments 218 tagname = parse_result.tagname 219 if len(bits) != 1: 220 raise TemplateSyntaxError("'%s' takes one argument" % tagname) 221 parent_name = parser.compile_filter(bits[0]) 215 222 nodelist = parser.parse() 216 223 if nodelist.get_nodes_by_type(ExtendsNode): 217 raise TemplateSyntaxError("'%s' cannot appear more than once in the same template" % bits[0])224 raise TemplateSyntaxError("'%s' cannot appear more than once in the same template" % tagname) 218 225 return ExtendsNode(nodelist, parent_name) 219 226 227 do_extends.grammar = Grammar('extends') 228 229 220 230 @register.tag('include') 221 231 def do_include(parser, token): 222 232 """ … … def do_include(parser, token): 234 244 {% include "foo/some_include" only %} 235 245 {% include "foo/some_include" with bar="1" only %} 236 246 """ 237 bits = token.split_contents() 238 if len(bits) < 2: 239 raise TemplateSyntaxError("%r tag takes at least one argument: the name of the template to be included." % bits[0]) 247 parse_result = do_include.grammar.parse(parser, token) 248 bits = parse_result.arguments 249 tagname = parse_result.tagname 250 251 if len(bits) < 1: 252 raise TemplateSyntaxError("%r tag takes at least one argument: the name of the template to be included." % tagname) 240 253 options = {} 241 remaining_bits = bits[ 2:]254 remaining_bits = bits[1:] 242 255 while remaining_bits: 243 256 option = remaining_bits.pop(0) 244 257 if option in options: … … def do_include(parser, token): 248 261 value = token_kwargs(remaining_bits, parser, support_legacy=False) 249 262 if not value: 250 263 raise TemplateSyntaxError('"with" in %r tag needs at least ' 251 'one keyword argument.' % bits[0])264 'one keyword argument.' % tagname) 252 265 elif option == 'only': 253 266 value = True 254 267 else: 255 268 raise TemplateSyntaxError('Unknown argument for %r tag: %r.' % 256 ( bits[0], option))269 (tagname, option)) 257 270 options[option] = value 258 271 isolated_context = options.get('only', False) 259 272 namemap = options.get('with', {}) 260 path = bits[ 1]273 path = bits[0] 261 274 if path[0] in ('"', "'") and path[-1] == path[0]: 262 275 return ConstantIncludeNode(path[1:-1], extra_context=namemap, 263 276 isolated_context=isolated_context) 264 return IncludeNode(parser.compile_filter(bits[ 1]), extra_context=namemap,277 return IncludeNode(parser.compile_filter(bits[0]), extra_context=namemap, 265 278 isolated_context=isolated_context) 279 280 do_include.grammar = Grammar('include') -
django/templatetags/cache.py
diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py index 215f117..0871099 100644
a b 1 1 from __future__ import unicode_literals 2 2 3 3 from django.core.cache.utils import make_template_fragment_key 4 from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist 4 from django.template import Library, TemplateSyntaxError, VariableDoesNotExist 5 from django.template.generic import TemplateTag, Grammar 5 6 from django.core.cache import cache 6 7 7 8 register = Library() 8 9 9 class CacheNode(Node): 10 def __init__(self, nodelist, expire_time_var, fragment_name, vary_on): 11 self.nodelist = nodelist 12 self.expire_time_var = expire_time_var 13 self.fragment_name = fragment_name 14 self.vary_on = vary_on 15 16 def render(self, context): 17 try: 18 expire_time = self.expire_time_var.resolve(context) 19 except VariableDoesNotExist: 20 raise TemplateSyntaxError('"cache" tag got an unknown variable: %r' % self.expire_time_var.var) 21 try: 22 expire_time = int(expire_time) 23 except (ValueError, TypeError): 24 raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time) 25 vary_on = [var.resolve(context) for var in self.vary_on] 26 cache_key = make_template_fragment_key(self.fragment_name, vary_on) 27 value = cache.get(cache_key) 28 if value is None: 29 value = self.nodelist.render(context) 30 cache.set(cache_key, value, expire_time) 31 return value 32 33 @register.tag('cache') 34 def do_cache(parser, token): 10 @register.tag 11 class CacheNode(TemplateTag): 35 12 """ 36 13 This will cache the contents of a template fragment for a given amount 37 14 of time. … … def do_cache(parser, token): 52 29 53 30 Each unique set of arguments will result in a unique cache entry. 54 31 """ 55 nodelist = parser.parse(('endcache',)) 56 parser.delete_first_token() 57 tokens = token.split_contents() 58 if len(tokens) < 3: 59 raise TemplateSyntaxError("'%r' tag requires at least 2 arguments." % tokens[0]) 60 return CacheNode(nodelist, 61 parser.compile_filter(tokens[1]), 62 tokens[2], # fragment_name can't be a variable. 63 [parser.compile_filter(token) for token in tokens[3:]]) 32 grammar = Grammar('cache endcache') 33 34 def __init__(self, parser, parse_result): 35 tokens = parse_result.arguments 36 if len(tokens) < 2: 37 raise TemplateSyntaxError("'%r' tag requires at least 2 arguments." % parse_result.tagname) 38 self.expire_time_var = parser.compile_filter(tokens[0]) 39 self.fragment_name = tokens[1] # fragment_name can't be a variable. 40 self.vary_on = [parser.compile_filter(token) for token in tokens[2:]] 41 42 def render(self, context): 43 try: 44 expire_time = self.expire_time_var.resolve(context) 45 except VariableDoesNotExist: 46 raise TemplateSyntaxError('"cache" tag got an unknown variable: %r' % self.expire_time_var.var) 47 try: 48 expire_time = int(expire_time) 49 except (ValueError, TypeError): 50 raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time) 51 vary_on = [var.resolve(context) for var in self.vary_on] 52 cache_key = make_template_fragment_key(self.fragment_name, vary_on) 53 value = cache.get(cache_key) 54 if value is None: 55 value = self.nodelist.render(context) 56 cache.set(cache_key, value, expire_time) 57 return value -
django/templatetags/future.py
diff --git a/django/templatetags/future.py b/django/templatetags/future.py index 7203f39..4deb120 100644
a b 1 1 from django.template import Library 2 from django.template import defaulttags 2 from django.template import defaulttags, TemplateTag, Grammar 3 3 4 4 register = Library() 5 5 6 6 7 7 @register.tag 8 def ssi(parser, token):8 class SsiNode(defaulttags.SsiNode): 9 9 # Used for deprecation path during 1.3/1.4, will be removed in 2.0 10 return defaulttags.ssi(parser, token)10 pass 11 11 12 12 13 13 @register.tag 14 def url(parser, token):14 class URLNode(defaulttags.URLNode): 15 15 # Used for deprecation path during 1.3/1.4, will be removed in 2.0 16 return defaulttags.url(parser, token)16 pass 17 17 18 18 19 19 @register.tag 20 def cycle(parser, token):20 class CycleNode(defaulttags.CycleNode): 21 21 """ 22 22 This is the future version of `cycle` with auto-escaping. 23 23 … … def cycle(parser, token): 33 33 34 34 {% cycle var1 var2|safe var3|safe as somecycle %} 35 35 """ 36 return defaulttags.cycle(parser, token, escape=True)36 escape = True 37 37 38 38 39 39 @register.tag 40 def firstof(parser, token):40 class FirstOfNode(defaulttags.FirstOfNode): 41 41 """ 42 42 This is the future version of `firstof` with auto-escaping. 43 43 … … def firstof(parser, token): 62 62 {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %} 63 63 64 64 """ 65 return defaulttags.firstof(parser, token, escape=True)65 escape = True -
django/templatetags/i18n.py
diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index 14f0382..82ba4c5 100644
a b from django.template import (Node, Variable, TemplateSyntaxError, 7 7 TokenParser, Library, TOKEN_TEXT, TOKEN_VAR) 8 8 from django.template.base import render_value_in_context 9 9 from django.template.defaulttags import token_kwargs 10 from django.template.generic import TemplateTag, Grammar 10 11 from django.utils import six 11 12 from django.utils import translation 12 13 13 14 14 15 register = Library() 15 16 17 @register.tag 18 class GetLanguageInfoNode(TemplateTag): 19 """ 20 This will store the language information dictionary for the given language 21 code in a context variable. 16 22 17 class GetAvailableLanguagesNode(Node): 18 def __init__(self, variable): 19 self.variable = variable 23 Usage:: 20 24 21 def render(self, context): 22 context[self.variable] = [(k, translation.ugettext(v)) for k, v in settings.LANGUAGES] 23 return '' 25 {% get_language_info for LANGUAGE_CODE as l %} 26 {{ l.code }} 27 {{ l.name }} 28 {{ l.name_local }} 29 {{ l.bidi|yesno:"bi-directional,uni-directional" }} 30 """ 31 grammar = Grammar("get_language_info") 24 32 33 def __init__(self, parser, parse_result): 34 args = parse_result.arguments 35 if len(args) != 4 or args[0] != 'for' or args[2] != 'as': 36 raise TemplateSyntaxError("'%s' requires 'for string as variable' (got %r)" % (block.name, args)) 25 37 26 class GetLanguageInfoNode(Node): 27 def __init__(self, lang_code, variable): 28 self.lang_code = lang_code 29 self.variable = variable 38 self.lang_code = parser.compile_filter(args[1]) 39 self.variable = args[3] 30 40 31 41 def render(self, context): 32 42 lang_code = self.lang_code.resolve(context) … … class GetLanguageInfoNode(Node): 34 44 return '' 35 45 36 46 37 class GetLanguageInfoListNode(Node): 38 def __init__(self, languages, variable): 39 self.languages = languages 40 self.variable = variable 47 @register.tag 48 class GetLanguageInfoListNode(TemplateTag): 49 """ 50 This will store a list of language information dictionaries for the given 51 language codes in a context variable. The language codes can be specified 52 either as a list of strings or a settings.LANGUAGES style tuple (or any 53 sequence of sequences whose first items are language codes). 54 55 Usage:: 56 57 {% get_language_info_list for LANGUAGES as langs %} 58 {% for l in langs %} 59 {{ l.code }} 60 {{ l.name }} 61 {{ l.name_local }} 62 {{ l.bidi|yesno:"bi-directional,uni-directional" }} 63 {% endfor %} 64 """ 65 grammar = Grammar("get_language_info_list") 66 67 def __init__(self, parser, parse_result): 68 args = parse_result.arguments 69 if len(args) != 4 or args[0] != 'for' or args[2] != 'as': 70 raise TemplateSyntaxError("'%s' requires 'for sequence as variable' (got %r)" % 71 (parse_result.tagname, args)) 72 73 self.languages = parser.compile_filter(args[1]) 74 self.variable = args[3] 41 75 42 76 def get_language_info(self, language): 43 77 # ``language`` is either a language code string or a sequence … … class GetLanguageInfoListNode(Node): 53 87 return '' 54 88 55 89 56 class GetCurrentLanguageNode(Node):57 def __init__(self, variable):58 self.variable = variable59 60 def render(self, context):61 context[self.variable] = translation.get_language()62 return ''63 64 65 class GetCurrentLanguageBidiNode(Node):66 def __init__(self, variable):67 self.variable = variable68 69 def render(self, context):70 context[self.variable] = translation.get_language_bidi()71 return ''72 73 74 90 class TranslateNode(Node): 75 91 def __init__(self, filter_expression, noop, asvar=None, 76 92 message_context=None): … … class BlockTranslateNode(Node): 160 176 result = self.render(context, nested=True) 161 177 return result 162 178 179 @register.tag 180 class LanguageNode(TemplateTag): 181 """ 182 This will enable the given language just for this block. 183 184 Usage:: 185 186 {% language "de" %} 187 This is {{ bar }} and {{ boo }}. 188 {% endlanguage %} 189 190 """ 191 grammar = Grammar('language endlanguage') 163 192 164 class LanguageNode(Node): 165 def __init__(self, nodelist, language): 166 self.nodelist = nodelist 167 self.language = language 193 def __init__(self, parser, parse_result): 194 bits = parse_result.arguments 195 if len(bits) != 1: 196 raise TemplateSyntaxError("'%s' takes one argument (language)" % parse_result.tagname) 197 self.language = parser.compile_filter(bits[0]) 168 198 169 199 def render(self, context): 170 200 with translation.override(self.language.resolve(context)): 171 201 output = self.nodelist.render(context) 172 202 return output 173 203 174 175 @register.tag("get_available_languages") 176 def do_get_available_languages(parser, token): 204 @register.assignment_tag 205 def get_available_languages(): 177 206 """ 178 207 This will store a list of available languages 179 208 in the context. … … def do_get_available_languages(parser, token): 189 218 your setting file (or the default settings) and 190 219 put it into the named variable. 191 220 """ 192 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 193 args = token.contents.split() 194 if len(args) != 3 or args[1] != 'as': 195 raise TemplateSyntaxError("'get_available_languages' requires 'as variable' (got %r)" % args) 196 return GetAvailableLanguagesNode(args[2]) 197 198 @register.tag("get_language_info") 199 def do_get_language_info(parser, token): 200 """ 201 This will store the language information dictionary for the given language 202 code in a context variable. 203 204 Usage:: 205 206 {% get_language_info for LANGUAGE_CODE as l %} 207 {{ l.code }} 208 {{ l.name }} 209 {{ l.name_local }} 210 {{ l.bidi|yesno:"bi-directional,uni-directional" }} 211 """ 212 args = token.split_contents() 213 if len(args) != 5 or args[1] != 'for' or args[3] != 'as': 214 raise TemplateSyntaxError("'%s' requires 'for string as variable' (got %r)" % (args[0], args[1:])) 215 return GetLanguageInfoNode(parser.compile_filter(args[2]), args[4]) 216 217 @register.tag("get_language_info_list") 218 def do_get_language_info_list(parser, token): 219 """ 220 This will store a list of language information dictionaries for the given 221 language codes in a context variable. The language codes can be specified 222 either as a list of strings or a settings.LANGUAGES style tuple (or any 223 sequence of sequences whose first items are language codes). 224 225 Usage:: 226 227 {% get_language_info_list for LANGUAGES as langs %} 228 {% for l in langs %} 229 {{ l.code }} 230 {{ l.name }} 231 {{ l.name_local }} 232 {{ l.bidi|yesno:"bi-directional,uni-directional" }} 233 {% endfor %} 234 """ 235 args = token.split_contents() 236 if len(args) != 5 or args[1] != 'for' or args[3] != 'as': 237 raise TemplateSyntaxError("'%s' requires 'for sequence as variable' (got %r)" % (args[0], args[1:])) 238 return GetLanguageInfoListNode(parser.compile_filter(args[2]), args[4]) 221 return [(k, translation.ugettext(v)) for k, v in settings.LANGUAGES] 239 222 240 223 @register.filter 241 224 def language_name(lang_code): … … def language_name_local(lang_code): 249 232 def language_bidi(lang_code): 250 233 return translation.get_language_info(lang_code)['bidi'] 251 234 252 @register. tag("get_current_language")253 def do_get_current_language(parser, token):235 @register.assignment_tag 236 def get_current_language(): 254 237 """ 255 238 This will store the current language in the context. 256 239 … … def do_get_current_language(parser, token): 262 245 put it's value into the ``language`` context 263 246 variable. 264 247 """ 265 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 266 args = token.contents.split() 267 if len(args) != 3 or args[1] != 'as': 268 raise TemplateSyntaxError("'get_current_language' requires 'as variable' (got %r)" % args) 269 return GetCurrentLanguageNode(args[2]) 270 271 @register.tag("get_current_language_bidi") 272 def do_get_current_language_bidi(parser, token): 248 return translation.get_language() 249 250 @register.assignment_tag 251 def get_current_language_bidi(): 273 252 """ 274 253 This will store the current language layout in the context. 275 254 … … def do_get_current_language_bidi(parser, token): 281 260 put it's value into the ``bidi`` context variable. 282 261 True indicates right-to-left layout, otherwise left-to-right 283 262 """ 284 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 285 args = token.contents.split() 286 if len(args) != 3 or args[1] != 'as': 287 raise TemplateSyntaxError("'get_current_language_bidi' requires 'as variable' (got %r)" % args) 288 return GetCurrentLanguageBidiNode(args[2]) 263 return translation.get_language_bidi() 289 264 290 265 @register.tag("trans") 291 266 def do_translate(parser, token): … … def do_translate(parser, token): 366 341 return TranslateNode(parser.compile_filter(value), noop, asvar, 367 342 message_context) 368 343 344 do_translate.grammar = Grammar('trans') 345 369 346 @register.tag("blocktrans") 370 347 def do_block_translate(parser, token): 371 348 """ … … def do_block_translate(parser, token): 466 443 467 444 return BlockTranslateNode(extra_context, singular, plural, countervar, 468 445 counter, message_context) 469 470 @register.tag 471 def language(parser, token): 472 """ 473 This will enable the given language just for this block. 474 475 Usage:: 476 477 {% language "de" %} 478 This is {{ bar }} and {{ boo }}. 479 {% endlanguage %} 480 481 """ 482 bits = token.split_contents() 483 if len(bits) != 2: 484 raise TemplateSyntaxError("'%s' takes one argument (language)" % bits[0]) 485 language = parser.compile_filter(bits[1]) 486 nodelist = parser.parse(('endlanguage',)) 487 parser.delete_first_token() 488 return LanguageNode(nodelist, language) 446 do_block_translate.grammar = Grammar('blocktrans endblocktrans') -
django/templatetags/l10n.py
diff --git a/django/templatetags/l10n.py b/django/templatetags/l10n.py index 667de24..6538873 100644
a b 1 from django.template import Node1 from django.template import TemplateTag, Grammar 2 2 from django.template import TemplateSyntaxError, Library 3 3 from django.utils import formats 4 4 from django.utils.encoding import force_text … … def unlocalize(value): 21 21 """ 22 22 return force_text(value) 23 23 24 class LocalizeNode(Node): 25 def __init__(self, nodelist, use_l10n): 26 self.nodelist = nodelist 27 self.use_l10n = use_l10n 28 29 def __repr__(self): 30 return "<LocalizeNode>" 31 32 def render(self, context): 33 old_setting = context.use_l10n 34 context.use_l10n = self.use_l10n 35 output = self.nodelist.render(context) 36 context.use_l10n = old_setting 37 return output 38 39 @register.tag('localize') 40 def localize_tag(parser, token): 24 @register.tag 25 class LocalizeNode(TemplateTag): 41 26 """ 42 27 Forces or prevents localization of values, regardless of the value of 43 28 `settings.USE_L10N`. … … def localize_tag(parser, token): 49 34 {% endlocalize %} 50 35 51 36 """ 52 use_l10n = None 53 bits = list(token.split_contents()) 54 if len(bits) == 1: 55 use_l10n = True 56 elif len(bits) > 2 or bits[1] not in ('on', 'off'): 57 raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % bits[0]) 58 else: 59 use_l10n = bits[1] == 'on' 60 nodelist = parser.parse(('endlocalize',)) 61 parser.delete_first_token() 62 return LocalizeNode(nodelist, use_l10n) 37 grammar = Grammar('localize endlocalize') 38 39 def __init__(self, parser, parse_result): 40 self.use_l10n = None 41 bits = parse_result.arguments 42 if len(bits) == 0: 43 self.use_l10n = True 44 elif len(bits) > 1 or bits[0] not in ('on', 'off'): 45 raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % self.parse_result.tagname) 46 else: 47 self.use_l10n = bits[0] == 'on' 48 49 def __repr__(self): 50 return "<LocalizeNode>" 51 52 def render(self, context): 53 old_setting = context.use_l10n 54 context.use_l10n = self.use_l10n 55 output = self.nodelist.render(context) 56 context.use_l10n = old_setting 57 return output 58 -
django/templatetags/static.py
diff --git a/django/templatetags/static.py b/django/templatetags/static.py index 68437e7..320a05b 100644
a b except ImportError: # Python 2 5 5 6 6 from django import template 7 7 from django.template.base import Node 8 from django.template.generic import TemplateTag, Grammar 8 9 from django.utils.encoding import iri_to_uri 9 10 10 11 register = template.Library() 11 12 12 13 13 class PrefixNode(template.Node): 14 class PrefixNode(TemplateTag): 15 name = None 16 grammar = None 14 17 15 18 def __repr__(self): 16 19 return "<PrefixNode for %r>" % self.name 17 20 18 def __init__(self, varname=None, name=None): 19 if name is None: 20 raise template.TemplateSyntaxError( 21 "Prefix nodes must be given a name to return.") 22 self.varname = varname 23 self.name = name 24 25 @classmethod 26 def handle_token(cls, parser, token, name): 21 def __init__(self, parser, parse_result): 27 22 """ 28 23 Class method to parse prefix node and return a Node. 29 24 """ 30 # token.split_contents() isn't useful here because tags using this method don't accept variable as arguments 31 tokens = token.contents.split() 32 if len(tokens) > 1 and tokens[1] != 'as': 25 tokens = parse_result.arguments.split() 26 if len(tokens) > 0 and tokens[0] != 'as': 33 27 raise template.TemplateSyntaxError( 34 "First argument in '%s' must be 'as'" % tokens[0])35 if len(tokens) > 1:36 varname = tokens[2]28 "First argument in '%s' must be 'as'" % parse_result.tagname) 29 if len(tokens) > 0: 30 self.varname = tokens[1] 37 31 else: 38 varname = None 39 return cls(varname, name) 32 self.varname = None 33 34 if self.name is None: 35 raise template.TemplateSyntaxError( 36 "Prefix nodes must be given a name to return.") 40 37 41 38 @classmethod 42 39 def handle_simple(cls, name): … … class PrefixNode(template.Node): 57 54 58 55 59 56 @register.tag 60 def get_static_prefix(parser, token):57 class StaticPrefixNode(PrefixNode): 61 58 """ 62 59 Populates a template variable with the static prefix, 63 60 ``settings.STATIC_URL``. … … def get_static_prefix(parser, token): 72 69 {% get_static_prefix as static_prefix %} 73 70 74 71 """ 75 return PrefixNode.handle_token(parser, token, "STATIC_URL") 72 # token.split_contents() isn't useful here because tags using this method don't accept variable as arguments 73 grammar = Grammar('get_static_prefix', _split_contents=False) 74 name = 'STATIC_URL' 76 75 77 76 78 77 @register.tag 79 def get_media_prefix(parser, token):78 class MediaPrefixNode(PrefixNode): 80 79 """ 81 80 Populates a template variable with the media prefix, 82 81 ``settings.MEDIA_URL``. … … def get_media_prefix(parser, token): 91 90 {% get_media_prefix as media_prefix %} 92 91 93 92 """ 94 return PrefixNode.handle_token(parser, token, "MEDIA_URL") 95 96 97 class StaticNode(Node): 98 def __init__(self, varname=None, path=None): 99 if path is None: 100 raise template.TemplateSyntaxError( 101 "Static template nodes must be given a path to return.") 102 self.path = path 103 self.varname = varname 104 105 def url(self, context): 106 path = self.path.resolve(context) 107 return self.handle_simple(path) 108 109 def render(self, context): 110 url = self.url(context) 111 if self.varname is None: 112 return url 113 context[self.varname] = url 114 return '' 115 116 @classmethod 117 def handle_simple(cls, path): 118 return urljoin(PrefixNode.handle_simple("STATIC_URL"), path) 119 120 @classmethod 121 def handle_token(cls, parser, token): 122 """ 123 Class method to parse prefix node and return a Node. 124 """ 125 bits = token.split_contents() 93 grammar = Grammar('get_media_prefix', _split_contents=False) 94 name = 'MEDIA_URL' 126 95 127 if len(bits) < 2:128 raise template.TemplateSyntaxError(129 "'%s' takes at least one argument (path to file)" % bits[0])130 131 path = parser.compile_filter(bits[1])132 133 if len(bits) >= 2 and bits[-2] == 'as':134 varname = bits[3]135 else:136 varname = None137 96 138 return cls(varname, path) 139 140 141 @register.tag('static') 142 def do_static(parser, token): 97 @register.tag 98 class StaticNode(TemplateTag): 143 99 """ 144 100 Joins the given path with the STATIC_URL setting. 145 101 … … def do_static(parser, token): 155 111 {% static variable_with_path as varname %} 156 112 157 113 """ 158 return StaticNode.handle_token(parser, token) 114 grammar = Grammar('static') 115 116 def __init__(self, parser, parse_result): 117 """ 118 Class method to parse prefix node and return a Node. 119 """ 120 bits = parse_result.arguments 121 if len(bits) < 1: 122 raise template.TemplateSyntaxError( 123 "'%s' takes at least one argument (path to file)" % parse_result.tagname) 124 125 self.path = parser.compile_filter(bits[0]) 126 127 if len(bits) >= 2 and bits[1] == 'as': 128 self.varname = bits[2] 129 else: 130 self.varname = None 131 132 def render(self, context): 133 url = self.url(context) 134 if self.varname is None: 135 return url 136 context[self.varname] = url 137 return '' 138 139 def url(self, context): 140 path = self.path.resolve(context) 141 return self.handle_simple(path) 142 143 @classmethod 144 def handle_simple(cls, path): 145 return urljoin(PrefixNode.handle_simple("STATIC_URL"), path) 159 146 160 147 161 148 def static(path): -
django/templatetags/tz.py
diff --git a/django/templatetags/tz.py b/django/templatetags/tz.py index 05088e1..e946f61 100644
a b except ImportError: 7 7 8 8 from django.template import Node 9 9 from django.template import TemplateSyntaxError, Library 10 from django.template.generic import TemplateTag, Grammar 10 11 from django.utils import six 11 12 from django.utils import timezone 12 13 … … def do_timezone(value, arg): 86 87 87 88 # Template tags 88 89 89 class LocalTimeNode(Node): 90 """ 91 Template node class used by ``localtime_tag``. 92 """ 93 def __init__(self, nodelist, use_tz): 94 self.nodelist = nodelist 95 self.use_tz = use_tz 96 97 def render(self, context): 98 old_setting = context.use_tz 99 context.use_tz = self.use_tz 100 output = self.nodelist.render(context) 101 context.use_tz = old_setting 102 return output 103 104 105 class TimezoneNode(Node): 106 """ 107 Template node class used by ``timezone_tag``. 108 """ 109 def __init__(self, nodelist, tz): 110 self.nodelist = nodelist 111 self.tz = tz 112 113 def render(self, context): 114 with timezone.override(self.tz.resolve(context)): 115 output = self.nodelist.render(context) 116 return output 117 118 119 class GetCurrentTimezoneNode(Node): 120 """ 121 Template node class used by ``get_current_timezone_tag``. 122 """ 123 def __init__(self, variable): 124 self.variable = variable 125 126 def render(self, context): 127 context[self.variable] = timezone.get_current_timezone_name() 128 return '' 129 130 131 @register.tag('localtime') 132 def localtime_tag(parser, token): 90 @register.tag 91 class LocalTimeNode(TemplateTag): 133 92 """ 134 93 Forces or prevents conversion of datetime objects to local time, 135 94 regardless of the value of ``settings.USE_TZ``. … … def localtime_tag(parser, token): 139 98 {% localtime off %}{{ value_in_utc }}{% endlocaltime %} 140 99 141 100 """ 142 bits = token.split_contents() 143 if len(bits) == 1: 144 use_tz = True 145 elif len(bits) > 2 or bits[1] not in ('on', 'off'): 146 raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % 147 bits[0]) 148 else: 149 use_tz = bits[1] == 'on' 150 nodelist = parser.parse(('endlocaltime',)) 151 parser.delete_first_token() 152 return LocalTimeNode(nodelist, use_tz) 101 grammar = Grammar('localtime endlocaltime') 153 102 103 def __init__(self, parser, parse_result): 104 bits = parse_result.arguments 105 tagname = parse_result.tagname 106 if len(bits) == 0: 107 self.use_tz = True 108 elif len(bits) > 1 or bits[0] not in ('on', 'off'): 109 raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % 110 tagname) 111 else: 112 self.use_tz = bits[0] == 'on' 154 113 155 @register.tag('timezone') 156 def timezone_tag(parser, token): 114 def render(self, context): 115 old_setting = context.use_tz 116 context.use_tz = self.use_tz 117 output = self.nodelist.render(context) 118 context.use_tz = old_setting 119 return output 120 121 @register.tag 122 class TimezoneNode(TemplateTag): 157 123 """ 158 124 Enables a given time zone just for this block. 159 125 … … def timezone_tag(parser, token): 168 134 {% endtimezone %} 169 135 170 136 """ 171 bits = token.split_contents() 172 if len(bits) != 2: 173 raise TemplateSyntaxError("'%s' takes one argument (timezone)" % 174 bits[0]) 175 tz = parser.compile_filter(bits[1]) 176 nodelist = parser.parse(('endtimezone',)) 177 parser.delete_first_token() 178 return TimezoneNode(nodelist, tz) 179 180 181 @register.tag("get_current_timezone") 182 def get_current_timezone_tag(parser, token): 137 grammar = Grammar('timezone endtimezone') 138 139 def __init__(self, parser, parse_result): 140 bits = parse_result.arguments 141 tagname = parse_result.tagname 142 if len(bits) != 1: 143 raise TemplateSyntaxError("'%s' takes one argument (timezone)" % 144 tagname) 145 self.tz = parser.compile_filter(bits[0]) 146 147 def render(self, context): 148 with timezone.override(self.tz.resolve(context)): 149 output = self.nodelist.render(context) 150 return output 151 152 153 @register.assignment_tag 154 def get_current_timezone(): 183 155 """ 184 156 Stores the name of the current time zone in the context. 185 157 … … def get_current_timezone_tag(parser, token): 190 162 This will fetch the currently active time zone and put its name 191 163 into the ``TIME_ZONE`` context variable. 192 164 """ 193 # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments 194 args = token.contents.split() 195 if len(args) != 3 or args[1] != 'as': 196 raise TemplateSyntaxError("'get_current_timezone' requires " 197 "'as variable' (got %r)" % args) 198 return GetCurrentTimezoneNode(args[2]) 165 return timezone.get_current_timezone_name()