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