测试gitnore
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user