Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/template/defaulttags.py: 22%

626 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1"""Default tags used by the template system, available to all templates.""" 

2import re 

3import sys 

4import warnings 

5from collections import namedtuple 

6from datetime import datetime 

7from itertools import cycle as itertools_cycle 

8from itertools import groupby 

9 

10from django.conf import settings 

11from django.utils import timezone 

12from django.utils.html import conditional_escape, escape, format_html 

13from django.utils.lorem_ipsum import paragraphs, words 

14from django.utils.safestring import mark_safe 

15 

16from .base import ( 

17 BLOCK_TAG_END, 

18 BLOCK_TAG_START, 

19 COMMENT_TAG_END, 

20 COMMENT_TAG_START, 

21 FILTER_SEPARATOR, 

22 SINGLE_BRACE_END, 

23 SINGLE_BRACE_START, 

24 VARIABLE_ATTRIBUTE_SEPARATOR, 

25 VARIABLE_TAG_END, 

26 VARIABLE_TAG_START, 

27 Node, 

28 NodeList, 

29 TemplateSyntaxError, 

30 VariableDoesNotExist, 

31 kwarg_re, 

32 render_value_in_context, 

33 token_kwargs, 

34) 

35from .context import Context 

36from .defaultfilters import date 

37from .library import Library 

38from .smartif import IfParser, Literal 

39 

40register = Library() 

41 

42 

43class AutoEscapeControlNode(Node): 

44 """Implement the actions of the autoescape tag.""" 

45 

46 def __init__(self, setting, nodelist): 

47 self.setting, self.nodelist = setting, nodelist 

48 

49 def render(self, context): 

50 old_setting = context.autoescape 

51 context.autoescape = self.setting 

52 output = self.nodelist.render(context) 

53 context.autoescape = old_setting 

54 if self.setting: 

55 return mark_safe(output) 

56 else: 

57 return output 

58 

59 

60class CommentNode(Node): 

61 child_nodelists = () 

62 

63 def render(self, context): 

64 return "" 

65 

66 

67class CsrfTokenNode(Node): 

68 child_nodelists = () 

69 

70 def render(self, context): 

71 csrf_token = context.get("csrf_token") 

72 if csrf_token: 

73 if csrf_token == "NOTPROVIDED": 

74 return format_html("") 

75 else: 

76 return format_html( 

77 '<input type="hidden" name="csrfmiddlewaretoken" value="{}">', 

78 csrf_token, 

79 ) 

80 else: 

81 # It's very probable that the token is missing because of 

82 # misconfiguration, so we raise a warning 

83 if settings.DEBUG: 

84 warnings.warn( 

85 "A {% csrf_token %} was used in a template, but the context " 

86 "did not provide the value. This is usually caused by not " 

87 "using RequestContext." 

88 ) 

89 return "" 

90 

91 

92class CycleNode(Node): 

93 def __init__(self, cyclevars, variable_name=None, silent=False): 

94 self.cyclevars = cyclevars 

95 self.variable_name = variable_name 

96 self.silent = silent 

97 

98 def render(self, context): 

99 if self not in context.render_context: 

100 # First time the node is rendered in template 

101 context.render_context[self] = itertools_cycle(self.cyclevars) 

102 cycle_iter = context.render_context[self] 

103 value = next(cycle_iter).resolve(context) 

104 if self.variable_name: 

105 context.set_upward(self.variable_name, value) 

106 if self.silent: 

107 return "" 

108 return render_value_in_context(value, context) 

109 

110 def reset(self, context): 

111 """ 

112 Reset the cycle iteration back to the beginning. 

113 """ 

114 context.render_context[self] = itertools_cycle(self.cyclevars) 

115 

116 

117class DebugNode(Node): 

118 def render(self, context): 

119 if not settings.DEBUG: 

120 return "" 

121 

122 from pprint import pformat 

123 

124 output = [escape(pformat(val)) for val in context] 

125 output.append("\n\n") 

126 output.append(escape(pformat(sys.modules))) 

127 return "".join(output) 

128 

129 

130class FilterNode(Node): 

131 def __init__(self, filter_expr, nodelist): 

132 self.filter_expr, self.nodelist = filter_expr, nodelist 

133 

134 def render(self, context): 

135 output = self.nodelist.render(context) 

136 # Apply filters. 

137 with context.push(var=output): 

138 return self.filter_expr.resolve(context) 

139 

140 

141class FirstOfNode(Node): 

142 def __init__(self, variables, asvar=None): 

143 self.vars = variables 

144 self.asvar = asvar 

145 

146 def render(self, context): 

147 first = "" 

148 for var in self.vars: 

149 value = var.resolve(context, ignore_failures=True) 

150 if value: 

151 first = render_value_in_context(value, context) 

152 break 

153 if self.asvar: 

154 context[self.asvar] = first 

155 return "" 

156 return first 

157 

158 

159class ForNode(Node): 

160 child_nodelists = ("nodelist_loop", "nodelist_empty") 

161 

162 def __init__( 

163 self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None 

164 ): 

165 self.loopvars, self.sequence = loopvars, sequence 

166 self.is_reversed = is_reversed 

167 self.nodelist_loop = nodelist_loop 

168 if nodelist_empty is None: 

169 self.nodelist_empty = NodeList() 

170 else: 

171 self.nodelist_empty = nodelist_empty 

172 

173 def __repr__(self): 

174 reversed_text = " reversed" if self.is_reversed else "" 

175 return "<%s: for %s in %s, tail_len: %d%s>" % ( 

176 self.__class__.__name__, 

177 ", ".join(self.loopvars), 

178 self.sequence, 

179 len(self.nodelist_loop), 

180 reversed_text, 

181 ) 

182 

183 def render(self, context): 

184 if "forloop" in context: 

185 parentloop = context["forloop"] 

186 else: 

187 parentloop = {} 

188 with context.push(): 

189 values = self.sequence.resolve(context, ignore_failures=True) 

190 if values is None: 

191 values = [] 

192 if not hasattr(values, "__len__"): 

193 values = list(values) 

194 len_values = len(values) 

195 if len_values < 1: 

196 return self.nodelist_empty.render(context) 

197 nodelist = [] 

198 if self.is_reversed: 

199 values = reversed(values) 

200 num_loopvars = len(self.loopvars) 

201 unpack = num_loopvars > 1 

202 # Create a forloop value in the context. We'll update counters on each 

203 # iteration just below. 

