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

549 statements  

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

1""" 

2This is the Django template system. 

3 

4How it works: 

5 

6The Lexer.tokenize() method converts a template string (i.e., a string 

7containing markup with custom template tags) to tokens, which can be either 

8plain text (TokenType.TEXT), variables (TokenType.VAR), or block statements 

9(TokenType.BLOCK). 

10 

11The Parser() class takes a list of tokens in its constructor, and its parse() 

12method returns a compiled template -- which is, under the hood, a list of 

13Node objects. 

14 

15Each Node is responsible for creating some sort of output -- e.g. simple text 

16(TextNode), variable values in a given context (VariableNode), results of basic 

17logic (IfNode), results of looping (ForNode), or anything else. The core Node 

18types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can 

19define their own custom node types. 

20 

21Each Node has a render() method, which takes a Context and returns a string of 

22the rendered node. For example, the render() method of a Variable Node returns 

23the variable's value as a string. The render() method of a ForNode returns the 

24rendered output of whatever was inside the loop, recursively. 

25 

26The Template class is a convenient wrapper that takes care of template 

27compilation and rendering. 

28 

29Usage: 

30 

31The only thing you should ever use directly in this file is the Template class. 

32Create a compiled template object with a template_string, then call render() 

33with a context. In the compilation stage, the TemplateSyntaxError exception 

34will be raised if the template doesn't have proper syntax. 

35 

36Sample code: 

37 

38>>> from django import template 

39>>> s = '<html>{% if test %}<h1>{{ varvalue }}</h1>{% endif %}</html>' 

40>>> t = template.Template(s) 

41 

42(t is now a compiled template, and its render() method can be called multiple 

43times with multiple contexts) 

44 

45>>> c = template.Context({'test':True, 'varvalue': 'Hello'}) 

46>>> t.render(c) 

47'<html><h1>Hello</h1></html>' 

48>>> c = template.Context({'test':False, 'varvalue': 'Hello'}) 

49>>> t.render(c) 

50'<html></html>' 

51""" 

52 

53import inspect 

54import logging 

55import re 

56from enum import Enum 

57 

58from django.template.context import BaseContext 

59from django.utils.formats import localize 

60from django.utils.html import conditional_escape, escape 

61from django.utils.regex_helper import _lazy_re_compile 

62from django.utils.safestring import SafeData, SafeString, mark_safe 

63from django.utils.text import get_text_list, smart_split, unescape_string_literal 

64from django.utils.timezone import template_localtime 

65from django.utils.translation import gettext_lazy, pgettext_lazy 

66 

67from .exceptions import TemplateSyntaxError 

68 

69# template syntax constants 

70FILTER_SEPARATOR = "|" 

71FILTER_ARGUMENT_SEPARATOR = ":" 

72VARIABLE_ATTRIBUTE_SEPARATOR = "." 

73BLOCK_TAG_START = "{%" 

74BLOCK_TAG_END = "%}" 

75VARIABLE_TAG_START = "{{" 

76VARIABLE_TAG_END = "}}" 

77COMMENT_TAG_START = "{#" 

78COMMENT_TAG_END = "#}" 

79SINGLE_BRACE_START = "{" 

80SINGLE_BRACE_END = "}" 

81 

82# what to report as the origin for templates that come from non-loader sources 

83# (e.g. strings) 

84UNKNOWN_SOURCE = "<unknown source>" 

85 

86# Match BLOCK_TAG_*, VARIABLE_TAG_*, and COMMENT_TAG_* tags and capture the 

87# entire tag, including start/end delimiters. Using re.compile() is faster 

88# than instantiating SimpleLazyObject with _lazy_re_compile(). 

89tag_re = re.compile(r"({%.*?%}|{{.*?}}|{#.*?#})") 

90 

91logger = logging.getLogger("django.template") 

92 

93 

94class TokenType(Enum): 

95 TEXT = 0 

96 VAR = 1 

97 BLOCK = 2 

98 COMMENT = 3 

99 

100 

101class VariableDoesNotExist(Exception): 

102 def __init__(self, msg, params=()): 

103 self.msg = msg 

104 self.params = params 

105 

106 def __str__(self): 

107 return self.msg % self.params 

108 

109 

110class Origin: 

111 def __init__(self, name, template_name=None, loader=None): 

112 self.name = name 

113 self.template_name = template_name 

114 self.loader = loader 

115 

116 def __str__(self): 

117 return self.name 

118 

119 def __repr__(self): 

120 return "<%s name=%r>" % (self.__class__.__qualname__, self.name) 

121 

122 def __eq__(self, other): 

123 return ( 

124 isinstance(other, Origin) 

125 and self.name == other.name 

126 and self.loader == other.loader 

127 ) 

128 

129 @property 

130 def loader_name(self): 

131 if self.loader: 

