summaryrefslogtreecommitdiff
path: root/django/template/base.py
diff options
context:
space:
mode:
Diffstat (limited to 'django/template/base.py')
-rw-r--r--django/template/base.py254
1 files changed, 136 insertions, 118 deletions
diff --git a/django/template/base.py b/django/template/base.py
index caadf89970..00f8283507 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -60,37 +60,35 @@ from django.utils.formats import localize
from django.utils.html import conditional_escape, escape
from django.utils.regex_helper import _lazy_re_compile
from django.utils.safestring import SafeData, SafeString, mark_safe
-from django.utils.text import (
- get_text_list, smart_split, unescape_string_literal,
-)
+from django.utils.text import get_text_list, smart_split, unescape_string_literal
from django.utils.timezone import template_localtime
from django.utils.translation import gettext_lazy, pgettext_lazy
from .exceptions import TemplateSyntaxError
# template syntax constants
-FILTER_SEPARATOR = '|'
-FILTER_ARGUMENT_SEPARATOR = ':'
-VARIABLE_ATTRIBUTE_SEPARATOR = '.'
-BLOCK_TAG_START = '{%'
-BLOCK_TAG_END = '%}'
-VARIABLE_TAG_START = '{{'
-VARIABLE_TAG_END = '}}'
-COMMENT_TAG_START = '{#'
-COMMENT_TAG_END = '#}'
-SINGLE_BRACE_START = '{'
-SINGLE_BRACE_END = '}'
+FILTER_SEPARATOR = "|"
+FILTER_ARGUMENT_SEPARATOR = ":"
+VARIABLE_ATTRIBUTE_SEPARATOR = "."
+BLOCK_TAG_START = "{%"
+BLOCK_TAG_END = "%}"
+VARIABLE_TAG_START = "{{"
+VARIABLE_TAG_END = "}}"
+COMMENT_TAG_START = "{#"
+COMMENT_TAG_END = "#}"
+SINGLE_BRACE_START = "{"
+SINGLE_BRACE_END = "}"
# what to report as the origin for templates that come from non-loader sources
# (e.g. strings)
-UNKNOWN_SOURCE = '<unknown source>'
+UNKNOWN_SOURCE = "<unknown source>"
# Match BLOCK_TAG_*, VARIABLE_TAG_*, and COMMENT_TAG_* tags and capture the
# entire tag, including start/end delimiters. Using re.compile() is faster
# than instantiating SimpleLazyObject with _lazy_re_compile().
-tag_re = re.compile(r'({%.*?%}|{{.*?}}|{#.*?#})')
+tag_re = re.compile(r"({%.*?%}|{{.*?}}|{#.*?#})")
-logger = logging.getLogger('django.template')
+logger = logging.getLogger("django.template")
class TokenType(Enum):
@@ -101,7 +99,6 @@ class TokenType(Enum):
class VariableDoesNotExist(Exception):
-
def __init__(self, msg, params=()):
self.msg = msg
self.params = params
@@ -120,20 +117,21 @@ class Origin:
return self.name
def __repr__(self):
- return '<%s name=%r>' % (self.__class__.__qualname__, self.name)
+ return "<%s name=%r>" % (self.__class__.__qualname__, self.name)
def __eq__(self, other):
return (
- isinstance(other, Origin) and
- self.name == other.name and
- self.loader == other.loader
+ isinstance(other, Origin)
+ and self.name == other.name
+ and self.loader == other.loader
)
@property
def loader_name(self):
if self.loader:
- return '%s.%s' % (
- self.loader.__module__, self.loader.__class__.__name__,
+ return "%s.%s" % (
+ self.loader.__module__,
+ self.loader.__class__.__name__,
)
@@ -145,6 +143,7 @@ class Template:
# e.g. Template('...').render(Context({...}))
if engine is None:
from .engine import Engine
+
engine = Engine.get_default()
if origin is None:
origin = Origin(UNKNOWN_SOURCE)
@@ -161,7 +160,7 @@ class Template:
def __repr__(self):
return '<%s template_string="%s...">' % (
self.__class__.__qualname__,
- self.source[:20].replace('\n', ''),
+ self.source[:20].replace("\n", ""),
)
def _render(self, context):
@@ -191,7 +190,9 @@ class Template:
tokens = lexer.tokenize()
parser = Parser(
- tokens, self.engine.template_libraries, self.engine.template_builtins,
+ tokens,
+ self.engine.template_libraries,
+ self.engine.template_builtins,
self.origin,
)
@@ -263,30 +264,30 @@ class Template:
try:
message = str(exception.args[0])
except (IndexError, UnicodeDecodeError):
- message = '(Could not get exception message)'
+ message = "(Could not get exception message)"
return {
- 'message': message,
- 'source_lines': source_lines[top:bottom],
- 'before': before,
- 'during': during,
- 'after': after,
- 'top': top,
- 'bottom': bottom,
- 'total': total,
- 'line': line,
- 'name': self.origin.name,
- 'start': start,
- 'end': end,
+ "message": message,
+ "source_lines": source_lines[top:bottom],
+ "before": before,
+ "during": during,
+ "after": after,
+ "top": top,
+ "bottom": bottom,
+ "total": total,
+ "line": line,
+ "name": self.origin.name,
+ "start": start,
+ "end": end,
}
def linebreak_iter(template_source):
yield 0
- p = template_source.find('\n')
+ p = template_source.find("\n")
while p >= 0:
yield p + 1
- p = template_source.find('\n', p + 1)
+ p = template_source.find("\n", p + 1)
yield len(template_source) + 1
@@ -316,8 +317,10 @@ class Token:
def __repr__(self):
token_name = self.token_type.name.capitalize()
- return ('<%s token: "%s...">' %
- (token_name, self.contents[:20].replace('\n', '')))
+ return '<%s token: "%s...">' % (
+ token_name,
+ self.contents[:20].replace("\n", ""),
+ )
def split_contents(self):
split = []
@@ -325,12 +328,12 @@ class Token:
for bit in bits:
# Handle translation-marked template pieces
if bit.startswith(('_("', "_('")):
- sentinel = bit[2] + ')'
+ sentinel = bit[2] + ")"
trans_bit = [bit]
while not bit.endswith(sentinel):
bit = next(bits)
trans_bit.append(bit)
- bit = ' '.join(trans_bit)
+ bit = " ".join(trans_bit)
split.append(bit)
return split
@@ -343,7 +346,7 @@ class Lexer:
def __repr__(self):
return '<%s template_string="%s...", verbatim=%s>' % (
self.__class__.__qualname__,
- self.template_string[:20].replace('\n', ''),
+ self.template_string[:20].replace("\n", ""),
self.verbatim,
)
@@ -357,7 +360,7 @@ class Lexer:
for token_string in tag_re.split(self.template_string):
if token_string:
result.append(self.create_token(token_string, None, lineno, in_tag))
- lineno += token_string.count('\n')
+ lineno += token_string.count("\n")
in_tag = not in_tag
return result
@@ -382,9 +385,9 @@ class Lexer:
return Token(TokenType.TEXT, token_string, position, lineno)
# Otherwise, the current verbatim block is ending.
self.verbatim = False
- elif content[:9] in ('verbatim', 'verbatim '):
+ elif content[:9] in ("verbatim", "verbatim "):
# Then a verbatim block is starting.
- self.verbatim = 'end%s' % content
+ self.verbatim = "end%s" % content
return Token(TokenType.BLOCK, content, position, lineno)
if not self.verbatim:
content = token_string[2:-2].strip()
@@ -425,7 +428,7 @@ class DebugLexer(Lexer):
for token_string, position in self._tag_re_split():
if token_string:
result.append(self.create_token(token_string, position, lineno, in_tag))
- lineno += token_string.count('\n')
+ lineno += token_string.count("\n")
in_tag = not in_tag
return result
@@ -450,7 +453,7 @@ class Parser:
self.origin = origin
def __repr__(self):
- return '<%s tokens=%r>' % (self.__class__.__qualname__, self.tokens)
+ return "<%s tokens=%r>" % (self.__class__.__qualname__, self.tokens)
def parse(self, parse_until=None):
"""
@@ -472,7 +475,9 @@ class Parser:
self.extend_nodelist(nodelist, TextNode(token.contents), token)
elif token_type == 1: # TokenType.VAR
if not token.contents:
- raise self.error(token, 'Empty variable tag on line %d' % token.lineno)
+ raise self.error(
+ token, "Empty variable tag on line %d" % token.lineno
+ )
try:
filter_expression = self.compile_filter(token.contents)
except TemplateSyntaxError as e:
@@ -483,7 +488,7 @@ class Parser:
try:
command = token.contents.split()[0]
except IndexError:
- raise self.error(token, 'Empty block tag on line %d' % token.lineno)
+ raise self.error(token, "Empty block tag on line %d" % token.lineno)
if command in parse_until:
# A matching token has been reached. Return control to
# the caller. Put the token back on the token list so the
@@ -524,7 +529,8 @@ class Parser:
# Check that non-text nodes don't appear before an extends tag.
if node.must_be_first and nodelist.contains_nontext:
raise self.error(
- token, '%r must be the first tag in the template.' % node,
+ token,
+ "%r must be the first tag in the template." % node,
)
if not isinstance(node, TextNode):
nodelist.contains_nontext = True
@@ -543,7 +549,7 @@ class Parser:
"""
if not isinstance(e, Exception):
e = TemplateSyntaxError(e)
- if not hasattr(e, 'token'):
+ if not hasattr(e, "token"):
e.token = token
return e
@@ -552,16 +558,17 @@ class Parser:
raise self.error(
token,
"Invalid block tag on line %d: '%s', expected %s. Did you "
- "forget to register or load this tag?" % (
+ "forget to register or load this tag?"
+ % (
token.lineno,
command,
- get_text_list(["'%s'" % p for p in parse_until], 'or'),
+ get_text_list(["'%s'" % p for p in parse_until], "or"),
),
)
raise self.error(
token,
"Invalid block tag on line %d: '%s'. Did you forget to register "
- "or load this tag?" % (token.lineno, command)
+ "or load this tag?" % (token.lineno, command),
)
def unclosed_block_tag(self, parse_until):
@@ -569,7 +576,7 @@ class Parser:
msg = "Unclosed tag on line %d: '%s'. Looking for one of: %s." % (
token.lineno,
command,
- ', '.join(parse_until),
+ ", ".join(parse_until),
)
raise self.error(token, msg)
@@ -608,10 +615,10 @@ constant_string = r"""
%(strdq)s|
%(strsq)s)
""" % {
- 'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
- 'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
- 'i18n_open': re.escape("_("),
- 'i18n_close': re.escape(")"),
+ "strdq": r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
+ "strsq": r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
+ "i18n_open": re.escape("_("),
+ "i18n_close": re.escape(")"),
}
constant_string = constant_string.replace("\n", "")
@@ -627,11 +634,11 @@ filter_raw_string = r"""
)
)?
)""" % {
- 'constant': constant_string,
- 'num': r'[-+\.]?\d[\d\.e]*',
- 'var_chars': r'\w\.',
- 'filter_sep': re.escape(FILTER_SEPARATOR),
- 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
+ "constant": constant_string,
+ "num": r"[-+\.]?\d[\d\.e]*",
+ "var_chars": r"\w\.",
+ "filter_sep": re.escape(FILTER_SEPARATOR),
+ "arg_sep": re.escape(FILTER_ARGUMENT_SEPARATOR),
}
filter_re = _lazy_re_compile(filter_raw_string, re.VERBOSE)
@@ -652,7 +659,7 @@ class FilterExpression:
<Variable: 'variable'>
"""
- __slots__ = ('token', 'filters', 'var', 'is_var')
+ __slots__ = ("token", "filters", "var", "is_var")
def __init__(self, token, parser):
self.token = token
@@ -663,12 +670,12 @@ class FilterExpression:
for match in matches:
start = match.start()
if upto != start:
- raise TemplateSyntaxError("Could not parse some characters: "
- "%s|%s|%s" %
- (token[:upto], token[upto:start],
- token[start:]))
+ raise TemplateSyntaxError(
+ "Could not parse some characters: "
+ "%s|%s|%s" % (token[:upto], token[upto:start], token[start:])
+ )
if var_obj is None:
- var, constant = match['var'], match['constant']
+ var, constant = match["var"], match["constant"]
if constant:
try:
var_obj = Variable(constant).resolve({})
@@ -681,9 +688,9 @@ class FilterExpression:
else:
var_obj = Variable(var)
else:
- filter_name = match['filter_name']
+ filter_name = match["filter_name"]
args = []
- constant_arg, var_arg = match['constant_arg'], match['var_arg']
+ constant_arg, var_arg = match["constant_arg"], match["var_arg"]
if constant_arg:
args.append((False, Variable(constant_arg).resolve({})))
elif var_arg:
@@ -693,8 +700,10 @@ class FilterExpression:
filters.append((filter_func, args))
upto = match.end()
if upto != len(token):
- raise TemplateSyntaxError("Could not parse the remainder: '%s' "
- "from '%s'" % (token[upto:], token))
+ raise TemplateSyntaxError(
+ "Could not parse the remainder: '%s' "
+ "from '%s'" % (token[upto:], token)
+ )
self.filters = filters
self.var = var_obj
@@ -710,7 +719,7 @@ class FilterExpression:
else:
string_if_invalid = context.template.engine.string_if_invalid
if string_if_invalid:
- if '%s' in string_if_invalid:
+ if "%s" in string_if_invalid:
return string_if_invalid % self.var
else:
return string_if_invalid
@@ -725,13 +734,13 @@ class FilterExpression:
arg_vals.append(mark_safe(arg))
else:
arg_vals.append(arg.resolve(context))
- if getattr(func, 'expects_localtime', False):
+ if getattr(func, "expects_localtime", False):
obj = template_localtime(obj, context.use_tz)
- if getattr(func, 'needs_autoescape', False):
+ if getattr(func, "needs_autoescape", False):
new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
else:
new_obj = func(obj, *arg_vals)
- if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
+ if getattr(func, "is_safe", False) and isinstance(obj, SafeData):
obj = mark_safe(new_obj)
else:
obj = new_obj
@@ -749,10 +758,12 @@ class FilterExpression:
dlen = len(defaults or [])
# Not enough OR Too many
if plen < (alen - dlen) or plen > alen:
- raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
- (name, alen - dlen, plen))
+ raise TemplateSyntaxError(
+ "%s requires %d arguments, %d provided" % (name, alen - dlen, plen)
+ )
return True
+
args_check = staticmethod(args_check)
def __str__(self):
@@ -781,7 +792,7 @@ class Variable:
(The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
"""
- __slots__ = ('var', 'literal', 'lookups', 'translate', 'message_context')
+ __slots__ = ("var", "literal", "lookups", "translate", "message_context")
def __init__(self, var):
self.var = var
@@ -791,8 +802,7 @@ class Variable:
self.message_context = None
if not isinstance(var, str):
- raise TypeError(
- "Variable must be a string or number, got %s" % type(var))
+ raise TypeError("Variable must be a string or number, got %s" % type(var))
try:
# First try to treat this variable as a number.
#
@@ -802,16 +812,16 @@ class Variable:
# Try to interpret values containing a period or an 'e'/'E'
# (possibly scientific notation) as a float; otherwise, try int.
- if '.' in var or 'e' in var.lower():
+ if "." in var or "e" in var.lower():
self.literal = float(var)
# "2." is invalid
- if var[-1] == '.':
+ if var[-1] == ".":
raise ValueError
else:
self.literal = int(var)
except ValueError:
# A ValueError means that the variable isn't a number.
- if var[0:2] == '_(' and var[-1] == ')':
+ if var[0:2] == "_(" and var[-1] == ")":
# The result of the lookup should be translated at rendering
# time.
self.translate = True
@@ -823,10 +833,11 @@ class Variable:
except ValueError:
# Otherwise we'll set self.lookups so that resolve() knows we're
# dealing with a bonafide variable
- if VARIABLE_ATTRIBUTE_SEPARATOR + '_' in var or var[0] == '_':
- raise TemplateSyntaxError("Variables and attributes may "
- "not begin with underscores: '%s'" %
- var)
+ if VARIABLE_ATTRIBUTE_SEPARATOR + "_" in var or var[0] == "_":
+ raise TemplateSyntaxError(
+ "Variables and attributes may "
+ "not begin with underscores: '%s'" % var
+ )
self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
def resolve(self, context):
@@ -839,7 +850,7 @@ class Variable:
value = self.literal
if self.translate:
is_safe = isinstance(value, SafeData)
- msgid = value.replace('%', '%%')
+ msgid = value.replace("%", "%%")
msgid = mark_safe(msgid) if is_safe else msgid
if self.message_context:
return pgettext_lazy(self.message_context, msgid)
@@ -872,7 +883,9 @@ class Variable:
except (TypeError, AttributeError, KeyError, ValueError, IndexError):
try: # attribute lookup
# Don't return class attributes if the class is the context:
- if isinstance(current, BaseContext) and getattr(type(current), bit):
+ if isinstance(current, BaseContext) and getattr(
+ type(current), bit
+ ):
raise AttributeError
current = getattr(current, bit)
except (TypeError, AttributeError):
@@ -881,18 +894,20 @@ class Variable:
raise
try: # list-index lookup
current = current[int(bit)]
- except (IndexError, # list index out of range
- ValueError, # invalid literal for int()
- KeyError, # current is a dict without `int(bit)` key
- TypeError): # unsubscriptable object
+ except (
+ IndexError, # list index out of range
+ ValueError, # invalid literal for int()
+ KeyError, # current is a dict without `int(bit)` key
+ TypeError,
+ ): # unsubscriptable object
raise VariableDoesNotExist(
"Failed lookup for key [%s] in %r",
(bit, current),
) # missing attribute
if callable(current):
- if getattr(current, 'do_not_call_in_templates', False):
+ if getattr(current, "do_not_call_in_templates", False):
pass
- elif getattr(current, 'alters_data', False):
+ elif getattr(current, "alters_data", False):
current = context.template.engine.string_if_invalid
else:
try: # method call (assuming no args required)
@@ -902,11 +917,13 @@ class Variable:
try:
signature.bind()
except TypeError: # arguments *were* required
- current = context.template.engine.string_if_invalid # invalid method call
+ current = (
+ context.template.engine.string_if_invalid
+ ) # invalid method call
else:
raise
except Exception as e:
- template_name = getattr(context, 'template_name', None) or 'unknown'
+ template_name = getattr(context, "template_name", None) or "unknown"
logger.debug(
"Exception while resolving variable '%s' in template '%s'.",
bit,
@@ -914,7 +931,7 @@ class Variable:
exc_info=True,
)
- if getattr(e, 'silent_variable_failure', False):
+ if getattr(e, "silent_variable_failure", False):
current = context.template.engine.string_if_invalid
else:
raise
@@ -926,7 +943,7 @@ class Node:
# Set this to True for nodes that must be first in the template (although
# they can be preceded by text nodes.
must_be_first = False
- child_nodelists = ('nodelist',)
+ child_nodelists = ("nodelist",)
token = None
def render(self, context):
@@ -947,14 +964,17 @@ class Node:
except Exception as e:
if context.template.engine.debug:
# Store the actual node that caused the exception.
- if not hasattr(e, '_culprit_node'):
+ if not hasattr(e, "_culprit_node"):
e._culprit_node = self
if (
- not hasattr(e, 'template_debug') and
- context.render_context.template.origin == e._culprit_node.origin
+ not hasattr(e, "template_debug")
+ and context.render_context.template.origin == e._culprit_node.origin
):
- e.template_debug = context.render_context.template.get_exception_info(
- e, e._culprit_node.token,
+ e.template_debug = (
+ context.render_context.template.get_exception_info(
+ e,
+ e._culprit_node.token,
+ )
)
raise
@@ -982,9 +1002,7 @@ class NodeList(list):
contains_nontext = False
def render(self, context):
- return SafeString(''.join([
- node.render_annotated(context) for node in self
- ]))
+ return SafeString("".join([node.render_annotated(context) for node in self]))
def get_nodes_by_type(self, nodetype):
"Return a list of all nodes of the given type"
@@ -1048,7 +1066,7 @@ class VariableNode(Node):
# Unicode conversion can fail sometimes for reasons out of our
# control (e.g. exception rendering). In that case, we fail
# quietly.
- return ''
+ return ""
return render_value_in_context(output, context)
@@ -1079,7 +1097,7 @@ def token_kwargs(bits, parser, support_legacy=False):
if not kwarg_format:
if not support_legacy:
return {}
- if len(bits) < 3 or bits[1] != 'as':
+ if len(bits) < 3 or bits[1] != "as":
return {}
kwargs = {}
@@ -1091,13 +1109,13 @@ def token_kwargs(bits, parser, support_legacy=False):
key, value = match.groups()
del bits[:1]
else:
- if len(bits) < 3 or bits[1] != 'as':
+ if len(bits) < 3 or bits[1] != "as":
return kwargs
key, value = bits[2], bits[0]
del bits[:3]
kwargs[key] = parser.compile_filter(value)
if bits and not kwarg_format:
- if bits[0] != 'and':
+ if bits[0] != "and":
return kwargs
del bits[:1]
return kwargs