204 loop_dict = context["forloop"] = {"parentloop": parentloop} 

205 for i, item in enumerate(values): 

206 # Shortcuts for current loop iteration number. 

207 loop_dict["counter0"] = i 

208 loop_dict["counter"] = i + 1 

209 # Reverse counter iteration numbers. 

210 loop_dict["revcounter"] = len_values - i 

211 loop_dict["revcounter0"] = len_values - i - 1 

212 # Boolean values designating first and last times through loop. 

213 loop_dict["first"] = i == 0 

214 loop_dict["last"] = i == len_values - 1 

215 

216 pop_context = False 

217 if unpack: 

218 # If there are multiple loop variables, unpack the item into 

219 # them. 

220 try: 

221 len_item = len(item) 

222 except TypeError: # not an iterable 

223 len_item = 1 

224 # Check loop variable count before unpacking 

225 if num_loopvars != len_item: 

226 raise ValueError( 

227 "Need {} values to unpack in for loop; got {}. ".format( 

228 num_loopvars, len_item 

229 ), 

230 ) 

231 unpacked_vars = dict(zip(self.loopvars, item)) 

232 pop_context = True 

233 context.update(unpacked_vars) 

234 else: 

235 context[self.loopvars[0]] = item 

236 

237 for node in self.nodelist_loop: 

238 nodelist.append(node.render_annotated(context)) 

239 

240 if pop_context: 

241 # Pop the loop variables pushed on to the context to avoid 

242 # the context ending up in an inconsistent state when other 

243 # tags (e.g., include and with) push data to context. 

244 context.pop() 

245 return mark_safe("".join(nodelist)) 

246 

247 

248class IfChangedNode(Node): 

249 child_nodelists = ("nodelist_true", "nodelist_false") 

250 

251 def __init__(self, nodelist_true, nodelist_false, *varlist): 

252 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 

253 self._varlist = varlist 

254 

255 def render(self, context): 

256 # Init state storage 

257 state_frame = self._get_context_stack_frame(context) 

258 state_frame.setdefault(self) 

259 

260 nodelist_true_output = None 

261 if self._varlist: 

262 # Consider multiple parameters. This behaves like an OR evaluation 

263 # of the multiple variables. 

264 compare_to = [ 

265 var.resolve(context, ignore_failures=True) for var in self._varlist 

266 ] 

267 else: 

268 # The "{% ifchanged %}" syntax (without any variables) compares 

269 # the rendered output. 

270 compare_to = nodelist_true_output = self.nodelist_true.render(context) 

271 

272 if compare_to != state_frame[self]: 

273 state_frame[self] = compare_to 

274 # render true block if not already rendered 

275 return nodelist_true_output or self.nodelist_true.render(context) 

276 elif self.nodelist_false: 

277 return self.nodelist_false.render(context) 

278 return "" 

279 

280 def _get_context_stack_frame(self, context): 

281 # The Context object behaves like a stack where each template tag can 

282 # create a new scope. Find the place where to store the state to detect 

283 # changes. 

284 if "forloop" in context: 

285 # Ifchanged is bound to the local for loop. 

286 # When there is a loop-in-loop, the state is bound to the inner loop, 

287 # so it resets when the outer loop continues. 

288 return context["forloop"] 

289 else: 

290 # Using ifchanged outside loops. Effectively this is a no-op 

291 # because the state is associated with 'self'. 

292 return context.render_context 

293 

294 

295class IfNode(Node): 

296 def __init__(self, conditions_nodelists): 

297 self.conditions_nodelists = conditions_nodelists 

298 

299 def __repr__(self): 

300 return "<%s>" % self.__class__.__name__ 

301 

302 def __iter__(self): 

303 for _, nodelist in self.conditions_nodelists: 

304 yield from nodelist 

305 

306 @property 

307 def nodelist(self): 

308 return NodeList(self) 

309 

310 def render(self, context): 

311 for condition, nodelist in self.conditions_nodelists: 

312 

313 if condition is not None: # if / elif clause 

314 try: 

315 match = condition.eval(context) 

316 except VariableDoesNotExist: 

317 match = None 

318 else: # else clause 

319 match = True 

320 

321 if match: 

322 return nodelist.render(context) 

323 

324 return "" 

325 

326 

327class LoremNode(Node): 

328 def __init__(self, count, method, common): 

329 self.count, self.method, self.common = count, method, common 

330 

331 def render(self, context): 

332 try: 

333 count = int(self.count.resolve(context)) 

334 except (ValueError, TypeError): 

335 count = 1 

336 if self.method == "w": 

337 return words(count, common=self.common) 

338 else: 

339 paras = paragraphs(count, common=self.common) 

340 if self.method == "p": 

341 paras = ["<p>%s</p>" % p for p in paras] 

342 return "\n\n".join(paras) 

343 

344 

345GroupedResult = namedtuple("GroupedResult", ["grouper", "list"]) 

346 

347 

348class RegroupNode(Node): 

349 def __init__(self, target, expression, var_name): 

350 self.target, self.expression = target, expression 

351 self.var_name = var_name 

352 

353 def resolve_expression(self, obj, context): 

354 # This method is called for each object in self.target. See regroup() 

355 # for the reason why we temporarily put the object in the context. 

356 context[self.var_name] = obj 

357 return self.expression.resolve(context, ignore_failures=True) 

358 

359 def render(self, context): 

360 obj_list = self.target.resolve(context, ignore_failures=True) 

361 if obj_list is None: 

362 # target variable wasn't found in context; fail silently. 

363 context[self.var_name] = [] 

364 return "" 

365 # List of dictionaries in the format: 

366 # {'grouper': 'key', 'list': [list of contents]}. 

367 context[self.var_name] = [ 

368 GroupedResult(grouper=key, list=list(val)) 

369 for key, val in groupby( 

370 obj_list, lambda obj: self.resolve_expression(obj, context) 

371 ) 

372 ] 

373 return "" 

374 

375 

376class LoadNode(Node): 

377 child_nodelists = () 

378 

379 def render(self, context): 

380 return "" 

381 

382 

383class NowNode(Node): 

384 def __init__(self, format_string, asvar=None): 

385 self.format_string = format_string 

386 self.asvar = asvar 

387 

388 def render(self, context): 

389 tzinfo = timezone.get_current_timezone() if settings.USE_TZ else None 

390 formatted = date(datetime.now(tz=tzinfo), self.format_string) 

