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
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""
2This is the Django template system.
4How it works:
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).
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.
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.
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.
26The Template class is a convenient wrapper that takes care of template
27compilation and rendering.
29Usage:
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.
36Sample code:
38>>> from django import template
39>>> s = '<html>{% if test %}<h1>{{ varvalue }}</h1>{% endif %}</html>'
40>>> t = template.Template(s)
42(t is now a compiled template, and its render() method can be called multiple
43times with multiple contexts)
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"""
53import inspect
54import logging
55import re
56from enum import Enum
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
67from .exceptions import TemplateSyntaxError
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 = "}"
82# what to report as the origin for templates that come from non-loader sources
83# (e.g. strings)
84UNKNOWN_SOURCE = "<unknown source>"
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"({%.*?%}|{{.*?}}|{#.*?#})")
91logger = logging.getLogger("django.template")
94class TokenType(Enum):
95 TEXT = 0
96 VAR = 1
97 BLOCK = 2
98 COMMENT = 3
101class VariableDoesNotExist(Exception):
102 def __init__(self, msg, params=()):
103 self.msg = msg
104 self.params = params
106 def __str__(self):
107 return self.msg % self.params
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
116 def __str__(self):
117 return self.name
119 def __repr__(self):
120 return "<%s name=%r>" % (self.__class__.__qualname__, self.name)
122 def __eq__(self, other):
123 return (
124 isinstance(other, Origin)
125 and self.name == other.name
126 and self.loader == other.loader
127 )
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 )
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
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()
156 def __iter__(self):
157 for node in self.nodelist:
158 yield from node
160 def __repr__(self):
161 return '<%s template_string="%s...">' % (
162 self.__class__.__qualname__,
163 self.source[:20].replace("\n", ""),
164 )
166 def _render(self, context):
167 return self.nodelist.render(context)
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)
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)
191 tokens = lexer.tokenize()
192 parser = Parser(
193 tokens,
194 self.engine.template_libraries,
195 self.engine.template_builtins,
196 self.origin,
197 )
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
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:
212 message
213 The message of the exception raised.
215 source_lines
216 The lines before, after, and including the line the exception
217 occurred on.
219 line
220 The line number the exception occurred on.
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.
228 total
229 The number of lines in source_lines.
231 top
232 The line number where source_lines starts.
234 bottom
235 The line number where source_lines ends.
237 start
238 The start position of the token in the template source.
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)
259 top = max(1, line - context_lines)
260 bottom = min(total, line + 1 + context_lines)
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)"
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 }
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
294class Token:
295 def __init__(self, token_type, contents, position=None, lineno=None):
296 """
297 A token representing a string from the template.
299 token_type
300 A TokenType, either .TEXT, .VAR, .BLOCK, or .COMMENT.
302 contents
303 The token source string.
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.
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
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 )
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
341class Lexer:
342 def __init__(self, template_string):
343 self.template_string = template_string
344 self.verbatim = False
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 )
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
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)
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)
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
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
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 = []
445 if libraries is None:
446 libraries = {}
447 if builtins is None:
448 builtins = []
450 self.libraries = libraries
451 for builtin in builtins:
452 self.add_library(builtin)
453 self.origin = origin
455 def __repr__(self):
456 return "<%s tokens=%r>" % (self.__class__.__qualname__, self.tokens)
458 def parse(self, parse_until=None):
459 """
460 Iterate through the parser tokens and compiles each one into a node.
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
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])
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)
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
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 )
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)
583 def next_token(self):
584 return self.tokens.pop()
586 def prepend_token(self, token):
587 self.tokens.append(token)
589 def delete_first_token(self):
590 del self.tokens[-1]
592 def add_library(self, lib):
593 self.tags.update(lib.tags)
594 self.filters.update(lib.filters)
596 def compile_filter(self, token):
597 """
598 Convenient wrapper for FilterExpression
599 """
600 return FilterExpression(token, self)
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)
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", "")
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}
644filter_re = _lazy_re_compile(filter_raw_string, re.VERBOSE)
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::
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 """
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 )
706 self.filters = filters
707 self.var = var_obj
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
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)
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 )
762 return True
764 args_check = staticmethod(args_check)
766 def __str__(self):
767 return self.token
769 def __repr__(self):
770 return "<%s %r>" % (self.__class__.__qualname__, self.token)
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)::
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'
789 (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
790 """
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
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.
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))
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
856 def __repr__(self):
857 return "<%s: %r>" % (self.__class__.__name__, self.var)
859 def __str__(self):
860 return self.var
862 def _resolve_lookup(self, context):
863 """
864 Perform resolution of a real variable (i.e. not a literal) against the
865 given context.
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 )
929 if getattr(e, "silent_variable_failure", False):
930 current = context.template.engine.string_if_invalid
931 else:
932 raise
934 return current
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
944 def render(self, context):
945 """
946 Return the node rendered as a string.
947 """
948 pass
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
976 def __iter__(self):
977 yield self
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
994class NodeList(list):
995 # Set to True the first time a non-TextNode is inserted by
996 # extend_nodelist().
997 contains_nontext = False
999 def render(self, context):
1000 return SafeString("".join([node.render_annotated(context) for node in self]))
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
1010class TextNode(Node):
1011 child_nodelists = ()
1013 def __init__(self, s):
1014 self.s = s
1016 def __repr__(self):
1017 return "<%s: %r>" % (self.__class__.__name__, self.s[:25])
1019 def render(self, context):
1020 return self.s
1022 def render_annotated(self, context):
1023 """
1024 Return the given value.
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
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)
1048class VariableNode(Node):
1049 child_nodelists = ()
1051 def __init__(self, filter_expression):
1052 self.filter_expression = filter_expression
1054 def __repr__(self):
1055 return "<Variable Node: %s>" % self.filter_expression
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)
1068# Regex for token keyword arguments
1069kwarg_re = _lazy_re_compile(r"(?:(\w+)=)?(.+)")
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.
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.
1081 `support_legacy` - if True, the legacy format ``1 as foo`` is accepted.
1082 Otherwise, only the standard ``foo=1`` format is allowed.
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 {}
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