测试gitnore

This commit is contained in:
ladeng07
2022-05-06 15:45:57 +08:00
parent 12f390949b
commit 51552904f9
2347 changed files with 120102 additions and 53549 deletions
@@ -46,30 +46,26 @@ from .utils import EngineHandler
engines = EngineHandler()
__all__ = ("Engine", "engines")
__all__ = ('Engine', 'engines')
# Django Template Language
# Public exceptions
from .base import VariableDoesNotExist # NOQA isort:skip
from .context import Context, ContextPopException, RequestContext # NOQA isort:skip
from .exceptions import TemplateDoesNotExist, TemplateSyntaxError # NOQA isort:skip
from .base import VariableDoesNotExist # NOQA isort:skip
from .context import Context, ContextPopException, RequestContext # NOQA isort:skip
from .exceptions import TemplateDoesNotExist, TemplateSyntaxError # NOQA isort:skip
# Template parts
from .base import ( # NOQA isort:skip
Node,
NodeList,
Origin,
Template,
Variable,
from .base import ( # NOQA isort:skip
Node, NodeList, Origin, Template, Variable,
)
# Library management
from .library import Library # NOQA isort:skip
from .library import Library # NOQA isort:skip
# Import the .autoreload module to trigger the registrations of signals.
from . import autoreload # NOQA isort:skip
from . import autoreload # NOQA isort:skip
__all__ += ("Template", "Context", "RequestContext")
__all__ += ('Template', 'Context', 'RequestContext')
@@ -4,7 +4,9 @@ from django.dispatch import receiver
from django.template import engines
from django.template.backends.django import DjangoTemplates
from django.utils._os import to_path
from django.utils.autoreload import autoreload_started, file_changed, is_django_path
from django.utils.autoreload import (
autoreload_started, file_changed, is_django_path,
)
def get_template_directories():
@@ -16,15 +18,15 @@ def get_template_directories():
if not isinstance(backend, DjangoTemplates):
continue
items.update(Path.cwd() / to_path(dir) for dir in backend.engine.dirs if dir)
items.update(Path.cwd() / to_path(dir) for dir in backend.engine.dirs)
for loader in backend.engine.template_loaders:
if not hasattr(loader, "get_dirs"):
if not hasattr(loader, 'get_dirs'):
continue
items.update(
Path.cwd() / to_path(directory)
for directory in loader.get_dirs()
if directory and not is_django_path(directory)
if not is_django_path(directory)
)
return items
@@ -37,13 +39,13 @@ def reset_loaders():
loader.reset()
@receiver(autoreload_started, dispatch_uid="template_loaders_watch_changes")
@receiver(autoreload_started, dispatch_uid='template_loaders_watch_changes')
def watch_for_template_changes(sender, **kwargs):
for directory in get_template_directories():
sender.watch_dir(directory, "**/*")
sender.watch_dir(directory, '**/*')
@receiver(file_changed, dispatch_uid="template_loaders_file_changed")
@receiver(file_changed, dispatch_uid='template_loaders_file_changed')
def template_changed(sender, file_path, **kwargs):
for template_dir in get_template_directories():
if template_dir in file_path.parents:
@@ -1,4 +1,6 @@
from django.core.exceptions import ImproperlyConfigured, SuspiciousFileOperation
from django.core.exceptions import (
ImproperlyConfigured, SuspiciousFileOperation,
)
from django.template.utils import get_app_template_dirs
from django.utils._os import safe_join
from django.utils.functional import cached_property
@@ -16,20 +18,18 @@ class BaseEngine:
`params` is a dict of configuration settings.
"""
params = params.copy()
self.name = params.pop("NAME")
self.dirs = list(params.pop("DIRS"))
self.app_dirs = params.pop("APP_DIRS")
self.name = params.pop('NAME')
self.dirs = list(params.pop('DIRS'))
self.app_dirs = params.pop('APP_DIRS')
if params:
raise ImproperlyConfigured(
"Unknown parameters: {}".format(", ".join(params))
)
"Unknown parameters: {}".format(", ".join(params)))
@property
def app_dirname(self):
raise ImproperlyConfigured(
"{} doesn't support loading templates from installed "
"applications.".format(self.__class__.__name__)
)
"applications.".format(self.__class__.__name__))
def from_string(self, template_code):
"""
@@ -38,8 +38,8 @@ class BaseEngine:
This method is optional.
"""
raise NotImplementedError(
"subclasses of BaseEngine should provide a from_string() method"
)
"subclasses of BaseEngine should provide "
"a from_string() method")
def get_template(self, template_name):
"""
@@ -48,8 +48,8 @@ class BaseEngine:
Raise TemplateDoesNotExist if no such template exists.
"""
raise NotImplementedError(
"subclasses of BaseEngine must provide a get_template() method"
)
"subclasses of BaseEngine must provide "
"a get_template() method")
# Utility methods: they are provided to minimize code duplication and
# security issues in third-party backends.
@@ -13,16 +13,16 @@ from .base import BaseEngine
class DjangoTemplates(BaseEngine):
app_dirname = "templates"
app_dirname = 'templates'
def __init__(self, params):
params = params.copy()
options = params.pop("OPTIONS").copy()
options.setdefault("autoescape", True)
options.setdefault("debug", settings.DEBUG)
options.setdefault("file_charset", "utf-8")
libraries = options.get("libraries", {})
options["libraries"] = self.get_templatetag_libraries(libraries)
options = params.pop('OPTIONS').copy()
options.setdefault('autoescape', True)
options.setdefault('debug', settings.DEBUG)
options.setdefault('file_charset', 'utf-8')
libraries = options.get('libraries', {})
options['libraries'] = self.get_templatetag_libraries(libraries)
super().__init__(params)
self.engine = Engine(self.dirs, self.app_dirs, **options)
@@ -46,6 +46,7 @@ class DjangoTemplates(BaseEngine):
class Template:
def __init__(self, template, backend):
self.template = template
self.backend = backend
@@ -55,9 +56,7 @@ class Template:
return self.template.origin
def render(self, context=None, request=None):
context = make_context(
context, request, autoescape=self.backend.engine.autoescape
)
context = make_context(context, request, autoescape=self.backend.engine.autoescape)
try:
return self.template.render(context)
except TemplateDoesNotExist as exc:
@@ -72,7 +71,7 @@ def copy_exception(exc, backend=None):
"""
backend = backend or exc.backend
new = exc.__class__(*exc.args, tried=exc.tried, backend=backend, chain=exc.chain)
if hasattr(exc, "template_debug"):
if hasattr(exc, 'template_debug'):
new.template_debug = exc.template_debug
return new
@@ -93,10 +92,10 @@ def get_installed_libraries():
django.templatetags.i18n is stored as i18n.
"""
libraries = {}
candidates = ["django.templatetags"]
candidates = ['django.templatetags']
candidates.extend(
"%s.templatetags" % app_config.name for app_config in apps.get_app_configs()
)
'%s.templatetags' % app_config.name
for app_config in apps.get_app_configs())
for candidate in candidates:
try:
@@ -105,9 +104,9 @@ def get_installed_libraries():
# No templatetags package defined. This is safe to ignore.
continue
if hasattr(pkg, "__path__"):
if hasattr(pkg, '__path__'):
for name in get_package_libraries(pkg):
libraries[name[len(candidate) + 1 :]] = name
libraries[name[len(candidate) + 1:]] = name
return libraries
@@ -117,7 +116,7 @@ def get_package_libraries(pkg):
Recursively yield template tag libraries defined in submodules of a
package.
"""
for entry in walk_packages(pkg.__path__, pkg.__name__ + "."):
for entry in walk_packages(pkg.__path__, pkg.__name__ + '.'):
try:
module = import_module(entry[1])
except ImportError as e:
@@ -126,5 +125,5 @@ def get_package_libraries(pkg):
"trying to load '%s': %s" % (entry[1], e)
) from e
if hasattr(module, "register"):
if hasattr(module, 'register'):
yield entry[1]
@@ -10,13 +10,14 @@ from .utils import csrf_input_lazy, csrf_token_lazy
class TemplateStrings(BaseEngine):
app_dirname = "template_strings"
app_dirname = 'template_strings'
def __init__(self, params):
params = params.copy()
options = params.pop("OPTIONS").copy()
options = params.pop('OPTIONS').copy()
if options:
raise ImproperlyConfigured("Unknown options: {}".format(", ".join(options)))
raise ImproperlyConfigured(
"Unknown options: {}".format(", ".join(options)))
super().__init__(params)
def from_string(self, template_code):
@@ -26,27 +27,26 @@ class TemplateStrings(BaseEngine):
tried = []
for template_file in self.iter_template_filenames(template_name):
try:
with open(template_file, encoding="utf-8") as fp:
with open(template_file, encoding='utf-8') as fp:
template_code = fp.read()
except FileNotFoundError:
tried.append(
(
Origin(template_file, template_name, self),
"Source does not exist",
)
)
tried.append((
Origin(template_file, template_name, self),
'Source does not exist',
))
else:
return Template(template_code)
raise TemplateDoesNotExist(template_name, tried=tried, backend=self)
class Template(string.Template):
def render(self, context=None, request=None):
if context is None:
context = {}
else:
context = {k: conditional_escape(v) for k, v in context.items()}
if request is not None:
context["csrf_input"] = csrf_input_lazy(request)
context["csrf_token"] = csrf_token_lazy(request)
context['csrf_input'] = csrf_input_lazy(request)
context['csrf_token'] = csrf_token_lazy(request)
return self.safe_substitute(context)
@@ -12,25 +12,24 @@ from .base import BaseEngine
class Jinja2(BaseEngine):
app_dirname = "jinja2"
app_dirname = 'jinja2'
def __init__(self, params):
params = params.copy()
options = params.pop("OPTIONS").copy()
options = params.pop('OPTIONS').copy()
super().__init__(params)
self.context_processors = options.pop("context_processors", [])
self.context_processors = options.pop('context_processors', [])
environment = options.pop("environment", "jinja2.Environment")
environment = options.pop('environment', 'jinja2.Environment')
environment_cls = import_string(environment)
if "loader" not in options:
options["loader"] = jinja2.FileSystemLoader(self.template_dirs)
options.setdefault("autoescape", True)
options.setdefault("auto_reload", settings.DEBUG)
options.setdefault(
"undefined", jinja2.DebugUndefined if settings.DEBUG else jinja2.Undefined
)
if 'loader' not in options:
options['loader'] = jinja2.FileSystemLoader(self.template_dirs)
options.setdefault('autoescape', True)
options.setdefault('auto_reload', settings.DEBUG)
options.setdefault('undefined',
jinja2.DebugUndefined if settings.DEBUG else jinja2.Undefined)
self.env = environment_cls(**options)
@@ -53,23 +52,22 @@ class Jinja2(BaseEngine):
class Template:
def __init__(self, template, backend):
self.template = template
self.backend = backend
self.origin = Origin(
name=template.filename,
template_name=template.name,
name=template.filename, template_name=template.name,
)
def render(self, context=None, request=None):
from .utils import csrf_input_lazy, csrf_token_lazy
if context is None:
context = {}
if request is not None:
context["request"] = request
context["csrf_input"] = csrf_input_lazy(request)
context["csrf_token"] = csrf_token_lazy(request)
context['request'] = request
context['csrf_input'] = csrf_input_lazy(request)
context['csrf_token'] = csrf_token_lazy(request)
for context_processor in self.backend.template_context_processors:
context.update(context_processor(request))
try:
@@ -85,7 +83,6 @@ class Origin:
A container to hold debug information as described in the template API
documentation.
"""
def __init__(self, name, template_name):
self.name = name
self.template_name = template_name
@@ -102,27 +99,27 @@ def get_exception_info(exception):
if source is None:
exception_file = Path(exception.filename)
if exception_file.exists():
with open(exception_file, "r") as fp:
with open(exception_file, 'r') as fp:
source = fp.read()
if source is not None:
lines = list(enumerate(source.strip().split("\n"), start=1))
lines = list(enumerate(source.strip().split('\n'), start=1))
during = lines[lineno - 1][1]
total = len(lines)
top = max(0, lineno - context_lines - 1)
bottom = min(total, lineno + context_lines)
else:
during = ""
during = ''
lines = []
total = top = bottom = 0
return {
"name": exception.filename,
"message": exception.message,
"source_lines": lines[top:bottom],
"line": lineno,
"before": "",
"during": during,
"after": "",
"total": total,
"top": top,
"bottom": bottom,
'name': exception.filename,
'message': exception.message,
'source_lines': lines[top:bottom],
'line': lineno,
'before': '',
'during': during,
'after': '',
'total': total,
'top': top,
'bottom': bottom,
}
@@ -7,8 +7,7 @@ from django.utils.safestring import SafeString
def csrf_input(request):
return format_html(
'<input type="hidden" name="csrfmiddlewaretoken" value="{}">',
get_token(request),
)
get_token(request))
csrf_input_lazy = lazy(csrf_input, SafeString, str)
+166 -237
View File
@@ -59,36 +59,41 @@ from django.template.context import BaseContext
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.safestring import SafeData, mark_safe
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 = '#}'
TRANSLATOR_COMMENT_MARK = 'Translators'
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"({%.*?%}|{{.*?}}|{#.*?#})")
# match a variable or block tag and capture the entire tag, including start/end
# delimiters
tag_re = (_lazy_re_compile('(%s.*?%s|%s.*?%s|%s.*?%s)' %
(re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))))
logger = logging.getLogger("django.template")
logger = logging.getLogger('django.template')
class TokenType(Enum):
@@ -99,6 +104,7 @@ class TokenType(Enum):
class VariableDoesNotExist(Exception):
def __init__(self, msg, params=()):
self.msg = msg
self.params = params
@@ -116,22 +122,18 @@ class Origin:
def __str__(self):
return self.name
def __repr__(self):
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__,
)
@@ -143,7 +145,6 @@ 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)
@@ -157,12 +158,6 @@ class Template:
for node in self.nodelist:
yield from node
def __repr__(self):
return '<%s template_string="%s...">' % (
self.__class__.__qualname__,
self.source[:20].replace("\n", ""),
)
def _render(self, context):
return self.nodelist.render(context)
@@ -190,9 +185,7 @@ 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,
)
@@ -264,30 +257,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
@@ -315,12 +308,10 @@ class Token:
self.lineno = lineno
self.position = position
def __repr__(self):
def __str__(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 = []
@@ -328,12 +319,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,13 +334,6 @@ class Lexer:
self.template_string = template_string
self.verbatim = False
def __repr__(self):
return '<%s template_string="%s...", verbatim=%s>' % (
self.__class__.__qualname__,
self.template_string[:20].replace("\n", ""),
self.verbatim,
)
def tokenize(self):
"""
Return a list of tokens from a given template_string.
@@ -357,11 +341,11 @@ class Lexer:
in_tag = False
lineno = 1
result = []
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")
for bit in tag_re.split(self.template_string):
if bit:
result.append(self.create_token(bit, None, lineno, in_tag))
in_tag = not in_tag
lineno += bit.count('\n')
return result
def create_token(self, token_string, position, lineno, in_tag):
@@ -370,66 +354,53 @@ class Lexer:
If in_tag is True, we are processing something that matched a tag,
otherwise it should be treated as a literal string.
"""
if in_tag:
# The [0:2] and [2:-2] ranges below strip off *_TAG_START and
# *_TAG_END. The 2's are hard-coded for performance. Using
# len(BLOCK_TAG_START) would permit BLOCK_TAG_START to be
# different, but it's not likely that the TAG_START values will
# change anytime soon.
token_start = token_string[0:2]
if token_start == BLOCK_TAG_START:
content = token_string[2:-2].strip()
if self.verbatim:
# Then a verbatim block is being processed.
if content != self.verbatim:
return Token(TokenType.TEXT, token_string, position, lineno)
# Otherwise, the current verbatim block is ending.
self.verbatim = False
elif content[:9] in ("verbatim", "verbatim "):
# Then a verbatim block is starting.
self.verbatim = "end%s" % content
return Token(TokenType.BLOCK, content, position, lineno)
if not self.verbatim:
content = token_string[2:-2].strip()
if token_start == VARIABLE_TAG_START:
return Token(TokenType.VAR, content, position, lineno)
# BLOCK_TAG_START was handled above.
assert token_start == COMMENT_TAG_START
if in_tag and token_string.startswith(BLOCK_TAG_START):
# The [2:-2] ranges below strip off *_TAG_START and *_TAG_END.
# We could do len(BLOCK_TAG_START) to be more "correct", but we've
# hard-coded the 2s here for performance. And it's not like
# the TAG_START values are going to change anytime, anyway.
block_content = token_string[2:-2].strip()
if self.verbatim and block_content == self.verbatim:
self.verbatim = False
if in_tag and not self.verbatim:
if token_string.startswith(VARIABLE_TAG_START):
return Token(TokenType.VAR, token_string[2:-2].strip(), position, lineno)
elif token_string.startswith(BLOCK_TAG_START):
if block_content[:9] in ('verbatim', 'verbatim '):
self.verbatim = 'end%s' % block_content
return Token(TokenType.BLOCK, block_content, position, lineno)
elif token_string.startswith(COMMENT_TAG_START):
content = ''
if token_string.find(TRANSLATOR_COMMENT_MARK):
content = token_string[2:-2].strip()
return Token(TokenType.COMMENT, content, position, lineno)
return Token(TokenType.TEXT, token_string, position, lineno)
else:
return Token(TokenType.TEXT, token_string, position, lineno)
class DebugLexer(Lexer):
def _tag_re_split_positions(self):
last = 0
for match in tag_re.finditer(self.template_string):
start, end = match.span()
yield last, start
yield start, end
last = end
yield last, len(self.template_string)
# This parallels the use of tag_re.split() in Lexer.tokenize().
def _tag_re_split(self):
for position in self._tag_re_split_positions():
yield self.template_string[slice(*position)], position
def tokenize(self):
"""
Split a template string into tokens and annotates each token with its
start and end position in the source. This is slower than the default
lexer so only use it when debug is True.
"""
# For maintainability, it is helpful if the implementation below can
# continue to closely parallel Lexer.tokenize()'s implementation.
in_tag = False
lineno = 1
result = []
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")
in_tag = not in_tag
upto = 0
for match in tag_re.finditer(self.template_string):
start, end = match.span()
if start > upto:
token_string = self.template_string[upto:start]
result.append(self.create_token(token_string, (upto, start), lineno, in_tag=False))
lineno += token_string.count('\n')
token_string = self.template_string[start:end]
result.append(self.create_token(token_string, (start, end), lineno, in_tag=True))
lineno += token_string.count('\n')
upto = end
last_bit = self.template_string[upto:]
if last_bit:
result.append(self.create_token(last_bit, (upto, upto + len(last_bit)), lineno, in_tag=False))
return result
@@ -452,9 +423,6 @@ class Parser:
self.add_library(builtin)
self.origin = origin
def __repr__(self):
return "<%s tokens=%r>" % (self.__class__.__qualname__, self.tokens)
def parse(self, parse_until=None):
"""
Iterate through the parser tokens and compiles each one into a node.
@@ -470,25 +438,22 @@ class Parser:
while self.tokens:
token = self.next_token()
# Use the raw values here for TokenType.* for a tiny performance boost.
token_type = token.token_type.value
if token_type == 0: # TokenType.TEXT
if token.token_type.value == 0: # TokenType.TEXT
self.extend_nodelist(nodelist, TextNode(token.contents), token)
elif token_type == 1: # TokenType.VAR
elif token.token_type.value == 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:
raise self.error(token, e)
var_node = VariableNode(filter_expression)
self.extend_nodelist(nodelist, var_node, token)
elif token_type == 2: # TokenType.BLOCK
elif token.token_type.value == 2: # TokenType.BLOCK
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
@@ -529,10 +494,9 @@ 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):
if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
nodelist.contains_nontext = True
# Set origin and token here since we can't modify the node __init__()
# method.
@@ -549,7 +513,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
@@ -558,17 +522,16 @@ 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):
@@ -576,7 +539,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)
@@ -615,10 +578,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", "")
@@ -634,11 +597,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)
@@ -658,7 +621,6 @@ class FilterExpression:
>>> fe.var
<Variable: 'variable'>
"""
def __init__(self, token, parser):
self.token = token
matches = filter_re.finditer(token)
@@ -668,27 +630,26 @@ 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({})
except VariableDoesNotExist:
var_obj = None
elif var is None:
raise TemplateSyntaxError(
"Could not find variable at start of %s." % token
)
raise TemplateSyntaxError("Could not find variable at "
"start of %s." % token)
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:
@@ -698,10 +659,8 @@ 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
@@ -716,7 +675,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
@@ -731,13 +690,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
@@ -755,20 +714,15 @@ 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):
return self.token
def __repr__(self):
return "<%s %r>" % (self.__class__.__qualname__, self.token)
class Variable:
"""
@@ -797,7 +751,8 @@ 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.
#
@@ -807,16 +762,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.endswith('.'):
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.startswith('_(') and var.endswith(')'):
# The result of the lookup should be translated at rendering
# time.
self.translate = True
@@ -828,11 +783,10 @@ 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 var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 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):
@@ -845,7 +799,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)
@@ -878,9 +832,7 @@ 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):
@@ -889,20 +841,17 @@ 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
raise VariableDoesNotExist(
"Failed lookup for key [%s] in %r",
(bit, current),
) # missing attribute
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)
@@ -912,13 +861,11 @@ 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,
@@ -926,7 +873,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
@@ -938,7 +885,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):
@@ -957,20 +904,8 @@ class Node:
try:
return self.render(context)
except Exception as e:
if context.template.engine.debug:
# Store the actual node that caused the exception.
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
):
e.template_debug = (
context.render_context.template.get_exception_info(
e,
e._culprit_node.token,
)
)
if context.template.engine.debug and not hasattr(e, 'template_debug'):
e.template_debug = context.render_context.template.get_exception_info(e, self.token)
raise
def __iter__(self):
@@ -997,7 +932,14 @@ class NodeList(list):
contains_nontext = False
def render(self, context):
return SafeString("".join([node.render_annotated(context) for node in self]))
bits = []
for node in self:
if isinstance(node, Node):
bit = node.render_annotated(context)
else:
bit = node
bits.append(str(bit))
return mark_safe(''.join(bits))
def get_nodes_by_type(self, nodetype):
"Return a list of all nodes of the given type"
@@ -1008,8 +950,6 @@ class NodeList(list):
class TextNode(Node):
child_nodelists = ()
def __init__(self, s):
self.s = s
@@ -1019,15 +959,6 @@ class TextNode(Node):
def render(self, context):
return self.s
def render_annotated(self, context):
"""
Return the given value.
The default implementation of this method handles exceptions raised
during rendering, which is not necessary for text nodes.
"""
return self.s
def render_value_in_context(value, context):
"""
@@ -1046,8 +977,6 @@ def render_value_in_context(value, context):
class VariableNode(Node):
child_nodelists = ()
def __init__(self, filter_expression):
self.filter_expression = filter_expression
@@ -1061,7 +990,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)
@@ -1092,7 +1021,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 = {}
@@ -1104,13 +1033,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
@@ -2,7 +2,7 @@ from contextlib import contextmanager
from copy import copy
# Hard-coded processor for easier use of CSRF protection.
_builtin_context_processors = ("django.template.context_processors.csrf",)
_builtin_context_processors = ('django.template.context_processors.csrf',)
class ContextPopException(Exception):
@@ -29,7 +29,7 @@ class BaseContext:
self._reset_dicts(dict_)
def _reset_dicts(self, value=None):
builtins = {"True": True, "False": False, "None": None}
builtins = {'True': True, 'False': False, 'None': None}
self.dicts = [builtins]
if value is not None:
self.dicts.append(value)
@@ -132,7 +132,6 @@ class BaseContext:
class Context(BaseContext):
"A stack container for variable context"
def __init__(self, dict_=None, autoescape=True, use_l10n=None, use_tz=None):
self.autoescape = autoescape
self.use_l10n = use_l10n
@@ -161,8 +160,8 @@ class Context(BaseContext):
def update(self, other_dict):
"Push other_dict to the stack of dictionaries in the Context"
if not hasattr(other_dict, "__getitem__"):
raise TypeError("other_dict must be a mapping (dictionary-like) object.")
if not hasattr(other_dict, '__getitem__'):
raise TypeError('other_dict must be a mapping (dictionary-like) object.')
if isinstance(other_dict, BaseContext):
other_dict = other_dict.dicts[1:].pop()
return ContextDict(self, other_dict)
@@ -183,7 +182,6 @@ class RenderContext(BaseContext):
rendering of other templates as they would if they were stored in the normal
template context.
"""
template = None
def __iter__(self):
@@ -219,16 +217,7 @@ class RequestContext(Context):
Additional processors can be specified as a list of callables
using the "processors" keyword argument.
"""
def __init__(
self,
request,
dict_=None,
processors=None,
use_l10n=None,
use_tz=None,
autoescape=True,
):
def __init__(self, request, dict_=None, processors=None, use_l10n=None, use_tz=None, autoescape=True):
super().__init__(dict_, use_l10n=use_l10n, use_tz=use_tz, autoescape=autoescape)
self.request = request
self._processors = () if processors is None else tuple(processors)
@@ -248,7 +237,8 @@ class RequestContext(Context):
self.template = template
# Set context processors according to the template engine's settings.
processors = template.engine.template_context_processors + self._processors
processors = (template.engine.template_context_processors +
self._processors)
updates = {}
for processor in processors:
updates.update(processor(self.request))
@@ -265,7 +255,7 @@ class RequestContext(Context):
new_context = super().new(values)
# This is for backwards-compatibility: RequestContexts created via
# Context.new don't include values from context processors.
if hasattr(new_context, "_processors_index"):
if hasattr(new_context, '_processors_index'):
del new_context._processors_index
return new_context
@@ -275,9 +265,7 @@ def make_context(context, request=None, **kwargs):
Create a suitable Context from a plain dict and optionally an HttpRequest.
"""
if context is not None and not isinstance(context, dict):
raise TypeError(
"context must be a dict rather than %s." % context.__class__.__name__
)
raise TypeError('context must be a dict rather than %s.' % context.__class__.__name__)
if request is None:
context = Context(context, **kwargs)
else:
@@ -19,18 +19,17 @@ def csrf(request):
Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if
it has not been provided by either a view decorator or the middleware
"""
def _get_val():
token = get_token(request)
if token is None:
# In order to be able to provide debugging info in the
# case of misconfiguration, we use a sentinel value
# instead of returning an empty dict.
return "NOTPROVIDED"
return 'NOTPROVIDED'
else:
return token
return {"csrf_token": SimpleLazyObject(_get_val)}
return {'csrf_token': SimpleLazyObject(_get_val)}
def debug(request):
@@ -38,52 +37,46 @@ def debug(request):
Return context variables helpful for debugging.
"""
context_extras = {}
if settings.DEBUG and request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS:
context_extras["debug"] = True
if settings.DEBUG and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
context_extras['debug'] = True
from django.db import connections
# Return a lazy reference that computes connection.queries on access,
# to ensure it contains queries triggered after this function runs.
context_extras["sql_queries"] = lazy(
lambda: list(
itertools.chain.from_iterable(
connections[x].queries for x in connections
)
),
list,
context_extras['sql_queries'] = lazy(
lambda: list(itertools.chain.from_iterable(connections[x].queries for x in connections)),
list
)
return context_extras
def i18n(request):
from django.utils import translation
return {
"LANGUAGES": settings.LANGUAGES,
"LANGUAGE_CODE": translation.get_language(),
"LANGUAGE_BIDI": translation.get_language_bidi(),
'LANGUAGES': settings.LANGUAGES,
'LANGUAGE_CODE': translation.get_language(),
'LANGUAGE_BIDI': translation.get_language_bidi(),
}
def tz(request):
from django.utils import timezone
return {"TIME_ZONE": timezone.get_current_timezone_name()}
return {'TIME_ZONE': timezone.get_current_timezone_name()}
def static(request):
"""
Add static-related context variables to the context.
"""
return {"STATIC_URL": settings.STATIC_URL}
return {'STATIC_URL': settings.STATIC_URL}
def media(request):
"""
Add media-related context variables to the context.
"""
return {"MEDIA_URL": settings.MEDIA_URL}
return {'MEDIA_URL': settings.MEDIA_URL}
def request(request):
return {"request": request}
return {'request': request}
@@ -11,18 +11,18 @@ from urllib.parse import quote
from django.utils import formats
from django.utils.dateformat import format, time_format
from django.utils.encoding import iri_to_uri
from django.utils.html import avoid_wrapping, conditional_escape, escape, escapejs
from django.utils.html import json_script as _json_script
from django.utils.html import linebreaks, strip_tags
from django.utils.html import urlize as _urlize
from django.utils.html import (
avoid_wrapping, conditional_escape, escape, escapejs,
json_script as _json_script, linebreaks, strip_tags, urlize as _urlize,
)
from django.utils.safestring import SafeData, mark_safe
from django.utils.text import Truncator, normalize_newlines, phone2numeric
from django.utils.text import slugify as _slugify
from django.utils.text import wrap
from django.utils.text import (
Truncator, normalize_newlines, phone2numeric, slugify as _slugify, wrap,
)
from django.utils.timesince import timesince, timeuntil
from django.utils.translation import gettext, ngettext
from .base import VARIABLE_ATTRIBUTE_SEPARATOR
from .base import Variable, VariableDoesNotExist
from .library import Library
register = Library()
@@ -32,26 +32,23 @@ register = Library()
# STRING DECORATOR #
#######################
def stringfilter(func):
"""
Decorator for filters which should only receive strings. The object
passed as the first positional argument will be converted to a string.
"""
def _dec(*args, **kwargs):
args = list(args)
args[0] = str(args[0])
if isinstance(args[0], SafeData) and getattr(
_dec._decorated_function, "is_safe", False
):
if (isinstance(args[0], SafeData) and
getattr(_dec._decorated_function, 'is_safe', False)):
return mark_safe(func(*args, **kwargs))
return func(*args, **kwargs)
# Include a reference to the real function (used to check original
# arguments by the template parser, and to bear the 'is_safe' attribute
# when multiple decorators are applied).
_dec._decorated_function = getattr(func, "_decorated_function", func)
_dec._decorated_function = getattr(func, '_decorated_function', func)
return wraps(func)(_dec)
@@ -60,7 +57,6 @@ def stringfilter(func):
# STRINGS #
###################
@register.filter(is_safe=True)
@stringfilter
def addslashes(value):
@@ -69,7 +65,7 @@ def addslashes(value):
example. Less useful for escaping JavaScript; use the ``escapejs``
filter instead.
"""
return value.replace("\\", "\\\\").replace('"', '\\"').replace("'", "\\'")
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
@register.filter(is_safe=True)
@@ -130,29 +126,13 @@ def floatformat(text, arg=-1):
* {{ 6666.6666|floatformat:"2g" }} displays "6,666.67"
* {{ 10000|floatformat:"g" }} displays "10,000"
If arg has the 'u' suffix, force the result to be unlocalized. When the
active locale is pl (Polish):
* {{ 66666.6666|floatformat:"2" }} displays "66666,67"
* {{ 66666.6666|floatformat:"2u" }} displays "66666.67"
If the input float is infinity or NaN, display the string representation
of that value.
"""
force_grouping = False
use_l10n = True
if isinstance(arg, str):
last_char = arg[-1]
if arg[-2:] in {"gu", "ug"}:
force_grouping = True
use_l10n = False
arg = arg[:-2] or -1
elif last_char == "g":
force_grouping = True
arg = arg[:-1] or -1
elif last_char == "u":
use_l10n = False
arg = arg[:-1] or -1
if isinstance(arg, str) and arg.endswith('g'):
force_grouping = True
arg = arg[:-1] or -1
try:
input_val = repr(text)
d = Decimal(input_val)
@@ -160,7 +140,7 @@ def floatformat(text, arg=-1):
try:
d = Decimal(str(float(text)))
except (ValueError, InvalidOperation, TypeError):
return ""
return ''
try:
p = int(arg)
except ValueError:
@@ -173,12 +153,7 @@ def floatformat(text, arg=-1):
if not m and p < 0:
return mark_safe(
formats.number_format(
"%d" % (int(d)),
0,
use_l10n=use_l10n,
force_grouping=force_grouping,
)
formats.number_format('%d' % (int(d)), 0, force_grouping=force_grouping),
)
exp = Decimal(1).scaleb(-abs(p))
@@ -194,18 +169,13 @@ def floatformat(text, arg=-1):
sign, digits, exponent = rounded_d.as_tuple()
digits = [str(digit) for digit in reversed(digits)]
while len(digits) <= abs(exponent):
digits.append("0")
digits.insert(-exponent, ".")
digits.append('0')
digits.insert(-exponent, '.')
if sign and rounded_d:
digits.append("-")
number = "".join(reversed(digits))
digits.append('-')
number = ''.join(reversed(digits))
return mark_safe(
formats.number_format(
number,
abs(p),
use_l10n=use_l10n,
force_grouping=force_grouping,
)
formats.number_format(number, abs(p), force_grouping=force_grouping),
)
@@ -220,7 +190,7 @@ def iriencode(value):
@stringfilter
def linenumbers(value, autoescape=True):
"""Display text with line numbers."""
lines = value.split("\n")
lines = value.split('\n')
# Find the maximum width of the line count, for use with zero padding
# string format command
width = str(len(str(len(lines))))
@@ -230,7 +200,7 @@ def linenumbers(value, autoescape=True):
else:
for i, line in enumerate(lines):
lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
return mark_safe("\n".join(lines))
return mark_safe('\n'.join(lines))
@register.filter(is_safe=True)
@@ -287,7 +257,7 @@ def stringformat(value, arg):
def title(value):
"""Convert a string into titlecase."""
t = re.sub("([a-z])'([A-Z])", lambda m: m[0].lower(), value.title())
return re.sub(r"\d([A-Z])", lambda m: m[0].lower(), t)
return re.sub(r'\d([A-Z])', lambda m: m[0].lower(), t)
@register.filter(is_safe=True)
@@ -326,7 +296,7 @@ def truncatewords(value, arg):
length = int(arg)
except ValueError: # Invalid literal for int().
return value # Fail silently.
return Truncator(value).words(length, truncate="")
return Truncator(value).words(length, truncate='')
@register.filter(is_safe=True)
@@ -340,7 +310,7 @@ def truncatewords_html(value, arg):
length = int(arg)
except ValueError: # invalid literal for int()
return value # Fail silently.
return Truncator(value).words(length, html=True, truncate="")
return Truncator(value).words(length, html=True, truncate='')
@register.filter(is_safe=False)
@@ -363,7 +333,7 @@ def urlencode(value, safe=None):
"""
kwargs = {}
if safe is not None:
kwargs["safe"] = safe
kwargs['safe'] = safe
return quote(value, **kwargs)
@@ -383,9 +353,7 @@ def urlizetrunc(value, limit, autoescape=True):
Argument: Length to truncate URLs to.
"""
return mark_safe(
_urlize(value, trim_url_limit=int(limit), nofollow=True, autoescape=autoescape)
)
return mark_safe(_urlize(value, trim_url_limit=int(limit), nofollow=True, autoescape=autoescape))
@register.filter(is_safe=False)
@@ -428,8 +396,8 @@ def center(value, arg):
def cut(value, arg):
"""Remove all values of arg from the given string."""
safe = isinstance(value, SafeData)
value = value.replace(arg, "")
if safe and arg != ";":
value = value.replace(arg, '')
if safe and arg != ';':
return mark_safe(value)
return value
@@ -438,7 +406,6 @@ def cut(value, arg):
# HTML STRINGS #
###################
@register.filter("escape", is_safe=True)
@stringfilter
def escape_filter(value):
@@ -480,7 +447,7 @@ def linebreaksbr(value, autoescape=True):
value = normalize_newlines(value)
if autoescape:
value = escape(value)
return mark_safe(value.replace("\n", "<br>"))
return mark_safe(value.replace('\n', '<br>'))
@register.filter(is_safe=True)
@@ -511,11 +478,10 @@ def striptags(value):
# LISTS #
###################
def _property_resolver(arg):
"""
When arg is convertible to float, behave like operator.itemgetter(arg)
Otherwise, chain __getitem__() and getattr().
Otherwise, behave like Variable(arg).resolve
>>> _property_resolver(1)('abc')
'b'
@@ -533,19 +499,7 @@ def _property_resolver(arg):
try:
float(arg)
except ValueError:
if VARIABLE_ATTRIBUTE_SEPARATOR + "_" in arg or arg[0] == "_":
raise AttributeError("Access to private variables is forbidden.")
parts = arg.split(VARIABLE_ATTRIBUTE_SEPARATOR)
def resolve(value):
for part in parts:
try:
value = value[part]
except (AttributeError, IndexError, KeyError, TypeError, ValueError):
value = getattr(value, part)
return value
return resolve
return Variable(arg).resolve
else:
return itemgetter(arg)
@@ -558,8 +512,8 @@ def dictsort(value, arg):
"""
try:
return sorted(value, key=_property_resolver(arg))
except (AttributeError, TypeError):
return ""
except (TypeError, VariableDoesNotExist):
return ''
@register.filter(is_safe=False)
@@ -570,8 +524,8 @@ def dictsortreversed(value, arg):
"""
try:
return sorted(value, key=_property_resolver(arg), reverse=True)
except (AttributeError, TypeError):
return ""
except (TypeError, VariableDoesNotExist):
return ''
@register.filter(is_safe=False)
@@ -580,7 +534,7 @@ def first(value):
try:
return value[0]
except IndexError:
return ""
return ''
@register.filter(is_safe=True, needs_autoescape=True)
@@ -601,7 +555,7 @@ def last(value):
try:
return value[-1]
except IndexError:
return ""
return ''
@register.filter(is_safe=False)
@@ -619,7 +573,7 @@ def length_is(value, arg):
try:
return len(value) == int(arg)
except (ValueError, TypeError):
return ""
return ''
@register.filter(is_safe=True)
@@ -635,7 +589,7 @@ def slice_filter(value, arg):
"""
try:
bits = []
for x in str(arg).split(":"):
for x in str(arg).split(':'):
if not x:
bits.append(None)
else:
@@ -671,7 +625,6 @@ def unordered_list(value, autoescape=True):
if autoescape:
escaper = conditional_escape
else:
def escaper(x):
return x
@@ -700,19 +653,16 @@ def unordered_list(value, autoescape=True):
pass
def list_formatter(item_list, tabs=1):
indent = "\t" * tabs
indent = '\t' * tabs
output = []
for item, children in walk_items(item_list):
sublist = ""
sublist = ''
if children:
sublist = "\n%s<ul>\n%s\n%s</ul>\n%s" % (
indent,
list_formatter(children, tabs + 1),
indent,
indent,
)
output.append("%s<li>%s%s</li>" % (indent, escaper(item), sublist))
return "\n".join(output)
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (
indent, list_formatter(children, tabs + 1), indent, indent)
output.append('%s<li>%s%s</li>' % (
indent, escaper(item), sublist))
return '\n'.join(output)
return mark_safe(list_formatter(value))
@@ -721,7 +671,6 @@ def unordered_list(value, autoescape=True):
# INTEGERS #
###################
@register.filter(is_safe=False)
def add(value, arg):
"""Add the arg to the value."""
@@ -731,7 +680,7 @@ def add(value, arg):
try:
return value + arg
except Exception:
return ""
return ''
@register.filter(is_safe=False)
@@ -759,64 +708,62 @@ def get_digit(value, arg):
# DATES #
###################
@register.filter(expects_localtime=True, is_safe=False)
def date(value, arg=None):
"""Format a date according to the given format."""
if value in (None, ""):
return ""
if value in (None, ''):
return ''
try:
return formats.date_format(value, arg)
except AttributeError:
try:
return format(value, arg)
except AttributeError:
return ""
return ''
@register.filter(expects_localtime=True, is_safe=False)
def time(value, arg=None):
"""Format a time according to the given format."""
if value in (None, ""):
return ""
if value in (None, ''):
return ''
try:
return formats.time_format(value, arg)
except (AttributeError, TypeError):
try:
return time_format(value, arg)
except (AttributeError, TypeError):
return ""
return ''
@register.filter("timesince", is_safe=False)
def timesince_filter(value, arg=None):
"""Format a date as the time since that date (i.e. "4 days, 6 hours")."""
if not value:
return ""
return ''
try:
if arg:
return timesince(value, arg)
return timesince(value)
except (ValueError, TypeError):
return ""
return ''
@register.filter("timeuntil", is_safe=False)
def timeuntil_filter(value, arg=None):
"""Format a date as the time until that date (i.e. "4 days, 6 hours")."""
if not value:
return ""
return ''
try:
return timeuntil(value, arg)
except (ValueError, TypeError):
return ""
return ''
###################
# LOGIC #
###################
@register.filter(is_safe=False)
def default(value, arg):
"""If value is unavailable, use given default."""
@@ -855,8 +802,8 @@ def yesno(value, arg=None):
"""
if arg is None:
# Translators: Please do not add spaces around commas.
arg = gettext("yes,no,maybe")
bits = arg.split(",")
arg = gettext('yes,no,maybe')
bits = arg.split(',')
if len(bits) < 2:
return value # Invalid arg.
try:
@@ -875,7 +822,6 @@ def yesno(value, arg=None):
# MISC #
###################
@register.filter(is_safe=True)
def filesizeformat(bytes_):
"""
@@ -885,7 +831,7 @@ def filesizeformat(bytes_):
try:
bytes_ = int(bytes_)
except (TypeError, ValueError, UnicodeDecodeError):
value = ngettext("%(size)d byte", "%(size)d bytes", 0) % {"size": 0}
value = ngettext("%(size)d byte", "%(size)d bytes", 0) % {'size': 0}
return avoid_wrapping(value)
def filesize_number_format(value):
@@ -902,7 +848,7 @@ def filesizeformat(bytes_):
bytes_ = -bytes_ # Allow formatting of negative numbers.
if bytes_ < KB:
value = ngettext("%(size)d byte", "%(size)d bytes", bytes_) % {"size": bytes_}
value = ngettext("%(size)d byte", "%(size)d bytes", bytes_) % {'size': bytes_}
elif bytes_ < MB:
value = gettext("%s KB") % filesize_number_format(bytes_ / KB)
elif bytes_ < GB:
@@ -920,7 +866,7 @@ def filesizeformat(bytes_):
@register.filter(is_safe=False)
def pluralize(value, arg="s"):
def pluralize(value, arg='s'):
"""
Return a plural suffix if the value is not 1, '1', or an object of
length 1. By default, use 's' as the suffix:
@@ -942,11 +888,11 @@ def pluralize(value, arg="s"):
* If value is 1, cand{{ value|pluralize:"y,ies" }} display "candy".
* If value is 2, cand{{ value|pluralize:"y,ies" }} display "candies".
"""
if "," not in arg:
arg = "," + arg
bits = arg.split(",")
if ',' not in arg:
arg = ',' + arg
bits = arg.split(',')
if len(bits) > 2:
return ""
return ''
singular_suffix, plural_suffix = bits[:2]
try:
@@ -958,7 +904,7 @@ def pluralize(value, arg="s"):
return singular_suffix if len(value) == 1 else plural_suffix
except TypeError: # len() of unsized object.
pass
return ""
return ''
@register.filter("phone2numeric", is_safe=True)
@@ -4,33 +4,21 @@ import sys
import warnings
from collections import namedtuple
from datetime import datetime
from itertools import cycle as itertools_cycle
from itertools import groupby
from itertools import cycle as itertools_cycle, groupby
from django.conf import settings
from django.utils import timezone
from django.utils.html import conditional_escape, escape, format_html
from django.utils.deprecation import RemovedInDjango40Warning
from django.utils.html import conditional_escape, format_html
from django.utils.lorem_ipsum import paragraphs, words
from django.utils.safestring import mark_safe
from .base import (
BLOCK_TAG_END,
BLOCK_TAG_START,
COMMENT_TAG_END,
COMMENT_TAG_START,
FILTER_SEPARATOR,
SINGLE_BRACE_END,
SINGLE_BRACE_START,
VARIABLE_ATTRIBUTE_SEPARATOR,
VARIABLE_TAG_END,
VARIABLE_TAG_START,
Node,
NodeList,
TemplateSyntaxError,
VariableDoesNotExist,
kwarg_re,
render_value_in_context,
token_kwargs,
BLOCK_TAG_END, BLOCK_TAG_START, COMMENT_TAG_END, COMMENT_TAG_START,
FILTER_SEPARATOR, SINGLE_BRACE_END, SINGLE_BRACE_START,
VARIABLE_ATTRIBUTE_SEPARATOR, VARIABLE_TAG_END, VARIABLE_TAG_START, Node,
NodeList, TemplateSyntaxError, VariableDoesNotExist, kwarg_re,
render_value_in_context, token_kwargs,
)
from .context import Context
from .defaultfilters import date
@@ -42,7 +30,6 @@ register = Library()
class AutoEscapeControlNode(Node):
"""Implement the actions of the autoescape tag."""
def __init__(self, setting, nodelist):
self.setting, self.nodelist = setting, nodelist
@@ -58,25 +45,18 @@ class AutoEscapeControlNode(Node):
class CommentNode(Node):
child_nodelists = ()
def render(self, context):
return ""
return ''
class CsrfTokenNode(Node):
child_nodelists = ()
def render(self, context):
csrf_token = context.get("csrf_token")
csrf_token = context.get('csrf_token')
if csrf_token:
if csrf_token == "NOTPROVIDED":
if csrf_token == 'NOTPROVIDED':
return format_html("")
else:
return format_html(
'<input type="hidden" name="csrfmiddlewaretoken" value="{}">',
csrf_token,
)
return format_html('<input type="hidden" name="csrfmiddlewaretoken" value="{}">', csrf_token)
else:
# It's very probable that the token is missing because of
# misconfiguration, so we raise a warning
@@ -86,7 +66,7 @@ class CsrfTokenNode(Node):
"did not provide the value. This is usually caused by not "
"using RequestContext."
)
return ""
return ''
class CycleNode(Node):
@@ -104,7 +84,7 @@ class CycleNode(Node):
if self.variable_name:
context.set_upward(self.variable_name, value)
if self.silent:
return ""
return ''
return render_value_in_context(value, context)
def reset(self, context):
@@ -116,15 +96,11 @@ class CycleNode(Node):
class DebugNode(Node):
def render(self, context):
if not settings.DEBUG:
return ""
from pprint import pformat
output = [escape(pformat(val)) for val in context]
output.append("\n\n")
output.append(escape(pformat(sys.modules)))
return "".join(output)
output = [pformat(val) for val in context]
output.append('\n\n')
output.append(pformat(sys.modules))
return ''.join(output)
class FilterNode(Node):
@@ -144,7 +120,7 @@ class FirstOfNode(Node):
self.asvar = asvar
def render(self, context):
first = ""
first = ''
for var in self.vars:
value = var.resolve(context, ignore_failures=True)
if value:
@@ -152,16 +128,14 @@ class FirstOfNode(Node):
break
if self.asvar:
context[self.asvar] = first
return ""
return ''
return first
class ForNode(Node):
child_nodelists = ("nodelist_loop", "nodelist_empty")
child_nodelists = ('nodelist_loop', 'nodelist_empty')
def __init__(
self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None
):
def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None):
self.loopvars, self.sequence = loopvars, sequence
self.is_reversed = is_reversed
self.nodelist_loop = nodelist_loop
@@ -171,25 +145,25 @@ class ForNode(Node):
self.nodelist_empty = nodelist_empty
def __repr__(self):
reversed_text = " reversed" if self.is_reversed else ""
return "<%s: for %s in %s, tail_len: %d%s>" % (
reversed_text = ' reversed' if self.is_reversed else ''
return '<%s: for %s in %s, tail_len: %d%s>' % (
self.__class__.__name__,
", ".join(self.loopvars),
', '.join(self.loopvars),
self.sequence,
len(self.nodelist_loop),
reversed_text,
)
def render(self, context):
if "forloop" in context:
parentloop = context["forloop"]
if 'forloop' in context:
parentloop = context['forloop']
else:
parentloop = {}
with context.push():
values = self.sequence.resolve(context, ignore_failures=True)
if values is None:
values = []
if not hasattr(values, "__len__"):
if not hasattr(values, '__len__'):
values = list(values)
len_values = len(values)
if len_values < 1:
@@ -201,17 +175,17 @@ class ForNode(Node):
unpack = num_loopvars > 1
# Create a forloop value in the context. We'll update counters on each
# iteration just below.
loop_dict = context["forloop"] = {"parentloop": parentloop}
loop_dict = context['forloop'] = {'parentloop': parentloop}
for i, item in enumerate(values):
# Shortcuts for current loop iteration number.
loop_dict["counter0"] = i
loop_dict["counter"] = i + 1
loop_dict['counter0'] = i
loop_dict['counter'] = i + 1
# Reverse counter iteration numbers.
loop_dict["revcounter"] = len_values - i
loop_dict["revcounter0"] = len_values - i - 1
loop_dict['revcounter'] = len_values - i
loop_dict['revcounter0'] = len_values - i - 1
# Boolean values designating first and last times through loop.
loop_dict["first"] = i == 0
loop_dict["last"] = i == len_values - 1
loop_dict['first'] = (i == 0)
loop_dict['last'] = (i == len_values - 1)
pop_context = False
if unpack:
@@ -224,9 +198,8 @@ class ForNode(Node):
# Check loop variable count before unpacking
if num_loopvars != len_item:
raise ValueError(
"Need {} values to unpack in for loop; got {}. ".format(
num_loopvars, len_item
),
"Need {} values to unpack in for loop; got {}. "
.format(num_loopvars, len_item),
)
unpacked_vars = dict(zip(self.loopvars, item))
pop_context = True
@@ -242,11 +215,11 @@ class ForNode(Node):
# the context ending up in an inconsistent state when other
# tags (e.g., include and with) push data to context.
context.pop()
return mark_safe("".join(nodelist))
return mark_safe(''.join(nodelist))
class IfChangedNode(Node):
child_nodelists = ("nodelist_true", "nodelist_false")
child_nodelists = ('nodelist_true', 'nodelist_false')
def __init__(self, nodelist_true, nodelist_false, *varlist):
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
@@ -261,9 +234,7 @@ class IfChangedNode(Node):
if self._varlist:
# Consider multiple parameters. This behaves like an OR evaluation
# of the multiple variables.
compare_to = [
var.resolve(context, ignore_failures=True) for var in self._varlist
]
compare_to = [var.resolve(context, ignore_failures=True) for var in self._varlist]
else:
# The "{% ifchanged %}" syntax (without any variables) compares
# the rendered output.
@@ -275,29 +246,48 @@ class IfChangedNode(Node):
return nodelist_true_output or self.nodelist_true.render(context)
elif self.nodelist_false:
return self.nodelist_false.render(context)
return ""
return ''
def _get_context_stack_frame(self, context):
# The Context object behaves like a stack where each template tag can
# create a new scope. Find the place where to store the state to detect
# changes.
if "forloop" in context:
# The Context object behaves like a stack where each template tag can create a new scope.
# Find the place where to store the state to detect changes.
if 'forloop' in context:
# Ifchanged is bound to the local for loop.
# When there is a loop-in-loop, the state is bound to the inner loop,
# so it resets when the outer loop continues.
return context["forloop"]
return context['forloop']
else:
# Using ifchanged outside loops. Effectively this is a no-op
# because the state is associated with 'self'.
# Using ifchanged outside loops. Effectively this is a no-op because the state is associated with 'self'.
return context.render_context
class IfEqualNode(Node):
# RemovedInDjango40Warning.
child_nodelists = ('nodelist_true', 'nodelist_false')
def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
self.var1, self.var2 = var1, var2
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
self.negate = negate
def __repr__(self):
return '<%s>' % self.__class__.__name__
def render(self, context):
val1 = self.var1.resolve(context, ignore_failures=True)
val2 = self.var2.resolve(context, ignore_failures=True)
if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
return self.nodelist_true.render(context)
return self.nodelist_false.render(context)
class IfNode(Node):
def __init__(self, conditions_nodelists):
self.conditions_nodelists = conditions_nodelists
def __repr__(self):
return "<%s>" % self.__class__.__name__
return '<%s>' % self.__class__.__name__
def __iter__(self):
for _, nodelist in self.conditions_nodelists:
@@ -310,18 +300,18 @@ class IfNode(Node):
def render(self, context):
for condition, nodelist in self.conditions_nodelists:
if condition is not None: # if / elif clause
if condition is not None: # if / elif clause
try:
match = condition.eval(context)
except VariableDoesNotExist:
match = None
else: # else clause
else: # else clause
match = True
if match:
return nodelist.render(context)
return ""
return ''
class LoremNode(Node):
@@ -333,16 +323,16 @@ class LoremNode(Node):
count = int(self.count.resolve(context))
except (ValueError, TypeError):
count = 1
if self.method == "w":
if self.method == 'w':
return words(count, common=self.common)
else:
paras = paragraphs(count, common=self.common)
if self.method == "p":
paras = ["<p>%s</p>" % p for p in paras]
return "\n\n".join(paras)
if self.method == 'p':
paras = ['<p>%s</p>' % p for p in paras]
return '\n\n'.join(paras)
GroupedResult = namedtuple("GroupedResult", ["grouper", "list"])
GroupedResult = namedtuple('GroupedResult', ['grouper', 'list'])
class RegroupNode(Node):
@@ -361,23 +351,20 @@ class RegroupNode(Node):
if obj_list is None:
# target variable wasn't found in context; fail silently.
context[self.var_name] = []
return ""
return ''
# List of dictionaries in the format:
# {'grouper': 'key', 'list': [list of contents]}.
context[self.var_name] = [
GroupedResult(grouper=key, list=list(val))
for key, val in groupby(
obj_list, lambda obj: self.resolve_expression(obj, context)
)
for key, val in
groupby(obj_list, lambda obj: self.resolve_expression(obj, context))
]
return ""
return ''
class LoadNode(Node):
child_nodelists = ()
def render(self, context):
return ""
return ''
class NowNode(Node):
@@ -391,7 +378,7 @@ class NowNode(Node):
if self.asvar:
context[self.asvar] = formatted
return ""
return ''
else:
return formatted
@@ -402,7 +389,7 @@ class ResetCycleNode(Node):
def render(self, context):
self.node.reset(context)
return ""
return ''
class SpacelessNode(Node):
@@ -411,50 +398,37 @@ class SpacelessNode(Node):
def render(self, context):
from django.utils.html import strip_spaces_between_tags
return strip_spaces_between_tags(self.nodelist.render(context).strip())
class TemplateTagNode(Node):
mapping = {
"openblock": BLOCK_TAG_START,
"closeblock": BLOCK_TAG_END,
"openvariable": VARIABLE_TAG_START,
"closevariable": VARIABLE_TAG_END,
"openbrace": SINGLE_BRACE_START,
"closebrace": SINGLE_BRACE_END,
"opencomment": COMMENT_TAG_START,
"closecomment": COMMENT_TAG_END,
'openblock': BLOCK_TAG_START,
'closeblock': BLOCK_TAG_END,
'openvariable': VARIABLE_TAG_START,
'closevariable': VARIABLE_TAG_END,
'openbrace': SINGLE_BRACE_START,
'closebrace': SINGLE_BRACE_END,
'opencomment': COMMENT_TAG_START,
'closecomment': COMMENT_TAG_END,
}
def __init__(self, tagtype):
self.tagtype = tagtype
def render(self, context):
return self.mapping.get(self.tagtype, "")
return self.mapping.get(self.tagtype, '')
class URLNode(Node):
child_nodelists = ()
def __init__(self, view_name, args, kwargs, asvar):
self.view_name = view_name
self.args = args
self.kwargs = kwargs
self.asvar = asvar
def __repr__(self):
return "<%s view_name='%s' args=%s kwargs=%s as=%s>" % (
self.__class__.__qualname__,
self.view_name,
repr(self.args),
repr(self.kwargs),
repr(self.asvar),
)
def render(self, context):
from django.urls import NoReverseMatch, reverse
args = [arg.resolve(context) for arg in self.args]
kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()}
view_name = self.view_name.resolve(context)
@@ -467,7 +441,7 @@ class URLNode(Node):
current_app = None
# Try to look up the URL. If it fails, raise NoReverseMatch unless the
# {% url ... as var %} construct is used, in which case return nothing.
url = ""
url = ''
try:
url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app)
except NoReverseMatch:
@@ -476,7 +450,7 @@ class URLNode(Node):
if self.asvar:
context[self.asvar] = url
return ""
return ''
else:
if context.autoescape:
url = conditional_escape(url)
@@ -504,7 +478,7 @@ class WidthRatioNode(Node):
max_value = self.max_expr.resolve(context)
max_width = int(self.max_width.resolve(context))
except VariableDoesNotExist:
return ""
return ''
except (ValueError, TypeError):
raise TemplateSyntaxError("widthratio final argument must be a number")
try:
@@ -513,13 +487,13 @@ class WidthRatioNode(Node):
ratio = (value / max_value) * max_width
result = str(round(ratio))
except ZeroDivisionError:
result = "0"
result = '0'
except (ValueError, TypeError, OverflowError):
result = ""
result = ''
if self.asvar:
context[self.asvar] = result
return ""
return ''
else:
return result
@@ -534,7 +508,7 @@ class WithNode(Node):
self.extra_context[name] = var
def __repr__(self):
return "<%s>" % self.__class__.__name__
return '<%s>' % self.__class__.__name__
def render(self, context):
values = {key: val.resolve(context) for key, val in self.extra_context.items()}
@@ -547,17 +521,16 @@ def autoescape(parser, token):
"""
Force autoescape behavior for this block.
"""
# token.split_contents() isn't useful here because this tag doesn't accept
# variable as arguments.
# token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
args = token.contents.split()
if len(args) != 2:
raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.")
arg = args[1]
if arg not in ("on", "off"):
if arg not in ('on', 'off'):
raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'")
nodelist = parser.parse(("endautoescape",))
nodelist = parser.parse(('endautoescape',))
parser.delete_first_token()
return AutoEscapeControlNode((arg == "on"), nodelist)
return AutoEscapeControlNode((arg == 'on'), nodelist)
@register.tag
@@ -565,7 +538,7 @@ def comment(parser, token):
"""
Ignore everything between ``{% comment %}`` and ``{% endcomment %}``.
"""
parser.skip_past("endcomment")
parser.skip_past('endcomment')
return CommentNode()
@@ -623,10 +596,8 @@ def cycle(parser, token):
if len(args) == 2:
# {% cycle foo %} case.
name = args[1]
if not hasattr(parser, "_named_cycle_nodes"):
raise TemplateSyntaxError(
"No named cycles in template. '%s' is not defined" % name
)
if not hasattr(parser, '_named_cycle_nodes'):
raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
if name not in parser._named_cycle_nodes:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._named_cycle_nodes[name]
@@ -637,10 +608,7 @@ def cycle(parser, token):
# {% cycle ... as foo [silent] %} case.
if args[-3] == "as":
if args[-1] != "silent":
raise TemplateSyntaxError(
"Only 'silent' flag is allowed after cycle's name, not '%s'."
% args[-1]
)
raise TemplateSyntaxError("Only 'silent' flag is allowed after cycle's name, not '%s'." % args[-1])
as_form = True
silent = True
args = args[:-1]
@@ -652,7 +620,7 @@ def cycle(parser, token):
name = args[-1]
values = [parser.compile_filter(arg) for arg in args[1:-2]]
node = CycleNode(values, name, silent=silent)
if not hasattr(parser, "_named_cycle_nodes"):
if not hasattr(parser, '_named_cycle_nodes'):
parser._named_cycle_nodes = {}
parser._named_cycle_nodes[name] = node
else:
@@ -682,7 +650,7 @@ def debug(parser, token):
return DebugNode()
@register.tag("filter")
@register.tag('filter')
def do_filter(parser, token):
"""
Filter the contents of the block through variable filters.
@@ -700,18 +668,14 @@ def do_filter(parser, token):
Instead, use the ``autoescape`` tag to manage autoescaping for blocks of
template code.
"""
# token.split_contents() isn't useful here because this tag doesn't accept
# variable as arguments.
# token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
_, rest = token.contents.split(None, 1)
filter_expr = parser.compile_filter("var|%s" % (rest))
for func, unused in filter_expr.filters:
filter_name = getattr(func, "_filter_name", None)
if filter_name in ("escape", "safe"):
raise TemplateSyntaxError(
'"filter %s" is not permitted. Use the "autoescape" tag instead.'
% filter_name
)
nodelist = parser.parse(("endfilter",))
filter_name = getattr(func, '_filter_name', None)
if filter_name in ('escape', 'safe'):
raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % filter_name)
nodelist = parser.parse(('endfilter',))
parser.delete_first_token()
return FilterNode(filter_expr, nodelist)
@@ -759,13 +723,13 @@ def firstof(parser, token):
if not bits:
raise TemplateSyntaxError("'firstof' statement requires at least one argument")
if len(bits) >= 2 and bits[-2] == "as":
if len(bits) >= 2 and bits[-2] == 'as':
asvar = bits[-1]
bits = bits[:-2]
return FirstOfNode([parser.compile_filter(bit) for bit in bits], asvar)
@register.tag("for")
@register.tag('for')
def do_for(parser, token):
"""
Loop over each item in an array.
@@ -830,42 +794,89 @@ def do_for(parser, token):
"""
bits = token.split_contents()
if len(bits) < 4:
raise TemplateSyntaxError(
"'for' statements should have at least four words: %s" % token.contents
)
raise TemplateSyntaxError("'for' statements should have at least four"
" words: %s" % token.contents)
is_reversed = bits[-1] == "reversed"
is_reversed = bits[-1] == 'reversed'
in_index = -3 if is_reversed else -2
if bits[in_index] != "in":
raise TemplateSyntaxError(
"'for' statements should use the format"
" 'for x in y': %s" % token.contents
)
if bits[in_index] != 'in':
raise TemplateSyntaxError("'for' statements should use the format"
" 'for x in y': %s" % token.contents)
invalid_chars = frozenset((" ", '"', "'", FILTER_SEPARATOR))
loopvars = re.split(r" *, *", " ".join(bits[1:in_index]))
invalid_chars = frozenset((' ', '"', "'", FILTER_SEPARATOR))
loopvars = re.split(r' *, *', ' '.join(bits[1:in_index]))
for var in loopvars:
if not var or not invalid_chars.isdisjoint(var):
raise TemplateSyntaxError(
"'for' tag received an invalid argument: %s" % token.contents
)
raise TemplateSyntaxError("'for' tag received an invalid argument:"
" %s" % token.contents)
sequence = parser.compile_filter(bits[in_index + 1])
nodelist_loop = parser.parse(
(
"empty",
"endfor",
)
)
nodelist_loop = parser.parse(('empty', 'endfor',))
token = parser.next_token()
if token.contents == "empty":
nodelist_empty = parser.parse(("endfor",))
if token.contents == 'empty':
nodelist_empty = parser.parse(('endfor',))
parser.delete_first_token()
else:
nodelist_empty = None
return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty)
def do_ifequal(parser, token, negate):
# RemovedInDjango40Warning.
bits = list(token.split_contents())
if len(bits) != 3:
raise TemplateSyntaxError("%r takes two arguments" % bits[0])
end_tag = 'end' + bits[0]
nodelist_true = parser.parse(('else', end_tag))
token = parser.next_token()
if token.contents == 'else':
nodelist_false = parser.parse((end_tag,))
parser.delete_first_token()
else:
nodelist_false = NodeList()
val1 = parser.compile_filter(bits[1])
val2 = parser.compile_filter(bits[2])
return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
@register.tag
def ifequal(parser, token):
"""
Output the contents of the block if the two arguments equal each other.
Examples::
{% ifequal user.id comment.user_id %}
...
{% endifequal %}
{% ifnotequal user.id comment.user_id %}
...
{% else %}
...
{% endifnotequal %}
"""
warnings.warn(
'The {% ifequal %} template tag is deprecated in favor of {% if %}.',
RemovedInDjango40Warning,
)
return do_ifequal(parser, token, False)
@register.tag
def ifnotequal(parser, token):
"""
Output the contents of the block if the two arguments are not equal.
See ifequal.
"""
warnings.warn(
'The {% ifnotequal %} template tag is deprecated in favor of '
'{% if %}.',
RemovedInDjango40Warning,
)
return do_ifequal(parser, token, True)
class TemplateLiteral(Literal):
def __init__(self, value, text):
self.value = value
@@ -889,7 +900,7 @@ class TemplateIfParser(IfParser):
return TemplateLiteral(self.template_parser.compile_filter(value), value)
@register.tag("if")
@register.tag('if')
def do_if(parser, token):
"""
Evaluate a variable, and if that variable is "true" (i.e., exists, is not
@@ -951,31 +962,27 @@ def do_if(parser, token):
# {% if ... %}
bits = token.split_contents()[1:]
condition = TemplateIfParser(parser, bits).parse()
nodelist = parser.parse(("elif", "else", "endif"))
nodelist = parser.parse(('elif', 'else', 'endif'))
conditions_nodelists = [(condition, nodelist)]
token = parser.next_token()
# {% elif ... %} (repeatable)
while token.contents.startswith("elif"):
while token.contents.startswith('elif'):
bits = token.split_contents()[1:]
condition = TemplateIfParser(parser, bits).parse()
nodelist = parser.parse(("elif", "else", "endif"))
nodelist = parser.parse(('elif', 'else', 'endif'))
conditions_nodelists.append((condition, nodelist))
token = parser.next_token()
# {% else %} (optional)
if token.contents == "else":
nodelist = parser.parse(("endif",))
if token.contents == 'else':
nodelist = parser.parse(('endif',))
conditions_nodelists.append((None, nodelist))
token = parser.next_token()
# {% endif %}
if token.contents != "endif":
raise TemplateSyntaxError(
'Malformed template tag at line {}: "{}"'.format(
token.lineno, token.contents
)
)
if token.contents != 'endif':
raise TemplateSyntaxError('Malformed template tag at line {}: "{}"'.format(token.lineno, token.contents))
return IfNode(conditions_nodelists)
@@ -1011,10 +1018,10 @@ def ifchanged(parser, token):
{% endfor %}
"""
bits = token.split_contents()
nodelist_true = parser.parse(("else", "endifchanged"))
nodelist_true = parser.parse(('else', 'endifchanged'))
token = parser.next_token()
if token.contents == "else":
nodelist_false = parser.parse(("endifchanged",))
if token.contents == 'else':
nodelist_false = parser.parse(('endifchanged',))
parser.delete_first_token()
else:
nodelist_false = NodeList()
@@ -1027,10 +1034,8 @@ def find_library(parser, name):
return parser.libraries[name]
except KeyError:
raise TemplateSyntaxError(
"'%s' is not a registered tag library. Must be one of:\n%s"
% (
name,
"\n".join(sorted(parser.libraries)),
"'%s' is not a registered tag library. Must be one of:\n%s" % (
name, "\n".join(sorted(parser.libraries)),
),
)
@@ -1050,10 +1055,8 @@ def load_from_library(library, label, names):
subset.filters[name] = library.filters[name]
if found is False:
raise TemplateSyntaxError(
"'%s' is not a valid tag or filter in tag library '%s'"
% (
name,
label,
"'%s' is not a valid tag or filter in tag library '%s'" % (
name, label,
),
)
return subset
@@ -1074,8 +1077,7 @@ def load(parser, token):
{% load byline from news %}
"""
# token.split_contents() isn't useful here because this tag doesn't accept
# variable as arguments.
# token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
bits = token.contents.split()
if len(bits) >= 4 and bits[-2] == "from":
# from syntax is used; load individual tags from the library
@@ -1119,19 +1121,19 @@ def lorem(parser, token):
bits = list(token.split_contents())
tagname = bits[0]
# Random bit
common = bits[-1] != "random"
common = bits[-1] != 'random'
if not common:
bits.pop()
# Method bit
if bits[-1] in ("w", "p", "b"):
if bits[-1] in ('w', 'p', 'b'):
method = bits.pop()
else:
method = "b"
method = 'b'
# Count bit
if len(bits) > 1:
count = bits.pop()
else:
count = "1"
count = '1'
count = parser.compile_filter(count)
if len(bits) != 1:
raise TemplateSyntaxError("Incorrect format for %r tag" % tagname)
@@ -1152,7 +1154,7 @@ def now(parser, token):
"""
bits = token.split_contents()
asvar = None
if len(bits) == 4 and bits[-2] == "as":
if len(bits) == 4 and bits[-2] == 'as':
asvar = bits[-1]
bits = bits[:-2]
if len(bits) != 2:
@@ -1212,10 +1214,11 @@ def regroup(parser, token):
if len(bits) != 6:
raise TemplateSyntaxError("'regroup' tag takes five arguments")
target = parser.compile_filter(bits[1])
if bits[2] != "by":
if bits[2] != 'by':
raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
if bits[4] != "as":
raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must be 'as'")
if bits[4] != 'as':
raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
" be 'as'")
var_name = bits[5]
# RegroupNode will take each item in 'target', put it in the context under
# 'var_name', evaluate 'var_name'.'expression' in the current context, and
@@ -1223,9 +1226,9 @@ def regroup(parser, token):
# save the final result in the context under 'var_name', thus clearing the
# temporary values. This hack is necessary because the template engine
# doesn't provide a context-aware equivalent of Python's getattr.
expression = parser.compile_filter(
var_name + VARIABLE_ATTRIBUTE_SEPARATOR + bits[3]
)
expression = parser.compile_filter(var_name +
VARIABLE_ATTRIBUTE_SEPARATOR +
bits[3])
return RegroupNode(target, expression, var_name)
@@ -1281,7 +1284,7 @@ def spaceless(parser, token):
</strong>
{% endspaceless %}
"""
nodelist = parser.parse(("endspaceless",))
nodelist = parser.parse(('endspaceless',))
parser.delete_first_token()
return SpacelessNode(nodelist)
@@ -1309,17 +1312,15 @@ def templatetag(parser, token):
``closecomment`` ``#}``
================== =======
"""
# token.split_contents() isn't useful here because this tag doesn't accept
# variable as arguments.
# token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
bits = token.contents.split()
if len(bits) != 2:
raise TemplateSyntaxError("'templatetag' statement takes one argument")
tag = bits[1]
if tag not in TemplateTagNode.mapping:
raise TemplateSyntaxError(
"Invalid templatetag argument: '%s'."
" Must be one of: %s" % (tag, list(TemplateTagNode.mapping))
)
raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
" Must be one of: %s" %
(tag, list(TemplateTagNode.mapping)))
return TemplateTagNode(tag)
@@ -1367,15 +1368,13 @@ def url(parser, token):
"""
bits = token.split_contents()
if len(bits) < 2:
raise TemplateSyntaxError(
"'%s' takes at least one argument, a URL pattern name." % bits[0]
)
raise TemplateSyntaxError("'%s' takes at least one argument, a URL pattern name." % bits[0])
viewname = parser.compile_filter(bits[1])
args = []
kwargs = {}
asvar = None
bits = bits[2:]
if len(bits) >= 2 and bits[-2] == "as":
if len(bits) >= 2 and bits[-2] == 'as':
asvar = bits[-1]
bits = bits[:-2]
@@ -1410,7 +1409,7 @@ def verbatim(parser, token):
...
{% endverbatim myblock %}
"""
nodelist = parser.parse(("endverbatim",))
nodelist = parser.parse(('endverbatim',))
parser.delete_first_token()
return VerbatimNode(nodelist.render(Context()))
@@ -1442,22 +1441,18 @@ def widthratio(parser, token):
asvar = None
elif len(bits) == 6:
tag, this_value_expr, max_value_expr, max_width, as_, asvar = bits
if as_ != "as":
raise TemplateSyntaxError(
"Invalid syntax in widthratio tag. Expecting 'as' keyword"
)
if as_ != 'as':
raise TemplateSyntaxError("Invalid syntax in widthratio tag. Expecting 'as' keyword")
else:
raise TemplateSyntaxError("widthratio takes at least three arguments")
return WidthRatioNode(
parser.compile_filter(this_value_expr),
parser.compile_filter(max_value_expr),
parser.compile_filter(max_width),
asvar=asvar,
)
return WidthRatioNode(parser.compile_filter(this_value_expr),
parser.compile_filter(max_value_expr),
parser.compile_filter(max_width),
asvar=asvar)
@register.tag("with")
@register.tag('with')
def do_with(parser, token):
"""
Add one or more values to the context (inside of this block) for caching
@@ -1482,13 +1477,11 @@ def do_with(parser, token):
remaining_bits = bits[1:]
extra_context = token_kwargs(remaining_bits, parser, support_legacy=True)
if not extra_context:
raise TemplateSyntaxError(
"%r expected at least one variable assignment" % bits[0]
)
raise TemplateSyntaxError("%r expected at least one variable "
"assignment" % bits[0])
if remaining_bits:
raise TemplateSyntaxError(
"%r received an invalid token: %r" % (bits[0], remaining_bits[0])
)
nodelist = parser.parse(("endwith",))
raise TemplateSyntaxError("%r received an invalid token: %r" %
(bits[0], remaining_bits[0]))
nodelist = parser.parse(('endwith',))
parser.delete_first_token()
return WithNode(None, None, nodelist, extra_context=extra_context)
@@ -12,39 +12,28 @@ from .library import import_library
class Engine:
default_builtins = [
"django.template.defaulttags",
"django.template.defaultfilters",
"django.template.loader_tags",
'django.template.defaulttags',
'django.template.defaultfilters',
'django.template.loader_tags',
]
def __init__(
self,
dirs=None,
app_dirs=False,
context_processors=None,
debug=False,
loaders=None,
string_if_invalid="",
file_charset="utf-8",
libraries=None,
builtins=None,
autoescape=True,
):
def __init__(self, dirs=None, app_dirs=False, context_processors=None,
debug=False, loaders=None, string_if_invalid='',
file_charset='utf-8', libraries=None, builtins=None, autoescape=True):
if dirs is None:
dirs = []
if context_processors is None:
context_processors = []
if loaders is None:
loaders = ["django.template.loaders.filesystem.Loader"]
loaders = ['django.template.loaders.filesystem.Loader']
if app_dirs:
loaders += ["django.template.loaders.app_directories.Loader"]
loaders += ['django.template.loaders.app_directories.Loader']
if not debug:
loaders = [("django.template.loaders.cached.Loader", loaders)]
loaders = [('django.template.loaders.cached.Loader', loaders)]
else:
if app_dirs:
raise ImproperlyConfigured(
"app_dirs must not be set when loaders is defined."
)
"app_dirs must not be set when loaders is defined.")
if libraries is None:
libraries = {}
if builtins is None:
@@ -63,26 +52,6 @@ class Engine:
self.builtins = self.default_builtins + builtins
self.template_builtins = self.get_template_builtins(self.builtins)
def __repr__(self):
return (
"<%s:%s app_dirs=%s%s debug=%s loaders=%s string_if_invalid=%s "
"file_charset=%s%s%s autoescape=%s>"
) % (
self.__class__.__qualname__,
"" if not self.dirs else " dirs=%s" % repr(self.dirs),
self.app_dirs,
""
if not self.context_processors
else " context_processors=%s" % repr(self.context_processors),
self.debug,
repr(self.loaders),
repr(self.string_if_invalid),
repr(self.file_charset),
"" if not self.libraries else " libraries=%s" % repr(self.libraries),
"" if not self.builtins else " builtins=%s" % repr(self.builtins),
repr(self.autoescape),
)
@staticmethod
@functools.lru_cache()
def get_default():
@@ -104,11 +73,10 @@ class Engine:
# local imports are required to avoid import loops.
from django.template import engines
from django.template.backends.django import DjangoTemplates
for engine in engines.all():
if isinstance(engine, DjangoTemplates):
return engine.engine
raise ImproperlyConfigured("No DjangoTemplates backend is configured.")
raise ImproperlyConfigured('No DjangoTemplates backend is configured.')
@cached_property
def template_context_processors(self):
@@ -148,8 +116,7 @@ class Engine:
return loader_class(self, *args)
else:
raise ImproperlyConfigured(
"Invalid value in template loaders configuration: %r" % loader
)
"Invalid value in template loaders configuration: %r" % loader)
def find_template(self, name, dirs=None, skip=None):
tried = []
@@ -174,7 +141,7 @@ class Engine:
handling template inheritance recursively.
"""
template, origin = self.find_template(template_name)
if not hasattr(template, "render"):
if not hasattr(template, 'render'):
# template needs to be compiled
template = Template(template, origin, template_name, engine=self)
return template
@@ -210,4 +177,4 @@ class Engine:
not_found.append(exc.args[0])
continue
# If we get here, none of the templates could be loaded
raise TemplateDoesNotExist(", ".join(not_found))
raise TemplateDoesNotExist(', '.join(not_found))
@@ -24,7 +24,6 @@ class TemplateDoesNotExist(Exception):
encapsulate multiple exceptions when loading templates from multiple
engines.
"""
def __init__(self, msg, tried=None, backend=None, chain=None):
self.backend = backend
if tried is None:
@@ -40,5 +39,4 @@ class TemplateSyntaxError(Exception):
"""
The exception used for syntax errors during parsing or rendering.
"""
pass
@@ -20,7 +20,6 @@ class Library:
The filter, simple_tag, and inclusion_tag methods provide a convenient
way to register callables as tags.
"""
def __init__(self):
self.filters = {}
self.tags = {}
@@ -37,7 +36,6 @@ class Library:
# @register.tag('somename') or @register.tag(name='somename')
def dec(func):
return self.tag(name, func)
return dec
elif name is not None and compile_function is not None:
# register.tag('somename', somefunc)
@@ -45,8 +43,8 @@ class Library:
return compile_function
else:
raise ValueError(
"Unsupported arguments to Library.tag: (%r, %r)"
% (name, compile_function),
"Unsupported arguments to Library.tag: (%r, %r)" %
(name, compile_function),
)
def tag_function(self, func):
@@ -65,7 +63,6 @@ class Library:
# @register.filter()
def dec(func):
return self.filter_function(func, **flags)
return dec
elif name is not None and filter_func is None:
if callable(name):
@@ -75,12 +72,11 @@ class Library:
# @register.filter('somename') or @register.filter(name='somename')
def dec(func):
return self.filter(name, func, **flags)
return dec
elif name is not None and filter_func is not None:
# register.filter('somename', somefunc)
self.filters[name] = filter_func
for attr in ("expects_localtime", "is_safe", "needs_autoescape"):
for attr in ('expects_localtime', 'is_safe', 'needs_autoescape'):
if attr in flags:
value = flags[attr]
# set the flag on the filter for FilterExpression.resolve
@@ -93,8 +89,8 @@ class Library:
return filter_func
else:
raise ValueError(
"Unsupported arguments to Library.filter: (%r, %r)"
% (name, filter_func),
"Unsupported arguments to Library.filter: (%r, %r)" %
(name, filter_func),
)
def filter_function(self, func, **flags):
@@ -109,40 +105,22 @@ class Library:
def hello(*args, **kwargs):
return 'world'
"""
def dec(func):
(
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
_,
) = getfullargspec(unwrap(func))
function_name = name or getattr(func, "_decorated_function", func).__name__
params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(unwrap(func))
function_name = (name or getattr(func, '_decorated_function', func).__name__)
@functools.wraps(func)
def compile_func(parser, token):
bits = token.split_contents()[1:]
target_var = None
if len(bits) >= 2 and bits[-2] == "as":
if len(bits) >= 2 and bits[-2] == 'as':
target_var = bits[-1]
bits = bits[:-2]
args, kwargs = parse_bits(
parser,
bits,
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
takes_context,
function_name,
parser, bits, params, varargs, varkw, defaults,
kwonly, kwonly_defaults, takes_context, function_name,
)
return SimpleNode(func, takes_context, args, kwargs, target_var)
self.tag(function_name, compile_func)
return func
@@ -164,45 +142,22 @@ class Library:
choices = poll.choice_set.all()
return {'choices': choices}
"""
def dec(func):
(
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
_,
) = getfullargspec(unwrap(func))
function_name = name or getattr(func, "_decorated_function", func).__name__
params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(unwrap(func))
function_name = (name or getattr(func, '_decorated_function', func).__name__)
@functools.wraps(func)
def compile_func(parser, token):
bits = token.split_contents()[1:]
args, kwargs = parse_bits(
parser,
bits,
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
takes_context,
function_name,
parser, bits, params, varargs, varkw, defaults,
kwonly, kwonly_defaults, takes_context, function_name,
)
return InclusionNode(
func,
takes_context,
args,
kwargs,
filename,
func, takes_context, args, kwargs, filename,
)
self.tag(function_name, compile_func)
return func
return dec
@@ -212,7 +167,6 @@ class TagHelperNode(Node):
Manages the positional and keyword arguments to be passed to the decorated
function.
"""
def __init__(self, func, takes_context, args, kwargs):
self.func = func
self.takes_context = takes_context
@@ -228,7 +182,6 @@ class TagHelperNode(Node):
class SimpleNode(TagHelperNode):
child_nodelists = ()
def __init__(self, func, takes_context, args, kwargs, target_var):
super().__init__(func, takes_context, args, kwargs)
@@ -239,13 +192,14 @@ class SimpleNode(TagHelperNode):
output = self.func(*resolved_args, **resolved_kwargs)
if self.target_var is not None:
context[self.target_var] = output
return ""
return ''
if context.autoescape:
output = conditional_escape(output)
return output
class InclusionNode(TagHelperNode):
def __init__(self, func, takes_context, args, kwargs, filename):
super().__init__(func, takes_context, args, kwargs)
self.filename = filename
@@ -263,7 +217,7 @@ class InclusionNode(TagHelperNode):
if t is None:
if isinstance(self.filename, Template):
t = self.filename
elif isinstance(getattr(self.filename, "template", None), Template):
elif isinstance(getattr(self.filename, 'template', None), Template):
t = self.filename.template
elif not isinstance(self.filename, str) and is_iterable(self.filename):
t = context.template.engine.select_template(self.filename)
@@ -274,42 +228,32 @@ class InclusionNode(TagHelperNode):
# Copy across the CSRF token, if present, because inclusion tags are
# often used for forms, and we need instructions for using CSRF
# protection to be as simple as possible.
csrf_token = context.get("csrf_token")
csrf_token = context.get('csrf_token')
if csrf_token is not None:
new_context["csrf_token"] = csrf_token
new_context['csrf_token'] = csrf_token
return t.render(new_context)
def parse_bits(
parser,
bits,
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
takes_context,
name,
):
def parse_bits(parser, bits, params, varargs, varkw, defaults,
kwonly, kwonly_defaults, takes_context, name):
"""
Parse bits for template tag helpers simple_tag and inclusion_tag, in
particular by detecting syntax errors and by extracting positional and
keyword arguments.
"""
if takes_context:
if params and params[0] == "context":
if params[0] == 'context':
params = params[1:]
else:
raise TemplateSyntaxError(
"'%s' is decorated with takes_context=True so it must "
"have a first argument of 'context'" % name
)
"have a first argument of 'context'" % name)
args = []
kwargs = {}
unhandled_params = list(params)
unhandled_kwargs = [
kwarg for kwarg in kwonly if not kwonly_defaults or kwarg not in kwonly_defaults
kwarg for kwarg in kwonly
if not kwonly_defaults or kwarg not in kwonly_defaults
]
for bit in bits:
# First we try to extract a potential kwarg from the bit
@@ -320,14 +264,13 @@ def parse_bits(
if param not in params and param not in kwonly and varkw is None:
# An unexpected keyword argument was supplied
raise TemplateSyntaxError(
"'%s' received unexpected keyword argument '%s'" % (name, param)
)
"'%s' received unexpected keyword argument '%s'" %
(name, param))
elif param in kwargs:
# The keyword argument has already been supplied once
raise TemplateSyntaxError(
"'%s' received multiple values for keyword argument '%s'"
% (name, param)
)
"'%s' received multiple values for keyword argument '%s'" %
(name, param))
else:
# All good, record the keyword argument
kwargs[str(param)] = value
@@ -342,8 +285,7 @@ def parse_bits(
if kwargs:
raise TemplateSyntaxError(
"'%s' received some positional argument(s) after some "
"keyword argument(s)" % name
)
"keyword argument(s)" % name)
else:
# Record the positional argument
args.append(parser.compile_filter(bit))
@@ -353,18 +295,17 @@ def parse_bits(
except IndexError:
if varargs is None:
raise TemplateSyntaxError(
"'%s' received too many positional arguments" % name
)
"'%s' received too many positional arguments" %
name)
if defaults is not None:
# Consider the last n params handled, where n is the
# number of defaults.
unhandled_params = unhandled_params[: -len(defaults)]
unhandled_params = unhandled_params[:-len(defaults)]
if unhandled_params or unhandled_kwargs:
# Some positional arguments were not supplied
raise TemplateSyntaxError(
"'%s' did not receive value(s) for the argument(s): %s"
% (name, ", ".join("'%s'" % p for p in unhandled_params + unhandled_kwargs))
)
"'%s' did not receive value(s) for the argument(s): %s" %
(name, ", ".join("'%s'" % p for p in unhandled_params + unhandled_kwargs)))
return args, kwargs
@@ -29,9 +29,9 @@ def select_template(template_name_list, using=None):
"""
if isinstance(template_name_list, str):
raise TypeError(
"select_template() takes an iterable of template names but got a "
"string: %r. Use get_template() if you want to load a single "
"template by name." % template_name_list
'select_template() takes an iterable of template names but got a '
'string: %r. Use get_template() if you want to load a single '
'template by name.' % template_name_list
)
chain = []
@@ -44,7 +44,7 @@ def select_template(template_name_list, using=None):
chain.append(e)
if template_name_list:
raise TemplateDoesNotExist(", ".join(template_name_list), chain=chain)
raise TemplateDoesNotExist(', '.join(template_name_list), chain=chain)
else:
raise TemplateDoesNotExist("No template names provided")
@@ -3,12 +3,14 @@ from collections import defaultdict
from django.utils.safestring import mark_safe
from .base import Node, Template, TemplateSyntaxError, TextNode, Variable, token_kwargs
from .base import (
Node, Template, TemplateSyntaxError, TextNode, Variable, token_kwargs,
)
from .library import Library
register = Library()
BLOCK_CONTEXT_KEY = "block_context"
BLOCK_CONTEXT_KEY = 'block_context'
class BlockContext:
@@ -16,9 +18,6 @@ class BlockContext:
# Dictionary of FIFO queues.
self.blocks = defaultdict(list)
def __repr__(self):
return f"<{self.__class__.__qualname__}: blocks={self.blocks!r}>"
def add_blocks(self, blocks):
for name, block in blocks.items():
self.blocks[name].insert(0, block)
@@ -50,7 +49,7 @@ class BlockNode(Node):
block_context = context.render_context.get(BLOCK_CONTEXT_KEY)
with context.push():
if block_context is None:
context["block"] = self
context['block'] = self
result = self.nodelist.render(context)
else:
push = block = block_context.pop(self.name)
@@ -59,30 +58,28 @@ class BlockNode(Node):
# Create new block so we can store context without thread-safety issues.
block = type(self)(block.name, block.nodelist)
block.context = context
context["block"] = block
context['block'] = block
result = block.nodelist.render(context)
if push is not None:
block_context.push(self.name, push)
return result
def super(self):
if not hasattr(self, "context"):
if not hasattr(self, 'context'):
raise TemplateSyntaxError(
"'%s' object has no attribute 'context'. Did you use "
"{{ block.super }} in a base template?" % self.__class__.__name__
)
render_context = self.context.render_context
if (
BLOCK_CONTEXT_KEY in render_context
and render_context[BLOCK_CONTEXT_KEY].get_block(self.name) is not None
):
if (BLOCK_CONTEXT_KEY in render_context and
render_context[BLOCK_CONTEXT_KEY].get_block(self.name) is not None):
return mark_safe(self.render(self.context))
return ""
return ''
class ExtendsNode(Node):
must_be_first = True
context_key = "extends_context"
context_key = 'extends_context'
def __init__(self, nodelist, parent_name, template_dirs=None):
self.nodelist = nodelist
@@ -91,7 +88,7 @@ class ExtendsNode(Node):
self.blocks = {n.name: n for n in nodelist.get_nodes_by_type(BlockNode)}
def __repr__(self):
return "<%s: extends %s>" % (self.__class__.__name__, self.parent_name.token)
return '<%s: extends %s>' % (self.__class__.__name__, self.parent_name.token)
def find_template(self, template_name, context):
"""
@@ -101,12 +98,10 @@ class ExtendsNode(Node):
without extending the same template twice.
"""
history = context.render_context.setdefault(
self.context_key,
[self.origin],
self.context_key, [self.origin],
)
template, origin = context.template.engine.find_template(
template_name,
skip=history,
template_name, skip=history,
)
history.append(origin)
return template
@@ -115,15 +110,15 @@ class ExtendsNode(Node):
parent = self.parent_name.resolve(context)
if not parent:
error_msg = "Invalid template name in 'extends' tag: %r." % parent
if self.parent_name.filters or isinstance(self.parent_name.var, Variable):
error_msg += (
" Got this from the '%s' variable." % self.parent_name.token
)
if self.parent_name.filters or\
isinstance(self.parent_name.var, Variable):
error_msg += " Got this from the '%s' variable." %\
self.parent_name.token
raise TemplateSyntaxError(error_msg)
if isinstance(parent, Template):
# parent is a django.template.Template
return parent
if isinstance(getattr(parent, "template", None), Template):
if isinstance(getattr(parent, 'template', None), Template):
# parent is a django.template.backends.django.Template
return parent.template
return self.find_template(parent, context)
@@ -144,10 +139,8 @@ class ExtendsNode(Node):
# The ExtendsNode has to be the first non-text node.
if not isinstance(node, TextNode):
if not isinstance(node, ExtendsNode):
blocks = {
n.name: n
for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)
}
blocks = {n.name: n for n in
compiled_parent.nodelist.get_nodes_by_type(BlockNode)}
block_context.add_blocks(blocks)
break
@@ -158,19 +151,14 @@ class ExtendsNode(Node):
class IncludeNode(Node):
context_key = "__include_context"
context_key = '__include_context'
def __init__(
self, template, *args, extra_context=None, isolated_context=False, **kwargs
):
def __init__(self, template, *args, extra_context=None, isolated_context=False, **kwargs):
self.template = template
self.extra_context = extra_context or {}
self.isolated_context = isolated_context
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<{self.__class__.__qualname__}: template={self.template!r}>"
def render(self, context):
"""
Render the specified template and context. Cache the template object
@@ -179,16 +167,14 @@ class IncludeNode(Node):
"""
template = self.template.resolve(context)
# Does this quack like a Template?
if not callable(getattr(template, "render", None)):
if not callable(getattr(template, 'render', None)):
# If not, try the cache and select_template().
template_name = template or ()
if isinstance(template_name, str):
template_name = (
construct_relative_path(
self.origin.template_name,
template_name,
),
)
template_name = (construct_relative_path(
self.origin.template_name,
template_name,
),)
else:
template_name = tuple(template_name)
cache = context.render_context.dicts[0].setdefault(self, {})
@@ -197,10 +183,11 @@ class IncludeNode(Node):
template = context.template.engine.select_template(template_name)
cache[template_name] = template
# Use the base.Template of a backends.django.Template.
elif hasattr(template, "template"):
elif hasattr(template, 'template'):
template = template.template
values = {
name: var.resolve(context) for name, var in self.extra_context.items()
name: var.resolve(context)
for name, var in self.extra_context.items()
}
if self.isolated_context:
return template.render(context.new(values))
@@ -208,13 +195,12 @@ class IncludeNode(Node):
return template.render(context)
@register.tag("block")
@register.tag('block')
def do_block(parser, token):
"""
Define a block that can be overridden by child templates.
"""
# token.split_contents() isn't useful here because this tag doesn't accept
# variable as arguments.
# token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
bits = token.contents.split()
if len(bits) != 2:
raise TemplateSyntaxError("'%s' tag takes only one argument" % bits[0])
@@ -223,19 +209,17 @@ def do_block(parser, token):
# check for duplication.
try:
if block_name in parser.__loaded_blocks:
raise TemplateSyntaxError(
"'%s' tag with name '%s' appears more than once" % (bits[0], block_name)
)
raise TemplateSyntaxError("'%s' tag with name '%s' appears more than once" % (bits[0], block_name))
parser.__loaded_blocks.append(block_name)
except AttributeError: # parser.__loaded_blocks isn't a list yet
parser.__loaded_blocks = [block_name]
nodelist = parser.parse(("endblock",))
nodelist = parser.parse(('endblock',))
# This check is kept for backwards-compatibility. See #3100.
endblock = parser.next_token()
acceptable_endblocks = ("endblock", "endblock %s" % block_name)
acceptable_endblocks = ('endblock', 'endblock %s' % block_name)
if endblock.contents not in acceptable_endblocks:
parser.invalid_block_tag(endblock, "endblock", acceptable_endblocks)
parser.invalid_block_tag(endblock, 'endblock', acceptable_endblocks)
return BlockNode(block_name, nodelist)
@@ -245,27 +229,28 @@ def construct_relative_path(current_template_name, relative_name):
Convert a relative path (starting with './' or '../') to the full template
name based on the current_template_name.
"""
has_quotes = (relative_name.startswith('"') and relative_name.endswith('"')) or (
relative_name.startswith("'") and relative_name.endswith("'")
has_quotes = (
(relative_name.startswith('"') and relative_name.endswith('"')) or
(relative_name.startswith("'") and relative_name.endswith("'"))
)
new_name = relative_name.strip("'\"")
if not new_name.startswith(("./", "../")):
new_name = relative_name.strip('\'"')
if not new_name.startswith(('./', '../')):
# relative_name is a variable or a literal that doesn't contain a
# relative path.
return relative_name
new_name = posixpath.normpath(
posixpath.join(
posixpath.dirname(current_template_name.lstrip("/")),
posixpath.dirname(current_template_name.lstrip('/')),
new_name,
)
)
if new_name.startswith("../"):
if new_name.startswith('../'):
raise TemplateSyntaxError(
"The relative path '%s' points outside the file hierarchy that "
"template '%s' is in." % (relative_name, current_template_name)
)
if current_template_name.lstrip("/") == new_name:
if current_template_name.lstrip('/') == new_name:
raise TemplateSyntaxError(
"The relative path '%s' was translated to template name '%s', the "
"same template in which the tag appears."
@@ -274,7 +259,7 @@ def construct_relative_path(current_template_name, relative_name):
return f'"{new_name}"' if has_quotes else new_name
@register.tag("extends")
@register.tag('extends')
def do_extends(parser, token):
"""
Signal that this template extends a parent template.
@@ -292,13 +277,11 @@ def do_extends(parser, token):
parent_name = parser.compile_filter(bits[1])
nodelist = parser.parse()
if nodelist.get_nodes_by_type(ExtendsNode):
raise TemplateSyntaxError(
"'%s' cannot appear more than once in the same template" % bits[0]
)
raise TemplateSyntaxError("'%s' cannot appear more than once in the same template" % bits[0])
return ExtendsNode(nodelist, parent_name)
@register.tag("include")
@register.tag('include')
def do_include(parser, token):
"""
Load a template and render it with the current context. You can pass
@@ -326,27 +309,21 @@ def do_include(parser, token):
while remaining_bits:
option = remaining_bits.pop(0)
if option in options:
raise TemplateSyntaxError(
"The %r option was specified more than once." % option
)
if option == "with":
raise TemplateSyntaxError('The %r option was specified more '
'than once.' % option)
if option == 'with':
value = token_kwargs(remaining_bits, parser, support_legacy=False)
if not value:
raise TemplateSyntaxError(
'"with" in %r tag needs at least one keyword argument.' % bits[0]
)
elif option == "only":
raise TemplateSyntaxError('"with" in %r tag needs at least '
'one keyword argument.' % bits[0])
elif option == 'only':
value = True
else:
raise TemplateSyntaxError(
"Unknown argument for %r tag: %r." % (bits[0], option)
)
raise TemplateSyntaxError('Unknown argument for %r tag: %r.' %
(bits[0], option))
options[option] = value
isolated_context = options.get("only", False)
namemap = options.get("with", {})
isolated_context = options.get('only', False)
namemap = options.get('with', {})
bits[1] = construct_relative_path(parser.origin.template_name, bits[1])
return IncludeNode(
parser.compile_filter(bits[1]),
extra_context=namemap,
isolated_context=isolated_context,
)
return IncludeNode(parser.compile_filter(bits[1]), extra_context=namemap,
isolated_context=isolated_context)
@@ -9,5 +9,6 @@ from .filesystem import Loader as FilesystemLoader
class Loader(FilesystemLoader):
def get_dirs(self):
return get_app_template_dirs("templates")
return get_app_template_dirs('templates')
@@ -2,6 +2,7 @@ from django.template import Template, TemplateDoesNotExist
class Loader:
def __init__(self, engine):
self.engine = engine
@@ -16,20 +17,17 @@ class Loader:
for origin in self.get_template_sources(template_name):
if skip is not None and origin in skip:
tried.append((origin, "Skipped to avoid recursion"))
tried.append((origin, 'Skipped to avoid recursion'))
continue
try:
contents = self.get_contents(origin)
except TemplateDoesNotExist:
tried.append((origin, "Source does not exist"))
tried.append((origin, 'Source does not exist'))
continue
else:
return Template(
contents,
origin,
origin.template_name,
self.engine,
contents, origin, origin.template_name, self.engine,
)
raise TemplateDoesNotExist(template_name, tried=tried)
@@ -40,7 +38,7 @@ class Loader:
template name.
"""
raise NotImplementedError(
"subclasses of Loader must provide a get_template_sources() method"
'subclasses of Loader must provide a get_template_sources() method'
)
def reset(self):
@@ -12,6 +12,7 @@ from .base import Loader as BaseLoader
class Loader(BaseLoader):
def __init__(self, engine, loaders):
self.get_template_cache = {}
self.loaders = engine.get_template_loaders(loaders)
@@ -56,9 +57,7 @@ class Loader(BaseLoader):
try:
template = super().get_template(template_name, skip)
except TemplateDoesNotExist as e:
self.get_template_cache[key] = (
copy_exception(e) if self.engine.debug else TemplateDoesNotExist
)
self.get_template_cache[key] = copy_exception(e) if self.engine.debug else TemplateDoesNotExist
raise
else:
self.get_template_cache[key] = template
@@ -81,19 +80,17 @@ class Loader(BaseLoader):
y -> a -> a
z -> a -> a
"""
skip_prefix = ""
skip_prefix = ''
if skip:
matching = [
origin.name for origin in skip if origin.template_name == template_name
]
matching = [origin.name for origin in skip if origin.template_name == template_name]
if matching:
skip_prefix = self.generate_hash(matching)
return "-".join(s for s in (str(template_name), skip_prefix) if s)
return '-'.join(s for s in (str(template_name), skip_prefix) if s)
def generate_hash(self, values):
return hashlib.sha1("|".join(values).encode()).hexdigest()
return hashlib.sha1('|'.join(values).encode()).hexdigest()
def reset(self):
"Empty the template cache."
@@ -10,6 +10,7 @@ from .base import Loader as BaseLoader
class Loader(BaseLoader):
def __init__(self, engine, dirs=None):
super().__init__(engine)
self.dirs = dirs
@@ -8,6 +8,7 @@ from .base import Loader as BaseLoader
class Loader(BaseLoader):
def __init__(self, engine, templates_dict):
self.templates_dict = templates_dict
super().__init__(engine)
@@ -8,18 +8,10 @@ class ContentNotRenderedError(Exception):
class SimpleTemplateResponse(HttpResponse):
rendering_attrs = ["template_name", "context_data", "_post_render_callbacks"]
rendering_attrs = ['template_name', 'context_data', '_post_render_callbacks']
def __init__(
self,
template,
context=None,
content_type=None,
status=None,
charset=None,
using=None,
headers=None,
):
def __init__(self, template, context=None, content_type=None, status=None,
charset=None, using=None, headers=None):
# It would seem obvious to call these next two members 'template' and
# 'context', but those names are reserved as part of the test Client
# API. To avoid the name collision, we use different names.
@@ -41,7 +33,7 @@ class SimpleTemplateResponse(HttpResponse):
# content argument doesn't make sense here because it will be replaced
# with rendered template so we always pass empty string in order to
# prevent errors and provide shorter signature.
super().__init__("", content_type, status, charset=charset, headers=headers)
super().__init__('', content_type, status, charset=charset, headers=headers)
# _is_rendered tracks whether the template and context has been baked
# into a final response.
@@ -57,9 +49,8 @@ class SimpleTemplateResponse(HttpResponse):
"""
obj_dict = self.__dict__.copy()
if not self._is_rendered:
raise ContentNotRenderedError(
"The response content must be rendered before it can be pickled."
)
raise ContentNotRenderedError('The response content must be '
'rendered before it can be pickled.')
for attr in self.rendering_attrs:
if attr in obj_dict:
del obj_dict[attr]
@@ -125,7 +116,7 @@ class SimpleTemplateResponse(HttpResponse):
def __iter__(self):
if not self._is_rendered:
raise ContentNotRenderedError(
"The response content must be rendered before it can be iterated over."
'The response content must be rendered before it can be iterated over.'
)
return super().__iter__()
@@ -133,7 +124,7 @@ class SimpleTemplateResponse(HttpResponse):
def content(self):
if not self._is_rendered:
raise ContentNotRenderedError(
"The response content must be rendered before it can be accessed."
'The response content must be rendered before it can be accessed.'
)
return super().content
@@ -145,20 +136,9 @@ class SimpleTemplateResponse(HttpResponse):
class TemplateResponse(SimpleTemplateResponse):
rendering_attrs = SimpleTemplateResponse.rendering_attrs + ["_request"]
rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_request']
def __init__(
self,
request,
template,
context=None,
content_type=None,
status=None,
charset=None,
using=None,
headers=None,
):
super().__init__(
template, context, content_type, status, charset, using, headers=headers
)
def __init__(self, request, template, context=None, content_type=None,
status=None, charset=None, using=None, headers=None):
super().__init__(template, context, content_type, status, charset, using, headers=headers)
self._request = request
@@ -13,7 +13,6 @@ class TokenBase:
Base class for operators and literals, mainly for debugging and for throwing
syntax errors.
"""
id = None # node/token type name
value = None # used by literals
first = second = None # used by tree nodes
@@ -46,7 +45,6 @@ def infix(bp, func):
Create an infix operator, given a binding power and a function that
evaluates the node.
"""
class Operator(TokenBase):
lbp = bp
@@ -72,7 +70,6 @@ def prefix(bp, func):
Create a prefix operator, given a binding power and a function that
evaluates the node.
"""
class Operator(TokenBase):
lbp = bp
@@ -94,19 +91,19 @@ def prefix(bp, func):
# We defer variable evaluation to the lambda to ensure that terms are
# lazily evaluated using Python's boolean parsing logic.
OPERATORS = {
"or": infix(6, lambda context, x, y: x.eval(context) or y.eval(context)),
"and": infix(7, lambda context, x, y: x.eval(context) and y.eval(context)),
"not": prefix(8, lambda context, x: not x.eval(context)),
"in": infix(9, lambda context, x, y: x.eval(context) in y.eval(context)),
"not in": infix(9, lambda context, x, y: x.eval(context) not in y.eval(context)),
"is": infix(10, lambda context, x, y: x.eval(context) is y.eval(context)),
"is not": infix(10, lambda context, x, y: x.eval(context) is not y.eval(context)),
"==": infix(10, lambda context, x, y: x.eval(context) == y.eval(context)),
"!=": infix(10, lambda context, x, y: x.eval(context) != y.eval(context)),
">": infix(10, lambda context, x, y: x.eval(context) > y.eval(context)),
">=": infix(10, lambda context, x, y: x.eval(context) >= y.eval(context)),
"<": infix(10, lambda context, x, y: x.eval(context) < y.eval(context)),
"<=": infix(10, lambda context, x, y: x.eval(context) <= y.eval(context)),
'or': infix(6, lambda context, x, y: x.eval(context) or y.eval(context)),
'and': infix(7, lambda context, x, y: x.eval(context) and y.eval(context)),
'not': prefix(8, lambda context, x: not x.eval(context)),
'in': infix(9, lambda context, x, y: x.eval(context) in y.eval(context)),
'not in': infix(9, lambda context, x, y: x.eval(context) not in y.eval(context)),
'is': infix(10, lambda context, x, y: x.eval(context) is y.eval(context)),
'is not': infix(10, lambda context, x, y: x.eval(context) is not y.eval(context)),
'==': infix(10, lambda context, x, y: x.eval(context) == y.eval(context)),
'!=': infix(10, lambda context, x, y: x.eval(context) != y.eval(context)),
'>': infix(10, lambda context, x, y: x.eval(context) > y.eval(context)),
'>=': infix(10, lambda context, x, y: x.eval(context) >= y.eval(context)),
'<': infix(10, lambda context, x, y: x.eval(context) < y.eval(context)),
'<=': infix(10, lambda context, x, y: x.eval(context) <= y.eval(context)),
}
# Assign 'id' to each:
@@ -118,7 +115,6 @@ class Literal(TokenBase):
"""
A basic self-resolvable object similar to a Django template variable.
"""
# IfParser uses Literal in create_var, but TemplateIfParser overrides
# create_var so that a proper implementation that actually resolves
# variables, filters etc. is used.
@@ -194,9 +190,8 @@ class IfParser:
retval = self.expression()
# Check that we have exhausted all the tokens
if self.current_token is not EndToken:
raise self.error_class(
"Unused '%s' at end of if expression." % self.current_token.display()
)
raise self.error_class("Unused '%s' at end of if expression." %
self.current_token.display())
return retval
def expression(self, rbp=0):
+13 -17
View File
@@ -33,34 +33,31 @@ class EngineHandler:
try:
# This will raise an exception if 'BACKEND' doesn't exist or
# isn't a string containing at least one dot.
default_name = tpl["BACKEND"].rsplit(".", 2)[-2]
default_name = tpl['BACKEND'].rsplit('.', 2)[-2]
except Exception:
invalid_backend = tpl.get("BACKEND", "<not defined>")
invalid_backend = tpl.get('BACKEND', '<not defined>')
raise ImproperlyConfigured(
"Invalid BACKEND for a template engine: {}. Check "
"your TEMPLATES setting.".format(invalid_backend)
)
"your TEMPLATES setting.".format(invalid_backend))
tpl = {
"NAME": default_name,
"DIRS": [],
"APP_DIRS": False,
"OPTIONS": {},
'NAME': default_name,
'DIRS': [],
'APP_DIRS': False,
'OPTIONS': {},
**tpl,
}
templates[tpl["NAME"]] = tpl
backend_names.append(tpl["NAME"])
templates[tpl['NAME']] = tpl
backend_names.append(tpl['NAME'])
counts = Counter(backend_names)
duplicates = [alias for alias, count in counts.most_common() if count > 1]
if duplicates:
raise ImproperlyConfigured(
"Template engine aliases aren't unique, duplicates: {}. "
"Set a unique NAME for each engine in settings.TEMPLATES.".format(
", ".join(duplicates)
)
)
"Set a unique NAME for each engine in settings.TEMPLATES."
.format(", ".join(duplicates)))
return templates
@@ -73,14 +70,13 @@ class EngineHandler:
except KeyError:
raise InvalidTemplateEngineError(
"Could not find config for '{}' "
"in settings.TEMPLATES".format(alias)
)
"in settings.TEMPLATES".format(alias))
# If importing or initializing the backend raises an exception,
# self._engines[alias] isn't set and this code may get executed
# again, so we must preserve the original params. See #24265.
params = params.copy()
backend = params.pop("BACKEND")
backend = params.pop('BACKEND')
engine_cls = import_string(backend)
engine = engine_cls(params)