391 

392 if self.asvar: 

393 context[self.asvar] = formatted 

394 return "" 

395 else: 

396 return formatted 

397 

398 

399class ResetCycleNode(Node): 

400 def __init__(self, node): 

401 self.node = node 

402 

403 def render(self, context): 

404 self.node.reset(context) 

405 return "" 

406 

407 

408class SpacelessNode(Node): 

409 def __init__(self, nodelist): 

410 self.nodelist = nodelist 

411 

412 def render(self, context): 

413 from django.utils.html import strip_spaces_between_tags 

414 

415 return strip_spaces_between_tags(self.nodelist.render(context).strip()) 

416 

417 

418class TemplateTagNode(Node): 

419 mapping = { 

420 "openblock": BLOCK_TAG_START, 

421 "closeblock": BLOCK_TAG_END, 

422 "openvariable": VARIABLE_TAG_START, 

423 "closevariable": VARIABLE_TAG_END, 

424 "openbrace": SINGLE_BRACE_START, 

425 "closebrace": SINGLE_BRACE_END, 

426 "opencomment": COMMENT_TAG_START, 

427 "closecomment": COMMENT_TAG_END, 

428 } 

429 

430 def __init__(self, tagtype): 

431 self.tagtype = tagtype 

432 

433 def render(self, context): 

434 return self.mapping.get(self.tagtype, "") 

435 

436 

437class URLNode(Node): 

438 child_nodelists = () 

439 

440 def __init__(self, view_name, args, kwargs, asvar): 

441 self.view_name = view_name 

442 self.args = args 

443 self.kwargs = kwargs 

444 self.asvar = asvar 

445 

446 def __repr__(self): 

447 return "<%s view_name='%s' args=%s kwargs=%s as=%s>" % ( 

448 self.__class__.__qualname__, 

449 self.view_name, 

450 repr(self.args), 

451 repr(self.kwargs), 

452 repr(self.asvar), 

453 ) 

454 

455 def render(self, context): 

456 from django.urls import NoReverseMatch, reverse 

457 

458 args = [arg.resolve(context) for arg in self.args] 

459 kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()} 

460 view_name = self.view_name.resolve(context) 

461 try: 

462 current_app = context.request.current_app 

463 except AttributeError: 

464 try: 

465 current_app = context.request.resolver_match.namespace 

466 except AttributeError: 

467 current_app = None 

468 # Try to look up the URL. If it fails, raise NoReverseMatch unless the 

469 # {% url ... as var %} construct is used, in which case return nothing. 

470 url = "" 

471 try: 

472 url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app) 

473 except NoReverseMatch: 

474 if self.asvar is None: 

475 raise 

476 

477 if self.asvar: 

478 context[self.asvar] = url 

479 return "" 

480 else: 

481 if context.autoescape: 

482 url = conditional_escape(url) 

483 return url 

484 

485 

486class VerbatimNode(Node): 

487 def __init__(self, content): 

488 self.content = content 

489 

490 def render(self, context): 

491 return self.content 

492 

493 

494class WidthRatioNode(Node): 

495 def __init__(self, val_expr, max_expr, max_width, asvar=None): 

496 self.val_expr = val_expr 

497 self.max_expr = max_expr 

498 self.max_width = max_width 

499 self.asvar = asvar 

500 

501 def render(self, context): 

502 try: 

503 value = self.val_expr.resolve(context) 

504 max_value = self.max_expr.resolve(context) 

505 max_width = int(self.max_width.resolve(context)) 

506 except VariableDoesNotExist: 

507 return "" 

508 except (ValueError, TypeError): 

509 raise TemplateSyntaxError("widthratio final argument must be a number") 

510 try: 

511 value = float(value) 

512 max_value = float(max_value) 

513 ratio = (value / max_value) * max_width 

514 result = str(round(ratio)) 

515 except ZeroDivisionError: 

516 result = "0" 

517 except (ValueError, TypeError, OverflowError): 

518 result = "" 

519 

520 if self.asvar: 

521 context[self.asvar] = result 

522 return "" 

523 else: 

524 return result 

525 

526 

527class WithNode(Node): 

528 def __init__(self, var, name, nodelist, extra_context=None): 

529 self.nodelist = nodelist 

530 # var and name are legacy attributes, being left in case they are used 

531 # by third-party subclasses of this Node. 

532 self.extra_context = extra_context or {} 

533 if name: 

534 self.extra_context[name] = var 

535 

536 def __repr__(self): 

537 return "<%s>" % self.__class__.__name__ 

538 

539 def render(self, context): 

540 values = {key: val.resolve(context) for key, val in self.extra_context.items()} 

541 with context.push(**values): 

542 return self.nodelist.render(context) 

543 

544 

545@register.tag 

546def autoescape(parser, token): 

547 """ 

548 Force autoescape behavior for this block. 

549 """ 

550 # token.split_contents() isn't useful here because this tag doesn't accept 

551 # variable as arguments. 

552 args = token.contents.split() 

553 if len(args) != 2: 

554 raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.") 

555 arg = args[1] 

556 if arg not in ("on", "off"): 

557 raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'") 

558 nodelist = parser.parse(("endautoescape",)) 

559 parser.delete_first_token() 

560 return AutoEscapeControlNode((arg == "on"), nodelist) 

561 

562 

563@register.tag 

564def comment(parser, token): 

565 """ 

566 Ignore everything between ``{% comment %}`` and ``{% endcomment %}``. 

567 """ 

568 parser.skip_past("endcomment") 

569 return CommentNode() 

570 

571 

572@register.tag 

573def cycle(parser, token): 

574 """ 

575 Cycle among the given strings each time this tag is encountered. 

576 

577 Within a loop, cycles among the given strings each time through 

578 the loop:: 

579 

580 {% for o in some_list %} 

581 <tr class="{% cycle 'row1' 'row2' %}"> 

582 ... 

583 </tr> 

584 {% endfor %} 

585 

586 Outside of a loop, give the values a unique name the first time you call 

587 it, then use that name each successive time through:: 

588 

589 <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr> 

590 <tr class="{% cycle rowcolors %}">...</tr> 

591 <tr class="{% cycle rowcolors %}">...</tr> 

592 

593 You can use any number of values, separated by spaces. Commas can also 

594 be used to separate values; if a comma is used, the cycle values are 

595 interpreted as literal strings. 

596 

597 The optional flag "silent" can be used to prevent the cycle declaration 

598 from returning any value:: 

599 

600 {% for o in some_list %} 

601 {% cycle 'row1' 'row2' as rowcolors silent %} 

602 <tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr> 

603 {% endfor %} 

604 """ 

