docker setup
This commit is contained in:
@ -0,0 +1,75 @@
|
||||
"""
|
||||
Django's support for templates.
|
||||
|
||||
The django.template namespace contains two independent subsystems:
|
||||
|
||||
1. Multiple Template Engines: support for pluggable template backends,
|
||||
built-in backends and backend-independent APIs
|
||||
2. Django Template Language: Django's own template engine, including its
|
||||
built-in loaders, context processors, tags and filters.
|
||||
|
||||
Ideally these subsystems would be implemented in distinct packages. However
|
||||
keeping them together made the implementation of Multiple Template Engines
|
||||
less disruptive .
|
||||
|
||||
Here's a breakdown of which modules belong to which subsystem.
|
||||
|
||||
Multiple Template Engines:
|
||||
|
||||
- django.template.backends.*
|
||||
- django.template.loader
|
||||
- django.template.response
|
||||
|
||||
Django Template Language:
|
||||
|
||||
- django.template.base
|
||||
- django.template.context
|
||||
- django.template.context_processors
|
||||
- django.template.loaders.*
|
||||
- django.template.debug
|
||||
- django.template.defaultfilters
|
||||
- django.template.defaulttags
|
||||
- django.template.engine
|
||||
- django.template.loader_tags
|
||||
- django.template.smartif
|
||||
|
||||
Shared:
|
||||
|
||||
- django.template.utils
|
||||
|
||||
"""
|
||||
|
||||
# Multiple Template Engines
|
||||
|
||||
from .engine import Engine
|
||||
from .utils import EngineHandler
|
||||
|
||||
engines = EngineHandler()
|
||||
|
||||
__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
|
||||
|
||||
# Template parts
|
||||
from .base import ( # NOQA isort:skip
|
||||
Node,
|
||||
NodeList,
|
||||
Origin,
|
||||
Template,
|
||||
Variable,
|
||||
)
|
||||
|
||||
# Library management
|
||||
from .library import Library # NOQA isort:skip
|
||||
|
||||
# Import the .autoreload module to trigger the registrations of signals.
|
||||
from . import autoreload # NOQA isort:skip
|
||||
|
||||
|
||||
__all__ += ("Template", "Context", "RequestContext")
|
@ -0,0 +1,54 @@
|
||||
from pathlib import Path
|
||||
|
||||
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
|
||||
|
||||
|
||||
def get_template_directories():
|
||||
# Iterate through each template backend and find
|
||||
# any template_loader that has a 'get_dirs' method.
|
||||
# Collect the directories, filtering out Django templates.
|
||||
cwd = Path.cwd()
|
||||
items = set()
|
||||
for backend in engines.all():
|
||||
if not isinstance(backend, DjangoTemplates):
|
||||
continue
|
||||
|
||||
items.update(cwd / to_path(dir) for dir in backend.engine.dirs if dir)
|
||||
|
||||
for loader in backend.engine.template_loaders:
|
||||
if not hasattr(loader, "get_dirs"):
|
||||
continue
|
||||
items.update(
|
||||
cwd / to_path(directory)
|
||||
for directory in loader.get_dirs()
|
||||
if directory and not is_django_path(directory)
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def reset_loaders():
|
||||
for backend in engines.all():
|
||||
if not isinstance(backend, DjangoTemplates):
|
||||
continue
|
||||
for loader in backend.engine.template_loaders:
|
||||
loader.reset()
|
||||
|
||||
|
||||
@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, "**/*")
|
||||
|
||||
|
||||
@receiver(file_changed, dispatch_uid="template_loaders_file_changed")
|
||||
def template_changed(sender, file_path, **kwargs):
|
||||
if file_path.suffix == ".py":
|
||||
return
|
||||
for template_dir in get_template_directories():
|
||||
if template_dir in file_path.parents:
|
||||
reset_loaders()
|
||||
return True
|
@ -0,0 +1,80 @@
|
||||
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
|
||||
|
||||
|
||||
class BaseEngine:
|
||||
# Core methods: engines have to provide their own implementation
|
||||
# (except for from_string which is optional).
|
||||
|
||||
def __init__(self, params):
|
||||
"""
|
||||
Initialize the template engine.
|
||||
|
||||
`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")
|
||||
if params:
|
||||
raise ImproperlyConfigured(
|
||||
"Unknown parameters: {}".format(", ".join(params))
|
||||
)
|
||||
|
||||
@property
|
||||
def app_dirname(self):
|
||||
raise ImproperlyConfigured(
|
||||
"{} doesn't support loading templates from installed "
|
||||
"applications.".format(self.__class__.__name__)
|
||||
)
|
||||
|
||||
def from_string(self, template_code):
|
||||
"""
|
||||
Create and return a template for the given source code.
|
||||
|
||||
This method is optional.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of BaseEngine should provide a from_string() method"
|
||||
)
|
||||
|
||||
def get_template(self, template_name):
|
||||
"""
|
||||
Load and return a template for the given name.
|
||||
|
||||
Raise TemplateDoesNotExist if no such template exists.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"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.
|
||||
|
||||
@cached_property
|
||||
def template_dirs(self):
|
||||
"""
|
||||
Return a list of directories to search for templates.
|
||||
"""
|
||||
# Immutable return value because it's cached and shared by callers.
|
||||
template_dirs = tuple(self.dirs)
|
||||
if self.app_dirs:
|
||||
template_dirs += get_app_template_dirs(self.app_dirname)
|
||||
return template_dirs
|
||||
|
||||
def iter_template_filenames(self, template_name):
|
||||
"""
|
||||
Iterate over candidate files for template_name.
|
||||
|
||||
Ignore files that don't lie inside configured template dirs to avoid
|
||||
directory traversal attacks.
|
||||
"""
|
||||
for template_dir in self.template_dirs:
|
||||
try:
|
||||
yield safe_join(template_dir, template_name)
|
||||
except SuspiciousFileOperation:
|
||||
# The joined path was located outside of this template_dir
|
||||
# (it might be inside another one, so this isn't fatal).
|
||||
pass
|
@ -0,0 +1,136 @@
|
||||
from importlib import import_module
|
||||
from pkgutil import walk_packages
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.template import TemplateDoesNotExist
|
||||
from django.template.context import make_context
|
||||
from django.template.engine import Engine
|
||||
from django.template.library import InvalidTemplateLibrary
|
||||
|
||||
from .base import BaseEngine
|
||||
|
||||
|
||||
class DjangoTemplates(BaseEngine):
|
||||
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)
|
||||
super().__init__(params)
|
||||
self.engine = Engine(self.dirs, self.app_dirs, **options)
|
||||
|
||||
def from_string(self, template_code):
|
||||
return Template(self.engine.from_string(template_code), self)
|
||||
|
||||
def get_template(self, template_name):
|
||||
try:
|
||||
return Template(self.engine.get_template(template_name), self)
|
||||
except TemplateDoesNotExist as exc:
|
||||
reraise(exc, self)
|
||||
|
||||
def get_templatetag_libraries(self, custom_libraries):
|
||||
"""
|
||||
Return a collation of template tag libraries from installed
|
||||
applications and the supplied custom_libraries argument.
|
||||
"""
|
||||
libraries = get_installed_libraries()
|
||||
libraries.update(custom_libraries)
|
||||
return libraries
|
||||
|
||||
|
||||
class Template:
|
||||
def __init__(self, template, backend):
|
||||
self.template = template
|
||||
self.backend = backend
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
return self.template.origin
|
||||
|
||||
def render(self, context=None, request=None):
|
||||
context = make_context(
|
||||
context, request, autoescape=self.backend.engine.autoescape
|
||||
)
|
||||
try:
|
||||
return self.template.render(context)
|
||||
except TemplateDoesNotExist as exc:
|
||||
reraise(exc, self.backend)
|
||||
|
||||
|
||||
def copy_exception(exc, backend=None):
|
||||
"""
|
||||
Create a new TemplateDoesNotExist. Preserve its declared attributes and
|
||||
template debug data but discard __traceback__, __context__, and __cause__
|
||||
to make this object suitable for keeping around (in a cache, for example).
|
||||
"""
|
||||
backend = backend or exc.backend
|
||||
new = exc.__class__(*exc.args, tried=exc.tried, backend=backend, chain=exc.chain)
|
||||
if hasattr(exc, "template_debug"):
|
||||
new.template_debug = exc.template_debug
|
||||
return new
|
||||
|
||||
|
||||
def reraise(exc, backend):
|
||||
"""
|
||||
Reraise TemplateDoesNotExist while maintaining template debug information.
|
||||
"""
|
||||
new = copy_exception(exc, backend)
|
||||
raise new from exc
|
||||
|
||||
|
||||
def get_template_tag_modules():
|
||||
"""
|
||||
Yield (module_name, module_path) pairs for all installed template tag
|
||||
libraries.
|
||||
"""
|
||||
candidates = ["django.templatetags"]
|
||||
candidates.extend(
|
||||
f"{app_config.name}.templatetags" for app_config in apps.get_app_configs()
|
||||
)
|
||||
|
||||
for candidate in candidates:
|
||||
try:
|
||||
pkg = import_module(candidate)
|
||||
except ImportError:
|
||||
# No templatetags package defined. This is safe to ignore.
|
||||
continue
|
||||
|
||||
if hasattr(pkg, "__path__"):
|
||||
for name in get_package_libraries(pkg):
|
||||
yield name[len(candidate) + 1 :], name
|
||||
|
||||
|
||||
def get_installed_libraries():
|
||||
"""
|
||||
Return the built-in template tag libraries and those from installed
|
||||
applications. Libraries are stored in a dictionary where keys are the
|
||||
individual module names, not the full module paths. Example:
|
||||
django.templatetags.i18n is stored as i18n.
|
||||
"""
|
||||
return {
|
||||
module_name: full_name for module_name, full_name in get_template_tag_modules()
|
||||
}
|
||||
|
||||
|
||||
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__ + "."):
|
||||
try:
|
||||
module = import_module(entry[1])
|
||||
except ImportError as e:
|
||||
raise InvalidTemplateLibrary(
|
||||
"Invalid template library specified. ImportError raised when "
|
||||
"trying to load '%s': %s" % (entry[1], e)
|
||||
) from e
|
||||
|
||||
if hasattr(module, "register"):
|
||||
yield entry[1]
|
@ -0,0 +1,51 @@
|
||||
import string
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.template import Origin, TemplateDoesNotExist
|
||||
from django.utils.html import conditional_escape
|
||||
|
||||
from .base import BaseEngine
|
||||
from .utils import csrf_input_lazy, csrf_token_lazy
|
||||
|
||||
|
||||
class TemplateStrings(BaseEngine):
|
||||
app_dirname = "template_strings"
|
||||
|
||||
def __init__(self, params):
|
||||
params = params.copy()
|
||||
options = params.pop("OPTIONS").copy()
|
||||
if options:
|
||||
raise ImproperlyConfigured("Unknown options: {}".format(", ".join(options)))
|
||||
super().__init__(params)
|
||||
|
||||
def from_string(self, template_code):
|
||||
return Template(template_code)
|
||||
|
||||
def get_template(self, template_name):
|
||||
tried = []
|
||||
for template_file in self.iter_template_filenames(template_name):
|
||||
try:
|
||||
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",
|
||||
)
|
||||
)
|
||||
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)
|
||||
return self.safe_substitute(context)
|
@ -0,0 +1,125 @@
|
||||
from pathlib import Path
|
||||
|
||||
import jinja2
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import TemplateDoesNotExist, TemplateSyntaxError
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
from .base import BaseEngine
|
||||
from .utils import csrf_input_lazy, csrf_token_lazy
|
||||
|
||||
|
||||
class Jinja2(BaseEngine):
|
||||
app_dirname = "jinja2"
|
||||
|
||||
def __init__(self, params):
|
||||
params = params.copy()
|
||||
options = params.pop("OPTIONS").copy()
|
||||
super().__init__(params)
|
||||
|
||||
self.context_processors = options.pop("context_processors", [])
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
self.env = environment_cls(**options)
|
||||
|
||||
def from_string(self, template_code):
|
||||
return Template(self.env.from_string(template_code), self)
|
||||
|
||||
def get_template(self, template_name):
|
||||
try:
|
||||
return Template(self.env.get_template(template_name), self)
|
||||
except jinja2.TemplateNotFound as exc:
|
||||
raise TemplateDoesNotExist(exc.name, backend=self) from exc
|
||||
except jinja2.TemplateSyntaxError as exc:
|
||||
new = TemplateSyntaxError(exc.args)
|
||||
new.template_debug = get_exception_info(exc)
|
||||
raise new from exc
|
||||
|
||||
@cached_property
|
||||
def template_context_processors(self):
|
||||
return [import_string(path) for path in self.context_processors]
|
||||
|
||||
|
||||
class Template:
|
||||
def __init__(self, template, backend):
|
||||
self.template = template
|
||||
self.backend = backend
|
||||
self.origin = Origin(
|
||||
name=template.filename,
|
||||
template_name=template.name,
|
||||
)
|
||||
|
||||
def render(self, context=None, request=None):
|
||||
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)
|
||||
for context_processor in self.backend.template_context_processors:
|
||||
context.update(context_processor(request))
|
||||
try:
|
||||
return self.template.render(context)
|
||||
except jinja2.TemplateSyntaxError as exc:
|
||||
new = TemplateSyntaxError(exc.args)
|
||||
new.template_debug = get_exception_info(exc)
|
||||
raise new from exc
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def get_exception_info(exception):
|
||||
"""
|
||||
Format exception information for display on the debug page using the
|
||||
structure described in the template API documentation.
|
||||
"""
|
||||
context_lines = 10
|
||||
lineno = exception.lineno
|
||||
source = exception.source
|
||||
if source is None:
|
||||
exception_file = Path(exception.filename)
|
||||
if exception_file.exists():
|
||||
source = exception_file.read_text()
|
||||
if source is not None:
|
||||
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 = ""
|
||||
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,
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
from django.middleware.csrf import get_token
|
||||
from django.utils.functional import lazy
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import SafeString
|
||||
|
||||
|
||||
def csrf_input(request):
|
||||
return format_html(
|
||||
'<input type="hidden" name="csrfmiddlewaretoken" value="{}">',
|
||||
get_token(request),
|
||||
)
|
||||
|
||||
|
||||
csrf_input_lazy = lazy(csrf_input, SafeString, str)
|
||||
csrf_token_lazy = lazy(get_token, str)
|
1121
srcs/.venv/lib/python3.11/site-packages/django/template/base.py
Normal file
1121
srcs/.venv/lib/python3.11/site-packages/django/template/base.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,290 @@
|
||||
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",)
|
||||
|
||||
|
||||
class ContextPopException(Exception):
|
||||
"pop() has been called more times than push()"
|
||||
pass
|
||||
|
||||
|
||||
class ContextDict(dict):
|
||||
def __init__(self, context, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
context.dicts.append(self)
|
||||
self.context = context
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.context.pop()
|
||||
|
||||
|
||||
class BaseContext:
|
||||
def __init__(self, dict_=None):
|
||||
self._reset_dicts(dict_)
|
||||
|
||||
def _reset_dicts(self, value=None):
|
||||
builtins = {"True": True, "False": False, "None": None}
|
||||
self.dicts = [builtins]
|
||||
if value is not None:
|
||||
self.dicts.append(value)
|
||||
|
||||
def __copy__(self):
|
||||
duplicate = copy(super())
|
||||
duplicate.dicts = self.dicts[:]
|
||||
return duplicate
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.dicts)
|
||||
|
||||
def __iter__(self):
|
||||
return reversed(self.dicts)
|
||||
|
||||
def push(self, *args, **kwargs):
|
||||
dicts = []
|
||||
for d in args:
|
||||
if isinstance(d, BaseContext):
|
||||
dicts += d.dicts[1:]
|
||||
else:
|
||||
dicts.append(d)
|
||||
return ContextDict(self, *dicts, **kwargs)
|
||||
|
||||
def pop(self):
|
||||
if len(self.dicts) == 1:
|
||||
raise ContextPopException
|
||||
return self.dicts.pop()
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"Set a variable in the current context"
|
||||
self.dicts[-1][key] = value
|
||||
|
||||
def set_upward(self, key, value):
|
||||
"""
|
||||
Set a variable in one of the higher contexts if it exists there,
|
||||
otherwise in the current context.
|
||||
"""
|
||||
context = self.dicts[-1]
|
||||
for d in reversed(self.dicts):
|
||||
if key in d:
|
||||
context = d
|
||||
break
|
||||
context[key] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
"Get a variable's value, starting at the current context and going upward"
|
||||
for d in reversed(self.dicts):
|
||||
if key in d:
|
||||
return d[key]
|
||||
raise KeyError(key)
|
||||
|
||||
def __delitem__(self, key):
|
||||
"Delete a variable from the current context"
|
||||
del self.dicts[-1][key]
|
||||
|
||||
def __contains__(self, key):
|
||||
return any(key in d for d in self.dicts)
|
||||
|
||||
def get(self, key, otherwise=None):
|
||||
for d in reversed(self.dicts):
|
||||
if key in d:
|
||||
return d[key]
|
||||
return otherwise
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
self[key] = default
|
||||
return default
|
||||
|
||||
def new(self, values=None):
|
||||
"""
|
||||
Return a new context with the same properties, but with only the
|
||||
values given in 'values' stored.
|
||||
"""
|
||||
new_context = copy(self)
|
||||
new_context._reset_dicts(values)
|
||||
return new_context
|
||||
|
||||
def flatten(self):
|
||||
"""
|
||||
Return self.dicts as one dictionary.
|
||||
"""
|
||||
flat = {}
|
||||
for d in self.dicts:
|
||||
flat.update(d)
|
||||
return flat
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Compare two contexts by comparing theirs 'dicts' attributes.
|
||||
"""
|
||||
if not isinstance(other, BaseContext):
|
||||
return NotImplemented
|
||||
# flatten dictionaries because they can be put in a different order.
|
||||
return self.flatten() == other.flatten()
|
||||
|
||||
|
||||
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
|
||||
self.use_tz = use_tz
|
||||
self.template_name = "unknown"
|
||||
self.render_context = RenderContext()
|
||||
# Set to the original template -- as opposed to extended or included
|
||||
# templates -- during rendering, see bind_template.
|
||||
self.template = None
|
||||
super().__init__(dict_)
|
||||
|
||||
@contextmanager
|
||||
def bind_template(self, template):
|
||||
if self.template is not None:
|
||||
raise RuntimeError("Context is already bound to a template")
|
||||
self.template = template
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.template = None
|
||||
|
||||
def __copy__(self):
|
||||
duplicate = super().__copy__()
|
||||
duplicate.render_context = copy(self.render_context)
|
||||
return duplicate
|
||||
|
||||
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 isinstance(other_dict, BaseContext):
|
||||
other_dict = other_dict.dicts[1:].pop()
|
||||
return ContextDict(self, other_dict)
|
||||
|
||||
|
||||
class RenderContext(BaseContext):
|
||||
"""
|
||||
A stack container for storing Template state.
|
||||
|
||||
RenderContext simplifies the implementation of template Nodes by providing a
|
||||
safe place to store state between invocations of a node's `render` method.
|
||||
|
||||
The RenderContext also provides scoping rules that are more sensible for
|
||||
'template local' variables. The render context stack is pushed before each
|
||||
template is rendered, creating a fresh scope with nothing in it. Name
|
||||
resolution fails if a variable is not found at the top of the RequestContext
|
||||
stack. Thus, variables are local to a specific template and don't affect the
|
||||
rendering of other templates as they would if they were stored in the normal
|
||||
template context.
|
||||
"""
|
||||
|
||||
template = None
|
||||
|
||||
def __iter__(self):
|
||||
yield from self.dicts[-1]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.dicts[-1]
|
||||
|
||||
def get(self, key, otherwise=None):
|
||||
return self.dicts[-1].get(key, otherwise)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.dicts[-1][key]
|
||||
|
||||
@contextmanager
|
||||
def push_state(self, template, isolated_context=True):
|
||||
initial = self.template
|
||||
self.template = template
|
||||
if isolated_context:
|
||||
self.push()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.template = initial
|
||||
if isolated_context:
|
||||
self.pop()
|
||||
|
||||
|
||||
class RequestContext(Context):
|
||||
"""
|
||||
This subclass of template.Context automatically populates itself using
|
||||
the processors defined in the engine's configuration.
|
||||
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,
|
||||
):
|
||||
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)
|
||||
self._processors_index = len(self.dicts)
|
||||
|
||||
# placeholder for context processors output
|
||||
self.update({})
|
||||
|
||||
# empty dict for any new modifications
|
||||
# (so that context processors don't overwrite them)
|
||||
self.update({})
|
||||
|
||||
@contextmanager
|
||||
def bind_template(self, template):
|
||||
if self.template is not None:
|
||||
raise RuntimeError("Context is already bound to a template")
|
||||
|
||||
self.template = template
|
||||
# Set context processors according to the template engine's settings.
|
||||
processors = template.engine.template_context_processors + self._processors
|
||||
updates = {}
|
||||
for processor in processors:
|
||||
updates.update(processor(self.request))
|
||||
self.dicts[self._processors_index] = updates
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.template = None
|
||||
# Unset context processors.
|
||||
self.dicts[self._processors_index] = {}
|
||||
|
||||
def new(self, values=None):
|
||||
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"):
|
||||
del new_context._processors_index
|
||||
return new_context
|
||||
|
||||
|
||||
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__
|
||||
)
|
||||
if request is None:
|
||||
context = Context(context, **kwargs)
|
||||
else:
|
||||
# The following pattern is required to ensure values from
|
||||
# context override those from template context processors.
|
||||
original_context = context
|
||||
context = RequestContext(request, **kwargs)
|
||||
if original_context:
|
||||
context.push(original_context)
|
||||
return context
|
@ -0,0 +1,89 @@
|
||||
"""
|
||||
A set of request processors that return dictionaries to be merged into a
|
||||
template context. Each function takes the request object as its only parameter
|
||||
and returns a dictionary to add to the context.
|
||||
|
||||
These are referenced from the 'context_processors' option of the configuration
|
||||
of a DjangoTemplates backend and used by RequestContext.
|
||||
"""
|
||||
|
||||
import itertools
|
||||
|
||||
from django.conf import settings
|
||||
from django.middleware.csrf import get_token
|
||||
from django.utils.functional import SimpleLazyObject, lazy
|
||||
|
||||
|
||||
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"
|
||||
else:
|
||||
return token
|
||||
|
||||
return {"csrf_token": SimpleLazyObject(_get_val)}
|
||||
|
||||
|
||||
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
|
||||
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,
|
||||
)
|
||||
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(),
|
||||
}
|
||||
|
||||
|
||||
def tz(request):
|
||||
from django.utils import timezone
|
||||
|
||||
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}
|
||||
|
||||
|
||||
def media(request):
|
||||
"""
|
||||
Add media-related context variables to the context.
|
||||
"""
|
||||
return {"MEDIA_URL": settings.MEDIA_URL}
|
||||
|
||||
|
||||
def request(request):
|
||||
return {"request": request}
|
@ -0,0 +1,979 @@
|
||||
"""Default variable filters."""
|
||||
import random as random_module
|
||||
import re
|
||||
import types
|
||||
import warnings
|
||||
from decimal import ROUND_HALF_UP, Context, Decimal, InvalidOperation, getcontext
|
||||
from functools import wraps
|
||||
from inspect import unwrap
|
||||
from operator import itemgetter
|
||||
from pprint import pformat
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.utils import formats
|
||||
from django.utils.dateformat import format, time_format
|
||||
from django.utils.deprecation import RemovedInDjango51Warning
|
||||
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.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.timesince import timesince, timeuntil
|
||||
from django.utils.translation import gettext, ngettext
|
||||
|
||||
from .base import VARIABLE_ATTRIBUTE_SEPARATOR
|
||||
from .library import Library
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def _dec(first, *args, **kwargs):
|
||||
first = str(first)
|
||||
result = func(first, *args, **kwargs)
|
||||
if isinstance(first, SafeData) and getattr(unwrap(func), "is_safe", False):
|
||||
result = mark_safe(result)
|
||||
return result
|
||||
|
||||
return _dec
|
||||
|
||||
|
||||
###################
|
||||
# STRINGS #
|
||||
###################
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def addslashes(value):
|
||||
"""
|
||||
Add slashes before quotes. Useful for escaping strings in CSV, for
|
||||
example. Less useful for escaping JavaScript; use the ``escapejs``
|
||||
filter instead.
|
||||
"""
|
||||
return value.replace("\\", "\\\\").replace('"', '\\"').replace("'", "\\'")
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def capfirst(value):
|
||||
"""Capitalize the first character of the value."""
|
||||
return value and value[0].upper() + value[1:]
|
||||
|
||||
|
||||
@register.filter("escapejs")
|
||||
@stringfilter
|
||||
def escapejs_filter(value):
|
||||
"""Hex encode characters for use in JavaScript strings."""
|
||||
return escapejs(value)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
def json_script(value, element_id=None):
|
||||
"""
|
||||
Output value JSON-encoded, wrapped in a <script type="application/json">
|
||||
tag (with an optional id).
|
||||
"""
|
||||
return _json_script(value, element_id)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
def floatformat(text, arg=-1):
|
||||
"""
|
||||
Display a float to a specified number of decimal places.
|
||||
|
||||
If called without an argument, display the floating point number with one
|
||||
decimal place -- but only if there's a decimal place to be displayed:
|
||||
|
||||
* num1 = 34.23234
|
||||
* num2 = 34.00000
|
||||
* num3 = 34.26000
|
||||
* {{ num1|floatformat }} displays "34.2"
|
||||
* {{ num2|floatformat }} displays "34"
|
||||
* {{ num3|floatformat }} displays "34.3"
|
||||
|
||||
If arg is positive, always display exactly arg number of decimal places:
|
||||
|
||||
* {{ num1|floatformat:3 }} displays "34.232"
|
||||
* {{ num2|floatformat:3 }} displays "34.000"
|
||||
* {{ num3|floatformat:3 }} displays "34.260"
|
||||
|
||||
If arg is negative, display arg number of decimal places -- but only if
|
||||
there are places to be displayed:
|
||||
|
||||
* {{ num1|floatformat:"-3" }} displays "34.232"
|
||||
* {{ num2|floatformat:"-3" }} displays "34"
|
||||
* {{ num3|floatformat:"-3" }} displays "34.260"
|
||||
|
||||
If arg has the 'g' suffix, force the result to be grouped by the
|
||||
THOUSAND_SEPARATOR for the active locale. When the active locale is
|
||||
en (English):
|
||||
|
||||
* {{ 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
|
||||
try:
|
||||
input_val = str(text)
|
||||
d = Decimal(input_val)
|
||||
except InvalidOperation:
|
||||
try:
|
||||
d = Decimal(str(float(text)))
|
||||
except (ValueError, InvalidOperation, TypeError):
|
||||
return ""
|
||||
try:
|
||||
p = int(arg)
|
||||
except ValueError:
|
||||
return input_val
|
||||
|
||||
try:
|
||||
m = int(d) - d
|
||||
except (ValueError, OverflowError, InvalidOperation):
|
||||
return input_val
|
||||
|
||||
if not m and p <= 0:
|
||||
return mark_safe(
|
||||
formats.number_format(
|
||||
"%d" % (int(d)),
|
||||
0,
|
||||
use_l10n=use_l10n,
|
||||
force_grouping=force_grouping,
|
||||
)
|
||||
)
|
||||
|
||||
exp = Decimal(1).scaleb(-abs(p))
|
||||
# Set the precision high enough to avoid an exception (#15789).
|
||||
tupl = d.as_tuple()
|
||||
units = len(tupl[1])
|
||||
units += -tupl[2] if m else tupl[2]
|
||||
prec = abs(p) + units + 1
|
||||
prec = max(getcontext().prec, prec)
|
||||
|
||||
# Avoid conversion to scientific notation by accessing `sign`, `digits`,
|
||||
# and `exponent` from Decimal.as_tuple() directly.
|
||||
rounded_d = d.quantize(exp, ROUND_HALF_UP, Context(prec=prec))
|
||||
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, ".")
|
||||
if sign and rounded_d:
|
||||
digits.append("-")
|
||||
number = "".join(reversed(digits))
|
||||
return mark_safe(
|
||||
formats.number_format(
|
||||
number,
|
||||
abs(p),
|
||||
use_l10n=use_l10n,
|
||||
force_grouping=force_grouping,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def iriencode(value):
|
||||
"""Escape an IRI value for use in a URL."""
|
||||
return iri_to_uri(value)
|
||||
|
||||
|
||||
@register.filter(is_safe=True, needs_autoescape=True)
|
||||
@stringfilter
|
||||
def linenumbers(value, autoescape=True):
|
||||
"""Display text with line numbers."""
|
||||
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))))
|
||||
if not autoescape or isinstance(value, SafeData):
|
||||
for i, line in enumerate(lines):
|
||||
lines[i] = ("%0" + width + "d. %s") % (i + 1, line)
|
||||
else:
|
||||
for i, line in enumerate(lines):
|
||||
lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
|
||||
return mark_safe("\n".join(lines))
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def lower(value):
|
||||
"""Convert a string into all lowercase."""
|
||||
return value.lower()
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
@stringfilter
|
||||
def make_list(value):
|
||||
"""
|
||||
Return the value turned into a list.
|
||||
|
||||
For an integer, it's a list of digits.
|
||||
For a string, it's a list of characters.
|
||||
"""
|
||||
return list(value)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def slugify(value):
|
||||
"""
|
||||
Convert to ASCII. Convert spaces to hyphens. Remove characters that aren't
|
||||
alphanumerics, underscores, or hyphens. Convert to lowercase. Also strip
|
||||
leading and trailing whitespace.
|
||||
"""
|
||||
return _slugify(value)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
def stringformat(value, arg):
|
||||
"""
|
||||
Format the variable according to the arg, a string formatting specifier.
|
||||
|
||||
This specifier uses Python string formatting syntax, with the exception
|
||||
that the leading "%" is dropped.
|
||||
|
||||
See https://docs.python.org/library/stdtypes.html#printf-style-string-formatting
|
||||
for documentation of Python string formatting.
|
||||
"""
|
||||
if isinstance(value, tuple):
|
||||
value = str(value)
|
||||
try:
|
||||
return ("%" + str(arg)) % value
|
||||
except (ValueError, TypeError):
|
||||
return ""
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
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)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def truncatechars(value, arg):
|
||||
"""Truncate a string after `arg` number of characters."""
|
||||
try:
|
||||
length = int(arg)
|
||||
except ValueError: # Invalid literal for int().
|
||||
return value # Fail silently.
|
||||
return Truncator(value).chars(length)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def truncatechars_html(value, arg):
|
||||
"""
|
||||
Truncate HTML after `arg` number of chars.
|
||||
Preserve newlines in the HTML.
|
||||
"""
|
||||
try:
|
||||
length = int(arg)
|
||||
except ValueError: # invalid literal for int()
|
||||
return value # Fail silently.
|
||||
return Truncator(value).chars(length, html=True)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def truncatewords(value, arg):
|
||||
"""
|
||||
Truncate a string after `arg` number of words.
|
||||
Remove newlines within the string.
|
||||
"""
|
||||
try:
|
||||
length = int(arg)
|
||||
except ValueError: # Invalid literal for int().
|
||||
return value # Fail silently.
|
||||
return Truncator(value).words(length, truncate=" …")
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def truncatewords_html(value, arg):
|
||||
"""
|
||||
Truncate HTML after `arg` number of words.
|
||||
Preserve newlines in the HTML.
|
||||
"""
|
||||
try:
|
||||
length = int(arg)
|
||||
except ValueError: # invalid literal for int()
|
||||
return value # Fail silently.
|
||||
return Truncator(value).words(length, html=True, truncate=" …")
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
@stringfilter
|
||||
def upper(value):
|
||||
"""Convert a string into all uppercase."""
|
||||
return value.upper()
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
@stringfilter
|
||||
def urlencode(value, safe=None):
|
||||
"""
|
||||
Escape a value for use in a URL.
|
||||
|
||||
The ``safe`` parameter determines the characters which should not be
|
||||
escaped by Python's quote() function. If not provided, use the default safe
|
||||
characters (but an empty string can be provided when *all* characters
|
||||
should be escaped).
|
||||
"""
|
||||
kwargs = {}
|
||||
if safe is not None:
|
||||
kwargs["safe"] = safe
|
||||
return quote(value, **kwargs)
|
||||
|
||||
|
||||
@register.filter(is_safe=True, needs_autoescape=True)
|
||||
@stringfilter
|
||||
def urlize(value, autoescape=True):
|
||||
"""Convert URLs in plain text into clickable links."""
|
||||
return mark_safe(_urlize(value, nofollow=True, autoescape=autoescape))
|
||||
|
||||
|
||||
@register.filter(is_safe=True, needs_autoescape=True)
|
||||
@stringfilter
|
||||
def urlizetrunc(value, limit, autoescape=True):
|
||||
"""
|
||||
Convert URLs into clickable links, truncating URLs to the given character
|
||||
limit, and adding 'rel=nofollow' attribute to discourage spamming.
|
||||
|
||||
Argument: Length to truncate URLs to.
|
||||
"""
|
||||
return mark_safe(
|
||||
_urlize(value, trim_url_limit=int(limit), nofollow=True, autoescape=autoescape)
|
||||
)
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
@stringfilter
|
||||
def wordcount(value):
|
||||
"""Return the number of words."""
|
||||
return len(value.split())
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def wordwrap(value, arg):
|
||||
"""Wrap words at `arg` line length."""
|
||||
return wrap(value, int(arg))
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def ljust(value, arg):
|
||||
"""Left-align the value in a field of a given width."""
|
||||
return value.ljust(int(arg))
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def rjust(value, arg):
|
||||
"""Right-align the value in a field of a given width."""
|
||||
return value.rjust(int(arg))
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def center(value, arg):
|
||||
"""Center the value in a field of a given width."""
|
||||
return value.center(int(arg))
|
||||
|
||||
|
||||
@register.filter
|
||||
@stringfilter
|
||||
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 != ";":
|
||||
return mark_safe(value)
|
||||
return value
|
||||
|
||||
|
||||
###################
|
||||
# HTML STRINGS #
|
||||
###################
|
||||
|
||||
|
||||
@register.filter("escape", is_safe=True)
|
||||
@stringfilter
|
||||
def escape_filter(value):
|
||||
"""Mark the value as a string that should be auto-escaped."""
|
||||
return conditional_escape(value)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def force_escape(value):
|
||||
"""
|
||||
Escape a string's HTML. Return a new string containing the escaped
|
||||
characters (as opposed to "escape", which marks the content for later
|
||||
possible escaping).
|
||||
"""
|
||||
return escape(value)
|
||||
|
||||
|
||||
@register.filter("linebreaks", is_safe=True, needs_autoescape=True)
|
||||
@stringfilter
|
||||
def linebreaks_filter(value, autoescape=True):
|
||||
"""
|
||||
Replace line breaks in plain text with appropriate HTML; a single
|
||||
newline becomes an HTML line break (``<br>``) and a new line
|
||||
followed by a blank line becomes a paragraph break (``</p>``).
|
||||
"""
|
||||
autoescape = autoescape and not isinstance(value, SafeData)
|
||||
return mark_safe(linebreaks(value, autoescape))
|
||||
|
||||
|
||||
@register.filter(is_safe=True, needs_autoescape=True)
|
||||
@stringfilter
|
||||
def linebreaksbr(value, autoescape=True):
|
||||
"""
|
||||
Convert all newlines in a piece of plain text to HTML line breaks
|
||||
(``<br>``).
|
||||
"""
|
||||
autoescape = autoescape and not isinstance(value, SafeData)
|
||||
value = normalize_newlines(value)
|
||||
if autoescape:
|
||||
value = escape(value)
|
||||
return mark_safe(value.replace("\n", "<br>"))
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def safe(value):
|
||||
"""Mark the value as a string that should not be auto-escaped."""
|
||||
return mark_safe(value)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
def safeseq(value):
|
||||
"""
|
||||
A "safe" filter for sequences. Mark each element in the sequence,
|
||||
individually, as safe, after converting them to strings. Return a list
|
||||
with the results.
|
||||
"""
|
||||
return [mark_safe(obj) for obj in value]
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
@stringfilter
|
||||
def striptags(value):
|
||||
"""Strip all [X]HTML tags."""
|
||||
return strip_tags(value)
|
||||
|
||||
|
||||
###################
|
||||
# LISTS #
|
||||
###################
|
||||
|
||||
|
||||
def _property_resolver(arg):
|
||||
"""
|
||||
When arg is convertible to float, behave like operator.itemgetter(arg)
|
||||
Otherwise, chain __getitem__() and getattr().
|
||||
|
||||
>>> _property_resolver(1)('abc')
|
||||
'b'
|
||||
>>> _property_resolver('1')('abc')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: string indices must be integers
|
||||
>>> class Foo:
|
||||
... a = 42
|
||||
... b = 3.14
|
||||
... c = 'Hey!'
|
||||
>>> _property_resolver('b')(Foo())
|
||||
3.14
|
||||
"""
|
||||
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
|
||||
else:
|
||||
return itemgetter(arg)
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def dictsort(value, arg):
|
||||
"""
|
||||
Given a list of dicts, return that list sorted by the property given in
|
||||
the argument.
|
||||
"""
|
||||
try:
|
||||
return sorted(value, key=_property_resolver(arg))
|
||||
except (AttributeError, TypeError):
|
||||
return ""
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def dictsortreversed(value, arg):
|
||||
"""
|
||||
Given a list of dicts, return that list sorted in reverse order by the
|
||||
property given in the argument.
|
||||
"""
|
||||
try:
|
||||
return sorted(value, key=_property_resolver(arg), reverse=True)
|
||||
except (AttributeError, TypeError):
|
||||
return ""
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def first(value):
|
||||
"""Return the first item in a list."""
|
||||
try:
|
||||
return value[0]
|
||||
except IndexError:
|
||||
return ""
|
||||
|
||||
|
||||
@register.filter(is_safe=True, needs_autoescape=True)
|
||||
def join(value, arg, autoescape=True):
|
||||
"""Join a list with a string, like Python's ``str.join(list)``."""
|
||||
try:
|
||||
if autoescape:
|
||||
value = [conditional_escape(v) for v in value]
|
||||
data = conditional_escape(arg).join(value)
|
||||
except TypeError: # Fail silently if arg isn't iterable.
|
||||
return value
|
||||
return mark_safe(data)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
def last(value):
|
||||
"""Return the last item in a list."""
|
||||
try:
|
||||
return value[-1]
|
||||
except IndexError:
|
||||
return ""
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def length(value):
|
||||
"""Return the length of the value - useful for lists."""
|
||||
try:
|
||||
return len(value)
|
||||
except (ValueError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def length_is(value, arg):
|
||||
"""Return a boolean of whether the value's length is the argument."""
|
||||
warnings.warn(
|
||||
"The length_is template filter is deprecated in favor of the length template "
|
||||
"filter and the == operator within an {% if %} tag.",
|
||||
RemovedInDjango51Warning,
|
||||
)
|
||||
try:
|
||||
return len(value) == int(arg)
|
||||
except (ValueError, TypeError):
|
||||
return ""
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
def random(value):
|
||||
"""Return a random item from the list."""
|
||||
return random_module.choice(value)
|
||||
|
||||
|
||||
@register.filter("slice", is_safe=True)
|
||||
def slice_filter(value, arg):
|
||||
"""
|
||||
Return a slice of the list using the same syntax as Python's list slicing.
|
||||
"""
|
||||
try:
|
||||
bits = []
|
||||
for x in str(arg).split(":"):
|
||||
if not x:
|
||||
bits.append(None)
|
||||
else:
|
||||
bits.append(int(x))
|
||||
return value[slice(*bits)]
|
||||
|
||||
except (ValueError, TypeError):
|
||||
return value # Fail silently.
|
||||
|
||||
|
||||
@register.filter(is_safe=True, needs_autoescape=True)
|
||||
def unordered_list(value, autoescape=True):
|
||||
"""
|
||||
Recursively take a self-nested list and return an HTML unordered list --
|
||||
WITHOUT opening and closing <ul> tags.
|
||||
|
||||
Assume the list is in the proper format. For example, if ``var`` contains:
|
||||
``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``, then
|
||||
``{{ var|unordered_list }}`` returns::
|
||||
|
||||
<li>States
|
||||
<ul>
|
||||
<li>Kansas
|
||||
<ul>
|
||||
<li>Lawrence</li>
|
||||
<li>Topeka</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Illinois</li>
|
||||
</ul>
|
||||
</li>
|
||||
"""
|
||||
if autoescape:
|
||||
escaper = conditional_escape
|
||||
else:
|
||||
|
||||
def escaper(x):
|
||||
return x
|
||||
|
||||
def walk_items(item_list):
|
||||
item_iterator = iter(item_list)
|
||||
try:
|
||||
item = next(item_iterator)
|
||||
while True:
|
||||
try:
|
||||
next_item = next(item_iterator)
|
||||
except StopIteration:
|
||||
yield item, None
|
||||
break
|
||||
if isinstance(next_item, (list, tuple, types.GeneratorType)):
|
||||
try:
|
||||
iter(next_item)
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
yield item, next_item
|
||||
item = next(item_iterator)
|
||||
continue
|
||||
yield item, None
|
||||
item = next_item
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
def list_formatter(item_list, tabs=1):
|
||||
indent = "\t" * tabs
|
||||
output = []
|
||||
for item, children in walk_items(item_list):
|
||||
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)
|
||||
|
||||
return mark_safe(list_formatter(value))
|
||||
|
||||
|
||||
###################
|
||||
# INTEGERS #
|
||||
###################
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def add(value, arg):
|
||||
"""Add the arg to the value."""
|
||||
try:
|
||||
return int(value) + int(arg)
|
||||
except (ValueError, TypeError):
|
||||
try:
|
||||
return value + arg
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def get_digit(value, arg):
|
||||
"""
|
||||
Given a whole number, return the requested digit of it, where 1 is the
|
||||
right-most digit, 2 is the second-right-most digit, etc. Return the
|
||||
original value for invalid input (if input or argument is not an integer,
|
||||
or if argument is less than 1). Otherwise, output is always an integer.
|
||||
"""
|
||||
try:
|
||||
arg = int(arg)
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
return value # Fail silently for an invalid argument
|
||||
if arg < 1:
|
||||
return value
|
||||
try:
|
||||
return int(str(value)[-arg])
|
||||
except IndexError:
|
||||
return 0
|
||||
|
||||
|
||||
###################
|
||||
# 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 ""
|
||||
try:
|
||||
return formats.date_format(value, arg)
|
||||
except AttributeError:
|
||||
try:
|
||||
return format(value, arg)
|
||||
except AttributeError:
|
||||
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 ""
|
||||
try:
|
||||
return formats.time_format(value, arg)
|
||||
except (AttributeError, TypeError):
|
||||
try:
|
||||
return time_format(value, arg)
|
||||
except (AttributeError, TypeError):
|
||||
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 ""
|
||||
try:
|
||||
if arg:
|
||||
return timesince(value, arg)
|
||||
return timesince(value)
|
||||
except (ValueError, TypeError):
|
||||
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 ""
|
||||
try:
|
||||
return timeuntil(value, arg)
|
||||
except (ValueError, TypeError):
|
||||
return ""
|
||||
|
||||
|
||||
###################
|
||||
# LOGIC #
|
||||
###################
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def default(value, arg):
|
||||
"""If value is unavailable, use given default."""
|
||||
return value or arg
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def default_if_none(value, arg):
|
||||
"""If value is None, use given default."""
|
||||
if value is None:
|
||||
return arg
|
||||
return value
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def divisibleby(value, arg):
|
||||
"""Return True if the value is divisible by the argument."""
|
||||
return int(value) % int(arg) == 0
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
def yesno(value, arg=None):
|
||||
"""
|
||||
Given a string mapping values for true, false, and (optionally) None,
|
||||
return one of those strings according to the value:
|
||||
|
||||
========== ====================== ==================================
|
||||
Value Argument Outputs
|
||||
========== ====================== ==================================
|
||||
``True`` ``"yeah,no,maybe"`` ``yeah``
|
||||
``False`` ``"yeah,no,maybe"`` ``no``
|
||||
``None`` ``"yeah,no,maybe"`` ``maybe``
|
||||
``None`` ``"yeah,no"`` ``"no"`` (converts None to False
|
||||
if no mapping for None is given.
|
||||
========== ====================== ==================================
|
||||
"""
|
||||
if arg is None:
|
||||
# Translators: Please do not add spaces around commas.
|
||||
arg = gettext("yes,no,maybe")
|
||||
bits = arg.split(",")
|
||||
if len(bits) < 2:
|
||||
return value # Invalid arg.
|
||||
try:
|
||||
yes, no, maybe = bits
|
||||
except ValueError:
|
||||
# Unpack list of wrong size (no "maybe" value provided).
|
||||
yes, no, maybe = bits[0], bits[1], bits[1]
|
||||
if value is None:
|
||||
return maybe
|
||||
if value:
|
||||
return yes
|
||||
return no
|
||||
|
||||
|
||||
###################
|
||||
# MISC #
|
||||
###################
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
def filesizeformat(bytes_):
|
||||
"""
|
||||
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
|
||||
102 bytes, etc.).
|
||||
"""
|
||||
try:
|
||||
bytes_ = int(bytes_)
|
||||
except (TypeError, ValueError, UnicodeDecodeError):
|
||||
value = ngettext("%(size)d byte", "%(size)d bytes", 0) % {"size": 0}
|
||||
return avoid_wrapping(value)
|
||||
|
||||
def filesize_number_format(value):
|
||||
return formats.number_format(round(value, 1), 1)
|
||||
|
||||
KB = 1 << 10
|
||||
MB = 1 << 20
|
||||
GB = 1 << 30
|
||||
TB = 1 << 40
|
||||
PB = 1 << 50
|
||||
|
||||
negative = bytes_ < 0
|
||||
if negative:
|
||||
bytes_ = -bytes_ # Allow formatting of negative numbers.
|
||||
|
||||
if bytes_ < KB:
|
||||
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:
|
||||
value = gettext("%s MB") % filesize_number_format(bytes_ / MB)
|
||||
elif bytes_ < TB:
|
||||
value = gettext("%s GB") % filesize_number_format(bytes_ / GB)
|
||||
elif bytes_ < PB:
|
||||
value = gettext("%s TB") % filesize_number_format(bytes_ / TB)
|
||||
else:
|
||||
value = gettext("%s PB") % filesize_number_format(bytes_ / PB)
|
||||
|
||||
if negative:
|
||||
value = "-%s" % value
|
||||
return avoid_wrapping(value)
|
||||
|
||||
|
||||
@register.filter(is_safe=False)
|
||||
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:
|
||||
|
||||
* If value is 0, vote{{ value|pluralize }} display "votes".
|
||||
* If value is 1, vote{{ value|pluralize }} display "vote".
|
||||
* If value is 2, vote{{ value|pluralize }} display "votes".
|
||||
|
||||
If an argument is provided, use that string instead:
|
||||
|
||||
* If value is 0, class{{ value|pluralize:"es" }} display "classes".
|
||||
* If value is 1, class{{ value|pluralize:"es" }} display "class".
|
||||
* If value is 2, class{{ value|pluralize:"es" }} display "classes".
|
||||
|
||||
If the provided argument contains a comma, use the text before the comma
|
||||
for the singular case and the text after the comma for the plural case:
|
||||
|
||||
* If value is 0, cand{{ value|pluralize:"y,ies" }} display "candies".
|
||||
* 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 len(bits) > 2:
|
||||
return ""
|
||||
singular_suffix, plural_suffix = bits[:2]
|
||||
|
||||
try:
|
||||
return singular_suffix if float(value) == 1 else plural_suffix
|
||||
except ValueError: # Invalid string that's not a number.
|
||||
pass
|
||||
except TypeError: # Value isn't a string or a number; maybe it's a list?
|
||||
try:
|
||||
return singular_suffix if len(value) == 1 else plural_suffix
|
||||
except TypeError: # len() of unsized object.
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
@register.filter("phone2numeric", is_safe=True)
|
||||
def phone2numeric_filter(value):
|
||||
"""Take a phone number and converts it in to its numerical equivalent."""
|
||||
return phone2numeric(value)
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
def pprint(value):
|
||||
"""A wrapper around pprint.pprint -- for debugging, really."""
|
||||
try:
|
||||
return pformat(value)
|
||||
except Exception as e:
|
||||
return "Error in formatting: %s: %s" % (e.__class__.__name__, e)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,212 @@
|
||||
import functools
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
from .base import Template
|
||||
from .context import Context, _builtin_context_processors
|
||||
from .exceptions import TemplateDoesNotExist
|
||||
from .library import import_library
|
||||
|
||||
|
||||
class Engine:
|
||||
default_builtins = [
|
||||
"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,
|
||||
):
|
||||
if dirs is None:
|
||||
dirs = []
|
||||
if context_processors is None:
|
||||
context_processors = []
|
||||
if loaders is None:
|
||||
loaders = ["django.template.loaders.filesystem.Loader"]
|
||||
if app_dirs:
|
||||
loaders += ["django.template.loaders.app_directories.Loader"]
|
||||
loaders = [("django.template.loaders.cached.Loader", loaders)]
|
||||
else:
|
||||
if app_dirs:
|
||||
raise ImproperlyConfigured(
|
||||
"app_dirs must not be set when loaders is defined."
|
||||
)
|
||||
if libraries is None:
|
||||
libraries = {}
|
||||
if builtins is None:
|
||||
builtins = []
|
||||
|
||||
self.dirs = dirs
|
||||
self.app_dirs = app_dirs
|
||||
self.autoescape = autoescape
|
||||
self.context_processors = context_processors
|
||||
self.debug = debug
|
||||
self.loaders = loaders
|
||||
self.string_if_invalid = string_if_invalid
|
||||
self.file_charset = file_charset
|
||||
self.libraries = libraries
|
||||
self.template_libraries = self.get_template_libraries(libraries)
|
||||
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():
|
||||
"""
|
||||
Return the first DjangoTemplates backend that's configured, or raise
|
||||
ImproperlyConfigured if none are configured.
|
||||
|
||||
This is required for preserving historical APIs that rely on a
|
||||
globally available, implicitly configured engine such as:
|
||||
|
||||
>>> from django.template import Context, Template
|
||||
>>> template = Template("Hello {{ name }}!")
|
||||
>>> context = Context({'name': "world"})
|
||||
>>> template.render(context)
|
||||
'Hello world!'
|
||||
"""
|
||||
# Since Engine is imported in django.template and since
|
||||
# DjangoTemplates is a wrapper around this Engine class,
|
||||
# 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.")
|
||||
|
||||
@cached_property
|
||||
def template_context_processors(self):
|
||||
context_processors = _builtin_context_processors
|
||||
context_processors += tuple(self.context_processors)
|
||||
return tuple(import_string(path) for path in context_processors)
|
||||
|
||||
def get_template_builtins(self, builtins):
|
||||
return [import_library(x) for x in builtins]
|
||||
|
||||
def get_template_libraries(self, libraries):
|
||||
loaded = {}
|
||||
for name, path in libraries.items():
|
||||
loaded[name] = import_library(path)
|
||||
return loaded
|
||||
|
||||
@cached_property
|
||||
def template_loaders(self):
|
||||
return self.get_template_loaders(self.loaders)
|
||||
|
||||
def get_template_loaders(self, template_loaders):
|
||||
loaders = []
|
||||
for template_loader in template_loaders:
|
||||
loader = self.find_template_loader(template_loader)
|
||||
if loader is not None:
|
||||
loaders.append(loader)
|
||||
return loaders
|
||||
|
||||
def find_template_loader(self, loader):
|
||||
if isinstance(loader, (tuple, list)):
|
||||
loader, *args = loader
|
||||
else:
|
||||
args = []
|
||||
|
||||
if isinstance(loader, str):
|
||||
loader_class = import_string(loader)
|
||||
return loader_class(self, *args)
|
||||
else:
|
||||
raise ImproperlyConfigured(
|
||||
"Invalid value in template loaders configuration: %r" % loader
|
||||
)
|
||||
|
||||
def find_template(self, name, dirs=None, skip=None):
|
||||
tried = []
|
||||
for loader in self.template_loaders:
|
||||
try:
|
||||
template = loader.get_template(name, skip=skip)
|
||||
return template, template.origin
|
||||
except TemplateDoesNotExist as e:
|
||||
tried.extend(e.tried)
|
||||
raise TemplateDoesNotExist(name, tried=tried)
|
||||
|
||||
def from_string(self, template_code):
|
||||
"""
|
||||
Return a compiled Template object for the given template code,
|
||||
handling template inheritance recursively.
|
||||
"""
|
||||
return Template(template_code, engine=self)
|
||||
|
||||
def get_template(self, template_name):
|
||||
"""
|
||||
Return a compiled Template object for the given template name,
|
||||
handling template inheritance recursively.
|
||||
"""
|
||||
template, origin = self.find_template(template_name)
|
||||
if not hasattr(template, "render"):
|
||||
# template needs to be compiled
|
||||
template = Template(template, origin, template_name, engine=self)
|
||||
return template
|
||||
|
||||
def render_to_string(self, template_name, context=None):
|
||||
"""
|
||||
Render the template specified by template_name with the given context.
|
||||
For use in Django's test suite.
|
||||
"""
|
||||
if isinstance(template_name, (list, tuple)):
|
||||
t = self.select_template(template_name)
|
||||
else:
|
||||
t = self.get_template(template_name)
|
||||
# Django < 1.8 accepted a Context in `context` even though that's
|
||||
# unintended. Preserve this ability but don't rewrap `context`.
|
||||
if isinstance(context, Context):
|
||||
return t.render(context)
|
||||
else:
|
||||
return t.render(Context(context, autoescape=self.autoescape))
|
||||
|
||||
def select_template(self, template_name_list):
|
||||
"""
|
||||
Given a list of template names, return the first that can be loaded.
|
||||
"""
|
||||
if not template_name_list:
|
||||
raise TemplateDoesNotExist("No template names provided")
|
||||
not_found = []
|
||||
for template_name in template_name_list:
|
||||
try:
|
||||
return self.get_template(template_name)
|
||||
except TemplateDoesNotExist as exc:
|
||||
if exc.args[0] not in not_found:
|
||||
not_found.append(exc.args[0])
|
||||
continue
|
||||
# If we get here, none of the templates could be loaded
|
||||
raise TemplateDoesNotExist(", ".join(not_found))
|
@ -0,0 +1,44 @@
|
||||
"""
|
||||
This module contains generic exceptions used by template backends. Although,
|
||||
due to historical reasons, the Django template language also internally uses
|
||||
these exceptions, other exceptions specific to the DTL should not be added
|
||||
here.
|
||||
"""
|
||||
|
||||
|
||||
class TemplateDoesNotExist(Exception):
|
||||
"""
|
||||
The exception used when a template does not exist. Optional arguments:
|
||||
|
||||
backend
|
||||
The template backend class used when raising this exception.
|
||||
|
||||
tried
|
||||
A list of sources that were tried when finding the template. This
|
||||
is formatted as a list of tuples containing (origin, status), where
|
||||
origin is an Origin object or duck type and status is a string with the
|
||||
reason the template wasn't found.
|
||||
|
||||
chain
|
||||
A list of intermediate TemplateDoesNotExist exceptions. This is used to
|
||||
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:
|
||||
tried = []
|
||||
self.tried = tried
|
||||
if chain is None:
|
||||
chain = []
|
||||
self.chain = chain
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class TemplateSyntaxError(Exception):
|
||||
"""
|
||||
The exception used for syntax errors during parsing or rendering.
|
||||
"""
|
||||
|
||||
pass
|
@ -0,0 +1,385 @@
|
||||
from functools import wraps
|
||||
from importlib import import_module
|
||||
from inspect import getfullargspec, unwrap
|
||||
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.itercompat import is_iterable
|
||||
|
||||
from .base import Node, Template, token_kwargs
|
||||
from .exceptions import TemplateSyntaxError
|
||||
|
||||
|
||||
class InvalidTemplateLibrary(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Library:
|
||||
"""
|
||||
A class for registering template tags and filters. Compiled filter and
|
||||
template tag functions are stored in the filters and tags attributes.
|
||||
The filter, simple_tag, and inclusion_tag methods provide a convenient
|
||||
way to register callables as tags.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.filters = {}
|
||||
self.tags = {}
|
||||
|
||||
def tag(self, name=None, compile_function=None):
|
||||
if name is None and compile_function is None:
|
||||
# @register.tag()
|
||||
return self.tag_function
|
||||
elif name is not None and compile_function is None:
|
||||
if callable(name):
|
||||
# @register.tag
|
||||
return self.tag_function(name)
|
||||
else:
|
||||
# @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)
|
||||
self.tags[name] = compile_function
|
||||
return compile_function
|
||||
else:
|
||||
raise ValueError(
|
||||
"Unsupported arguments to Library.tag: (%r, %r)"
|
||||
% (name, compile_function),
|
||||
)
|
||||
|
||||
def tag_function(self, func):
|
||||
self.tags[func.__name__] = func
|
||||
return func
|
||||
|
||||
def filter(self, name=None, filter_func=None, **flags):
|
||||
"""
|
||||
Register a callable as a template filter. Example:
|
||||
|
||||
@register.filter
|
||||
def lower(value):
|
||||
return value.lower()
|
||||
"""
|
||||
if name is None and filter_func is None:
|
||||
# @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):
|
||||
# @register.filter
|
||||
return self.filter_function(name, **flags)
|
||||
else:
|
||||
# @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"):
|
||||
if attr in flags:
|
||||
value = flags[attr]
|
||||
# set the flag on the filter for FilterExpression.resolve
|
||||
setattr(filter_func, attr, value)
|
||||
# set the flag on the innermost decorated function
|
||||
# for decorators that need it, e.g. stringfilter
|
||||
setattr(unwrap(filter_func), attr, value)
|
||||
filter_func._filter_name = name
|
||||
return filter_func
|
||||
else:
|
||||
raise ValueError(
|
||||
"Unsupported arguments to Library.filter: (%r, %r)"
|
||||
% (name, filter_func),
|
||||
)
|
||||
|
||||
def filter_function(self, func, **flags):
|
||||
return self.filter(func.__name__, func, **flags)
|
||||
|
||||
def simple_tag(self, func=None, takes_context=None, name=None):
|
||||
"""
|
||||
Register a callable as a compiled template tag. Example:
|
||||
|
||||
@register.simple_tag
|
||||
def hello(*args, **kwargs):
|
||||
return 'world'
|
||||
"""
|
||||
|
||||
def dec(func):
|
||||
(
|
||||
params,
|
||||
varargs,
|
||||
varkw,
|
||||
defaults,
|
||||
kwonly,
|
||||
kwonly_defaults,
|
||||
_,
|
||||
) = getfullargspec(unwrap(func))
|
||||
function_name = name or func.__name__
|
||||
|
||||
@wraps(func)
|
||||
def compile_func(parser, token):
|
||||
bits = token.split_contents()[1:]
|
||||
target_var = None
|
||||
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,
|
||||
)
|
||||
return SimpleNode(func, takes_context, args, kwargs, target_var)
|
||||
|
||||
self.tag(function_name, compile_func)
|
||||
return func
|
||||
|
||||
if func is None:
|
||||
# @register.simple_tag(...)
|
||||
return dec
|
||||
elif callable(func):
|
||||
# @register.simple_tag
|
||||
return dec(func)
|
||||
else:
|
||||
raise ValueError("Invalid arguments provided to simple_tag")
|
||||
|
||||
def inclusion_tag(self, filename, func=None, takes_context=None, name=None):
|
||||
"""
|
||||
Register a callable as an inclusion tag:
|
||||
|
||||
@register.inclusion_tag('results.html')
|
||||
def show_results(poll):
|
||||
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 func.__name__
|
||||
|
||||
@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,
|
||||
)
|
||||
return InclusionNode(
|
||||
func,
|
||||
takes_context,
|
||||
args,
|
||||
kwargs,
|
||||
filename,
|
||||
)
|
||||
|
||||
self.tag(function_name, compile_func)
|
||||
return func
|
||||
|
||||
return dec
|
||||
|
||||
|
||||
class TagHelperNode(Node):
|
||||
"""
|
||||
Base class for tag helper nodes such as SimpleNode and InclusionNode.
|
||||
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
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def get_resolved_arguments(self, context):
|
||||
resolved_args = [var.resolve(context) for var in self.args]
|
||||
if self.takes_context:
|
||||
resolved_args = [context] + resolved_args
|
||||
resolved_kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()}
|
||||
return resolved_args, resolved_kwargs
|
||||
|
||||
|
||||
class SimpleNode(TagHelperNode):
|
||||
child_nodelists = ()
|
||||
|
||||
def __init__(self, func, takes_context, args, kwargs, target_var):
|
||||
super().__init__(func, takes_context, args, kwargs)
|
||||
self.target_var = target_var
|
||||
|
||||
def render(self, context):
|
||||
resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
|
||||
output = self.func(*resolved_args, **resolved_kwargs)
|
||||
if self.target_var is not None:
|
||||
context[self.target_var] = output
|
||||
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
|
||||
|
||||
def render(self, context):
|
||||
"""
|
||||
Render the specified template and context. Cache the template object
|
||||
in render_context to avoid reparsing and loading when used in a for
|
||||
loop.
|
||||
"""
|
||||
resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
|
||||
_dict = self.func(*resolved_args, **resolved_kwargs)
|
||||
|
||||
t = context.render_context.get(self)
|
||||
if t is None:
|
||||
if isinstance(self.filename, Template):
|
||||
t = self.filename
|
||||
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)
|
||||
else:
|
||||
t = context.template.engine.get_template(self.filename)
|
||||
context.render_context[self] = t
|
||||
new_context = context.new(_dict)
|
||||
# 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")
|
||||
if csrf_token is not None:
|
||||
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,
|
||||
):
|
||||
"""
|
||||
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":
|
||||
params = params[1:]
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
"'%s' is decorated with takes_context=True so it must "
|
||||
"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
|
||||
]
|
||||
for bit in bits:
|
||||
# First we try to extract a potential kwarg from the bit
|
||||
kwarg = token_kwargs([bit], parser)
|
||||
if kwarg:
|
||||
# The kwarg was successfully extracted
|
||||
param, value = kwarg.popitem()
|
||||
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)
|
||||
)
|
||||
elif param in kwargs:
|
||||
# The keyword argument has already been supplied once
|
||||
raise TemplateSyntaxError(
|
||||
"'%s' received multiple values for keyword argument '%s'"
|
||||
% (name, param)
|
||||
)
|
||||
else:
|
||||
# All good, record the keyword argument
|
||||
kwargs[str(param)] = value
|
||||
if param in unhandled_params:
|
||||
# If using the keyword syntax for a positional arg, then
|
||||
# consume it.
|
||||
unhandled_params.remove(param)
|
||||
elif param in unhandled_kwargs:
|
||||
# Same for keyword-only arguments
|
||||
unhandled_kwargs.remove(param)
|
||||
else:
|
||||
if kwargs:
|
||||
raise TemplateSyntaxError(
|
||||
"'%s' received some positional argument(s) after some "
|
||||
"keyword argument(s)" % name
|
||||
)
|
||||
else:
|
||||
# Record the positional argument
|
||||
args.append(parser.compile_filter(bit))
|
||||
try:
|
||||
# Consume from the list of expected positional arguments
|
||||
unhandled_params.pop(0)
|
||||
except IndexError:
|
||||
if varargs is None:
|
||||
raise TemplateSyntaxError(
|
||||
"'%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)]
|
||||
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))
|
||||
)
|
||||
return args, kwargs
|
||||
|
||||
|
||||
def import_library(name):
|
||||
"""
|
||||
Load a Library object from a template tag module.
|
||||
"""
|
||||
try:
|
||||
module = import_module(name)
|
||||
except ImportError as e:
|
||||
raise InvalidTemplateLibrary(
|
||||
"Invalid template library specified. ImportError raised when "
|
||||
"trying to load '%s': %s" % (name, e)
|
||||
)
|
||||
try:
|
||||
return module.register
|
||||
except AttributeError:
|
||||
raise InvalidTemplateLibrary(
|
||||
"Module %s does not have a variable named 'register'" % name,
|
||||
)
|
@ -0,0 +1,66 @@
|
||||
from . import engines
|
||||
from .exceptions import TemplateDoesNotExist
|
||||
|
||||
|
||||
def get_template(template_name, using=None):
|
||||
"""
|
||||
Load and return a template for the given name.
|
||||
|
||||
Raise TemplateDoesNotExist if no such template exists.
|
||||
"""
|
||||
chain = []
|
||||
engines = _engine_list(using)
|
||||
for engine in engines:
|
||||
try:
|
||||
return engine.get_template(template_name)
|
||||
except TemplateDoesNotExist as e:
|
||||
chain.append(e)
|
||||
|
||||
raise TemplateDoesNotExist(template_name, chain=chain)
|
||||
|
||||
|
||||
def select_template(template_name_list, using=None):
|
||||
"""
|
||||
Load and return a template for one of the given names.
|
||||
|
||||
Try names in order and return the first template found.
|
||||
|
||||
Raise TemplateDoesNotExist if no such template exists.
|
||||
"""
|
||||
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
|
||||
)
|
||||
|
||||
chain = []
|
||||
engines = _engine_list(using)
|
||||
for template_name in template_name_list:
|
||||
for engine in engines:
|
||||
try:
|
||||
return engine.get_template(template_name)
|
||||
except TemplateDoesNotExist as e:
|
||||
chain.append(e)
|
||||
|
||||
if template_name_list:
|
||||
raise TemplateDoesNotExist(", ".join(template_name_list), chain=chain)
|
||||
else:
|
||||
raise TemplateDoesNotExist("No template names provided")
|
||||
|
||||
|
||||
def render_to_string(template_name, context=None, request=None, using=None):
|
||||
"""
|
||||
Load a template and render it with a context. Return a string.
|
||||
|
||||
template_name may be a string or a list of strings.
|
||||
"""
|
||||
if isinstance(template_name, (list, tuple)):
|
||||
template = select_template(template_name, using=using)
|
||||
else:
|
||||
template = get_template(template_name, using=using)
|
||||
return template.render(context, request)
|
||||
|
||||
|
||||
def _engine_list(using=None):
|
||||
return engines.all() if using is None else [engines[using]]
|
@ -0,0 +1,352 @@
|
||||
import posixpath
|
||||
from collections import defaultdict
|
||||
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from .base import Node, Template, TemplateSyntaxError, TextNode, Variable, token_kwargs
|
||||
from .library import Library
|
||||
|
||||
register = Library()
|
||||
|
||||
BLOCK_CONTEXT_KEY = "block_context"
|
||||
|
||||
|
||||
class BlockContext:
|
||||
def __init__(self):
|
||||
# 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)
|
||||
|
||||
def pop(self, name):
|
||||
try:
|
||||
return self.blocks[name].pop()
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def push(self, name, block):
|
||||
self.blocks[name].append(block)
|
||||
|
||||
def get_block(self, name):
|
||||
try:
|
||||
return self.blocks[name][-1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
|
||||
class BlockNode(Node):
|
||||
def __init__(self, name, nodelist, parent=None):
|
||||
self.name, self.nodelist, self.parent = name, nodelist, parent
|
||||
|
||||
def __repr__(self):
|
||||
return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
|
||||
|
||||
def render(self, context):
|
||||
block_context = context.render_context.get(BLOCK_CONTEXT_KEY)
|
||||
with context.push():
|
||||
if block_context is None:
|
||||
context["block"] = self
|
||||
result = self.nodelist.render(context)
|
||||
else:
|
||||
push = block = block_context.pop(self.name)
|
||||
if block is None:
|
||||
block = self
|
||||
# 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
|
||||
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"):
|
||||
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
|
||||
):
|
||||
return mark_safe(self.render(self.context))
|
||||
return ""
|
||||
|
||||
|
||||
class ExtendsNode(Node):
|
||||
must_be_first = True
|
||||
context_key = "extends_context"
|
||||
|
||||
def __init__(self, nodelist, parent_name, template_dirs=None):
|
||||
self.nodelist = nodelist
|
||||
self.parent_name = parent_name
|
||||
self.template_dirs = template_dirs
|
||||
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)
|
||||
|
||||
def find_template(self, template_name, context):
|
||||
"""
|
||||
This is a wrapper around engine.find_template(). A history is kept in
|
||||
the render_context attribute between successive extends calls and
|
||||
passed as the skip argument. This enables extends to work recursively
|
||||
without extending the same template twice.
|
||||
"""
|
||||
history = context.render_context.setdefault(
|
||||
self.context_key,
|
||||
[self.origin],
|
||||
)
|
||||
template, origin = context.template.engine.find_template(
|
||||
template_name,
|
||||
skip=history,
|
||||
)
|
||||
history.append(origin)
|
||||
return template
|
||||
|
||||
def get_parent(self, context):
|
||||
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
|
||||
)
|
||||
raise TemplateSyntaxError(error_msg)
|
||||
if isinstance(parent, Template):
|
||||
# parent is a django.template.Template
|
||||
return parent
|
||||
if isinstance(getattr(parent, "template", None), Template):
|
||||
# parent is a django.template.backends.django.Template
|
||||
return parent.template
|
||||
return self.find_template(parent, context)
|
||||
|
||||
def render(self, context):
|
||||
compiled_parent = self.get_parent(context)
|
||||
|
||||
if BLOCK_CONTEXT_KEY not in context.render_context:
|
||||
context.render_context[BLOCK_CONTEXT_KEY] = BlockContext()
|
||||
block_context = context.render_context[BLOCK_CONTEXT_KEY]
|
||||
|
||||
# Add the block nodes from this node to the block context
|
||||
block_context.add_blocks(self.blocks)
|
||||
|
||||
# If this block's parent doesn't have an extends node it is the root,
|
||||
# and its block nodes also need to be added to the block context.
|
||||
for node in compiled_parent.nodelist:
|
||||
# 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)
|
||||
}
|
||||
block_context.add_blocks(blocks)
|
||||
break
|
||||
|
||||
# Call Template._render explicitly so the parser context stays
|
||||
# the same.
|
||||
with context.render_context.push_state(compiled_parent, isolated_context=False):
|
||||
return compiled_parent._render(context)
|
||||
|
||||
|
||||
class IncludeNode(Node):
|
||||
context_key = "__include_context"
|
||||
|
||||
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
|
||||
in render_context to avoid reparsing and loading when used in a for
|
||||
loop.
|
||||
"""
|
||||
template = self.template.resolve(context)
|
||||
# Does this quack like a Template?
|
||||
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,
|
||||
),
|
||||
)
|
||||
else:
|
||||
template_name = tuple(template_name)
|
||||
cache = context.render_context.dicts[0].setdefault(self, {})
|
||||
template = cache.get(template_name)
|
||||
if template is None:
|
||||
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"):
|
||||
template = template.template
|
||||
values = {
|
||||
name: var.resolve(context) for name, var in self.extra_context.items()
|
||||
}
|
||||
if self.isolated_context:
|
||||
return template.render(context.new(values))
|
||||
with context.push(**values):
|
||||
return template.render(context)
|
||||
|
||||
|
||||
@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.
|
||||
bits = token.contents.split()
|
||||
if len(bits) != 2:
|
||||
raise TemplateSyntaxError("'%s' tag takes only one argument" % bits[0])
|
||||
block_name = bits[1]
|
||||
# Keep track of the names of BlockNodes found in this template, so we can
|
||||
# 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)
|
||||
)
|
||||
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",))
|
||||
|
||||
# This check is kept for backwards-compatibility. See #3100.
|
||||
endblock = parser.next_token()
|
||||
acceptable_endblocks = ("endblock", "endblock %s" % block_name)
|
||||
if endblock.contents not in acceptable_endblocks:
|
||||
parser.invalid_block_tag(endblock, "endblock", acceptable_endblocks)
|
||||
|
||||
return BlockNode(block_name, nodelist)
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
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("/")),
|
||||
new_name,
|
||||
)
|
||||
)
|
||||
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:
|
||||
raise TemplateSyntaxError(
|
||||
"The relative path '%s' was translated to template name '%s', the "
|
||||
"same template in which the tag appears."
|
||||
% (relative_name, current_template_name)
|
||||
)
|
||||
has_quotes = (
|
||||
relative_name.startswith(('"', "'")) and relative_name[0] == relative_name[-1]
|
||||
)
|
||||
return f'"{new_name}"' if has_quotes else new_name
|
||||
|
||||
|
||||
@register.tag("extends")
|
||||
def do_extends(parser, token):
|
||||
"""
|
||||
Signal that this template extends a parent template.
|
||||
|
||||
This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
|
||||
uses the literal value "base" as the name of the parent template to extend,
|
||||
or ``{% extends variable %}`` uses the value of ``variable`` as either the
|
||||
name of the parent template to extend (if it evaluates to a string) or as
|
||||
the parent template itself (if it evaluates to a Template object).
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) != 2:
|
||||
raise TemplateSyntaxError("'%s' takes one argument" % bits[0])
|
||||
bits[1] = construct_relative_path(parser.origin.template_name, bits[1])
|
||||
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]
|
||||
)
|
||||
return ExtendsNode(nodelist, parent_name)
|
||||
|
||||
|
||||
@register.tag("include")
|
||||
def do_include(parser, token):
|
||||
"""
|
||||
Load a template and render it with the current context. You can pass
|
||||
additional context using keyword arguments.
|
||||
|
||||
Example::
|
||||
|
||||
{% include "foo/some_include" %}
|
||||
{% include "foo/some_include" with bar="BAZZ!" baz="BING!" %}
|
||||
|
||||
Use the ``only`` argument to exclude the current context when rendering
|
||||
the included template::
|
||||
|
||||
{% include "foo/some_include" only %}
|
||||
{% include "foo/some_include" with bar="1" only %}
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) < 2:
|
||||
raise TemplateSyntaxError(
|
||||
"%r tag takes at least one argument: the name of the template to "
|
||||
"be included." % bits[0]
|
||||
)
|
||||
options = {}
|
||||
remaining_bits = bits[2:]
|
||||
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":
|
||||
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":
|
||||
value = True
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
"Unknown argument for %r tag: %r." % (bits[0], option)
|
||||
)
|
||||
options[option] = value
|
||||
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,
|
||||
)
|
@ -0,0 +1,13 @@
|
||||
"""
|
||||
Wrapper for loading templates from "templates" directories in INSTALLED_APPS
|
||||
packages.
|
||||
"""
|
||||
|
||||
from django.template.utils import get_app_template_dirs
|
||||
|
||||
from .filesystem import Loader as FilesystemLoader
|
||||
|
||||
|
||||
class Loader(FilesystemLoader):
|
||||
def get_dirs(self):
|
||||
return get_app_template_dirs("templates")
|
@ -0,0 +1,51 @@
|
||||
from django.template import Template, TemplateDoesNotExist
|
||||
|
||||
|
||||
class Loader:
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
|
||||
def get_template(self, template_name, skip=None):
|
||||
"""
|
||||
Call self.get_template_sources() and return a Template object for
|
||||
the first template matching template_name. If skip is provided, ignore
|
||||
template origins in skip. This is used to avoid recursion during
|
||||
template extending.
|
||||
"""
|
||||
tried = []
|
||||
|
||||
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"))
|
||||
continue
|
||||
|
||||
try:
|
||||
contents = self.get_contents(origin)
|
||||
except TemplateDoesNotExist:
|
||||
tried.append((origin, "Source does not exist"))
|
||||
continue
|
||||
else:
|
||||
return Template(
|
||||
contents,
|
||||
origin,
|
||||
origin.template_name,
|
||||
self.engine,
|
||||
)
|
||||
|
||||
raise TemplateDoesNotExist(template_name, tried=tried)
|
||||
|
||||
def get_template_sources(self, template_name):
|
||||
"""
|
||||
An iterator that yields possible matching template paths for a
|
||||
template name.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"subclasses of Loader must provide a get_template_sources() method"
|
||||
)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Reset any state maintained by the loader instance (e.g. cached
|
||||
templates or cached loader modules).
|
||||
"""
|
||||
pass
|
@ -0,0 +1,100 @@
|
||||
"""
|
||||
Wrapper class that takes a list of template loaders as an argument and attempts
|
||||
to load templates from them in order, caching the result.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
|
||||
from django.template import TemplateDoesNotExist
|
||||
from django.template.backends.django import copy_exception
|
||||
|
||||
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)
|
||||
super().__init__(engine)
|
||||
|
||||
def get_dirs(self):
|
||||
for loader in self.loaders:
|
||||
if hasattr(loader, "get_dirs"):
|
||||
yield from loader.get_dirs()
|
||||
|
||||
def get_contents(self, origin):
|
||||
return origin.loader.get_contents(origin)
|
||||
|
||||
def get_template(self, template_name, skip=None):
|
||||
"""
|
||||
Perform the caching that gives this loader its name. Often many of the
|
||||
templates attempted will be missing, so memory use is of concern here.
|
||||
To keep it in check, caching behavior is a little complicated when a
|
||||
template is not found. See ticket #26306 for more details.
|
||||
|
||||
With template debugging disabled, cache the TemplateDoesNotExist class
|
||||
for every missing template and raise a new instance of it after
|
||||
fetching it from the cache.
|
||||
|
||||
With template debugging enabled, a unique TemplateDoesNotExist object
|
||||
is cached for each missing template to preserve debug data. When
|
||||
raising an exception, Python sets __traceback__, __context__, and
|
||||
__cause__ attributes on it. Those attributes can contain references to
|
||||
all sorts of objects up the call chain and caching them creates a
|
||||
memory leak. Thus, unraised copies of the exceptions are cached and
|
||||
copies of those copies are raised after they're fetched from the cache.
|
||||
"""
|
||||
key = self.cache_key(template_name, skip)
|
||||
cached = self.get_template_cache.get(key)
|
||||
if cached:
|
||||
if isinstance(cached, type) and issubclass(cached, TemplateDoesNotExist):
|
||||
raise cached(template_name)
|
||||
elif isinstance(cached, TemplateDoesNotExist):
|
||||
raise copy_exception(cached)
|
||||
return cached
|
||||
|
||||
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
|
||||
)
|
||||
raise
|
||||
else:
|
||||
self.get_template_cache[key] = template
|
||||
|
||||
return template
|
||||
|
||||
def get_template_sources(self, template_name):
|
||||
for loader in self.loaders:
|
||||
yield from loader.get_template_sources(template_name)
|
||||
|
||||
def cache_key(self, template_name, skip=None):
|
||||
"""
|
||||
Generate a cache key for the template name and skip.
|
||||
|
||||
If skip is provided, only origins that match template_name are included
|
||||
in the cache key. This ensures each template is only parsed and cached
|
||||
once if contained in different extend chains like:
|
||||
|
||||
x -> a -> a
|
||||
y -> a -> a
|
||||
z -> a -> a
|
||||
"""
|
||||
skip_prefix = ""
|
||||
|
||||
if skip:
|
||||
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)
|
||||
|
||||
def generate_hash(self, values):
|
||||
return hashlib.sha1("|".join(values).encode()).hexdigest()
|
||||
|
||||
def reset(self):
|
||||
"Empty the template cache."
|
||||
self.get_template_cache.clear()
|
@ -0,0 +1,45 @@
|
||||
"""
|
||||
Wrapper for loading templates from the filesystem.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import SuspiciousFileOperation
|
||||
from django.template import Origin, TemplateDoesNotExist
|
||||
from django.utils._os import safe_join
|
||||
|
||||
from .base import Loader as BaseLoader
|
||||
|
||||
|
||||
class Loader(BaseLoader):
|
||||
def __init__(self, engine, dirs=None):
|
||||
super().__init__(engine)
|
||||
self.dirs = dirs
|
||||
|
||||
def get_dirs(self):
|
||||
return self.dirs if self.dirs is not None else self.engine.dirs
|
||||
|
||||
def get_contents(self, origin):
|
||||
try:
|
||||
with open(origin.name, encoding=self.engine.file_charset) as fp:
|
||||
return fp.read()
|
||||
except FileNotFoundError:
|
||||
raise TemplateDoesNotExist(origin)
|
||||
|
||||
def get_template_sources(self, template_name):
|
||||
"""
|
||||
Return an Origin object pointing to an absolute path in each directory
|
||||
in template_dirs. For security reasons, if a path doesn't lie inside
|
||||
one of the template_dirs it is excluded from the result set.
|
||||
"""
|
||||
for template_dir in self.get_dirs():
|
||||
try:
|
||||
name = safe_join(template_dir, template_name)
|
||||
except SuspiciousFileOperation:
|
||||
# The joined path was located outside of this template_dir
|
||||
# (it might be inside another one, so this isn't fatal).
|
||||
continue
|
||||
|
||||
yield Origin(
|
||||
name=name,
|
||||
template_name=template_name,
|
||||
loader=self,
|
||||
)
|
@ -0,0 +1,26 @@
|
||||
"""
|
||||
Wrapper for loading templates from a plain Python dict.
|
||||
"""
|
||||
|
||||
from django.template import Origin, TemplateDoesNotExist
|
||||
|
||||
from .base import Loader as BaseLoader
|
||||
|
||||
|
||||
class Loader(BaseLoader):
|
||||
def __init__(self, engine, templates_dict):
|
||||
self.templates_dict = templates_dict
|
||||
super().__init__(engine)
|
||||
|
||||
def get_contents(self, origin):
|
||||
try:
|
||||
return self.templates_dict[origin.name]
|
||||
except KeyError:
|
||||
raise TemplateDoesNotExist(origin)
|
||||
|
||||
def get_template_sources(self, template_name):
|
||||
yield Origin(
|
||||
name=template_name,
|
||||
template_name=template_name,
|
||||
loader=self,
|
||||
)
|
@ -0,0 +1,164 @@
|
||||
from django.http import HttpResponse
|
||||
|
||||
from .loader import get_template, select_template
|
||||
|
||||
|
||||
class ContentNotRenderedError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SimpleTemplateResponse(HttpResponse):
|
||||
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,
|
||||
):
|
||||
# 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.
|
||||
self.template_name = template
|
||||
self.context_data = context
|
||||
|
||||
self.using = using
|
||||
|
||||
self._post_render_callbacks = []
|
||||
|
||||
# _request stores the current request object in subclasses that know
|
||||
# about requests, like TemplateResponse. It's defined in the base class
|
||||
# to minimize code duplication.
|
||||
# It's called self._request because self.request gets overwritten by
|
||||
# django.test.client.Client. Unlike template_name and context_data,
|
||||
# _request should not be considered part of the public API.
|
||||
self._request = None
|
||||
|
||||
# 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)
|
||||
|
||||
# _is_rendered tracks whether the template and context has been baked
|
||||
# into a final response.
|
||||
# Super __init__ doesn't know any better than to set self.content to
|
||||
# the empty string we just gave it, which wrongly sets _is_rendered
|
||||
# True, so we initialize it to False after the call to super __init__.
|
||||
self._is_rendered = False
|
||||
|
||||
def __getstate__(self):
|
||||
"""
|
||||
Raise an exception if trying to pickle an unrendered response. Pickle
|
||||
only rendered data, not the data used to construct the response.
|
||||
"""
|
||||
obj_dict = self.__dict__.copy()
|
||||
if not self._is_rendered:
|
||||
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]
|
||||
|
||||
return obj_dict
|
||||
|
||||
def resolve_template(self, template):
|
||||
"""Accept a template object, path-to-template, or list of paths."""
|
||||
if isinstance(template, (list, tuple)):
|
||||
return select_template(template, using=self.using)
|
||||
elif isinstance(template, str):
|
||||
return get_template(template, using=self.using)
|
||||
else:
|
||||
return template
|
||||
|
||||
def resolve_context(self, context):
|
||||
return context
|
||||
|
||||
@property
|
||||
def rendered_content(self):
|
||||
"""Return the freshly rendered content for the template and context
|
||||
described by the TemplateResponse.
|
||||
|
||||
This *does not* set the final content of the response. To set the
|
||||
response content, you must either call render(), or set the
|
||||
content explicitly using the value of this property.
|
||||
"""
|
||||
template = self.resolve_template(self.template_name)
|
||||
context = self.resolve_context(self.context_data)
|
||||
return template.render(context, self._request)
|
||||
|
||||
def add_post_render_callback(self, callback):
|
||||
"""Add a new post-rendering callback.
|
||||
|
||||
If the response has already been rendered,
|
||||
invoke the callback immediately.
|
||||
"""
|
||||
if self._is_rendered:
|
||||
callback(self)
|
||||
else:
|
||||
self._post_render_callbacks.append(callback)
|
||||
|
||||
def render(self):
|
||||
"""Render (thereby finalizing) the content of the response.
|
||||
|
||||
If the content has already been rendered, this is a no-op.
|
||||
|
||||
Return the baked response instance.
|
||||
"""
|
||||
retval = self
|
||||
if not self._is_rendered:
|
||||
self.content = self.rendered_content
|
||||
for post_callback in self._post_render_callbacks:
|
||||
newretval = post_callback(retval)
|
||||
if newretval is not None:
|
||||
retval = newretval
|
||||
return retval
|
||||
|
||||
@property
|
||||
def is_rendered(self):
|
||||
return self._is_rendered
|
||||
|
||||
def __iter__(self):
|
||||
if not self._is_rendered:
|
||||
raise ContentNotRenderedError(
|
||||
"The response content must be rendered before it can be iterated over."
|
||||
)
|
||||
return super().__iter__()
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
if not self._is_rendered:
|
||||
raise ContentNotRenderedError(
|
||||
"The response content must be rendered before it can be accessed."
|
||||
)
|
||||
return super().content
|
||||
|
||||
@content.setter
|
||||
def content(self, value):
|
||||
"""Set the content for the response."""
|
||||
HttpResponse.content.fset(self, value)
|
||||
self._is_rendered = True
|
||||
|
||||
|
||||
class TemplateResponse(SimpleTemplateResponse):
|
||||
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
|
||||
)
|
||||
self._request = request
|
@ -0,0 +1,213 @@
|
||||
"""
|
||||
Parser and utilities for the smart 'if' tag
|
||||
"""
|
||||
# Using a simple top down parser, as described here:
|
||||
# http://effbot.org/zone/simple-top-down-parsing.htm.
|
||||
# 'led' = left denotation
|
||||
# 'nud' = null denotation
|
||||
# 'bp' = binding power (left = lbp, right = rbp)
|
||||
|
||||
|
||||
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
|
||||
|
||||
def nud(self, parser):
|
||||
# Null denotation - called in prefix context
|
||||
raise parser.error_class(
|
||||
"Not expecting '%s' in this position in if tag." % self.id
|
||||
)
|
||||
|
||||
def led(self, left, parser):
|
||||
# Left denotation - called in infix context
|
||||
raise parser.error_class(
|
||||
"Not expecting '%s' as infix operator in if tag." % self.id
|
||||
)
|
||||
|
||||
def display(self):
|
||||
"""
|
||||
Return what to display in error messages for this node
|
||||
"""
|
||||
return self.id
|
||||
|
||||
def __repr__(self):
|
||||
out = [str(x) for x in [self.id, self.first, self.second] if x is not None]
|
||||
return "(" + " ".join(out) + ")"
|
||||
|
||||
|
||||
def infix(bp, func):
|
||||
"""
|
||||
Create an infix operator, given a binding power and a function that
|
||||
evaluates the node.
|
||||
"""
|
||||
|
||||
class Operator(TokenBase):
|
||||
lbp = bp
|
||||
|
||||
def led(self, left, parser):
|
||||
self.first = left
|
||||
self.second = parser.expression(bp)
|
||||
return self
|
||||
|
||||
def eval(self, context):
|
||||
try:
|
||||
return func(context, self.first, self.second)
|
||||
except Exception:
|
||||
# Templates shouldn't throw exceptions when rendering. We are
|
||||
# most likely to get exceptions for things like {% if foo in bar
|
||||
# %} where 'bar' does not support 'in', so default to False
|
||||
return False
|
||||
|
||||
return Operator
|
||||
|
||||
|
||||
def prefix(bp, func):
|
||||
"""
|
||||
Create a prefix operator, given a binding power and a function that
|
||||
evaluates the node.
|
||||
"""
|
||||
|
||||
class Operator(TokenBase):
|
||||
lbp = bp
|
||||
|
||||
def nud(self, parser):
|
||||
self.first = parser.expression(bp)
|
||||
self.second = None
|
||||
return self
|
||||
|
||||
def eval(self, context):
|
||||
try:
|
||||
return func(context, self.first)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return Operator
|
||||
|
||||
|
||||
# Operator precedence follows Python.
|
||||
# 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)),
|
||||
}
|
||||
|
||||
# Assign 'id' to each:
|
||||
for key, op in OPERATORS.items():
|
||||
op.id = key
|
||||
|
||||
|
||||
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.
|
||||
id = "literal"
|
||||
lbp = 0
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def display(self):
|
||||
return repr(self.value)
|
||||
|
||||
def nud(self, parser):
|
||||
return self
|
||||
|
||||
def eval(self, context):
|
||||
return self.value
|
||||
|
||||
def __repr__(self):
|
||||
return "(%s %r)" % (self.id, self.value)
|
||||
|
||||
|
||||
class EndToken(TokenBase):
|
||||
lbp = 0
|
||||
|
||||
def nud(self, parser):
|
||||
raise parser.error_class("Unexpected end of expression in if tag.")
|
||||
|
||||
|
||||
EndToken = EndToken()
|
||||
|
||||
|
||||
class IfParser:
|
||||
error_class = ValueError
|
||||
|
||||
def __init__(self, tokens):
|
||||
# Turn 'is','not' and 'not','in' into single tokens.
|
||||
num_tokens = len(tokens)
|
||||
mapped_tokens = []
|
||||
i = 0
|
||||
while i < num_tokens:
|
||||
token = tokens[i]
|
||||
if token == "is" and i + 1 < num_tokens and tokens[i + 1] == "not":
|
||||
token = "is not"
|
||||
i += 1 # skip 'not'
|
||||
elif token == "not" and i + 1 < num_tokens and tokens[i + 1] == "in":
|
||||
token = "not in"
|
||||
i += 1 # skip 'in'
|
||||
mapped_tokens.append(self.translate_token(token))
|
||||
i += 1
|
||||
|
||||
self.tokens = mapped_tokens
|
||||
self.pos = 0
|
||||
self.current_token = self.next_token()
|
||||
|
||||
def translate_token(self, token):
|
||||
try:
|
||||
op = OPERATORS[token]
|
||||
except (KeyError, TypeError):
|
||||
return self.create_var(token)
|
||||
else:
|
||||
return op()
|
||||
|
||||
def next_token(self):
|
||||
if self.pos >= len(self.tokens):
|
||||
return EndToken
|
||||
else:
|
||||
retval = self.tokens[self.pos]
|
||||
self.pos += 1
|
||||
return retval
|
||||
|
||||
def parse(self):
|
||||
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()
|
||||
)
|
||||
return retval
|
||||
|
||||
def expression(self, rbp=0):
|
||||
t = self.current_token
|
||||
self.current_token = self.next_token()
|
||||
left = t.nud(self)
|
||||
while rbp < self.current_token.lbp:
|
||||
t = self.current_token
|
||||
self.current_token = self.next_token()
|
||||
left = t.led(left, self)
|
||||
return left
|
||||
|
||||
def create_var(self, value):
|
||||
return Literal(value)
|
111
srcs/.venv/lib/python3.11/site-packages/django/template/utils.py
Normal file
111
srcs/.venv/lib/python3.11/site-packages/django/template/utils.py
Normal file
@ -0,0 +1,111 @@
|
||||
import functools
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
|
||||
class InvalidTemplateEngineError(ImproperlyConfigured):
|
||||
pass
|
||||
|
||||
|
||||
class EngineHandler:
|
||||
def __init__(self, templates=None):
|
||||
"""
|
||||
templates is an optional list of template engine definitions
|
||||
(structured like settings.TEMPLATES).
|
||||
"""
|
||||
self._templates = templates
|
||||
self._engines = {}
|
||||
|
||||
@cached_property
|
||||
def templates(self):
|
||||
if self._templates is None:
|
||||
self._templates = settings.TEMPLATES
|
||||
|
||||
templates = {}
|
||||
backend_names = []
|
||||
for tpl in self._templates:
|
||||
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]
|
||||
except Exception:
|
||||
invalid_backend = tpl.get("BACKEND", "<not defined>")
|
||||
raise ImproperlyConfigured(
|
||||
"Invalid BACKEND for a template engine: {}. Check "
|
||||
"your TEMPLATES setting.".format(invalid_backend)
|
||||
)
|
||||
|
||||
tpl = {
|
||||
"NAME": default_name,
|
||||
"DIRS": [],
|
||||
"APP_DIRS": False,
|
||||
"OPTIONS": {},
|
||||
**tpl,
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
)
|
||||
|
||||
return templates
|
||||
|
||||
def __getitem__(self, alias):
|
||||
try:
|
||||
return self._engines[alias]
|
||||
except KeyError:
|
||||
try:
|
||||
params = self.templates[alias]
|
||||
except KeyError:
|
||||
raise InvalidTemplateEngineError(
|
||||
"Could not find config for '{}' "
|
||||
"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")
|
||||
engine_cls = import_string(backend)
|
||||
engine = engine_cls(params)
|
||||
|
||||
self._engines[alias] = engine
|
||||
return engine
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.templates)
|
||||
|
||||
def all(self):
|
||||
return [self[alias] for alias in self]
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def get_app_template_dirs(dirname):
|
||||
"""
|
||||
Return an iterable of paths of directories to load app templates from.
|
||||
|
||||
dirname is the name of the subdirectory containing templates inside
|
||||
installed applications.
|
||||
"""
|
||||
template_dirs = [
|
||||
Path(app_config.path) / dirname
|
||||
for app_config in apps.get_app_configs()
|
||||
if app_config.path and (Path(app_config.path) / dirname).is_dir()
|
||||
]
|
||||
# Immutable return value because it will be cached and shared by callers.
|
||||
return tuple(template_dirs)
|
Reference in New Issue
Block a user