132 return "%s.%s" % ( 

133 self.loader.__module__, 

134 self.loader.__class__.__name__, 

135 ) 

136 

137 

138class Template: 

139 def __init__(self, template_string, origin=None, name=None, engine=None): 

140 # If Template is instantiated directly rather than from an Engine and 

141 # exactly one Django template engine is configured, use that engine. 

142 # This is required to preserve backwards-compatibility for direct use 

143 # e.g. Template('...').render(Context({...})) 

144 if engine is None: 

145 from .engine import Engine 

146 

147 engine = Engine.get_default() 

148 if origin is None: 

149 origin = Origin(UNKNOWN_SOURCE) 

150 self.name = name 

151 self.origin = origin 

152 self.engine = engine 

153 self.source = str(template_string) # May be lazy. 

154 self.nodelist = self.compile_nodelist() 

155 

156 def __iter__(self): 

157 for node in self.nodelist: 

158 yield from node 

159 

160 def __repr__(self): 

161 return '<%s template_string="%s...">' % ( 

162 self.__class__.__qualname__, 

163 self.source[:20].replace("\n", ""), 

164 ) 

165 

166 def _render(self, context): 

167 return self.nodelist.render(context) 

168 

169 def render(self, context): 

170 "Display stage -- can be called many times" 

171 with context.render_context.push_state(self): 

172 if context.template is None: 

173 with context.bind_template(self): 

174 context.template_name = self.name 

175 return self._render(context) 

176 else: 

177 return self._render(context) 

178 

179 def compile_nodelist(self): 

180 """ 

181 Parse and compile the template source into a nodelist. If debug 

182 is True and an exception occurs during parsing, the exception is 

183 annotated with contextual line information where it occurred in the 

184 template source. 

185 """ 

186 if self.engine.debug: 

187 lexer = DebugLexer(self.source) 

188 else: 

189 lexer = Lexer(self.source) 

190 

191 tokens = lexer.tokenize() 

192 parser = Parser( 

193 tokens, 

194 self.engine.template_libraries, 

195 self.engine.template_builtins, 

196 self.origin, 

197 ) 

198 

199 try: 

200 return parser.parse() 

201 except Exception as e: 

202 if self.engine.debug: 

203 e.template_debug = self.get_exception_info(e, e.token) 

204 raise 

205 

206 def get_exception_info(self, exception, token): 

207 """ 

208 Return a dictionary containing contextual line information of where 

209 the exception occurred in the template. The following information is 

210 provided: 

211 

212 message 

213 The message of the exception raised. 

214 

215 source_lines 

216 The lines before, after, and including the line the exception 

217 occurred on. 

218 

219 line 

220 The line number the exception occurred on. 

221 

222 before, during, after 

223 The line the exception occurred on split into three parts: 

224 1. The content before the token that raised the error. 

225 2. The token that raised the error. 

226 3. The content after the token that raised the error. 

227 

228 total 

229 The number of lines in source_lines. 

230 

231 top 

232 The line number where source_lines starts. 

233 

234 bottom 

235 The line number where source_lines ends. 

236 

237 start 

238 The start position of the token in the template source. 

239 

240 end 

241 The end position of the token in the template source. 

242 """ 

243 start, end = token.position 

244 context_lines = 10 

245 line = 0 

246 upto = 0 

247 source_lines = [] 

248 before = during = after = "" 

249 for num, next in enumerate(linebreak_iter(self.source)): 

250 if start >= upto and end <= next: 

251 line = num 

252 before = escape(self.source[upto:start]) 

253 during = escape(self.source[start:end]) 

254 after = escape(self.source[end:next]) 

255 source_lines.append((num, escape(self.source[upto:next]))) 

256 upto = next 

257 total = len(source_lines) 

258 

259 top = max(1, line - context_lines) 

260 bottom = min(total, line + 1 + context_lines) 

261 

262 # In some rare cases exc_value.args can be empty or an invalid 

263 # string. 

264 try: 

265 message = str(exception.args[0]) 

266 except (IndexError, UnicodeDecodeError): 

267 message = "(Could not get exception message)" 

268 

269 return { 

270 "message": message, 

271 "source_lines": source_lines[top:bottom], 

272 "before": before, 

273 "during": during, 

274 "after": after, 

275 "top": top, 

276 "bottom": bottom, 

277 "total": total, 

278 "line": line, 

279 "name": self.origin.name, 

280 "start": start, 

281 "end": end, 

282 } 

283 

284 

285def linebreak_iter(template_source): 

286 yield 0 

287 p = template_source.find("\n") 

288 while p >= 0: 

289 yield p + 1 

290 p = template_source.find("\n", p + 1) 

291 yield len(template_source) + 1 

292 

293 

294class Token: 

295 def __init__(self, token_type, contents, position=None, lineno=None): 