605 # Note: This returns the exact same node on each {% cycle name %} call; 

606 # that is, the node object returned from {% cycle a b c as name %} and the 

607 # one returned from {% cycle name %} are the exact same object. This 

608 # shouldn't cause problems (heh), but if it does, now you know. 

609 # 

610 # Ugly hack warning: This stuffs the named template dict into parser so 

611 # that names are only unique within each template (as opposed to using 

612 # a global variable, which would make cycle names have to be unique across 

613 # *all* templates. 

614 # 

615 # It keeps the last node in the parser to be able to reset it with 

616 # {% resetcycle %}. 

617 

618 args = token.split_contents() 

619 

620 if len(args) < 2: 

621 raise TemplateSyntaxError("'cycle' tag requires at least two arguments") 

622 

623 if len(args) == 2: 

624 # {% cycle foo %} case. 

625 name = args[1] 

626 if not hasattr(parser, "_named_cycle_nodes"): 

627 raise TemplateSyntaxError( 

628 "No named cycles in template. '%s' is not defined" % name 

629 ) 

630 if name not in parser._named_cycle_nodes: 

631 raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) 

632 return parser._named_cycle_nodes[name] 

633 

634 as_form = False 

635 

636 if len(args) > 4: 

637 # {% cycle ... as foo [silent] %} case. 

638 if args[-3] == "as": 

639 if args[-1] != "silent": 

640 raise TemplateSyntaxError( 

641 "Only 'silent' flag is allowed after cycle's name, not '%s'." 

642 % args[-1] 

643 ) 

644 as_form = True 

645 silent = True 

646 args = args[:-1] 

647 elif args[-2] == "as": 

648 as_form = True 

649 silent = False 

650 

651 if as_form: 

652 name = args[-1] 

653 values = [parser.compile_filter(arg) for arg in args[1:-2]] 

654 node = CycleNode(values, name, silent=silent) 

655 if not hasattr(parser, "_named_cycle_nodes"): 

656 parser._named_cycle_nodes = {} 

657 parser._named_cycle_nodes[name] = node 

658 else: 

659 values = [parser.compile_filter(arg) for arg in args[1:]] 

660 node = CycleNode(values) 

661 parser._last_cycle_node = node 

662 return node 

663 

664 

665@register.tag 

666def csrf_token(parser, token): 

667 return CsrfTokenNode() 

668 

669 

670@register.tag 

671def debug(parser, token): 

672 """ 

673 Output a whole load of debugging information, including the current 

674 context and imported modules. 

675 

676 Sample usage:: 

677 

678 <pre> 

679 {% debug %} 

680 </pre> 

681 """ 

682 return DebugNode() 

683 

684 

685@register.tag("filter") 

686def do_filter(parser, token): 

687 """ 

688 Filter the contents of the block through variable filters. 

689 

690 Filters can also be piped through each other, and they can have 

691 arguments -- just like in variable syntax. 

692 

693 Sample usage:: 

694 

695 {% filter force_escape|lower %} 

696 This text will be HTML-escaped, and will appear in lowercase. 

697 {% endfilter %} 

698 

699 Note that the ``escape`` and ``safe`` filters are not acceptable arguments. 

700 Instead, use the ``autoescape`` tag to manage autoescaping for blocks of 

701 template code. 

702 """ 

703 # token.split_contents() isn't useful here because this tag doesn't accept 

704 # variable as arguments. 

705 _, rest = token.contents.split(None, 1) 

706 filter_expr = parser.compile_filter("var|%s" % (rest)) 

707 for func, unused in filter_expr.filters: 

708 filter_name = getattr(func, "_filter_name", None) 

709 if filter_name in ("escape", "safe"): 

710 raise TemplateSyntaxError( 

711 '"filter %s" is not permitted. Use the "autoescape" tag instead.' 

712 % filter_name 

713 ) 

714 nodelist = parser.parse(("endfilter",)) 

715 parser.delete_first_token() 

716 return FilterNode(filter_expr, nodelist) 

717 

718 

719@register.tag 

720def firstof(parser, token): 

721 """ 

722 Output the first variable passed that is not False. 

723 

724 Output nothing if all the passed variables are False. 

725 

726 Sample usage:: 

727 

728 {% firstof var1 var2 var3 as myvar %} 

729 

730 This is equivalent to:: 

731 

732 {% if var1 %} 

733 {{ var1 }} 

734 {% elif var2 %} 

735 {{ var2 }} 

736 {% elif var3 %} 

737 {{ var3 }} 

738 {% endif %} 

739 

740 but much cleaner! 

741 

742 You can also use a literal string as a fallback value in case all 

743 passed variables are False:: 

744 

745 {% firstof var1 var2 var3 "fallback value" %} 

746 

747 If you want to disable auto-escaping of variables you can use:: 

748 

749 {% autoescape off %} 

750 {% firstof var1 var2 var3 "<strong>fallback value</strong>" %} 

751 {% autoescape %} 

752 

753 Or if only some variables should be escaped, you can use:: 

754 

755 {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %} 

756 """ 

757 bits = token.split_contents()[1:] 

758 asvar = None 

759 if not bits: 

760 raise TemplateSyntaxError("'firstof' statement requires at least one argument") 

761 

762 if len(bits) >= 2 and bits[-2] == "as": 

763 asvar = bits[-1] 

764 bits = bits[:-2] 

765 return FirstOfNode([parser.compile_filter(bit) for bit in bits], asvar) 

766 

767 

768@register.tag("for") 

769def do_for(parser, token): 

