Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/template/base.py: 22%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

565 statements  

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 

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 __repr__(self): 

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

158 self.__class__.__qualname__, 

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

160 ) 

161 

162 def _render(self, context): 

163 return self.nodelist.render(context) 

164 

165 def render(self, context): 

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

167 with context.render_context.push_state(self): 

168 if context.template is None: 

169 with context.bind_template(self): 

170 context.template_name = self.name 

171 return self._render(context) 

172 else: 

173 return self._render(context) 

174 

175 def compile_nodelist(self): 

176 """ 

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

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

179 annotated with contextual line information where it occurred in the 

180 template source. 

181 """ 

182 if self.engine.debug: 

183 lexer = DebugLexer(self.source) 

184 else: 

185 lexer = Lexer(self.source) 

186 

187 tokens = lexer.tokenize() 

188 parser = Parser( 

189 tokens, 

190 self.engine.template_libraries, 

191 self.engine.template_builtins, 

192 self.origin, 

193 ) 

194 

195 try: 

196 nodelist = parser.parse() 

197 self.extra_data = parser.extra_data 

198 return nodelist 

199 except Exception as e: 

200 if self.engine.debug: 

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

202 raise 

203 

204 def get_exception_info(self, exception, token): 

205 """ 

206 Return a dictionary containing contextual line information of where 

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

208 provided: 

209 

210 message 

211 The message of the exception raised. 

212 

213 source_lines 

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

215 occurred on. 

216 

217 line 

218 The line number the exception occurred on. 

219 

220 before, during, after 

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

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

223 2. The token that raised the error. 

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

225 

226 total 

227 The number of lines in source_lines. 

228 

229 top 

230 The line number where source_lines starts. 

231 

232 bottom 

233 The line number where source_lines ends. 

234 

235 start 

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

237 

238 end 

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

240 """ 

241 start, end = token.position 

242 context_lines = 10 

243 line = 0 

244 upto = 0 

245 source_lines = [] 

246 before = during = after = "" 

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

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

249 line = num 

250 before = self.source[upto:start] 

251 during = self.source[start:end] 

252 after = self.source[end:next] 

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

254 upto = next 

255 total = len(source_lines) 

256 

257 top = max(1, line - context_lines) 

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

259 

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

261 # string. 

262 try: 

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

264 except (IndexError, UnicodeDecodeError): 

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

266 

267 return { 

268 "message": message, 

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

270 "before": before, 

271 "during": during, 

272 "after": after, 

273 "top": top, 

274 "bottom": bottom, 

275 "total": total, 

276 "line": line, 

277 "name": self.origin.name, 

278 "start": start, 

279 "end": end, 

280 } 

281 

282 

283def linebreak_iter(template_source): 

284 yield 0 

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

286 while p >= 0: 

287 yield p + 1 

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

289 yield len(template_source) + 1 

290 

291 

292class Token: 

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

294 """ 

295 A token representing a string from the template. 

296 

297 token_type 

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

299 

300 contents 

301 The token source string. 

302 

303 position 

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

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

306 when debug is on. 

307 

308 lineno 

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

310 This is used for traceback information and gettext files. 

311 """ 

312 self.token_type = token_type 

313 self.contents = contents 

314 self.lineno = lineno 

315 self.position = position 

316 

317 def __repr__(self): 

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

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

320 token_name, 

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

322 ) 

323 

324 def split_contents(self): 

325 split = [] 

326 bits = smart_split(self.contents) 

327 for bit in bits: 

328 # Handle translation-marked template pieces 

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

330 sentinel = bit[2] + ")" 

331 trans_bit = [bit] 

332 while not bit.endswith(sentinel): 

333 bit = next(bits) 

334 trans_bit.append(bit) 

335 bit = " ".join(trans_bit) 

336 split.append(bit) 

337 return split 

338 

339 

340class Lexer: 

341 def __init__(self, template_string): 

342 self.template_string = template_string 

343 self.verbatim = False 

344 

345 def __repr__(self): 

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

347 self.__class__.__qualname__, 

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

349 self.verbatim, 

350 ) 

351 

352 def tokenize(self): 

353 """ 

354 Return a list of tokens from a given template_string. 

355 """ 

356 in_tag = False 

357 lineno = 1 

358 result = [] 

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

360 if token_string: 

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

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