296 """ 

297 A token representing a string from the template. 

298 

299 token_type 

300 A TokenType, either .TEXT, .VAR, .BLOCK, or .COMMENT. 

301 

302 contents 

303 The token source string. 

304 

305 position 

306 An optional tuple containing the start and end index of the token 

307 in the template source. This is used for traceback information 

308 when debug is on. 

309 

310 lineno 

311 The line number the token appears on in the template source. 

312 This is used for traceback information and gettext files. 

313 """ 

314 self.token_type, self.contents = token_type, contents 

315 self.lineno = lineno 

316 self.position = position 

317 

318 def __repr__(self): 

319 token_name = self.token_type.name.capitalize() 

320 return '<%s token: "%s...">' % ( 

321 token_name, 

322 self.contents[:20].replace("\n", ""), 

323 ) 

324 

325 def split_contents(self): 

326 split = [] 

327 bits = smart_split(self.contents) 

328 for bit in bits: 

329 # Handle translation-marked template pieces 

330 if bit.startswith(('_("', "_('")): 

331 sentinel = bit[2] + ")" 

332 trans_bit = [bit] 

333 while not bit.endswith(sentinel): 

334 bit = next(bits) 

335 trans_bit.append(bit) 

336 bit = " ".join(trans_bit) 

337 split.append(bit) 

338 return split 

339 

340 

341class Lexer: 

342 def __init__(self, template_string): 

343 self.template_string = template_string 

344 self.verbatim = False 

345 

346 def __repr__(self): 

347 return '<%s template_string="%s...", verbatim=%s>' % ( 

348 self.__class__.__qualname__, 

349 self.template_string[:20].replace("\n", ""), 

350 self.verbatim, 

351 ) 

352 

353 def tokenize(self): 

354 """ 

355 Return a list of tokens from a given template_string. 

356 """ 

357 in_tag = False 

358 lineno = 1 

359 result = [] 

360 for token_string in tag_re.split(self.template_string): 

361 if token_string: 

362 result.append(self.create_token(token_string, None, lineno, in_tag)) 

363 lineno += token_string.count("\n") 

364 in_tag = not in_tag 

365 return result 

366 

367 def create_token(self, token_string, position, lineno, in_tag): 

368 """ 

369 Convert the given token string into a new Token object and return it. 

370 If in_tag is True, we are processing something that matched a tag, 

371 otherwise it should be treated as a literal string. 

372 """ 

373 if in_tag: 

374 # The [0:2] and [2:-2] ranges below strip off *_TAG_START and 

375 # *_TAG_END. The 2's are hard-coded for performance. Using 

376 # len(BLOCK_TAG_START) would permit BLOCK_TAG_START to be 

377 # different, but it's not likely that the TAG_START values will 

378 # change anytime soon. 

379 token_start = token_string[0:2] 

380 if token_start == BLOCK_TAG_START: 

381 content = token_string[2:-2].strip() 

382 if self.verbatim: 

383 # Then a verbatim block is being processed. 

384 if content != self.verbatim: 

385 return Token(TokenType.TEXT, token_string, position, lineno) 

386 # Otherwise, the current verbatim block is ending. 

387 self.verbatim = False 

388 elif content[:9] in ("verbatim", "verbatim "): 

389 # Then a verbatim block is starting. 

390 self.verbatim = "end%s" % content 

391 return Token(TokenType.BLOCK, content, position, lineno) 

392 if not self.verbatim: 

393 content = token_string[2:-2].strip() 

394 if token_start == VARIABLE_TAG_START: 

395 return Token(TokenType.VAR, content, position, lineno) 

396 # BLOCK_TAG_START was handled above. 

397 assert token_start == COMMENT_TAG_START 

398 return Token(TokenType.COMMENT, content, position, lineno) 

399 return Token(TokenType.TEXT, token_string, position, lineno) 

400 

401 

402class DebugLexer(Lexer): 

403 def _tag_re_split_positions(self): 

404 last = 0 

405 for match in tag_re.finditer(self.template_string): 

406 start, end = match.span() 

407 yield last, start 

408 yield start, end 

409 last = end 

410 yield last, len(self.template_string) 

411 

412 # This parallels the use of tag_re.split() in Lexer.tokenize(). 

413 def _tag_re_split(self): 

414 for position in self._tag_re_split_positions(): 

415 yield self.template_string[slice(*position)], position 

416 

417 def tokenize(self): 

418 """ 

419 Split a template string into tokens and annotates each token with its 

420 start and end position in the source. This is slower than the default 

421 lexer so only use it when debug is True. 

422 """ 

423 # For maintainability, it is helpful if the implementation below can 

424 # continue to closely parallel Lexer.tokenize()'s implementation. 

425 in_tag = False 

426 lineno = 1 

427 result = [] 

428 for token_string, position in self._tag_re_split(): 

429 if token_string: 