770 """ 

771 Loop over each item in an array. 

772 

773 For example, to display a list of athletes given ``athlete_list``:: 

774 

775 <ul> 

776 {% for athlete in athlete_list %} 

777 <li>{{ athlete.name }}</li> 

778 {% endfor %} 

779 </ul> 

780 

781 You can loop over a list in reverse by using 

782 ``{% for obj in list reversed %}``. 

783 

784 You can also unpack multiple values from a two-dimensional array:: 

785 

786 {% for key,value in dict.items %} 

787 {{ key }}: {{ value }} 

788 {% endfor %} 

789 

790 The ``for`` tag can take an optional ``{% empty %}`` clause that will 

791 be displayed if the given array is empty or could not be found:: 

792 

793 <ul> 

794 {% for athlete in athlete_list %} 

795 <li>{{ athlete.name }}</li> 

796 {% empty %} 

797 <li>Sorry, no athletes in this list.</li> 

798 {% endfor %} 

799 <ul> 

800 

801 The above is equivalent to -- but shorter, cleaner, and possibly faster 

802 than -- the following:: 

803 

804 <ul> 

805 {% if athlete_list %} 

806 {% for athlete in athlete_list %} 

807 <li>{{ athlete.name }}</li> 

808 {% endfor %} 

809 {% else %} 

810 <li>Sorry, no athletes in this list.</li> 

811 {% endif %} 

812 </ul> 

813 

814 The for loop sets a number of variables available within the loop: 

815 

816 ========================== ================================================ 

817 Variable Description 

818 ========================== ================================================ 

819 ``forloop.counter`` The current iteration of the loop (1-indexed) 

820 ``forloop.counter0`` The current iteration of the loop (0-indexed) 

821 ``forloop.revcounter`` The number of iterations from the end of the 

822 loop (1-indexed) 

823 ``forloop.revcounter0`` The number of iterations from the end of the 

824 loop (0-indexed) 

825 ``forloop.first`` True if this is the first time through the loop 

826 ``forloop.last`` True if this is the last time through the loop 

827 ``forloop.parentloop`` For nested loops, this is the loop "above" the 

828 current one 

829 ========================== ================================================ 

830 """ 

831 bits = token.split_contents() 

832 if len(bits) < 4: 

833 raise TemplateSyntaxError( 

834 "'for' statements should have at least four words: %s" % token.contents 

835 ) 

836 

837 is_reversed = bits[-1] == "reversed" 

838 in_index = -3 if is_reversed else -2 

839 if bits[in_index] != "in": 

840 raise TemplateSyntaxError( 

841 "'for' statements should use the format" 

842 " 'for x in y': %s" % token.contents 

843 ) 

844 

845 invalid_chars = frozenset((" ", '"', "'", FILTER_SEPARATOR)) 

846 loopvars = re.split(r" *, *", " ".join(bits[1:in_index])) 

847 for var in loopvars: 

848 if not var or not invalid_chars.isdisjoint(var): 

849 raise TemplateSyntaxError( 

850 "'for' tag received an invalid argument: %s" % token.contents 

851 ) 

852 

853 sequence = parser.compile_filter(bits[in_index + 1]) 

854 nodelist_loop = parser.parse( 

855 ( 

856 "empty", 

857 "endfor", 

858 ) 

859 ) 

860 token = parser.next_token() 

861 if token.contents == "empty": 

862 nodelist_empty = parser.parse(("endfor",)) 

863 parser.delete_first_token() 

864 else: 

865 nodelist_empty = None 

866 return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty) 

867 

868 

869class TemplateLiteral(Literal): 

870 def __init__(self, value, text): 

871 self.value = value 

872 self.text = text # for better error messages 

873 

874 def display(self): 

875 return self.text 

876 

877 def eval(self, context): 

878 return self.value.resolve(context, ignore_failures=True) 

879 

880 

881class TemplateIfParser(IfParser): 

882 error_class = TemplateSyntaxError 

883 

884 def __init__(self, parser, *args, **kwargs): 

885 self.template_parser = parser 

886 super().__init__(*args, **kwargs) 

887 

888 def create_var(self, value): 

889 return TemplateLiteral(self.template_parser.compile_filter(value), value) 

890 

891 

892@register.tag("if") 

893def do_if(parser, token): 

894 """ 

895 Evaluate a variable, and if that variable is "true" (i.e., exists, is not 

896 empty, and is not a false boolean value), output the contents of the block: 

897 

898 :: 

899 

900 {% if athlete_list %} 

901 Number of athletes: {{ athlete_list|count }} 

902 {% elif athlete_in_locker_room_list %} 

903 Athletes should be out of the locker room soon! 

904 {% else %} 

905 No athletes. 

906 {% endif %} 

907 

908 In the above, if ``athlete_list`` is not empty, the number of athletes will 

909 be displayed by the ``{{ athlete_list|count }}`` variable. 

910 

911 The ``if`` tag may take one or several `` {% elif %}`` clauses, as well as 

912 an ``{% else %}`` clause that will be displayed if all previous conditions 

913 fail. These clauses are optional. 

914 

915 ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of 

916 variables or to negate a given variable:: 

917 

918 {% if not athlete_list %} 

919 There are no athletes. 

920 {% endif %} 

921 

922 {% if athlete_list or coach_list %} 

923 There are some athletes or some coaches. 

924 {% endif %} 

925 

926 {% if athlete_list and coach_list %} 

927 Both athletes and coaches are available. 

928 {% endif %} 

929 

930 {% if not athlete_list or coach_list %} 

931 There are no athletes, or there are some coaches. 

932 {% endif %} 

933 

934 {% if athlete_list and not coach_list %} 

935 There are some athletes and absolutely no coaches. 

936 {% endif %} 

937 

938 Comparison operators are also available, and the use of filters is also 

939 allowed, for example:: 

940 

941 {% if articles|length >= 5 %}...{% endif %} 

942 

943 Arguments and operators _must_ have a space between them, so 

944 ``{% if 1>2 %}`` is not a valid if tag. 

945 

946 All supported operators are: ``or``, ``and``, ``in``, ``not in`` 

947 ``==``, ``!=``, ``>``, ``>=``, ``<`` and ``<=``. 

948 

949 Operator precedence follows Python. 

950 """ 

951 # {% if ... %} 

952 bits = token.split_contents()[1:] 

953 condition = TemplateIfParser(parser, bits).parse() 

954 nodelist = parser.parse(("elif", "else", "endif")) 

955 conditions_nodelists = [(condition, nodelist)] 

956 token = parser.next_token() 

957 

958 # {% elif ... %} (repeatable) 

959 while token.contents.startswith("elif"): 

960 bits = token.split_contents()[1:] 

961 condition = TemplateIfParser(parser, bits).parse() 

962 nodelist = parser.parse(("elif", "else", "endif")) 

963 conditions_nodelists.append((condition, nodelist)) 

964 token = parser.next_token() 

965 

966 # {% else %} (optional) 

967 if token.contents == "else": 

968 nodelist = parser.parse(("endif",)) 

969 conditions_nodelists.append((None, nodelist)) 