363 in_tag = not in_tag 

364 return result 

365 

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

367 """ 

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

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

370 otherwise it should be treated as a literal string. 

371 """ 

372 if in_tag: 

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

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

375 # len(BLOCK_TAG_START) would permit BLOCK_TAG_START to be 

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

377 # change anytime soon. 

378 token_start = token_string[0:2] 

379 if token_start == BLOCK_TAG_START: 

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

381 if self.verbatim: 

382 # Then a verbatim block is being processed. 

383 if content != self.verbatim: 

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

385 # Otherwise, the current verbatim block is ending. 

386 self.verbatim = False 

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

388 # Then a verbatim block is starting. 

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

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

391 if not self.verbatim: 

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

393 if token_start == VARIABLE_TAG_START: 

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

395 # BLOCK_TAG_START was handled above. 

396 assert token_start == COMMENT_TAG_START 

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

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

399 

400 

401class DebugLexer(Lexer): 

402 def _tag_re_split_positions(self): 

403 last = 0 

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

405 start, end = match.span() 

406 yield last, start 

407 yield start, end 

408 last = end 

409 yield last, len(self.template_string) 

410 

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

412 def _tag_re_split(self): 

413 for position in self._tag_re_split_positions(): 

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

415 

416 def tokenize(self): 

417 """ 

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

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

420 lexer so only use it when debug is True. 

421 """ 

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

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

424 in_tag = False 

425 lineno = 1 

426 result = [] 

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

428 if token_string: 

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

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

431 in_tag = not in_tag 

432 return result 

433 

434 

435class Parser: 

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

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

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

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

440 self.tags = {} 

441 self.filters = {} 

442 self.command_stack = [] 

443 

444 # Custom template tags may store additional data on the parser that 

445 # will be made available on the template instance. Library authors 

446 # should use a key to namespace any added data. The 'django' namespace 

447 # is reserved for internal use. 

448 self.extra_data = {} 

449 

450 if libraries is None: 

451 libraries = {} 

452 if builtins is None: 

453 builtins = [] 

454 

455 self.libraries = libraries 

456 for builtin in builtins: 

457 self.add_library(builtin) 

458 self.origin = origin 

459 

460 def __repr__(self): 

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

462 

463 def parse(self, parse_until=None): 

464 """ 

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

466 

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

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

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

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

471 """ 

472 if parse_until is None: 

473 parse_until = [] 

474 nodelist = NodeList() 

475 while self.tokens: 

476 token = self.next_token() 

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

478 token_type = token.token_type.value 

479 if token_type == 0: # TokenType.TEXT 

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

481 elif token_type == 1: # TokenType.VAR 

482 if not token.contents: 

483 raise self.error( 

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

485 ) 

486 try: 

487 filter_expression = self.compile_filter(token.contents) 

488 except TemplateSyntaxError as e: 

489 raise self.error(token, e) 

490 var_node = VariableNode(filter_expression) 

491 self.extend_nodelist(nodelist, var_node, token) 

492 elif token_type == 2: # TokenType.BLOCK 

493 try: 

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

495 except IndexError: 

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

497 if command in parse_until: 

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

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

500 # caller knows where it terminated. 

501 self.prepend_token(token) 

502 return nodelist 

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

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

505 # tag. 

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

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

508 # the parser. 

509 try: 

510 compile_func = self.tags[command] 

511 except KeyError: 

512 self.invalid_block_tag(token, command, parse_until) 

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

514 # the node list. 

515 try: 

516 compiled_result = compile_func(self, token) 

517 except Exception as e: 

518 raise self.error(token, e) 

519 self.extend_nodelist(nodelist, compiled_result, token) 

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

521 self.command_stack.pop() 

522 if parse_until: 

523 self.unclosed_block_tag(parse_until) 

524 return nodelist 

525 

526 def skip_past(self, endtag): 

527 while self.tokens: 

528 token = self.next_token() 

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

530 return 

531 self.unclosed_block_tag([endtag]) 

532 

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

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

535 if node.must_be_first and nodelist.contains_nontext: 

536 if self.origin.template_name: 

537 origin = repr(self.origin.template_name) 

538 else: 

539 origin = "the template" 

540 raise self.error( 

541 token, 

542 "{%% %s %%} must be the first tag in %s." % (token.contents, origin), 

543 ) 