430 result.append(self.create_token(token_string, position, lineno, in_tag)) 

431 lineno += token_string.count("\n") 

432 in_tag = not in_tag 

433 return result 

434 

435 

436class Parser: 

437 def __init__(self, tokens, libraries=None, builtins=None, origin=None): 

438 # Reverse the tokens so delete_first_token(), prepend_token(), and 

439 # next_token() can operate at the end of the list in constant time. 

440 self.tokens = list(reversed(tokens)) 

441 self.tags = {} 

442 self.filters = {} 

443 self.command_stack = [] 

444 

445 if libraries is None: 

446 libraries = {} 

447 if builtins is None: 

448 builtins = [] 

449 

450 self.libraries = libraries 

451 for builtin in builtins: 

452 self.add_library(builtin) 

453 self.origin = origin 

454 

455 def __repr__(self): 

456 return "<%s tokens=%r>" % (self.__class__.__qualname__, self.tokens) 

457 

458 def parse(self, parse_until=None): 

459 """ 

460 Iterate through the parser tokens and compiles each one into a node. 

461 

462 If parse_until is provided, parsing will stop once one of the 

463 specified tokens has been reached. This is formatted as a list of 

464 tokens, e.g. ['elif', 'else', 'endif']. If no matching token is 

465 reached, raise an exception with the unclosed block tag details. 

466 """ 

467 if parse_until is None: 

468 parse_until = [] 

469 nodelist = NodeList() 

470 while self.tokens: 

471 token = self.next_token() 

472 # Use the raw values here for TokenType.* for a tiny performance boost. 

473 token_type = token.token_type.value 

474 if token_type == 0: # TokenType.TEXT 

475 self.extend_nodelist(nodelist, TextNode(token.contents), token) 

476 elif token_type == 1: # TokenType.VAR 

477 if not token.contents: 

478 raise self.error( 

479 token, "Empty variable tag on line %d" % token.lineno 

480 ) 

481 try: 

482 filter_expression = self.compile_filter(token.contents) 

483 except TemplateSyntaxError as e: 

484 raise self.error(token, e) 

485 var_node = VariableNode(filter_expression) 

486 self.extend_nodelist(nodelist, var_node, token) 

487 elif token_type == 2: # TokenType.BLOCK 

488 try: 

489 command = token.contents.split()[0] 

490 except IndexError: 

491 raise self.error(token, "Empty block tag on line %d" % token.lineno) 

492 if command in parse_until: 

493 # A matching token has been reached. Return control to 

494 # the caller. Put the token back on the token list so the 

495 # caller knows where it terminated. 

496 self.prepend_token(token) 

497 return nodelist 

498 # Add the token to the command stack. This is used for error 

499 # messages if further parsing fails due to an unclosed block 

500 # tag. 

501 self.command_stack.append((command, token)) 

502 # Get the tag callback function from the ones registered with 

503 # the parser. 

504 try: 

505 compile_func = self.tags[command] 

506 except KeyError: 

507 self.invalid_block_tag(token, command, parse_until) 

508 # Compile the callback into a node object and add it to 

509 # the node list. 

510 try: 

511 compiled_result = compile_func(self, token) 

512 except Exception as e: 

513 raise self.error(token, e) 

514 self.extend_nodelist(nodelist, compiled_result, token) 

515 # Compile success. Remove the token from the command stack. 

516 self.command_stack.pop() 

517 if parse_until: 

518 self.unclosed_block_tag(parse_until) 

519 return nodelist 

520 

521 def skip_past(self, endtag): 

522 while self.tokens: 

523 token = self.next_token() 

524 if token.token_type == TokenType.BLOCK and token.contents == endtag: 

525 return 

526 self.unclosed_block_tag([endtag]) 

527 

528 def extend_nodelist(self, nodelist, node, token): 

529 # Check that non-text nodes don't appear before an extends tag. 

530 if node.must_be_first and nodelist.contains_nontext: 

531 raise self.error( 

532 token, 

533 "%r must be the first tag in the template." % node, 

534 ) 

535 if not isinstance(node, TextNode): 

536 nodelist.contains_nontext = True 

537 # Set origin and token here since we can't modify the node __init__() 

538 # method. 

539 node.token = token 

540 node.origin = self.origin 

541 nodelist.append(node) 

542 

543 def error(self, token, e): 

544 """ 

545 Return an exception annotated with the originating token. Since the 

546 parser can be called recursively, check if a token is already set. This 

547 ensures the innermost token is highlighted if an exception occurs, 

548 e.g. a compile error within the body of an if statement. 

549 """ 

550 if not isinstance(e, Exception): 

551 e = TemplateSyntaxError(e) 

552 if not hasattr(e, "token"): 

553 e.token = token 

554 return e 

555 

556 def invalid_block_tag(self, token, command, parse_until=None): 