970 token = parser.next_token() 

971 

972 # {% endif %} 

973 if token.contents != "endif": 

974 raise TemplateSyntaxError( 

975 'Malformed template tag at line {}: "{}"'.format( 

976 token.lineno, token.contents 

977 ) 

978 ) 

979 

980 return IfNode(conditions_nodelists) 

981 

982 

983@register.tag 

984def ifchanged(parser, token): 

985 """ 

986 Check if a value has changed from the last iteration of a loop. 

987 

988 The ``{% ifchanged %}`` block tag is used within a loop. It has two 

989 possible uses. 

990 

991 1. Check its own rendered contents against its previous state and only 

992 displays the content if it has changed. For example, this displays a 

993 list of days, only displaying the month if it changes:: 

994 

995 <h1>Archive for {{ year }}</h1> 

996 

997 {% for date in days %} 

998 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %} 

999 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> 

1000 {% endfor %} 

1001 

1002 2. If given one or more variables, check whether any variable has changed. 

1003 For example, the following shows the date every time it changes, while 

1004 showing the hour if either the hour or the date has changed:: 

1005 

1006 {% for date in days %} 

1007 {% ifchanged date.date %} {{ date.date }} {% endifchanged %} 

1008 {% ifchanged date.hour date.date %} 

1009 {{ date.hour }} 

1010 {% endifchanged %} 

1011 {% endfor %} 

1012 """ 

1013 bits = token.split_contents() 

1014 nodelist_true = parser.parse(("else", "endifchanged")) 

1015 token = parser.next_token() 

1016 if token.contents == "else": 

1017 nodelist_false = parser.parse(("endifchanged",)) 

1018 parser.delete_first_token() 

1019 else: 

1020 nodelist_false = NodeList() 

1021 values = [parser.compile_filter(bit) for bit in bits[1:]] 

1022 return IfChangedNode(nodelist_true, nodelist_false, *values) 

1023 

1024 

1025def find_library(parser, name): 

1026 try: 

1027 return parser.libraries[name] 

1028 except KeyError: 

1029 raise TemplateSyntaxError( 

1030 "'%s' is not a registered tag library. Must be one of:\n%s" 

1031 % ( 

1032 name, 

1033 "\n".join(sorted(parser.libraries)), 

1034 ), 

1035 ) 

1036 

1037 

1038def load_from_library(library, label, names): 

1039 """ 

1040 Return a subset of tags and filters from a library. 

1041 """ 

1042 subset = Library() 

1043 for name in names: 

1044 found = False 

1045 if name in library.tags: 

1046 found = True 

1047 subset.tags[name] = library.tags[name] 

1048 if name in library.filters: 

1049 found = True 

1050 subset.filters[name] = library.filters[name] 

1051 if found is False: 

1052 raise TemplateSyntaxError( 

1053 "'%s' is not a valid tag or filter in tag library '%s'" 

1054 % ( 

1055 name, 

1056 label, 

1057 ), 

1058 ) 

1059 return subset 

1060 

1061 

1062@register.tag 

1063def load(parser, token): 

1064 """ 

1065 Load a custom template tag library into the parser. 

1066 

1067 For example, to load the template tags in 

1068 ``django/templatetags/news/photos.py``:: 

1069 

1070 {% load news.photos %} 

1071 

1072 Can also be used to load an individual tag/filter from 

1073 a library:: 

1074 

1075 {% load byline from news %} 

1076 """ 

1077 # token.split_contents() isn't useful here because this tag doesn't accept 

1078 # variable as arguments. 

1079 bits = token.contents.split() 

1080 if len(bits) >= 4 and bits[-2] == "from": 

1081 # from syntax is used; load individual tags from the library 

1082 name = bits[-1] 

1083 lib = find_library(parser, name) 

1084 subset = load_from_library(lib, name, bits[1:-2]) 

1085 parser.add_library(subset) 

1086 else: 

1087 # one or more libraries are specified; load and add them to the parser 

1088 for name in bits[1:]: 

1089 lib = find_library(parser, name) 

1090 parser.add_library(lib) 

1091 return LoadNode() 

1092 

1093 

1094@register.tag 

1095def lorem(parser, token): 

1096 """ 

1097 Create random Latin text useful for providing test data in templates. 

1098 

1099 Usage format:: 

1100 

1101 {% lorem [count] [method] [random] %} 

1102 

1103 ``count`` is a number (or variable) containing the number of paragraphs or 

1104 words to generate (default is 1). 

1105 

1106 ``method`` is either ``w`` for words, ``p`` for HTML paragraphs, ``b`` for 

1107 plain-text paragraph blocks (default is ``b``). 

1108 

1109 ``random`` is the word ``random``, which if given, does not use the common 

1110 paragraph (starting "Lorem ipsum dolor sit amet, consectetuer..."). 

1111 

1112 Examples: 

1113 

1114 * ``{% lorem %}`` outputs the common "lorem ipsum" paragraph 

1115 * ``{% lorem 3 p %}`` outputs the common "lorem ipsum" paragraph 

1116 and two random paragraphs each wrapped in HTML ``<p>`` tags 

1117 * ``{% lorem 2 w random %}`` outputs two random latin words 

1118 """ 

1119 bits = list(token.split_contents()) 

1120 tagname = bits[0] 

1121 # Random bit 

1122 common = bits[-1] != "random" 

1123 if not common: 

1124 bits.pop() 

1125 # Method bit 

1126 if bits[-1] in ("w", "p", "b"): 

1127 method = bits.pop() 

1128 else: 

1129 method = "b" 

1130 # Count bit 

1131 if len(bits) > 1: 

1132 count = bits.pop() 

1133 else: 

1134 count = "1" 

1135 count = parser.compile_filter(count) 

1136 if len(bits) != 1: 

1137 raise TemplateSyntaxError("Incorrect format for %r tag" % tagname) 

1138 return LoremNode(count, method, common) 

1139 

1140 

1141@register.tag 

1142def now(parser, token): 

1143 """ 

1144 Display the date, formatted according to the given string. 

1145 

1146 Use the same format as PHP's ``date()`` function; see https://php.net/date 

1147 for all the possible values. 

1148 

1149 Sample usage:: 

1150 

1151 It is {% now "jS F Y H:i" %} 

1152 """ 

1153 bits = token.split_contents() 

1154 asvar = None 

1155 if len(bits) == 4 and bits[-2] == "as": 