544 if not isinstance(node, TextNode): 

545 nodelist.contains_nontext = True 

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

547 # method. 

548 node.token = token 

549 node.origin = self.origin 

550 nodelist.append(node) 

551 

552 def error(self, token, e): 

553 """ 

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

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

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

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

558 """ 

559 if not isinstance(e, Exception): 

560 e = TemplateSyntaxError(e) 

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

562 e.token = token 

563 return e 

564 

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

566 if parse_until: 

567 raise self.error( 

568 token, 

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

570 "forget to register or load this tag?" 

571 % ( 

572 token.lineno, 

573 command, 

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

575 ), 

576 ) 

577 raise self.error( 

578 token, 

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

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

581 ) 

582 

583 def unclosed_block_tag(self, parse_until): 

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

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

586 token.lineno, 

587 command, 

588 ", ".join(parse_until), 

589 ) 

590 raise self.error(token, msg) 

591 

592 def next_token(self): 

593 return self.tokens.pop() 

594 

595 def prepend_token(self, token): 

596 self.tokens.append(token) 

597 

598 def delete_first_token(self): 

599 del self.tokens[-1] 

600 

601 def add_library(self, lib): 

602 self.tags.update(lib.tags) 

603 self.filters.update(lib.filters) 

604 

605 def compile_filter(self, token): 

606 """ 

607 Convenient wrapper for FilterExpression 

608 """ 

609 return FilterExpression(token, self) 

610 

611 def find_filter(self, filter_name): 

612 if filter_name in self.filters: 

613 return self.filters[filter_name] 

614 else: 

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

616 

617 

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

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

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

621constant_string = r""" 

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

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

624%(strdq)s| 

625%(strsq)s) 

626""" % { 

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

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

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

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

631} 

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

633 

634filter_raw_string = r""" 

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

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

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

638 (?P<filter_name>\w+) 

639 (?:%(arg_sep)s 

640 (?: 

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

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

643 ) 

644 )? 

645 )""" % { 

646 "constant": constant_string, 

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

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

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

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

651} 

652 

653filter_re = _lazy_re_compile(filter_raw_string, re.VERBOSE) 

654 

655 

656class FilterExpression: 

657 """ 

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

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

660 Sample:: 

661 

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

663 >>> p = Parser('') 

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

665 >>> len(fe.filters) 

666 2 

667 >>> fe.var 

668 <Variable: 'variable'> 

669 """ 

670 

671 __slots__ = ("token", "filters", "var", "is_var") 

672 

673 def __init__(self, token, parser): 

674 self.token = token 

675 matches = filter_re.finditer(token) 

676 var_obj = None 

677 filters = [] 

678 upto = 0 

679 for match in matches: 

680 start = match.start() 

681 if upto != start: 

682 raise TemplateSyntaxError( 

683 "Could not parse some characters: " 

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

685 ) 

686 if var_obj is None: 

687 if constant := match["constant"]: 

688 try: 

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

690 except VariableDoesNotExist: 

691 var_obj = None 

692 elif (var := match["var"]) is None: 

693 raise TemplateSyntaxError( 

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

695 ) 

696 else: 

697 var_obj = Variable(var) 

698 else: 

699 filter_name = match["filter_name"] 

700 args = [] 

701 if constant_arg := match["constant_arg"]: 

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

703 elif var_arg := match["var_arg"]: 

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

705 filter_func = parser.find_filter(filter_name) 

706 self.args_check(filter_name, filter_func, args) 

707 filters.append((filter_func, args)) 

708 upto = match.end() 

709 if upto != len(token): 

710 raise TemplateSyntaxError( 

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

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

713 ) 

714 

715 self.filters = filters 

716 self.var = var_obj 

717 self.is_var = isinstance(var_obj, Variable) 

718 

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

720 if self.is_var: 

721 try: 

722 obj = self.var.resolve(context) 

723 except VariableDoesNotExist: 

724 if ignore_failures: 

725 obj = None 

726 else: 

727 string_if_invalid = context.template.engine.string_if_invalid 

728 if string_if_invalid: 

729 if "%s" in string_if_invalid: 

730 return string_if_invalid % self.var 

731 else: 

732 return string_if_invalid 

733 else: 

734 obj = string_if_invalid 

735 else: 