557 if parse_until: 

558 raise self.error( 

559 token, 

560 "Invalid block tag on line %d: '%s', expected %s. Did you " 

561 "forget to register or load this tag?" 

562 % ( 

563 token.lineno, 

564 command, 

565 get_text_list(["'%s'" % p for p in parse_until], "or"), 

566 ), 

567 ) 

568 raise self.error( 

569 token, 

570 "Invalid block tag on line %d: '%s'. Did you forget to register " 

571 "or load this tag?" % (token.lineno, command), 

572 ) 

573 

574 def unclosed_block_tag(self, parse_until): 

575 command, token = self.command_stack.pop() 

576 msg = "Unclosed tag on line %d: '%s'. Looking for one of: %s." % ( 

577 token.lineno, 

578 command, 

579 ", ".join(parse_until), 

580 ) 

581 raise self.error(token, msg) 

582 

583 def next_token(self): 

584 return self.tokens.pop() 

585 

586 def prepend_token(self, token): 

587 self.tokens.append(token) 

588 

589 def delete_first_token(self): 

590 del self.tokens[-1] 

591 

592 def add_library(self, lib): 

593 self.tags.update(lib.tags) 

594 self.filters.update(lib.filters) 

595 

596 def compile_filter(self, token): 

597 """ 

598 Convenient wrapper for FilterExpression 

599 """ 

600 return FilterExpression(token, self) 

601 

602 def find_filter(self, filter_name): 

603 if filter_name in self.filters: 

604 return self.filters[filter_name] 

605 else: 

606 raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name) 

607 

608 

609# This only matches constant *strings* (things in quotes or marked for 

610# translation). Numbers are treated as variables for implementation reasons 

611# (so that they retain their type when passed to filters). 

612constant_string = r""" 

613(?:%(i18n_open)s%(strdq)s%(i18n_close)s| 

614%(i18n_open)s%(strsq)s%(i18n_close)s| 

615%(strdq)s| 

616%(strsq)s) 

617""" % { 

618 "strdq": r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string 

619 "strsq": r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string 

620 "i18n_open": re.escape("_("), 

621 "i18n_close": re.escape(")"), 

622} 

623constant_string = constant_string.replace("\n", "") 

624 

625filter_raw_string = r""" 

626^(?P<constant>%(constant)s)| 

627^(?P<var>[%(var_chars)s]+|%(num)s)| 

628 (?:\s*%(filter_sep)s\s* 

629 (?P<filter_name>\w+) 

630 (?:%(arg_sep)s 

631 (?: 

632 (?P<constant_arg>%(constant)s)| 

633 (?P<var_arg>[%(var_chars)s]+|%(num)s) 

634 ) 

635 )? 

636 )""" % { 

637 "constant": constant_string, 

638 "num": r"[-+\.]?\d[\d\.e]*", 

639 "var_chars": r"\w\.", 

640 "filter_sep": re.escape(FILTER_SEPARATOR), 

641 "arg_sep": re.escape(FILTER_ARGUMENT_SEPARATOR), 

642} 

643 

644filter_re = _lazy_re_compile(filter_raw_string, re.VERBOSE) 

645 

646 

647class FilterExpression: 

648 """ 

649 Parse a variable token and its optional filters (all as a single string), 

650 and return a list of tuples of the filter name and arguments. 

651 Sample:: 

652 

653 >>> token = 'variable|default:"Default value"|date:"Y-m-d"' 

654 >>> p = Parser('') 

655 >>> fe = FilterExpression(token, p) 

656 >>> len(fe.filters) 

657 2 

658 >>> fe.var 

659 <Variable: 'variable'> 

660 """ 

661 

662 def __init__(self, token, parser): 

663 self.token = token 

664 matches = filter_re.finditer(token) 

665 var_obj = None 

666 filters = [] 

667 upto = 0 

668 for match in matches: 

669 start = match.start() 

670 if upto != start: 

671 raise TemplateSyntaxError( 

672 "Could not parse some characters: " 

673 "%s|%s|%s" % (token[:upto], token[upto:start], token[start:]) 

674 ) 

675 if var_obj is None: 

676 var, constant = match["var"], match["constant"] 

677 if constant: 

678 try: 

679 var_obj = Variable(constant).resolve({}) 

680 except VariableDoesNotExist: 

681 var_obj = None 

682 elif var is None: 

683 raise TemplateSyntaxError( 

684 "Could not find variable at start of %s." % token 

685 ) 

686 else: 

687 var_obj = Variable(var) 

688 else: 

689 filter_name = match["filter_name"] 

690 args = [] 

691 constant_arg, var_arg = match["constant_arg"], match["var_arg"] 

692 if constant_arg: 

693 args.append((False, Variable(constant_arg).resolve({}))) 

694 elif var_arg: 

695 args.append((True, Variable(var_arg))) 