1156 asvar = bits[-1] 

1157 bits = bits[:-2] 

1158 if len(bits) != 2: 

1159 raise TemplateSyntaxError("'now' statement takes one argument") 

1160 format_string = bits[1][1:-1] 

1161 return NowNode(format_string, asvar) 

1162 

1163 

1164@register.tag 

1165def regroup(parser, token): 

1166 """ 

1167 Regroup a list of alike objects by a common attribute. 

1168 

1169 This complex tag is best illustrated by use of an example: say that 

1170 ``musicians`` is a list of ``Musician`` objects that have ``name`` and 

1171 ``instrument`` attributes, and you'd like to display a list that 

1172 looks like: 

1173 

1174 * Guitar: 

1175 * Django Reinhardt 

1176 * Emily Remler 

1177 * Piano: 

1178 * Lovie Austin 

1179 * Bud Powell 

1180 * Trumpet: 

1181 * Duke Ellington 

1182 

1183 The following snippet of template code would accomplish this dubious task:: 

1184 

1185 {% regroup musicians by instrument as grouped %} 

1186 <ul> 

1187 {% for group in grouped %} 

1188 <li>{{ group.grouper }} 

1189 <ul> 

1190 {% for musician in group.list %} 

1191 <li>{{ musician.name }}</li> 

1192 {% endfor %} 

1193 </ul> 

1194 {% endfor %} 

1195 </ul> 

1196 

1197 As you can see, ``{% regroup %}`` populates a variable with a list of 

1198 objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the 

1199 item that was grouped by; ``list`` contains the list of objects that share 

1200 that ``grouper``. In this case, ``grouper`` would be ``Guitar``, ``Piano`` 

1201 and ``Trumpet``, and ``list`` is the list of musicians who play this 

1202 instrument. 

1203 

1204 Note that ``{% regroup %}`` does not work when the list to be grouped is not 

1205 sorted by the key you are grouping by! This means that if your list of 

1206 musicians was not sorted by instrument, you'd need to make sure it is sorted 

1207 before using it, i.e.:: 

1208 

1209 {% regroup musicians|dictsort:"instrument" by instrument as grouped %} 

1210 """ 

1211 bits = token.split_contents() 

1212 if len(bits) != 6: 

1213 raise TemplateSyntaxError("'regroup' tag takes five arguments") 

1214 target = parser.compile_filter(bits[1]) 

1215 if bits[2] != "by": 

1216 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") 

1217 if bits[4] != "as": 

1218 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must be 'as'") 

1219 var_name = bits[5] 

1220 # RegroupNode will take each item in 'target', put it in the context under 

1221 # 'var_name', evaluate 'var_name'.'expression' in the current context, and 

1222 # group by the resulting value. After all items are processed, it will 

1223 # save the final result in the context under 'var_name', thus clearing the 

1224 # temporary values. This hack is necessary because the template engine 

1225 # doesn't provide a context-aware equivalent of Python's getattr. 

1226 expression = parser.compile_filter( 

1227 var_name + VARIABLE_ATTRIBUTE_SEPARATOR + bits[3] 

1228 ) 

1229 return RegroupNode(target, expression, var_name) 

1230 

1231 

1232@register.tag 

1233def resetcycle(parser, token): 

1234 """ 

1235 Reset a cycle tag. 

1236 

1237 If an argument is given, reset the last rendered cycle tag whose name 

1238 matches the argument, else reset the last rendered cycle tag (named or 

1239 unnamed). 

1240 """ 

1241 args = token.split_contents() 

1242 

1243 if len(args) > 2: 

1244 raise TemplateSyntaxError("%r tag accepts at most one argument." % args[0]) 

1245 

1246 if len(args) == 2: 

1247 name = args[1] 

1248 try: 

1249 return ResetCycleNode(parser._named_cycle_nodes[name]) 

1250 except (AttributeError, KeyError): 

1251 raise TemplateSyntaxError("Named cycle '%s' does not exist." % name) 

1252 try: 

1253 return ResetCycleNode(parser._last_cycle_node) 

1254 except AttributeError: 

1255 raise TemplateSyntaxError("No cycles in template.") 

1256 

1257 

1258@register.tag 

1259def spaceless(parser, token): 

1260 """ 

1261 Remove whitespace between HTML tags, including tab and newline characters. 

1262 

1263 Example usage:: 

1264 

1265 {% spaceless %} 

1266 <p> 

1267 <a href="foo/">Foo</a> 

1268 </p> 

1269 {% endspaceless %} 

1270 

1271 This example returns this HTML:: 

1272 

1273 <p><a href="foo/">Foo</a></p> 

1274 

1275 Only space between *tags* is normalized -- not space between tags and text. 

1276 In this example, the space around ``Hello`` isn't stripped:: 

1277 

1278 {% spaceless %} 

1279 <strong> 

1280 Hello 

1281 </strong> 

1282 {% endspaceless %} 

1283 """ 

1284 nodelist = parser.parse(("endspaceless",)) 

1285 parser.delete_first_token() 

1286 return SpacelessNode(nodelist) 

1287 

1288 

1289@register.tag 

1290def templatetag(parser, token): 

1291 """ 

1292 Output one of the bits used to compose template tags. 

1293 

1294 Since the template system has no concept of "escaping", to display one of 

1295 the bits used in template tags, you must use the ``{% templatetag %}`` tag. 

1296 

1297 The argument tells which template bit to output: 

1298 

1299 ================== ======= 

1300 Argument Outputs 

1301 ================== ======= 

1302 ``openblock`` ``{%`` 

1303 ``closeblock`` ``%}`` 

1304 ``openvariable`` ``{{`` 

1305 ``closevariable`` ``}}`` 

1306 ``openbrace`` ``{`` 

1307 ``closebrace`` ``}`` 

1308 ``opencomment`` ``{#`` 

1309 ``closecomment`` ``#}`` 

1310 ================== ======= 

1311 """ 

1312 # token.split_contents() isn't useful here because this tag doesn't accept 

1313 # variable as arguments. 

1314 bits = token.contents.split() 

1315 if len(bits) != 2: 

1316 raise TemplateSyntaxError("'templatetag' statement takes one argument") 

1317 tag = bits[1] 

1318 if tag not in TemplateTagNode.mapping: 

1319 raise TemplateSyntaxError( 

1320 "Invalid templatetag argument: '%s'." 

1321 " Must be one of: %s" % (tag, list(TemplateTagNode.mapping)) 

1322 ) 