736 obj = self.var 

737 for func, args in self.filters: 

738 arg_vals = [] 

739 for lookup, arg in args: 

740 if not lookup: 

741 arg_vals.append(mark_safe(arg)) 

742 else: 

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

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

745 obj = template_localtime(obj, context.use_tz) 

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

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

748 else: 

749 new_obj = func(obj, *arg_vals) 

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

751 obj = mark_safe(new_obj) 

752 else: 

753 obj = new_obj 

754 return obj 

755 

756 def args_check(name, func, provided): 

757 provided = list(provided) 

758 # First argument, filter input, is implied. 

759 plen = len(provided) + 1 

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

761 func = inspect.unwrap(func) 

762 

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

764 alen = len(args) 

765 dlen = len(defaults or []) 

766 # Not enough OR Too many 

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

768 raise TemplateSyntaxError( 

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

770 ) 

771 

772 return True 

773 

774 args_check = staticmethod(args_check) 

775 

776 def __str__(self): 

777 return self.token 

778 

779 def __repr__(self): 

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

781 

782 

783class Variable: 

784 """ 

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

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

787 marks):: 

788 

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

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

791 'News' 

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

793 {'section': 'News'} 

794 >>> class AClass: pass 

795 >>> c = AClass() 

796 >>> c.article = AClass() 

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

798 

799 (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') 

800 """ 

801 

802 __slots__ = ("var", "literal", "lookups", "translate", "message_context") 

803 

804 def __init__(self, var): 

805 self.var = var 

806 self.literal = None 

807 self.lookups = None 

808 self.translate = False 

809 self.message_context = None 

810 

811 if not isinstance(var, str): 

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

813 try: 

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

815 # 

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

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

818 # probably OK. 

819 

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

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

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

823 self.literal = float(var) 

824 # "2." is invalid 

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

826 raise ValueError 

827 else: 

828 self.literal = int(var) 

829 except ValueError: 

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

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

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

833 # time. 

834 self.translate = True 

835 var = var[2:-1] 

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

837 # we're also dealing with a literal. 

838 try: 

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

840 except ValueError: 

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

842 # dealing with a bonafide variable 

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

844 raise TemplateSyntaxError( 

845 "Variables and attributes may " 

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

847 ) 

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

849 

850 def resolve(self, context): 

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

852 if self.lookups is not None: 

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

854 value = self._resolve_lookup(context) 

855 else: 

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

857 value = self.literal 

858 if self.translate: 

859 is_safe = isinstance(value, SafeData) 

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

861 msgid = mark_safe(msgid) if is_safe else msgid 

862 if self.message_context: 

863 return pgettext_lazy(self.message_context, msgid) 

864 else: 

865 return gettext_lazy(msgid) 

866 return value 

867 

868 def __repr__(self): 

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

870 

871 def __str__(self): 

872 return self.var 

873 

874 def _resolve_lookup(self, context): 

875 """ 

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

877 given context. 

878 

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

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

881 instead. 

882 """ 

883 current = context 

884 try: # catch-all for silent variable failures 

885 for bit in self.lookups: 

886 try: # dictionary lookup 

887 # Only allow if the metaclass implements __getitem__. See 

888 # https://docs.python.org/3/reference/datamodel.html#classgetitem-versus-getitem 

889 if not hasattr(type(current), "__getitem__"): 

890 raise TypeError 

891 current = current[bit] 

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

893 # numpy < 1.9 and 1.9+ respectively 

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

895 try: # attribute lookup 

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

897 if isinstance(current, BaseContext) and getattr( 

898 type(current), bit 

899 ): 

900 raise AttributeError 

901 current = getattr(current, bit) 

902 except (TypeError, AttributeError): 

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

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

905 raise 

906 try: # list-index lookup 

907 current = current[int(bit)] 

908 except ( 

909 IndexError, # list index out of range 

910 ValueError, # invalid literal for int() 

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

912 TypeError, 

913 ): # unsubscriptable object 

914 raise VariableDoesNotExist( 

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

916 (bit, current), 

917 ) # missing attribute 

918 if callable(current): 

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

920 pass 

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

922 current = context.template.engine.string_if_invalid 

923 else: 

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

925 current = current() 

926 except TypeError: 

927 try: 

928 signature = inspect.signature(current) 