696 filter_func = parser.find_filter(filter_name) 

697 self.args_check(filter_name, filter_func, args) 

698 filters.append((filter_func, args)) 

699 upto = match.end() 

700 if upto != len(token): 

701 raise TemplateSyntaxError( 

702 "Could not parse the remainder: '%s' " 

703 "from '%s'" % (token[upto:], token) 

704 ) 

705 

706 self.filters = filters 

707 self.var = var_obj 

708 

709 def resolve(self, context, ignore_failures=False): 

710 if isinstance(self.var, Variable): 

711 try: 

712 obj = self.var.resolve(context) 

713 except VariableDoesNotExist: 

714 if ignore_failures: 

715 obj = None 

716 else: 

717 string_if_invalid = context.template.engine.string_if_invalid 

718 if string_if_invalid: 

719 if "%s" in string_if_invalid: 

720 return string_if_invalid % self.var 

721 else: 

722 return string_if_invalid 

723 else: 

724 obj = string_if_invalid 

725 else: 

726 obj = self.var 

727 for func, args in self.filters: 

728 arg_vals = [] 

729 for lookup, arg in args: 

730 if not lookup: 

731 arg_vals.append(mark_safe(arg)) 

732 else: 

733 arg_vals.append(arg.resolve(context)) 

734 if getattr(func, "expects_localtime", False): 

735 obj = template_localtime(obj, context.use_tz) 

736 if getattr(func, "needs_autoescape", False): 

737 new_obj = func(obj, autoescape=context.autoescape, *arg_vals) 

738 else: 

739 new_obj = func(obj, *arg_vals) 

740 if getattr(func, "is_safe", False) and isinstance(obj, SafeData): 

741 obj = mark_safe(new_obj) 

742 else: 

743 obj = new_obj 

744 return obj 

745 

746 def args_check(name, func, provided): 

747 provided = list(provided) 

748 # First argument, filter input, is implied. 

749 plen = len(provided) + 1 

750 # Check to see if a decorator is providing the real function. 

751 func = inspect.unwrap(func) 

752 

753 args, _, _, defaults, _, _, _ = inspect.getfullargspec(func) 

754 alen = len(args) 

755 dlen = len(defaults or []) 

756 # Not enough OR Too many 

757 if plen < (alen - dlen) or plen > alen: 

758 raise TemplateSyntaxError( 

759 "%s requires %d arguments, %d provided" % (name, alen - dlen, plen) 

760 ) 

761 

762 return True 

763 

764 args_check = staticmethod(args_check) 

765 

766 def __str__(self): 

767 return self.token 

768 

769 def __repr__(self): 

770 return "<%s %r>" % (self.__class__.__qualname__, self.token) 

771 

772 

773class Variable: 

774 """ 

775 A template variable, resolvable against a given context. The variable may 

776 be a hard-coded string (if it begins and ends with single or double quote 

777 marks):: 

778 

779 >>> c = {'article': {'section':'News'}} 

780 >>> Variable('article.section').resolve(c) 

781 'News' 

782 >>> Variable('article').resolve(c) 

783 {'section': 'News'} 

784 >>> class AClass: pass 

785 >>> c = AClass() 

786 >>> c.article = AClass() 

787 >>> c.article.section = 'News' 

788 

789 (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') 

790 """ 

791 

792 def __init__(self, var): 

793 self.var = var 

794 self.literal = None 

795 self.lookups = None 

796 self.translate = False 

797 self.message_context = None 

798 

799 if not isinstance(var, str): 

800 raise TypeError("Variable must be a string or number, got %s" % type(var)) 

801 try: 

802 # First try to treat this variable as a number. 

803 # 

804 # Note that this could cause an OverflowError here that we're not 

805 # catching. Since this should only happen at compile time, that's 

806 # probably OK. 

807 

808 # Try to interpret values containing a period or an 'e'/'E' 

809 # (possibly scientific notation) as a float; otherwise, try int. 

810 if "." in var or "e" in var.lower(): 

811 self.literal = float(var) 

812 # "2." is invalid 

813 if var[-1] == ".": 

814 raise ValueError 

815 else: 

816 self.literal = int(var) 

817 except ValueError: 

818 # A ValueError means that the variable isn't a number. 

819 if var[0:2] == "_(" and var[-1] == ")": 

820 # The result of the lookup should be translated at rendering 

821 # time. 

822 self.translate = True 

823 var = var[2:-1] 

824 # If it's wrapped with quotes (single or double), then 

825 # we're also dealing with a literal. 

826 try: 

827 self.literal = mark_safe(unescape_string_literal(var)) 

828 except ValueError: 

829 # Otherwise we'll set self.lookups so that resolve() knows we're 

830 # dealing with a bonafide variable 

831 if VARIABLE_ATTRIBUTE_SEPARATOR + "_" in var or var[0] == "_": 