1323 return TemplateTagNode(tag) 

1324 

1325 

1326@register.tag 

1327def url(parser, token): 

1328 r""" 

1329 Return an absolute URL matching the given view with its parameters. 

1330 

1331 This is a way to define links that aren't tied to a particular URL 

1332 configuration:: 

1333 

1334 {% url "url_name" arg1 arg2 %} 

1335 

1336 or 

1337 

1338 {% url "url_name" name1=value1 name2=value2 %} 

1339 

1340 The first argument is a URL pattern name. Other arguments are 

1341 space-separated values that will be filled in place of positional and 

1342 keyword arguments in the URL. Don't mix positional and keyword arguments. 

1343 All arguments for the URL must be present. 

1344 

1345 For example, if you have a view ``app_name.views.client_details`` taking 

1346 the client's id and the corresponding line in a URLconf looks like this:: 

1347 

1348 path('client/<int:id>/', views.client_details, name='client-detail-view') 

1349 

1350 and this app's URLconf is included into the project's URLconf under some 

1351 path:: 

1352 

1353 path('clients/', include('app_name.urls')) 

1354 

1355 then in a template you can create a link for a certain client like this:: 

1356 

1357 {% url "client-detail-view" client.id %} 

1358 

1359 The URL will look like ``/clients/client/123/``. 

1360 

1361 The first argument may also be the name of a template variable that will be 

1362 evaluated to obtain the view name or the URL name, e.g.:: 

1363 

1364 {% with url_name="client-detail-view" %} 

1365 {% url url_name client.id %} 

1366 {% endwith %} 

1367 """ 

1368 bits = token.split_contents() 

1369 if len(bits) < 2: 

1370 raise TemplateSyntaxError( 

1371 "'%s' takes at least one argument, a URL pattern name." % bits[0] 

1372 ) 

1373 viewname = parser.compile_filter(bits[1]) 

1374 args = [] 

1375 kwargs = {} 

1376 asvar = None 

1377 bits = bits[2:] 

1378 if len(bits) >= 2 and bits[-2] == "as": 

1379 asvar = bits[-1] 

1380 bits = bits[:-2] 

1381 

1382 for bit in bits: 

1383 match = kwarg_re.match(bit) 

1384 if not match: 

1385 raise TemplateSyntaxError("Malformed arguments to url tag") 

1386 name, value = match.groups() 

1387 if name: 

1388 kwargs[name] = parser.compile_filter(value) 

1389 else: 

1390 args.append(parser.compile_filter(value)) 

1391 

1392 return URLNode(viewname, args, kwargs, asvar) 

1393 

1394 

1395@register.tag 

1396def verbatim(parser, token): 

1397 """ 

1398 Stop the template engine from rendering the contents of this block tag. 

1399 

1400 Usage:: 

1401 

1402 {% verbatim %} 

1403 {% don't process this %} 

1404 {% endverbatim %} 

1405 

1406 You can also designate a specific closing tag block (allowing the 

1407 unrendered use of ``{% endverbatim %}``):: 

1408 

1409 {% verbatim myblock %} 

1410 ... 

1411 {% endverbatim myblock %} 

1412 """ 

1413 nodelist = parser.parse(("endverbatim",)) 

1414 parser.delete_first_token() 

1415 return VerbatimNode(nodelist.render(Context())) 

1416 

1417 

1418@register.tag 

1419def widthratio(parser, token): 

1420 """ 

1421 For creating bar charts and such. Calculate the ratio of a given value to a 

1422 maximum value, and then apply that ratio to a constant. 

1423 

1424 For example:: 

1425 

1426 <img src="bar.png" alt="Bar" 

1427 height="10" width="{% widthratio this_value max_value max_width %}"> 

1428 

1429 If ``this_value`` is 175, ``max_value`` is 200, and ``max_width`` is 100, 

1430 the image in the above example will be 88 pixels wide 

1431 (because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88). 

1432 

1433 In some cases you might want to capture the result of widthratio in a 

1434 variable. It can be useful for instance in a blocktranslate like this:: 

1435 

1436 {% widthratio this_value max_value max_width as width %} 

1437 {% blocktranslate %}The width is: {{ width }}{% endblocktranslate %} 

1438 """ 

1439 bits = token.split_contents() 

1440 if len(bits) == 4: 

1441 tag, this_value_expr, max_value_expr, max_width = bits 

1442 asvar = None 

1443 elif len(bits) == 6: 

1444 tag, this_value_expr, max_value_expr, max_width, as_, asvar = bits 

1445 if as_ != "as": 

1446 raise TemplateSyntaxError( 

1447 "Invalid syntax in widthratio tag. Expecting 'as' keyword" 

1448 ) 

1449 else: 

1450 raise TemplateSyntaxError("widthratio takes at least three arguments") 

1451 

1452 return WidthRatioNode( 

1453 parser.compile_filter(this_value_expr), 

1454 parser.compile_filter(max_value_expr), 

1455 parser.compile_filter(max_width), 

1456 asvar=asvar, 

1457 ) 

1458 

1459 

1460@register.tag("with") 

1461def do_with(parser, token): 

1462 """ 

1463 Add one or more values to the context (inside of this block) for caching 

1464 and easy access. 

1465 

1466 For example:: 

1467 

1468 {% with total=person.some_sql_method %} 

1469 {{ total }} object{{ total|pluralize }} 

1470 {% endwith %} 

1471 

1472 Multiple values can be added to the context:: 

1473 

1474 {% with foo=1 bar=2 %} 

1475 ... 

1476 {% endwith %} 

1477 

1478 The legacy format of ``{% with person.some_sql_method as total %}`` is 

1479 still accepted. 

1480 """ 

1481 bits = token.split_contents() 

1482 remaining_bits = bits[1:] 

1483 extra_context = token_kwargs(remaining_bits, parser, support_legacy=True) 

1484 if not extra_context: 

1485 raise TemplateSyntaxError( 

1486 "%r expected at least one variable assignment" % bits[0] 

1487 ) 

1488 if remaining_bits: 

1489 raise TemplateSyntaxError( 

1490 "%r received an invalid token: %r" % (bits[0], remaining_bits[0]) 

1491 ) 

1492 nodelist = parser.parse(("endwith",)) 

1493 parser.delete_first_token() 

1494 return WithNode(None, None, nodelist, extra_context=extra_context)