929 except ValueError: # No signature found. 

930 current = context.template.engine.string_if_invalid 

931 else: 

932 try: 

933 signature.bind() 

934 except TypeError: # Arguments *were* required. 

935 # Invalid method call. 

936 current = context.template.engine.string_if_invalid 

937 else: 

938 raise 

939 except Exception as e: 

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

941 logger.debug( 

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

943 bit, 

944 template_name, 

945 exc_info=True, 

946 ) 

947 

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

949 current = context.template.engine.string_if_invalid 

950 else: 

951 raise 

952 

953 return current 

954 

955 

956class Node: 

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

958 # they can be preceded by text nodes. 

959 must_be_first = False 

960 child_nodelists = ("nodelist",) 

961 token = None 

962 

963 def render(self, context): 

964 """ 

965 Return the node rendered as a string. 

966 """ 

967 pass 

968 

969 def render_annotated(self, context): 

970 """ 

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

972 rendering, the exception is annotated with contextual line information 

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

974 preferred over using the render method directly. 

975 """ 

976 try: 

977 return self.render(context) 

978 except Exception as e: 

979 if context.template.engine.debug: 

980 # Store the actual node that caused the exception. 

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

982 e._culprit_node = self 

983 if ( 

984 not hasattr(e, "template_debug") 

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

986 ): 

987 e.template_debug = ( 

988 context.render_context.template.get_exception_info( 

989 e, 

990 e._culprit_node.token, 

991 ) 

992 ) 

993 raise 

994 

995 def get_nodes_by_type(self, nodetype): 

996 """ 

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

998 of the given type 

999 """ 

1000 nodes = [] 

1001 if isinstance(self, nodetype): 

1002 nodes.append(self) 

1003 for attr in self.child_nodelists: 

1004 nodelist = getattr(self, attr, None) 

1005 if nodelist: 

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

1007 return nodes 

1008 

1009 

1010class NodeList(list): 

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

1012 # extend_nodelist(). 

1013 contains_nontext = False 

1014 

1015 def render(self, context): 

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

1017 

1018 def get_nodes_by_type(self, nodetype): 

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

1020 nodes = [] 

1021 for node in self: 

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

1023 return nodes 

1024 

1025 

1026class TextNode(Node): 

1027 child_nodelists = () 

1028 

1029 def __init__(self, s): 

1030 self.s = s 

1031 

1032 def __repr__(self): 

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

1034 

1035 def render(self, context): 

1036 return self.s 

1037 

1038 def render_annotated(self, context): 

1039 """ 

1040 Return the given value. 

1041 

1042 The default implementation of this method handles exceptions raised 

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

1044 """ 

1045 return self.s 

1046 

1047 

1048def render_value_in_context(value, context): 

1049 """ 

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

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

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

1053 """ 

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

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

1056 if context.autoescape: 

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

1058 value = str(value) 

1059 return conditional_escape(value) 

1060 else: 

1061 return str(value) 

1062 

1063 

1064class VariableNode(Node): 

1065 child_nodelists = () 

1066 

1067 def __init__(self, filter_expression): 

1068 self.filter_expression = filter_expression 

1069 

1070 def __repr__(self): 

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

1072 

1073 def render(self, context): 

1074 try: 

1075 output = self.filter_expression.resolve(context) 

1076 except UnicodeDecodeError: 

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

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

1079 # quietly. 

1080 return "" 

1081 return render_value_in_context(output, context) 

1082 

1083 

1084# Regex for token keyword arguments 

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

1086 

1087 

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

1089 """ 

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

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

1092 

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

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

1095 list. 

1096 

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

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

1099 

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

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

1102 is reached. 

1103 """ 

1104 if not bits: 

1105 return {} 

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

1107 kwarg_format = match and match[1] 

1108 if not kwarg_format: 

1109 if not support_legacy: 

1110 return {} 

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

1112 return {} 

1113 

1114 kwargs = {} 

1115 while bits: 

1116 if kwarg_format: 

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

1118 if not match or not match[1]: 

1119 return kwargs 

1120 key, value = match.groups() 

1121 del bits[:1] 

1122 else: 

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

1124 return kwargs 

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

1126 del bits[:3] 

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

1128 if bits and not kwarg_format: 

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

1130 return kwargs 

1131 del bits[:1] 

1132 return kwargs