832 raise TemplateSyntaxError( 

833 "Variables and attributes may " 

834 "not begin with underscores: '%s'" % var 

835 ) 

836 self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR)) 

837 

838 def resolve(self, context): 

839 """Resolve this variable against a given context.""" 

840 if self.lookups is not None: 

841 # We're dealing with a variable that needs to be resolved 

842 value = self._resolve_lookup(context) 

843 else: 

844 # We're dealing with a literal, so it's already been "resolved" 

845 value = self.literal 

846 if self.translate: 

847 is_safe = isinstance(value, SafeData) 

848 msgid = value.replace("%", "%%") 

849 msgid = mark_safe(msgid) if is_safe else msgid 

850 if self.message_context: 

851 return pgettext_lazy(self.message_context, msgid) 

852 else: 

853 return gettext_lazy(msgid) 

854 return value 

855 

856 def __repr__(self): 

857 return "<%s: %r>" % (self.__class__.__name__, self.var) 

858 

859 def __str__(self): 

860 return self.var 

861 

862 def _resolve_lookup(self, context): 

863 """ 

864 Perform resolution of a real variable (i.e. not a literal) against the 

865 given context. 

866 

867 As indicated by the method's name, this method is an implementation 

868 detail and shouldn't be called by external code. Use Variable.resolve() 

869 instead. 

870 """ 

871 current = context 

872 try: # catch-all for silent variable failures 

873 for bit in self.lookups: 

874 try: # dictionary lookup 

875 current = current[bit] 

876 # ValueError/IndexError are for numpy.array lookup on 

877 # numpy < 1.9 and 1.9+ respectively 

878 except (TypeError, AttributeError, KeyError, ValueError, IndexError): 

879 try: # attribute lookup 

880 # Don't return class attributes if the class is the context: 

881 if isinstance(current, BaseContext) and getattr( 

882 type(current), bit 

883 ): 

884 raise AttributeError 

885 current = getattr(current, bit) 

886 except (TypeError, AttributeError): 

887 # Reraise if the exception was raised by a @property 

888 if not isinstance(current, BaseContext) and bit in dir(current): 

889 raise 

890 try: # list-index lookup 

891 current = current[int(bit)] 

892 except ( 

893 IndexError, # list index out of range 

894 ValueError, # invalid literal for int() 

895 KeyError, # current is a dict without `int(bit)` key 

896 TypeError, 

897 ): # unsubscriptable object 

898 raise VariableDoesNotExist( 

899 "Failed lookup for key [%s] in %r", 

900 (bit, current), 

901 ) # missing attribute 

902 if callable(current): 

903 if getattr(current, "do_not_call_in_templates", False): 

904 pass 

905 elif getattr(current, "alters_data", False): 

906 current = context.template.engine.string_if_invalid 

907 else: 

908 try: # method call (assuming no args required) 

909 current = current() 

910 except TypeError: 

911 signature = inspect.signature(current) 

912 try: 

913 signature.bind() 

914 except TypeError: # arguments *were* required 

915 current = ( 

916 context.template.engine.string_if_invalid 

917 ) # invalid method call 

918 else: 

919 raise 

920 except Exception as e: 

921 template_name = getattr(context, "template_name", None) or "unknown" 

922 logger.debug( 

923 "Exception while resolving variable '%s' in template '%s'.", 

924 bit, 

925 template_name, 

926 exc_info=True, 

927 ) 

928 

929 if getattr(e, "silent_variable_failure", False): 

930 current = context.template.engine.string_if_invalid 

931 else: 

932 raise 

933 

934 return current 

935 

936 

937class Node: 

938 # Set this to True for nodes that must be first in the template (although 

939 # they can be preceded by text nodes. 

940 must_be_first = False 

941 child_nodelists = ("nodelist",) 

942 token = None 

943 

944 def render(self, context): 

945 """ 

946 Return the node rendered as a string. 

947 """ 

948 pass 

949 

950 def render_annotated(self, context): 

951 """ 

952 Render the node. If debug is True and an exception occurs during 

953 rendering, the exception is annotated with contextual line information 

954 where it occurred in the template. For internal usage this method is 

955 preferred over using the render method directly. 

956 """ 

957 try: 

958 return self.render(context) 

959 except Exception as e: 

960 if context.template.engine.debug: 

961 # Store the actual node that caused the exception. 

962 if not hasattr(e, "_culprit_node"): 

963 e._culprit_node = self 

964 if ( 

965 not hasattr(e, "template_debug") 

966 and context.render_context.template.origin == e._culprit_node.origin 

967 ): 

968 e.template_debug = ( 

969 context.render_context.template.get_exception_info( 

970 e, 

971 e._culprit_node.token, 

972 ) 

973 ) 

974 raise 

975 

976 def __iter__(self): 

