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