977 yield self 

978 

979 def get_nodes_by_type(self, nodetype): 

980 """ 

981 Return a list of all nodes (within this node and its nodelist) 

982 of the given type 

983 """ 

984 nodes = [] 

985 if isinstance(self, nodetype): 

986 nodes.append(self) 

987 for attr in self.child_nodelists: 

988 nodelist = getattr(self, attr, None) 

989 if nodelist: 

990 nodes.extend(nodelist.get_nodes_by_type(nodetype)) 

991 return nodes 

992 

993 

994class NodeList(list): 

995 # Set to True the first time a non-TextNode is inserted by 

996 # extend_nodelist(). 

997 contains_nontext = False 

998 

999 def render(self, context): 

1000 return SafeString("".join([node.render_annotated(context) for node in self])) 

1001 

1002 def get_nodes_by_type(self, nodetype): 

1003 "Return a list of all nodes of the given type" 

1004 nodes = [] 

1005 for node in self: 

1006 nodes.extend(node.get_nodes_by_type(nodetype)) 

1007 return nodes 

1008 

1009 

1010class TextNode(Node): 

1011 child_nodelists = () 

1012 

1013 def __init__(self, s): 

1014 self.s = s 

1015 

1016 def __repr__(self): 

1017 return "<%s: %r>" % (self.__class__.__name__, self.s[:25]) 

1018 

1019 def render(self, context): 

1020 return self.s 

1021 

1022 def render_annotated(self, context): 

1023 """ 

1024 Return the given value. 

1025 

1026 The default implementation of this method handles exceptions raised 

1027 during rendering, which is not necessary for text nodes. 

1028 """ 

1029 return self.s 

1030 

1031 

1032def render_value_in_context(value, context): 

1033 """ 

1034 Convert any value to a string to become part of a rendered template. This 

1035 means escaping, if required, and conversion to a string. If value is a 

1036 string, it's expected to already be translated. 

1037 """ 

1038 value = template_localtime(value, use_tz=context.use_tz) 

1039 value = localize(value, use_l10n=context.use_l10n) 

1040 if context.autoescape: 

1041 if not issubclass(type(value), str): 

1042 value = str(value) 

1043 return conditional_escape(value) 

1044 else: 

1045 return str(value) 

1046 

1047 

1048class VariableNode(Node): 

1049 child_nodelists = () 

1050 

1051 def __init__(self, filter_expression): 

1052 self.filter_expression = filter_expression 

1053 

1054 def __repr__(self): 

1055 return "<Variable Node: %s>" % self.filter_expression 

1056 

1057 def render(self, context): 

1058 try: 

1059 output = self.filter_expression.resolve(context) 

1060 except UnicodeDecodeError: 

1061 # Unicode conversion can fail sometimes for reasons out of our 

1062 # control (e.g. exception rendering). In that case, we fail 

1063 # quietly. 

1064 return "" 

1065 return render_value_in_context(output, context) 

1066 

1067 

1068# Regex for token keyword arguments 

1069kwarg_re = _lazy_re_compile(r"(?:(\w+)=)?(.+)") 

1070 

1071 

1072def token_kwargs(bits, parser, support_legacy=False): 

1073 """ 

1074 Parse token keyword arguments and return a dictionary of the arguments 

1075 retrieved from the ``bits`` token list. 

1076 

1077 `bits` is a list containing the remainder of the token (split by spaces) 

1078 that is to be checked for arguments. Valid arguments are removed from this 

1079 list. 

1080 

1081 `support_legacy` - if True, the legacy format ``1 as foo`` is accepted. 

1082 Otherwise, only the standard ``foo=1`` format is allowed. 

1083 

1084 There is no requirement for all remaining token ``bits`` to be keyword 

1085 arguments, so return the dictionary as soon as an invalid argument format 

1086 is reached. 

1087 """ 

1088 if not bits: 

1089 return {} 

1090 match = kwarg_re.match(bits[0]) 

1091 kwarg_format = match and match[1] 

1092 if not kwarg_format: 

1093 if not support_legacy: 

1094 return {} 

1095 if len(bits) < 3 or bits[1] != "as": 

1096 return {} 

1097 

1098 kwargs = {} 

1099 while bits: 

1100 if kwarg_format: 

1101 match = kwarg_re.match(bits[0]) 

1102 if not match or not match[1]: 

1103 return kwargs 

1104 key, value = match.groups() 

1105 del bits[:1] 

1106 else: 

1107 if len(bits) < 3 or bits[1] != "as": 

1108 return kwargs 

1109 key, value = bits[2], bits[0] 

1110 del bits[:3] 

1111 kwargs[key] = parser.compile_filter(value) 

1112 if bits and not kwarg_format: 

1113 if bits[0] != "and": 

1114 return kwargs 

1115 del bits[:1] 

1116 return kwargs