diff options
| author | django-bot <ops@djangoproject.com> | 2022-02-03 20:24:19 +0100 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2022-02-07 20:37:05 +0100 |
| commit | 9c19aff7c7561e3a82978a272ecdaad40dda5c00 (patch) | |
| tree | f0506b668a013d0063e5fba3dbf4863b466713ba /django/template | |
| parent | f68fa8b45dfac545cfc4111d4e52804c86db68d3 (diff) | |
Refs #33476 -- Reformatted code with Black.
Diffstat (limited to 'django/template')
25 files changed, 845 insertions, 613 deletions
diff --git a/django/template/__init__.py b/django/template/__init__.py index 7414d0fef5..adb431c00d 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -46,26 +46,30 @@ from .utils import EngineHandler engines = EngineHandler() -__all__ = ('Engine', 'engines') +__all__ = ("Engine", "engines") # Django Template Language # Public exceptions -from .base import VariableDoesNotExist # NOQA isort:skip -from .context import Context, ContextPopException, RequestContext # NOQA isort:skip -from .exceptions import TemplateDoesNotExist, TemplateSyntaxError # NOQA isort:skip +from .base import VariableDoesNotExist # NOQA isort:skip +from .context import Context, ContextPopException, RequestContext # NOQA isort:skip +from .exceptions import TemplateDoesNotExist, TemplateSyntaxError # NOQA isort:skip # Template parts -from .base import ( # NOQA isort:skip - Node, NodeList, Origin, Template, Variable, +from .base import ( # NOQA isort:skip + Node, + NodeList, + Origin, + Template, + Variable, ) # Library management -from .library import Library # NOQA isort:skip +from .library import Library # NOQA isort:skip # Import the .autoreload module to trigger the registrations of signals. -from . import autoreload # NOQA isort:skip +from . import autoreload # NOQA isort:skip -__all__ += ('Template', 'Context', 'RequestContext') +__all__ += ("Template", "Context", "RequestContext") diff --git a/django/template/autoreload.py b/django/template/autoreload.py index 7242d68f2d..84c8554165 100644 --- a/django/template/autoreload.py +++ b/django/template/autoreload.py @@ -4,9 +4,7 @@ from django.dispatch import receiver from django.template import engines from django.template.backends.django import DjangoTemplates from django.utils._os import to_path -from django.utils.autoreload import ( - autoreload_started, file_changed, is_django_path, -) +from django.utils.autoreload import autoreload_started, file_changed, is_django_path def get_template_directories(): @@ -22,7 +20,7 @@ def get_template_directories(): items.update(cwd / to_path(dir) for dir in backend.engine.dirs) for loader in backend.engine.template_loaders: - if not hasattr(loader, 'get_dirs'): + if not hasattr(loader, "get_dirs"): continue items.update( cwd / to_path(directory) @@ -40,15 +38,15 @@ def reset_loaders(): loader.reset() -@receiver(autoreload_started, dispatch_uid='template_loaders_watch_changes') +@receiver(autoreload_started, dispatch_uid="template_loaders_watch_changes") def watch_for_template_changes(sender, **kwargs): for directory in get_template_directories(): - sender.watch_dir(directory, '**/*') + sender.watch_dir(directory, "**/*") -@receiver(file_changed, dispatch_uid='template_loaders_file_changed') +@receiver(file_changed, dispatch_uid="template_loaders_file_changed") def template_changed(sender, file_path, **kwargs): - if file_path.suffix == '.py': + if file_path.suffix == ".py": return for template_dir in get_template_directories(): if template_dir in file_path.parents: diff --git a/django/template/backends/base.py b/django/template/backends/base.py index f1fa142362..240733e6f4 100644 --- a/django/template/backends/base.py +++ b/django/template/backends/base.py @@ -1,6 +1,4 @@ -from django.core.exceptions import ( - ImproperlyConfigured, SuspiciousFileOperation, -) +from django.core.exceptions import ImproperlyConfigured, SuspiciousFileOperation from django.template.utils import get_app_template_dirs from django.utils._os import safe_join from django.utils.functional import cached_property @@ -18,18 +16,20 @@ class BaseEngine: `params` is a dict of configuration settings. """ params = params.copy() - self.name = params.pop('NAME') - self.dirs = list(params.pop('DIRS')) - self.app_dirs = params.pop('APP_DIRS') + self.name = params.pop("NAME") + self.dirs = list(params.pop("DIRS")) + self.app_dirs = params.pop("APP_DIRS") if params: raise ImproperlyConfigured( - "Unknown parameters: {}".format(", ".join(params))) + "Unknown parameters: {}".format(", ".join(params)) + ) @property def app_dirname(self): raise ImproperlyConfigured( "{} doesn't support loading templates from installed " - "applications.".format(self.__class__.__name__)) + "applications.".format(self.__class__.__name__) + ) def from_string(self, template_code): """ diff --git a/django/template/backends/django.py b/django/template/backends/django.py index 48257e5e4e..218e5e0bc1 100644 --- a/django/template/backends/django.py +++ b/django/template/backends/django.py @@ -13,16 +13,16 @@ from .base import BaseEngine class DjangoTemplates(BaseEngine): - app_dirname = 'templates' + app_dirname = "templates" def __init__(self, params): params = params.copy() - options = params.pop('OPTIONS').copy() - options.setdefault('autoescape', True) - options.setdefault('debug', settings.DEBUG) - options.setdefault('file_charset', 'utf-8') - libraries = options.get('libraries', {}) - options['libraries'] = self.get_templatetag_libraries(libraries) + options = params.pop("OPTIONS").copy() + options.setdefault("autoescape", True) + options.setdefault("debug", settings.DEBUG) + options.setdefault("file_charset", "utf-8") + libraries = options.get("libraries", {}) + options["libraries"] = self.get_templatetag_libraries(libraries) super().__init__(params) self.engine = Engine(self.dirs, self.app_dirs, **options) @@ -46,7 +46,6 @@ class DjangoTemplates(BaseEngine): class Template: - def __init__(self, template, backend): self.template = template self.backend = backend @@ -56,7 +55,9 @@ class Template: return self.template.origin def render(self, context=None, request=None): - context = make_context(context, request, autoescape=self.backend.engine.autoescape) + context = make_context( + context, request, autoescape=self.backend.engine.autoescape + ) try: return self.template.render(context) except TemplateDoesNotExist as exc: @@ -71,7 +72,7 @@ def copy_exception(exc, backend=None): """ backend = backend or exc.backend new = exc.__class__(*exc.args, tried=exc.tried, backend=backend, chain=exc.chain) - if hasattr(exc, 'template_debug'): + if hasattr(exc, "template_debug"): new.template_debug = exc.template_debug return new @@ -89,10 +90,9 @@ def get_template_tag_modules(): Yield (module_name, module_path) pairs for all installed template tag libraries. """ - candidates = ['django.templatetags'] + candidates = ["django.templatetags"] candidates.extend( - f'{app_config.name}.templatetags' - for app_config in apps.get_app_configs() + f"{app_config.name}.templatetags" for app_config in apps.get_app_configs() ) for candidate in candidates: @@ -102,9 +102,9 @@ def get_template_tag_modules(): # No templatetags package defined. This is safe to ignore. continue - if hasattr(pkg, '__path__'): + if hasattr(pkg, "__path__"): for name in get_package_libraries(pkg): - yield name[len(candidate) + 1:], name + yield name[len(candidate) + 1 :], name def get_installed_libraries(): @@ -115,8 +115,7 @@ def get_installed_libraries(): django.templatetags.i18n is stored as i18n. """ return { - module_name: full_name - for module_name, full_name in get_template_tag_modules() + module_name: full_name for module_name, full_name in get_template_tag_modules() } @@ -125,7 +124,7 @@ def get_package_libraries(pkg): Recursively yield template tag libraries defined in submodules of a package. """ - for entry in walk_packages(pkg.__path__, pkg.__name__ + '.'): + for entry in walk_packages(pkg.__path__, pkg.__name__ + "."): try: module = import_module(entry[1]) except ImportError as e: @@ -134,5 +133,5 @@ def get_package_libraries(pkg): "trying to load '%s': %s" % (entry[1], e) ) from e - if hasattr(module, 'register'): + if hasattr(module, "register"): yield entry[1] diff --git a/django/template/backends/dummy.py b/django/template/backends/dummy.py index 6be05ca614..692382b6b1 100644 --- a/django/template/backends/dummy.py +++ b/django/template/backends/dummy.py @@ -10,14 +10,13 @@ from .utils import csrf_input_lazy, csrf_token_lazy class TemplateStrings(BaseEngine): - app_dirname = 'template_strings' + app_dirname = "template_strings" def __init__(self, params): params = params.copy() - options = params.pop('OPTIONS').copy() + options = params.pop("OPTIONS").copy() if options: - raise ImproperlyConfigured( - "Unknown options: {}".format(", ".join(options))) + raise ImproperlyConfigured("Unknown options: {}".format(", ".join(options))) super().__init__(params) def from_string(self, template_code): @@ -27,26 +26,27 @@ class TemplateStrings(BaseEngine): tried = [] for template_file in self.iter_template_filenames(template_name): try: - with open(template_file, encoding='utf-8') as fp: + with open(template_file, encoding="utf-8") as fp: template_code = fp.read() except FileNotFoundError: - tried.append(( - Origin(template_file, template_name, self), - 'Source does not exist', - )) + tried.append( + ( + Origin(template_file, template_name, self), + "Source does not exist", + ) + ) else: return Template(template_code) raise TemplateDoesNotExist(template_name, tried=tried, backend=self) class Template(string.Template): - def render(self, context=None, request=None): if context is None: context = {} else: context = {k: conditional_escape(v) for k, v in context.items()} if request is not None: - context['csrf_input'] = csrf_input_lazy(request) - context['csrf_token'] = csrf_token_lazy(request) + context["csrf_input"] = csrf_input_lazy(request) + context["csrf_token"] = csrf_token_lazy(request) return self.safe_substitute(context) diff --git a/django/template/backends/jinja2.py b/django/template/backends/jinja2.py index f0540d389c..199d62b429 100644 --- a/django/template/backends/jinja2.py +++ b/django/template/backends/jinja2.py @@ -12,24 +12,25 @@ from .base import BaseEngine class Jinja2(BaseEngine): - app_dirname = 'jinja2' + app_dirname = "jinja2" def __init__(self, params): params = params.copy() - options = params.pop('OPTIONS').copy() + options = params.pop("OPTIONS").copy() super().__init__(params) - self.context_processors = options.pop('context_processors', []) + self.context_processors = options.pop("context_processors", []) - environment = options.pop('environment', 'jinja2.Environment') + environment = options.pop("environment", "jinja2.Environment") environment_cls = import_string(environment) - if 'loader' not in options: - options['loader'] = jinja2.FileSystemLoader(self.template_dirs) - options.setdefault('autoescape', True) - options.setdefault('auto_reload', settings.DEBUG) - options.setdefault('undefined', - jinja2.DebugUndefined if settings.DEBUG else jinja2.Undefined) + if "loader" not in options: + options["loader"] = jinja2.FileSystemLoader(self.template_dirs) + options.setdefault("autoescape", True) + options.setdefault("auto_reload", settings.DEBUG) + options.setdefault( + "undefined", jinja2.DebugUndefined if settings.DEBUG else jinja2.Undefined + ) self.env = environment_cls(**options) @@ -52,22 +53,23 @@ class Jinja2(BaseEngine): class Template: - def __init__(self, template, backend): self.template = template self.backend = backend self.origin = Origin( - name=template.filename, template_name=template.name, + name=template.filename, + template_name=template.name, ) def render(self, context=None, request=None): from .utils import csrf_input_lazy, csrf_token_lazy + if context is None: context = {} if request is not None: - context['request'] = request - context['csrf_input'] = csrf_input_lazy(request) - context['csrf_token'] = csrf_token_lazy(request) + context["request"] = request + context["csrf_input"] = csrf_input_lazy(request) + context["csrf_token"] = csrf_token_lazy(request) for context_processor in self.backend.template_context_processors: context.update(context_processor(request)) try: @@ -83,6 +85,7 @@ 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 @@ -101,24 +104,24 @@ def get_exception_info(exception): if exception_file.exists(): source = exception_file.read_text() if source is not None: - lines = list(enumerate(source.strip().split('\n'), start=1)) + lines = list(enumerate(source.strip().split("\n"), start=1)) during = lines[lineno - 1][1] total = len(lines) top = max(0, lineno - context_lines - 1) bottom = min(total, lineno + context_lines) else: - during = '' + during = "" lines = [] total = top = bottom = 0 return { - 'name': exception.filename, - 'message': exception.message, - 'source_lines': lines[top:bottom], - 'line': lineno, - 'before': '', - 'during': during, - 'after': '', - 'total': total, - 'top': top, - 'bottom': bottom, + "name": exception.filename, + "message": exception.message, + "source_lines": lines[top:bottom], + "line": lineno, + "before": "", + "during": during, + "after": "", + "total": total, + "top": top, + "bottom": bottom, } diff --git a/django/template/backends/utils.py b/django/template/backends/utils.py index 1396ae7095..880959d6a1 100644 --- a/django/template/backends/utils.py +++ b/django/template/backends/utils.py @@ -7,7 +7,8 @@ from django.utils.safestring import SafeString def csrf_input(request): return format_html( '<input type="hidden" name="csrfmiddlewaretoken" value="{}">', - get_token(request)) + get_token(request), + ) csrf_input_lazy = lazy(csrf_input, SafeString, str) diff --git a/django/template/base.py b/django/template/base.py index caadf89970..00f8283507 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -60,37 +60,35 @@ from django.utils.formats import localize from django.utils.html import conditional_escape, escape from django.utils.regex_helper import _lazy_re_compile from django.utils.safestring import SafeData, SafeString, mark_safe -from django.utils.text import ( - get_text_list, smart_split, unescape_string_literal, -) +from django.utils.text import get_text_list, smart_split, unescape_string_literal from django.utils.timezone import template_localtime from django.utils.translation import gettext_lazy, pgettext_lazy from .exceptions import TemplateSyntaxError # template syntax constants -FILTER_SEPARATOR = '|' -FILTER_ARGUMENT_SEPARATOR = ':' -VARIABLE_ATTRIBUTE_SEPARATOR = '.' -BLOCK_TAG_START = '{%' -BLOCK_TAG_END = '%}' -VARIABLE_TAG_START = '{{' -VARIABLE_TAG_END = '}}' -COMMENT_TAG_START = '{#' -COMMENT_TAG_END = '#}' -SINGLE_BRACE_START = '{' -SINGLE_BRACE_END = '}' +FILTER_SEPARATOR = "|" +FILTER_ARGUMENT_SEPARATOR = ":" +VARIABLE_ATTRIBUTE_SEPARATOR = "." +BLOCK_TAG_START = "{%" +BLOCK_TAG_END = "%}" +VARIABLE_TAG_START = "{{" +VARIABLE_TAG_END = "}}" +COMMENT_TAG_START = "{#" +COMMENT_TAG_END = "#}" +SINGLE_BRACE_START = "{" +SINGLE_BRACE_END = "}" # what to report as the origin for templates that come from non-loader sources # (e.g. strings) -UNKNOWN_SOURCE = '<unknown source>' +UNKNOWN_SOURCE = "<unknown source>" # Match BLOCK_TAG_*, VARIABLE_TAG_*, and COMMENT_TAG_* tags and capture the # entire tag, including start/end delimiters. Using re.compile() is faster # than instantiating SimpleLazyObject with _lazy_re_compile(). -tag_re = re.compile(r'({%.*?%}|{{.*?}}|{#.*?#})') +tag_re = re.compile(r"({%.*?%}|{{.*?}}|{#.*?#})") -logger = logging.getLogger('django.template') +logger = logging.getLogger("django.template") class TokenType(Enum): @@ -101,7 +99,6 @@ class TokenType(Enum): class VariableDoesNotExist(Exception): - def __init__(self, msg, params=()): self.msg = msg self.params = params @@ -120,20 +117,21 @@ class Origin: return self.name def __repr__(self): - return '<%s name=%r>' % (self.__class__.__qualname__, self.name) + return "<%s name=%r>" % (self.__class__.__qualname__, self.name) def __eq__(self, other): return ( - isinstance(other, Origin) and - self.name == other.name and - self.loader == other.loader + isinstance(other, Origin) + and self.name == other.name + and self.loader == other.loader ) @property def loader_name(self): if self.loader: - return '%s.%s' % ( - self.loader.__module__, self.loader.__class__.__name__, + return "%s.%s" % ( + self.loader.__module__, + self.loader.__class__.__name__, ) @@ -145,6 +143,7 @@ class Template: # e.g. Template('...').render(Context({...})) if engine is None: from .engine import Engine + engine = Engine.get_default() if origin is None: origin = Origin(UNKNOWN_SOURCE) @@ -161,7 +160,7 @@ class Template: def __repr__(self): return '<%s template_string="%s...">' % ( self.__class__.__qualname__, - self.source[:20].replace('\n', ''), + self.source[:20].replace("\n", ""), ) def _render(self, context): @@ -191,7 +190,9 @@ class Template: tokens = lexer.tokenize() parser = Parser( - tokens, self.engine.template_libraries, self.engine.template_builtins, + tokens, + self.engine.template_libraries, + self.engine.template_builtins, self.origin, ) @@ -263,30 +264,30 @@ class Template: try: message = str(exception.args[0]) except (IndexError, UnicodeDecodeError): - message = '(Could not get exception message)' + message = "(Could not get exception message)" return { - 'message': message, - 'source_lines': source_lines[top:bottom], - 'before': before, - 'during': during, - 'after': after, - 'top': top, - 'bottom': bottom, - 'total': total, - 'line': line, - 'name': self.origin.name, - 'start': start, - 'end': end, + "message": message, + "source_lines": source_lines[top:bottom], + "before": before, + "during": during, + "after": after, + "top": top, + "bottom": bottom, + "total": total, + "line": line, + "name": self.origin.name, + "start": start, + "end": end, } def linebreak_iter(template_source): yield 0 - p = template_source.find('\n') + p = template_source.find("\n") while p >= 0: yield p + 1 - p = template_source.find('\n', p + 1) + p = template_source.find("\n", p + 1) yield len(template_source) + 1 @@ -316,8 +317,10 @@ class Token: def __repr__(self): token_name = self.token_type.name.capitalize() - return ('<%s token: "%s...">' % - (token_name, self.contents[:20].replace('\n', ''))) + return '<%s token: "%s...">' % ( + token_name, + self.contents[:20].replace("\n", ""), + ) def split_contents(self): split = [] @@ -325,12 +328,12 @@ class Token: for bit in bits: # Handle translation-marked template pieces if bit.startswith(('_("', "_('")): - sentinel = bit[2] + ')' + sentinel = bit[2] + ")" trans_bit = [bit] while not bit.endswith(sentinel): bit = next(bits) trans_bit.append(bit) - bit = ' '.join(trans_bit) + bit = " ".join(trans_bit) split.append(bit) return split @@ -343,7 +346,7 @@ class Lexer: def __repr__(self): return '<%s template_string="%s...", verbatim=%s>' % ( self.__class__.__qualname__, - self.template_string[:20].replace('\n', ''), + self.template_string[:20].replace("\n", ""), self.verbatim, ) @@ -357,7 +360,7 @@ class Lexer: for token_string in tag_re.split(self.template_string): if token_string: result.append(self.create_token(token_string, None, lineno, in_tag)) - lineno += token_string.count('\n') + lineno += token_string.count("\n") in_tag = not in_tag return result @@ -382,9 +385,9 @@ class Lexer: return Token(TokenType.TEXT, token_string, position, lineno) # Otherwise, the current verbatim block is ending. self.verbatim = False - elif content[:9] in ('verbatim', 'verbatim '): + elif content[:9] in ("verbatim", "verbatim "): # Then a verbatim block is starting. - self.verbatim = 'end%s' % content + self.verbatim = "end%s" % content return Token(TokenType.BLOCK, content, position, lineno) if not self.verbatim: content = token_string[2:-2].strip() @@ -425,7 +428,7 @@ class DebugLexer(Lexer): for token_string, position in self._tag_re_split(): if token_string: result.append(self.create_token(token_string, position, lineno, in_tag)) - lineno += token_string.count('\n') + lineno += token_string.count("\n") in_tag = not in_tag return result @@ -450,7 +453,7 @@ class Parser: self.origin = origin def __repr__(self): - return '<%s tokens=%r>' % (self.__class__.__qualname__, self.tokens) + return "<%s tokens=%r>" % (self.__class__.__qualname__, self.tokens) def parse(self, parse_until=None): """ @@ -472,7 +475,9 @@ class Parser: self.extend_nodelist(nodelist, TextNode(token.contents), token) elif token_type == 1: # TokenType.VAR if not token.contents: - raise self.error(token, 'Empty variable tag on line %d' % token.lineno) + raise self.error( + token, "Empty variable tag on line %d" % token.lineno + ) try: filter_expression = self.compile_filter(token.contents) except TemplateSyntaxError as e: @@ -483,7 +488,7 @@ class Parser: try: command = token.contents.split()[0] except IndexError: - raise self.error(token, 'Empty block tag on line %d' % token.lineno) + raise self.error(token, "Empty block tag on line %d" % token.lineno) if command in parse_until: # A matching token has been reached. Return control to # the caller. Put the token back on the token list so the @@ -524,7 +529,8 @@ class Parser: # Check that non-text nodes don't appear before an extends tag. if node.must_be_first and nodelist.contains_nontext: raise self.error( - token, '%r must be the first tag in the template.' % node, + token, + "%r must be the first tag in the template." % node, ) if not isinstance(node, TextNode): nodelist.contains_nontext = True @@ -543,7 +549,7 @@ class Parser: """ if not isinstance(e, Exception): e = TemplateSyntaxError(e) - if not hasattr(e, 'token'): + if not hasattr(e, "token"): e.token = token return e @@ -552,16 +558,17 @@ class Parser: raise self.error( token, "Invalid block tag on line %d: '%s', expected %s. Did you " - "forget to register or load this tag?" % ( + "forget to register or load this tag?" + % ( token.lineno, command, - get_text_list(["'%s'" % p for p in parse_until], 'or'), + get_text_list(["'%s'" % p for p in parse_until], "or"), ), ) raise self.error( token, "Invalid block tag on line %d: '%s'. Did you forget to register " - "or load this tag?" % (token.lineno, command) + "or load this tag?" % (token.lineno, command), ) def unclosed_block_tag(self, parse_until): @@ -569,7 +576,7 @@ class Parser: msg = "Unclosed tag on line %d: '%s'. Looking for one of: %s." % ( token.lineno, command, - ', '.join(parse_until), + ", ".join(parse_until), ) raise self.error(token, msg) @@ -608,10 +615,10 @@ constant_string = r""" %(strdq)s| %(strsq)s) """ % { - 'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string - 'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string - 'i18n_open': re.escape("_("), - 'i18n_close': re.escape(")"), + "strdq": r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string + "strsq": r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string + "i18n_open": re.escape("_("), + "i18n_close": re.escape(")"), } constant_string = constant_string.replace("\n", "") @@ -627,11 +634,11 @@ filter_raw_string = r""" ) )? )""" % { - 'constant': constant_string, - 'num': r'[-+\.]?\d[\d\.e]*', - 'var_chars': r'\w\.', - 'filter_sep': re.escape(FILTER_SEPARATOR), - 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR), + "constant": constant_string, + "num": r"[-+\.]?\d[\d\.e]*", + "var_chars": r"\w\.", + "filter_sep": re.escape(FILTER_SEPARATOR), + "arg_sep": re.escape(FILTER_ARGUMENT_SEPARATOR), } filter_re = _lazy_re_compile(filter_raw_string, re.VERBOSE) @@ -652,7 +659,7 @@ class FilterExpression: <Variable: 'variable'> """ - __slots__ = ('token', 'filters', 'var', 'is_var') + __slots__ = ("token", "filters", "var", "is_var") def __init__(self, token, parser): self.token = token @@ -663,12 +670,12 @@ class FilterExpression: for match in matches: start = match.start() if upto != start: - raise TemplateSyntaxError("Could not parse some characters: " - "%s|%s|%s" % - (token[:upto], token[upto:start], - token[start:])) + raise TemplateSyntaxError( + "Could not parse some characters: " + "%s|%s|%s" % (token[:upto], token[upto:start], token[start:]) + ) if var_obj is None: - var, constant = match['var'], match['constant'] + var, constant = match["var"], match["constant"] if constant: try: var_obj = Variable(constant).resolve({}) @@ -681,9 +688,9 @@ class FilterExpression: else: var_obj = Variable(var) else: - filter_name = match['filter_name'] + filter_name = match["filter_name"] args = [] - constant_arg, var_arg = match['constant_arg'], match['var_arg'] + constant_arg, var_arg = match["constant_arg"], match["var_arg"] if constant_arg: args.append((False, Variable(constant_arg).resolve({}))) elif var_arg: @@ -693,8 +700,10 @@ class FilterExpression: filters.append((filter_func, args)) upto = match.end() if upto != len(token): - raise TemplateSyntaxError("Could not parse the remainder: '%s' " - "from '%s'" % (token[upto:], token)) + raise TemplateSyntaxError( + "Could not parse the remainder: '%s' " + "from '%s'" % (token[upto:], token) + ) self.filters = filters self.var = var_obj @@ -710,7 +719,7 @@ class FilterExpression: else: string_if_invalid = context.template.engine.string_if_invalid if string_if_invalid: - if '%s' in string_if_invalid: + if "%s" in string_if_invalid: return string_if_invalid % self.var else: return string_if_invalid @@ -725,13 +734,13 @@ class FilterExpression: arg_vals.append(mark_safe(arg)) else: arg_vals.append(arg.resolve(context)) - if getattr(func, 'expects_localtime', False): + if getattr(func, "expects_localtime", False): obj = template_localtime(obj, context.use_tz) - if getattr(func, 'needs_autoescape', False): + if getattr(func, "needs_autoescape", False): new_obj = func(obj, autoescape=context.autoescape, *arg_vals) else: new_obj = func(obj, *arg_vals) - if getattr(func, 'is_safe', False) and isinstance(obj, SafeData): + if getattr(func, "is_safe", False) and isinstance(obj, SafeData): obj = mark_safe(new_obj) else: obj = new_obj @@ -749,10 +758,12 @@ class FilterExpression: dlen = len(defaults or []) # Not enough OR Too many if plen < (alen - dlen) or plen > alen: - raise TemplateSyntaxError("%s requires %d arguments, %d provided" % - (name, alen - dlen, plen)) + raise TemplateSyntaxError( + "%s requires %d arguments, %d provided" % (name, alen - dlen, plen) + ) return True + args_check = staticmethod(args_check) def __str__(self): @@ -781,7 +792,7 @@ class Variable: (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') """ - __slots__ = ('var', 'literal', 'lookups', 'translate', 'message_context') + __slots__ = ("var", "literal", "lookups", "translate", "message_context") def __init__(self, var): self.var = var @@ -791,8 +802,7 @@ class Variable: self.message_context = None if not isinstance(var, str): - raise TypeError( - "Variable must be a string or number, got %s" % type(var)) + raise TypeError("Variable must be a string or number, got %s" % type(var)) try: # First try to treat this variable as a number. # @@ -802,16 +812,16 @@ class Variable: # Try to interpret values containing a period or an 'e'/'E' # (possibly scientific notation) as a float; otherwise, try int. - if '.' in var or 'e' in var.lower(): + if "." in var or "e" in var.lower(): self.literal = float(var) # "2." is invalid - if var[-1] == '.': + if var[-1] == ".": raise ValueError else: self.literal = int(var) except ValueError: # A ValueError means that the variable isn't a number. - if var[0:2] == '_(' and var[-1] == ')': + if var[0:2] == "_(" and var[-1] == ")": # The result of the lookup should be translated at rendering # time. self.translate = True @@ -823,10 +833,11 @@ class Variable: except ValueError: # Otherwise we'll set self.lookups so that resolve() knows we're # dealing with a bonafide variable - if VARIABLE_ATTRIBUTE_SEPARATOR + '_' in var or var[0] == '_': - raise TemplateSyntaxError("Variables and attributes may " - "not begin with underscores: '%s'" % - var) + if VARIABLE_ATTRIBUTE_SEPARATOR + "_" in var or var[0] == "_": + raise TemplateSyntaxError( + "Variables and attributes may " + "not begin with underscores: '%s'" % var + ) self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR)) def resolve(self, context): @@ -839,7 +850,7 @@ class Variable: value = self.literal if self.translate: is_safe = isinstance(value, SafeData) - msgid = value.replace('%', '%%') + msgid = value.replace("%", "%%") msgid = mark_safe(msgid) if is_safe else msgid if self.message_context: return pgettext_lazy(self.message_context, msgid) @@ -872,7 +883,9 @@ class Variable: except (TypeError, AttributeError, KeyError, ValueError, IndexError): try: # attribute lookup # Don't return class attributes if the class is the context: - if isinstance(current, BaseContext) and getattr(type(current), bit): + if isinstance(current, BaseContext) and getattr( + type(current), bit + ): raise AttributeError current = getattr(current, bit) except (TypeError, AttributeError): @@ -881,18 +894,20 @@ class Variable: raise try: # list-index lookup current = current[int(bit)] - except (IndexError, # list index out of range - ValueError, # invalid literal for int() - KeyError, # current is a dict without `int(bit)` key - TypeError): # unsubscriptable object + except ( + IndexError, # list index out of range + ValueError, # invalid literal for int() + KeyError, # current is a dict without `int(bit)` key + TypeError, + ): # unsubscriptable object raise VariableDoesNotExist( "Failed lookup for key [%s] in %r", (bit, current), ) # missing attribute if callable(current): - if getattr(current, 'do_not_call_in_templates', False): + if getattr(current, "do_not_call_in_templates", False): pass - elif getattr(current, 'alters_data', False): + elif getattr(current, "alters_data", False): current = context.template.engine.string_if_invalid else: try: # method call (assuming no args required) @@ -902,11 +917,13 @@ class Variable: try: signature.bind() except TypeError: # arguments *were* required - current = context.template.engine.string_if_invalid # invalid method call + current = ( + context.template.engine.string_if_invalid + ) # invalid method call else: raise except Exception as e: - template_name = getattr(context, 'template_name', None) or 'unknown' + template_name = getattr(context, "template_name", None) or "unknown" logger.debug( "Exception while resolving variable '%s' in template '%s'.", bit, @@ -914,7 +931,7 @@ class Variable: exc_info=True, ) - if getattr(e, 'silent_variable_failure', False): + if getattr(e, "silent_variable_failure", False): current = context.template.engine.string_if_invalid else: raise @@ -926,7 +943,7 @@ class Node: # Set this to True for nodes that must be first in the template (although # they can be preceded by text nodes. must_be_first = False - child_nodelists = ('nodelist',) + child_nodelists = ("nodelist",) token = None def render(self, context): @@ -947,14 +964,17 @@ class Node: except Exception as e: if context.template.engine.debug: # Store the actual node that caused the exception. - if not hasattr(e, '_culprit_node'): + if not hasattr(e, "_culprit_node"): e._culprit_node = self if ( - not hasattr(e, 'template_debug') and - context.render_context.template.origin == e._culprit_node.origin + not hasattr(e, "template_debug") + and context.render_context.template.origin == e._culprit_node.origin ): - e.template_debug = context.render_context.template.get_exception_info( - e, e._culprit_node.token, + e.template_debug = ( + context.render_context.template.get_exception_info( + e, + e._culprit_node.token, + ) ) raise @@ -982,9 +1002,7 @@ class NodeList(list): contains_nontext = False def render(self, context): - return SafeString(''.join([ - node.render_annotated(context) for node in self - ])) + return SafeString("".join([node.render_annotated(context) for node in self])) def get_nodes_by_type(self, nodetype): "Return a list of all nodes of the given type" @@ -1048,7 +1066,7 @@ class VariableNode(Node): # Unicode conversion can fail sometimes for reasons out of our # control (e.g. exception rendering). In that case, we fail # quietly. - return '' + return "" return render_value_in_context(output, context) @@ -1079,7 +1097,7 @@ def token_kwargs(bits, parser, support_legacy=False): if not kwarg_format: if not support_legacy: return {} - if len(bits) < 3 or bits[1] != 'as': + if len(bits) < 3 or bits[1] != "as": return {} kwargs = {} @@ -1091,13 +1109,13 @@ def token_kwargs(bits, parser, support_legacy=False): key, value = match.groups() del bits[:1] else: - if len(bits) < 3 or bits[1] != 'as': + if len(bits) < 3 or bits[1] != "as": return kwargs key, value = bits[2], bits[0] del bits[:3] kwargs[key] = parser.compile_filter(value) if bits and not kwarg_format: - if bits[0] != 'and': + if bits[0] != "and": return kwargs del bits[:1] return kwargs diff --git a/django/template/context.py b/django/template/context.py index f0a0cf2a00..ccf0b430dc 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -2,7 +2,7 @@ from contextlib import contextmanager from copy import copy # Hard-coded processor for easier use of CSRF protection. -_builtin_context_processors = ('django.template.context_processors.csrf',) +_builtin_context_processors = ("django.template.context_processors.csrf",) class ContextPopException(Exception): @@ -29,7 +29,7 @@ class BaseContext: self._reset_dicts(dict_) def _reset_dicts(self, value=None): - builtins = {'True': True, 'False': False, 'None': None} + builtins = {"True": True, "False": False, "None": None} self.dicts = [builtins] if value is not None: self.dicts.append(value) @@ -132,6 +132,7 @@ class BaseContext: class Context(BaseContext): "A stack container for variable context" + def __init__(self, dict_=None, autoescape=True, use_l10n=None, use_tz=None): self.autoescape = autoescape self.use_l10n = use_l10n @@ -160,8 +161,8 @@ class Context(BaseContext): def update(self, other_dict): "Push other_dict to the stack of dictionaries in the Context" - if not hasattr(other_dict, '__getitem__'): - raise TypeError('other_dict must be a mapping (dictionary-like) object.') + if not hasattr(other_dict, "__getitem__"): + raise TypeError("other_dict must be a mapping (dictionary-like) object.") if isinstance(other_dict, BaseContext): other_dict = other_dict.dicts[1:].pop() return ContextDict(self, other_dict) @@ -182,6 +183,7 @@ class RenderContext(BaseContext): rendering of other templates as they would if they were stored in the normal template context. """ + template = None def __iter__(self): @@ -217,7 +219,16 @@ class RequestContext(Context): Additional processors can be specified as a list of callables using the "processors" keyword argument. """ - def __init__(self, request, dict_=None, processors=None, use_l10n=None, use_tz=None, autoescape=True): + + def __init__( + self, + request, + dict_=None, + processors=None, + use_l10n=None, + use_tz=None, + autoescape=True, + ): super().__init__(dict_, use_l10n=use_l10n, use_tz=use_tz, autoescape=autoescape) self.request = request self._processors = () if processors is None else tuple(processors) @@ -237,8 +248,7 @@ class RequestContext(Context): self.template = template # Set context processors according to the template engine's settings. - processors = (template.engine.template_context_processors + - self._processors) + processors = template.engine.template_context_processors + self._processors updates = {} for processor in processors: updates.update(processor(self.request)) @@ -255,7 +265,7 @@ class RequestContext(Context): new_context = super().new(values) # This is for backwards-compatibility: RequestContexts created via # Context.new don't include values from context processors. - if hasattr(new_context, '_processors_index'): + if hasattr(new_context, "_processors_index"): del new_context._processors_index return new_context @@ -265,7 +275,9 @@ def make_context(context, request=None, **kwargs): Create a suitable Context from a plain dict and optionally an HttpRequest. """ if context is not None and not isinstance(context, dict): - raise TypeError('context must be a dict rather than %s.' % context.__class__.__name__) + raise TypeError( + "context must be a dict rather than %s." % context.__class__.__name__ + ) if request is None: context = Context(context, **kwargs) else: diff --git a/django/template/context_processors.py b/django/template/context_processors.py index 25ac1f2661..32753032fc 100644 --- a/django/template/context_processors.py +++ b/django/template/context_processors.py @@ -19,17 +19,18 @@ def csrf(request): Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if it has not been provided by either a view decorator or the middleware """ + def _get_val(): token = get_token(request) if token is None: # In order to be able to provide debugging info in the # case of misconfiguration, we use a sentinel value # instead of returning an empty dict. - return 'NOTPROVIDED' + return "NOTPROVIDED" else: return token - return {'csrf_token': SimpleLazyObject(_get_val)} + return {"csrf_token": SimpleLazyObject(_get_val)} def debug(request): @@ -37,46 +38,52 @@ def debug(request): Return context variables helpful for debugging. """ context_extras = {} - if settings.DEBUG and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS: - context_extras['debug'] = True + if settings.DEBUG and request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS: + context_extras["debug"] = True from django.db import connections # Return a lazy reference that computes connection.queries on access, # to ensure it contains queries triggered after this function runs. - context_extras['sql_queries'] = lazy( - lambda: list(itertools.chain.from_iterable(connections[x].queries for x in connections)), - list + context_extras["sql_queries"] = lazy( + lambda: list( + itertools.chain.from_iterable( + connections[x].queries for x in connections + ) + ), + list, ) return context_extras def i18n(request): from django.utils import translation + return { - 'LANGUAGES': settings.LANGUAGES, - 'LANGUAGE_CODE': translation.get_language(), - 'LANGUAGE_BIDI': translation.get_language_bidi(), + "LANGUAGES": settings.LANGUAGES, + "LANGUAGE_CODE": translation.get_language(), + "LANGUAGE_BIDI": translation.get_language_bidi(), } def tz(request): from django.utils import timezone - return {'TIME_ZONE': timezone.get_current_timezone_name()} + + return {"TIME_ZONE": timezone.get_current_timezone_name()} def static(request): """ Add static-related context variables to the context. """ - return {'STATIC_URL': settings.STATIC_URL} + return {"STATIC_URL": settings.STATIC_URL} def media(request): """ Add media-related context variables to the context. """ - return {'MEDIA_URL': settings.MEDIA_URL} + return {"MEDIA_URL": settings.MEDIA_URL} def request(request): - return {'request': request} + return {"request": request} diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index f78a96e3eb..46334791c6 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -12,14 +12,14 @@ from urllib.parse import quote from django.utils import formats from django.utils.dateformat import format, time_format from django.utils.encoding import iri_to_uri -from django.utils.html import ( - avoid_wrapping, conditional_escape, escape, escapejs, - json_script as _json_script, linebreaks, strip_tags, urlize as _urlize, -) +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, slugify as _slugify, wrap, -) +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 @@ -33,16 +33,18 @@ 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): + if isinstance(first, SafeData) and getattr(unwrap(func), "is_safe", False): result = mark_safe(result) return result @@ -53,6 +55,7 @@ def stringfilter(func): # STRINGS # ################### + @register.filter(is_safe=True) @stringfilter def addslashes(value): @@ -61,7 +64,7 @@ def addslashes(value): example. Less useful for escaping JavaScript; use the ``escapejs`` filter instead. """ - return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") + return value.replace("\\", "\\\\").replace('"', '\\"').replace("'", "\\'") @register.filter(is_safe=True) @@ -135,14 +138,14 @@ def floatformat(text, arg=-1): use_l10n = True if isinstance(arg, str): last_char = arg[-1] - if arg[-2:] in {'gu', 'ug'}: + if arg[-2:] in {"gu", "ug"}: force_grouping = True use_l10n = False arg = arg[:-2] or -1 - elif last_char == 'g': + elif last_char == "g": force_grouping = True arg = arg[:-1] or -1 - elif last_char == 'u': + elif last_char == "u": use_l10n = False arg = arg[:-1] or -1 try: @@ -152,7 +155,7 @@ def floatformat(text, arg=-1): try: d = Decimal(str(float(text))) except (ValueError, InvalidOperation, TypeError): - return '' + return "" try: p = int(arg) except ValueError: @@ -164,12 +167,14 @@ def floatformat(text, arg=-1): 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, - )) + 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). @@ -184,17 +189,19 @@ def floatformat(text, arg=-1): sign, digits, exponent = rounded_d.as_tuple() digits = [str(digit) for digit in reversed(digits)] while len(digits) <= abs(exponent): - digits.append('0') - digits.insert(-exponent, '.') + digits.append("0") + digits.insert(-exponent, ".") if sign and rounded_d: - digits.append('-') - number = ''.join(reversed(digits)) - return mark_safe(formats.number_format( - number, - abs(p), - use_l10n=use_l10n, - force_grouping=force_grouping, - )) + 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) @@ -208,7 +215,7 @@ def iriencode(value): @stringfilter def linenumbers(value, autoescape=True): """Display text with line numbers.""" - lines = value.split('\n') + lines = value.split("\n") # Find the maximum width of the line count, for use with zero padding # string format command width = str(len(str(len(lines)))) @@ -218,7 +225,7 @@ def linenumbers(value, autoescape=True): else: for i, line in enumerate(lines): lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line)) - return mark_safe('\n'.join(lines)) + return mark_safe("\n".join(lines)) @register.filter(is_safe=True) @@ -275,7 +282,7 @@ def stringformat(value, arg): def title(value): """Convert a string into titlecase.""" t = re.sub("([a-z])'([A-Z])", lambda m: m[0].lower(), value.title()) - return re.sub(r'\d([A-Z])', lambda m: m[0].lower(), t) + return re.sub(r"\d([A-Z])", lambda m: m[0].lower(), t) @register.filter(is_safe=True) @@ -314,7 +321,7 @@ def truncatewords(value, arg): length = int(arg) except ValueError: # Invalid literal for int(). return value # Fail silently. - return Truncator(value).words(length, truncate=' …') + return Truncator(value).words(length, truncate=" …") @register.filter(is_safe=True) @@ -328,7 +335,7 @@ def truncatewords_html(value, arg): length = int(arg) except ValueError: # invalid literal for int() return value # Fail silently. - return Truncator(value).words(length, html=True, truncate=' …') + return Truncator(value).words(length, html=True, truncate=" …") @register.filter(is_safe=False) @@ -351,7 +358,7 @@ def urlencode(value, safe=None): """ kwargs = {} if safe is not None: - kwargs['safe'] = safe + kwargs["safe"] = safe return quote(value, **kwargs) @@ -371,7 +378,9 @@ def urlizetrunc(value, limit, autoescape=True): Argument: Length to truncate URLs to. """ - return mark_safe(_urlize(value, trim_url_limit=int(limit), nofollow=True, autoescape=autoescape)) + return mark_safe( + _urlize(value, trim_url_limit=int(limit), nofollow=True, autoescape=autoescape) + ) @register.filter(is_safe=False) @@ -414,8 +423,8 @@ def center(value, arg): def cut(value, arg): """Remove all values of arg from the given string.""" safe = isinstance(value, SafeData) - value = value.replace(arg, '') - if safe and arg != ';': + value = value.replace(arg, "") + if safe and arg != ";": return mark_safe(value) return value @@ -424,6 +433,7 @@ def cut(value, arg): # HTML STRINGS # ################### + @register.filter("escape", is_safe=True) @stringfilter def escape_filter(value): @@ -465,7 +475,7 @@ def linebreaksbr(value, autoescape=True): value = normalize_newlines(value) if autoescape: value = escape(value) - return mark_safe(value.replace('\n', '<br>')) + return mark_safe(value.replace("\n", "<br>")) @register.filter(is_safe=True) @@ -496,6 +506,7 @@ def striptags(value): # LISTS # ################### + def _property_resolver(arg): """ When arg is convertible to float, behave like operator.itemgetter(arg) @@ -517,8 +528,8 @@ def _property_resolver(arg): try: float(arg) except ValueError: - if VARIABLE_ATTRIBUTE_SEPARATOR + '_' in arg or arg[0] == '_': - raise AttributeError('Access to private variables is forbidden.') + 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): @@ -543,7 +554,7 @@ def dictsort(value, arg): try: return sorted(value, key=_property_resolver(arg)) except (AttributeError, TypeError): - return '' + return "" @register.filter(is_safe=False) @@ -555,7 +566,7 @@ def dictsortreversed(value, arg): try: return sorted(value, key=_property_resolver(arg), reverse=True) except (AttributeError, TypeError): - return '' + return "" @register.filter(is_safe=False) @@ -564,7 +575,7 @@ def first(value): try: return value[0] except IndexError: - return '' + return "" @register.filter(is_safe=True, needs_autoescape=True) @@ -585,7 +596,7 @@ def last(value): try: return value[-1] except IndexError: - return '' + return "" @register.filter(is_safe=False) @@ -603,7 +614,7 @@ def length_is(value, arg): try: return len(value) == int(arg) except (ValueError, TypeError): - return '' + return "" @register.filter(is_safe=True) @@ -619,7 +630,7 @@ def slice_filter(value, arg): """ try: bits = [] - for x in str(arg).split(':'): + for x in str(arg).split(":"): if not x: bits.append(None) else: @@ -655,6 +666,7 @@ def unordered_list(value, autoescape=True): if autoescape: escaper = conditional_escape else: + def escaper(x): return x @@ -683,16 +695,19 @@ def unordered_list(value, autoescape=True): pass def list_formatter(item_list, tabs=1): - indent = '\t' * tabs + indent = "\t" * tabs output = [] for item, children in walk_items(item_list): - sublist = '' + sublist = "" if children: - sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % ( - indent, list_formatter(children, tabs + 1), indent, indent) - output.append('%s<li>%s%s</li>' % ( - indent, escaper(item), sublist)) - return '\n'.join(output) + sublist = "\n%s<ul>\n%s\n%s</ul>\n%s" % ( + indent, + list_formatter(children, tabs + 1), + indent, + indent, + ) + output.append("%s<li>%s%s</li>" % (indent, escaper(item), sublist)) + return "\n".join(output) return mark_safe(list_formatter(value)) @@ -701,6 +716,7 @@ def unordered_list(value, autoescape=True): # INTEGERS # ################### + @register.filter(is_safe=False) def add(value, arg): """Add the arg to the value.""" @@ -710,7 +726,7 @@ def add(value, arg): try: return value + arg except Exception: - return '' + return "" @register.filter(is_safe=False) @@ -738,62 +754,64 @@ def get_digit(value, arg): # DATES # ################### + @register.filter(expects_localtime=True, is_safe=False) def date(value, arg=None): """Format a date according to the given format.""" - if value in (None, ''): - return '' + if value in (None, ""): + return "" try: return formats.date_format(value, arg) except AttributeError: try: return format(value, arg) except AttributeError: - return '' + return "" @register.filter(expects_localtime=True, is_safe=False) def time(value, arg=None): """Format a time according to the given format.""" - if value in (None, ''): - return '' + if value in (None, ""): + return "" try: return formats.time_format(value, arg) except (AttributeError, TypeError): try: return time_format(value, arg) except (AttributeError, TypeError): - return '' + return "" @register.filter("timesince", is_safe=False) def timesince_filter(value, arg=None): """Format a date as the time since that date (i.e. "4 days, 6 hours").""" if not value: - return '' + return "" try: if arg: return timesince(value, arg) return timesince(value) except (ValueError, TypeError): - return '' + return "" @register.filter("timeuntil", is_safe=False) def timeuntil_filter(value, arg=None): """Format a date as the time until that date (i.e. "4 days, 6 hours").""" if not value: - return '' + return "" try: return timeuntil(value, arg) except (ValueError, TypeError): - return '' + return "" ################### # LOGIC # ################### + @register.filter(is_safe=False) def default(value, arg): """If value is unavailable, use given default.""" @@ -832,8 +850,8 @@ def yesno(value, arg=None): """ if arg is None: # Translators: Please do not add spaces around commas. - arg = gettext('yes,no,maybe') - bits = arg.split(',') + arg = gettext("yes,no,maybe") + bits = arg.split(",") if len(bits) < 2: return value # Invalid arg. try: @@ -852,6 +870,7 @@ def yesno(value, arg=None): # MISC # ################### + @register.filter(is_safe=True) def filesizeformat(bytes_): """ @@ -861,7 +880,7 @@ def filesizeformat(bytes_): try: bytes_ = int(bytes_) except (TypeError, ValueError, UnicodeDecodeError): - value = ngettext("%(size)d byte", "%(size)d bytes", 0) % {'size': 0} + value = ngettext("%(size)d byte", "%(size)d bytes", 0) % {"size": 0} return avoid_wrapping(value) def filesize_number_format(value): @@ -878,7 +897,7 @@ def filesizeformat(bytes_): bytes_ = -bytes_ # Allow formatting of negative numbers. if bytes_ < KB: - value = ngettext("%(size)d byte", "%(size)d bytes", bytes_) % {'size': bytes_} + value = ngettext("%(size)d byte", "%(size)d bytes", bytes_) % {"size": bytes_} elif bytes_ < MB: value = gettext("%s KB") % filesize_number_format(bytes_ / KB) elif bytes_ < GB: @@ -896,7 +915,7 @@ def filesizeformat(bytes_): @register.filter(is_safe=False) -def pluralize(value, arg='s'): +def pluralize(value, arg="s"): """ Return a plural suffix if the value is not 1, '1', or an object of length 1. By default, use 's' as the suffix: @@ -918,11 +937,11 @@ def pluralize(value, arg='s'): * If value is 1, cand{{ value|pluralize:"y,ies" }} display "candy". * If value is 2, cand{{ value|pluralize:"y,ies" }} display "candies". """ - if ',' not in arg: - arg = ',' + arg - bits = arg.split(',') + if "," not in arg: + arg = "," + arg + bits = arg.split(",") if len(bits) > 2: - return '' + return "" singular_suffix, plural_suffix = bits[:2] try: @@ -934,7 +953,7 @@ def pluralize(value, arg='s'): return singular_suffix if len(value) == 1 else plural_suffix except TypeError: # len() of unsized object. pass - return '' + return "" @register.filter("phone2numeric", is_safe=True) diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 9090b14e40..7762b94723 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -4,7 +4,8 @@ import sys import warnings from collections import namedtuple from datetime import datetime -from itertools import cycle as itertools_cycle, groupby +from itertools import cycle as itertools_cycle +from itertools import groupby from django.conf import settings from django.utils import timezone @@ -13,11 +14,23 @@ from django.utils.lorem_ipsum import paragraphs, words from django.utils.safestring import mark_safe from .base import ( - BLOCK_TAG_END, BLOCK_TAG_START, COMMENT_TAG_END, COMMENT_TAG_START, - FILTER_SEPARATOR, SINGLE_BRACE_END, SINGLE_BRACE_START, - VARIABLE_ATTRIBUTE_SEPARATOR, VARIABLE_TAG_END, VARIABLE_TAG_START, Node, - NodeList, TemplateSyntaxError, VariableDoesNotExist, kwarg_re, - render_value_in_context, token_kwargs, + BLOCK_TAG_END, + BLOCK_TAG_START, + COMMENT_TAG_END, + COMMENT_TAG_START, + FILTER_SEPARATOR, + SINGLE_BRACE_END, + SINGLE_BRACE_START, + VARIABLE_ATTRIBUTE_SEPARATOR, + VARIABLE_TAG_END, + VARIABLE_TAG_START, + Node, + NodeList, + TemplateSyntaxError, + VariableDoesNotExist, + kwarg_re, + render_value_in_context, + token_kwargs, ) from .context import Context from .defaultfilters import date @@ -29,6 +42,7 @@ register = Library() class AutoEscapeControlNode(Node): """Implement the actions of the autoescape tag.""" + def __init__(self, setting, nodelist): self.setting, self.nodelist = setting, nodelist @@ -47,19 +61,22 @@ class CommentNode(Node): child_nodelists = () def render(self, context): - return '' + return "" class CsrfTokenNode(Node): child_nodelists = () def render(self, context): - csrf_token = context.get('csrf_token') + csrf_token = context.get("csrf_token") if csrf_token: - if csrf_token == 'NOTPROVIDED': + if csrf_token == "NOTPROVIDED": return format_html("") else: - return format_html('<input type="hidden" name="csrfmiddlewaretoken" value="{}">', csrf_token) + return format_html( + '<input type="hidden" name="csrfmiddlewaretoken" value="{}">', + csrf_token, + ) else: # It's very probable that the token is missing because of # misconfiguration, so we raise a warning @@ -69,7 +86,7 @@ class CsrfTokenNode(Node): "did not provide the value. This is usually caused by not " "using RequestContext." ) - return '' + return "" class CycleNode(Node): @@ -87,7 +104,7 @@ class CycleNode(Node): if self.variable_name: context.set_upward(self.variable_name, value) if self.silent: - return '' + return "" return render_value_in_context(value, context) def reset(self, context): @@ -100,13 +117,14 @@ class CycleNode(Node): class DebugNode(Node): def render(self, context): if not settings.DEBUG: - return '' + return "" from pprint import pformat + output = [escape(pformat(val)) for val in context] - output.append('\n\n') + output.append("\n\n") output.append(escape(pformat(sys.modules))) - return ''.join(output) + return "".join(output) class FilterNode(Node): @@ -126,7 +144,7 @@ class FirstOfNode(Node): self.asvar = asvar def render(self, context): - first = '' + first = "" for var in self.vars: value = var.resolve(context, ignore_failures=True) if value: @@ -134,14 +152,16 @@ class FirstOfNode(Node): break if self.asvar: context[self.asvar] = first - return '' + return "" return first class ForNode(Node): - child_nodelists = ('nodelist_loop', 'nodelist_empty') + child_nodelists = ("nodelist_loop", "nodelist_empty") - def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None): + def __init__( + self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None + ): self.loopvars, self.sequence = loopvars, sequence self.is_reversed = is_reversed self.nodelist_loop = nodelist_loop @@ -151,25 +171,25 @@ class ForNode(Node): self.nodelist_empty = nodelist_empty def __repr__(self): - reversed_text = ' reversed' if self.is_reversed else '' - return '<%s: for %s in %s, tail_len: %d%s>' % ( + reversed_text = " reversed" if self.is_reversed else "" + return "<%s: for %s in %s, tail_len: %d%s>" % ( self.__class__.__name__, - ', '.join(self.loopvars), + ", ".join(self.loopvars), self.sequence, len(self.nodelist_loop), reversed_text, ) def render(self, context): - if 'forloop' in context: - parentloop = context['forloop'] + if "forloop" in context: + parentloop = context["forloop"] else: parentloop = {} with context.push(): values = self.sequence.resolve(context, ignore_failures=True) if values is None: values = [] - if not hasattr(values, '__len__'): + if not hasattr(values, "__len__"): values = list(values) len_values = len(values) if len_values < 1: @@ -181,17 +201,17 @@ class ForNode(Node): unpack = num_loopvars > 1 # Create a forloop value in the context. We'll update counters on each # iteration just below. - loop_dict = context['forloop'] = {'parentloop': parentloop} + loop_dict = context["forloop"] = {"parentloop": parentloop} for i, item in enumerate(values): # Shortcuts for current loop iteration number. - loop_dict['counter0'] = i - loop_dict['counter'] = i + 1 + loop_dict["counter0"] = i + loop_dict["counter"] = i + 1 # Reverse counter iteration numbers. - loop_dict['revcounter'] = len_values - i - loop_dict['revcounter0'] = len_values - i - 1 + loop_dict["revcounter"] = len_values - i + loop_dict["revcounter0"] = len_values - i - 1 # Boolean values designating first and last times through loop. - loop_dict['first'] = (i == 0) - loop_dict['last'] = (i == len_values - 1) + loop_dict["first"] = i == 0 + loop_dict["last"] = i == len_values - 1 pop_context = False if unpack: @@ -204,8 +224,9 @@ class ForNode(Node): # Check loop variable count before unpacking if num_loopvars != len_item: raise ValueError( - "Need {} values to unpack in for loop; got {}. " - .format(num_loopvars, len_item), + "Need {} values to unpack in for loop; got {}. ".format( + num_loopvars, len_item + ), ) unpacked_vars = dict(zip(self.loopvars, item)) pop_context = True @@ -221,11 +242,11 @@ class ForNode(Node): # the context ending up in an inconsistent state when other # tags (e.g., include and with) push data to context. context.pop() - return mark_safe(''.join(nodelist)) + return mark_safe("".join(nodelist)) class IfChangedNode(Node): - child_nodelists = ('nodelist_true', 'nodelist_false') + child_nodelists = ("nodelist_true", "nodelist_false") def __init__(self, nodelist_true, nodelist_false, *varlist): self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false @@ -240,7 +261,9 @@ class IfChangedNode(Node): if self._varlist: # Consider multiple parameters. This behaves like an OR evaluation # of the multiple variables. - compare_to = [var.resolve(context, ignore_failures=True) for var in self._varlist] + compare_to = [ + var.resolve(context, ignore_failures=True) for var in self._varlist + ] else: # The "{% ifchanged %}" syntax (without any variables) compares # the rendered output. @@ -252,28 +275,27 @@ class IfChangedNode(Node): return nodelist_true_output or self.nodelist_true.render(context) elif self.nodelist_false: return self.nodelist_false.render(context) - return '' + return "" def _get_context_stack_frame(self, context): # The Context object behaves like a stack where each template tag can create a new scope. # Find the place where to store the state to detect changes. - if 'forloop' in context: + if "forloop" in context: # Ifchanged is bound to the local for loop. # When there is a loop-in-loop, the state is bound to the inner loop, # so it resets when the outer loop continues. - return context['forloop'] + return context["forloop"] else: # Using ifchanged outside loops. Effectively this is a no-op because the state is associated with 'self'. return context.render_context class IfNode(Node): - def __init__(self, conditions_nodelists): self.conditions_nodelists = conditions_nodelists def __repr__(self): - return '<%s>' % self.__class__.__name__ + return "<%s>" % self.__class__.__name__ def __iter__(self): for _, nodelist in self.conditions_nodelists: @@ -286,18 +308,18 @@ class IfNode(Node): def render(self, context): for condition, nodelist in self.conditions_nodelists: - if condition is not None: # if / elif clause + if condition is not None: # if / elif clause try: match = condition.eval(context) except VariableDoesNotExist: match = None - else: # else clause + else: # else clause match = True if match: return nodelist.render(context) - return '' + return "" class LoremNode(Node): @@ -309,16 +331,16 @@ class LoremNode(Node): count = int(self.count.resolve(context)) except (ValueError, TypeError): count = 1 - if self.method == 'w': + if self.method == "w": return words(count, common=self.common) else: paras = paragraphs(count, common=self.common) - if self.method == 'p': - paras = ['<p>%s</p>' % p for p in paras] - return '\n\n'.join(paras) + if self.method == "p": + paras = ["<p>%s</p>" % p for p in paras] + return "\n\n".join(paras) -GroupedResult = namedtuple('GroupedResult', ['grouper', 'list']) +GroupedResult = namedtuple("GroupedResult", ["grouper", "list"]) class RegroupNode(Node): @@ -337,22 +359,23 @@ class RegroupNode(Node): if obj_list is None: # target variable wasn't found in context; fail silently. context[self.var_name] = [] - return '' + return "" # List of dictionaries in the format: # {'grouper': 'key', 'list': [list of contents]}. context[self.var_name] = [ GroupedResult(grouper=key, list=list(val)) - for key, val in - groupby(obj_list, lambda obj: self.resolve_expression(obj, context)) + for key, val in groupby( + obj_list, lambda obj: self.resolve_expression(obj, context) + ) ] - return '' + return "" class LoadNode(Node): child_nodelists = () def render(self, context): - return '' + return "" class NowNode(Node): @@ -366,7 +389,7 @@ class NowNode(Node): if self.asvar: context[self.asvar] = formatted - return '' + return "" else: return formatted @@ -377,7 +400,7 @@ class ResetCycleNode(Node): def render(self, context): self.node.reset(context) - return '' + return "" class SpacelessNode(Node): @@ -386,26 +409,27 @@ class SpacelessNode(Node): def render(self, context): from django.utils.html import strip_spaces_between_tags + return strip_spaces_between_tags(self.nodelist.render(context).strip()) class TemplateTagNode(Node): mapping = { - 'openblock': BLOCK_TAG_START, - 'closeblock': BLOCK_TAG_END, - 'openvariable': VARIABLE_TAG_START, - 'closevariable': VARIABLE_TAG_END, - 'openbrace': SINGLE_BRACE_START, - 'closebrace': SINGLE_BRACE_END, - 'opencomment': COMMENT_TAG_START, - 'closecomment': COMMENT_TAG_END, + "openblock": BLOCK_TAG_START, + "closeblock": BLOCK_TAG_END, + "openvariable": VARIABLE_TAG_START, + "closevariable": VARIABLE_TAG_END, + "openbrace": SINGLE_BRACE_START, + "closebrace": SINGLE_BRACE_END, + "opencomment": COMMENT_TAG_START, + "closecomment": COMMENT_TAG_END, } def __init__(self, tagtype): self.tagtype = tagtype def render(self, context): - return self.mapping.get(self.tagtype, '') + return self.mapping.get(self.tagtype, "") class URLNode(Node): @@ -428,6 +452,7 @@ class URLNode(Node): def render(self, context): from django.urls import NoReverseMatch, reverse + args = [arg.resolve(context) for arg in self.args] kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()} view_name = self.view_name.resolve(context) @@ -440,7 +465,7 @@ class URLNode(Node): current_app = None # Try to look up the URL. If it fails, raise NoReverseMatch unless the # {% url ... as var %} construct is used, in which case return nothing. - url = '' + url = "" try: url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app) except NoReverseMatch: @@ -449,7 +474,7 @@ class URLNode(Node): if self.asvar: context[self.asvar] = url - return '' + return "" else: if context.autoescape: url = conditional_escape(url) @@ -477,7 +502,7 @@ class WidthRatioNode(Node): max_value = self.max_expr.resolve(context) max_width = int(self.max_width.resolve(context)) except VariableDoesNotExist: - return '' + return "" except (ValueError, TypeError): raise TemplateSyntaxError("widthratio final argument must be a number") try: @@ -486,13 +511,13 @@ class WidthRatioNode(Node): ratio = (value / max_value) * max_width result = str(round(ratio)) except ZeroDivisionError: - result = '0' + result = "0" except (ValueError, TypeError, OverflowError): - result = '' + result = "" if self.asvar: context[self.asvar] = result - return '' + return "" else: return result @@ -507,7 +532,7 @@ class WithNode(Node): self.extra_context[name] = var def __repr__(self): - return '<%s>' % self.__class__.__name__ + return "<%s>" % self.__class__.__name__ def render(self, context): values = {key: val.resolve(context) for key, val in self.extra_context.items()} @@ -525,11 +550,11 @@ def autoescape(parser, token): if len(args) != 2: raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.") arg = args[1] - if arg not in ('on', 'off'): + if arg not in ("on", "off"): raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'") - nodelist = parser.parse(('endautoescape',)) + nodelist = parser.parse(("endautoescape",)) parser.delete_first_token() - return AutoEscapeControlNode((arg == 'on'), nodelist) + return AutoEscapeControlNode((arg == "on"), nodelist) @register.tag @@ -537,7 +562,7 @@ def comment(parser, token): """ Ignore everything between ``{% comment %}`` and ``{% endcomment %}``. """ - parser.skip_past('endcomment') + parser.skip_past("endcomment") return CommentNode() @@ -595,8 +620,10 @@ def cycle(parser, token): if len(args) == 2: # {% cycle foo %} case. name = args[1] - if not hasattr(parser, '_named_cycle_nodes'): - raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name) + if not hasattr(parser, "_named_cycle_nodes"): + raise TemplateSyntaxError( + "No named cycles in template. '%s' is not defined" % name + ) if name not in parser._named_cycle_nodes: raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) return parser._named_cycle_nodes[name] @@ -607,7 +634,10 @@ def cycle(parser, token): # {% cycle ... as foo [silent] %} case. if args[-3] == "as": if args[-1] != "silent": - raise TemplateSyntaxError("Only 'silent' flag is allowed after cycle's name, not '%s'." % args[-1]) + raise TemplateSyntaxError( + "Only 'silent' flag is allowed after cycle's name, not '%s'." + % args[-1] + ) as_form = True silent = True args = args[:-1] @@ -619,7 +649,7 @@ def cycle(parser, token): name = args[-1] values = [parser.compile_filter(arg) for arg in args[1:-2]] node = CycleNode(values, name, silent=silent) - if not hasattr(parser, '_named_cycle_nodes'): + if not hasattr(parser, "_named_cycle_nodes"): parser._named_cycle_nodes = {} parser._named_cycle_nodes[name] = node else: @@ -649,7 +679,7 @@ def debug(parser, token): return DebugNode() -@register.tag('filter') +@register.tag("filter") def do_filter(parser, token): """ Filter the contents of the block through variable filters. @@ -671,10 +701,13 @@ def do_filter(parser, token): _, rest = token.contents.split(None, 1) filter_expr = parser.compile_filter("var|%s" % (rest)) for func, unused in filter_expr.filters: - filter_name = getattr(func, '_filter_name', None) - if filter_name in ('escape', 'safe'): - raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % filter_name) - nodelist = parser.parse(('endfilter',)) + filter_name = getattr(func, "_filter_name", None) + if filter_name in ("escape", "safe"): + raise TemplateSyntaxError( + '"filter %s" is not permitted. Use the "autoescape" tag instead.' + % filter_name + ) + nodelist = parser.parse(("endfilter",)) parser.delete_first_token() return FilterNode(filter_expr, nodelist) @@ -722,13 +755,13 @@ def firstof(parser, token): if not bits: raise TemplateSyntaxError("'firstof' statement requires at least one argument") - if len(bits) >= 2 and bits[-2] == 'as': + if len(bits) >= 2 and bits[-2] == "as": asvar = bits[-1] bits = bits[:-2] return FirstOfNode([parser.compile_filter(bit) for bit in bits], asvar) -@register.tag('for') +@register.tag("for") def do_for(parser, token): """ Loop over each item in an array. @@ -797,14 +830,16 @@ def do_for(parser, token): "'for' statements should have at least four words: %s" % token.contents ) - is_reversed = bits[-1] == 'reversed' + is_reversed = bits[-1] == "reversed" in_index = -3 if is_reversed else -2 - if bits[in_index] != 'in': - raise TemplateSyntaxError("'for' statements should use the format" - " 'for x in y': %s" % token.contents) + if bits[in_index] != "in": + raise TemplateSyntaxError( + "'for' statements should use the format" + " 'for x in y': %s" % token.contents + ) - invalid_chars = frozenset((' ', '"', "'", FILTER_SEPARATOR)) - loopvars = re.split(r' *, *', ' '.join(bits[1:in_index])) + invalid_chars = frozenset((" ", '"', "'", FILTER_SEPARATOR)) + loopvars = re.split(r" *, *", " ".join(bits[1:in_index])) for var in loopvars: if not var or not invalid_chars.isdisjoint(var): raise TemplateSyntaxError( @@ -812,10 +847,15 @@ def do_for(parser, token): ) sequence = parser.compile_filter(bits[in_index + 1]) - nodelist_loop = parser.parse(('empty', 'endfor',)) + nodelist_loop = parser.parse( + ( + "empty", + "endfor", + ) + ) token = parser.next_token() - if token.contents == 'empty': - nodelist_empty = parser.parse(('endfor',)) + if token.contents == "empty": + nodelist_empty = parser.parse(("endfor",)) parser.delete_first_token() else: nodelist_empty = None @@ -845,7 +885,7 @@ class TemplateIfParser(IfParser): return TemplateLiteral(self.template_parser.compile_filter(value), value) -@register.tag('if') +@register.tag("if") def do_if(parser, token): """ Evaluate a variable, and if that variable is "true" (i.e., exists, is not @@ -907,27 +947,31 @@ def do_if(parser, token): # {% if ... %} bits = token.split_contents()[1:] condition = TemplateIfParser(parser, bits).parse() - nodelist = parser.parse(('elif', 'else', 'endif')) + nodelist = parser.parse(("elif", "else", "endif")) conditions_nodelists = [(condition, nodelist)] token = parser.next_token() # {% elif ... %} (repeatable) - while token.contents.startswith('elif'): + while token.contents.startswith("elif"): bits = token.split_contents()[1:] condition = TemplateIfParser(parser, bits).parse() - nodelist = parser.parse(('elif', 'else', 'endif')) + nodelist = parser.parse(("elif", "else", "endif")) conditions_nodelists.append((condition, nodelist)) token = parser.next_token() # {% else %} (optional) - if token.contents == 'else': - nodelist = parser.parse(('endif',)) + if token.contents == "else": + nodelist = parser.parse(("endif",)) conditions_nodelists.append((None, nodelist)) token = parser.next_token() # {% endif %} - if token.contents != 'endif': - raise TemplateSyntaxError('Malformed template tag at line {}: "{}"'.format(token.lineno, token.contents)) + if token.contents != "endif": + raise TemplateSyntaxError( + 'Malformed template tag at line {}: "{}"'.format( + token.lineno, token.contents + ) + ) return IfNode(conditions_nodelists) @@ -963,10 +1007,10 @@ def ifchanged(parser, token): {% endfor %} """ bits = token.split_contents() - nodelist_true = parser.parse(('else', 'endifchanged')) + nodelist_true = parser.parse(("else", "endifchanged")) token = parser.next_token() - if token.contents == 'else': - nodelist_false = parser.parse(('endifchanged',)) + if token.contents == "else": + nodelist_false = parser.parse(("endifchanged",)) parser.delete_first_token() else: nodelist_false = NodeList() @@ -979,8 +1023,10 @@ def find_library(parser, name): return parser.libraries[name] except KeyError: raise TemplateSyntaxError( - "'%s' is not a registered tag library. Must be one of:\n%s" % ( - name, "\n".join(sorted(parser.libraries)), + "'%s' is not a registered tag library. Must be one of:\n%s" + % ( + name, + "\n".join(sorted(parser.libraries)), ), ) @@ -1000,8 +1046,10 @@ def load_from_library(library, label, names): subset.filters[name] = library.filters[name] if found is False: raise TemplateSyntaxError( - "'%s' is not a valid tag or filter in tag library '%s'" % ( - name, label, + "'%s' is not a valid tag or filter in tag library '%s'" + % ( + name, + label, ), ) return subset @@ -1066,19 +1114,19 @@ def lorem(parser, token): bits = list(token.split_contents()) tagname = bits[0] # Random bit - common = bits[-1] != 'random' + common = bits[-1] != "random" if not common: bits.pop() # Method bit - if bits[-1] in ('w', 'p', 'b'): + if bits[-1] in ("w", "p", "b"): method = bits.pop() else: - method = 'b' + method = "b" # Count bit if len(bits) > 1: count = bits.pop() else: - count = '1' + count = "1" count = parser.compile_filter(count) if len(bits) != 1: raise TemplateSyntaxError("Incorrect format for %r tag" % tagname) @@ -1099,7 +1147,7 @@ def now(parser, token): """ bits = token.split_contents() asvar = None - if len(bits) == 4 and bits[-2] == 'as': + if len(bits) == 4 and bits[-2] == "as": asvar = bits[-1] bits = bits[:-2] if len(bits) != 2: @@ -1159,12 +1207,10 @@ def regroup(parser, token): if len(bits) != 6: raise TemplateSyntaxError("'regroup' tag takes five arguments") target = parser.compile_filter(bits[1]) - if bits[2] != 'by': + if bits[2] != "by": raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") - if bits[4] != 'as': - raise TemplateSyntaxError( - "next-to-last argument to 'regroup' tag must be 'as'" - ) + if bits[4] != "as": + raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must be 'as'") var_name = bits[5] # RegroupNode will take each item in 'target', put it in the context under # 'var_name', evaluate 'var_name'.'expression' in the current context, and @@ -1172,9 +1218,9 @@ def regroup(parser, token): # save the final result in the context under 'var_name', thus clearing the # temporary values. This hack is necessary because the template engine # doesn't provide a context-aware equivalent of Python's getattr. - expression = parser.compile_filter(var_name + - VARIABLE_ATTRIBUTE_SEPARATOR + - bits[3]) + expression = parser.compile_filter( + var_name + VARIABLE_ATTRIBUTE_SEPARATOR + bits[3] + ) return RegroupNode(target, expression, var_name) @@ -1230,7 +1276,7 @@ def spaceless(parser, token): </strong> {% endspaceless %} """ - nodelist = parser.parse(('endspaceless',)) + nodelist = parser.parse(("endspaceless",)) parser.delete_first_token() return SpacelessNode(nodelist) @@ -1264,9 +1310,10 @@ def templatetag(parser, token): raise TemplateSyntaxError("'templatetag' statement takes one argument") tag = bits[1] if tag not in TemplateTagNode.mapping: - raise TemplateSyntaxError("Invalid templatetag argument: '%s'." - " Must be one of: %s" % - (tag, list(TemplateTagNode.mapping))) + raise TemplateSyntaxError( + "Invalid templatetag argument: '%s'." + " Must be one of: %s" % (tag, list(TemplateTagNode.mapping)) + ) return TemplateTagNode(tag) @@ -1314,13 +1361,15 @@ def url(parser, token): """ bits = token.split_contents() if len(bits) < 2: - raise TemplateSyntaxError("'%s' takes at least one argument, a URL pattern name." % bits[0]) + raise TemplateSyntaxError( + "'%s' takes at least one argument, a URL pattern name." % bits[0] + ) viewname = parser.compile_filter(bits[1]) args = [] kwargs = {} asvar = None bits = bits[2:] - if len(bits) >= 2 and bits[-2] == 'as': + if len(bits) >= 2 and bits[-2] == "as": asvar = bits[-1] bits = bits[:-2] @@ -1355,7 +1404,7 @@ def verbatim(parser, token): ... {% endverbatim myblock %} """ - nodelist = parser.parse(('endverbatim',)) + nodelist = parser.parse(("endverbatim",)) parser.delete_first_token() return VerbatimNode(nodelist.render(Context())) @@ -1387,18 +1436,22 @@ def widthratio(parser, token): asvar = None elif len(bits) == 6: tag, this_value_expr, max_value_expr, max_width, as_, asvar = bits - if as_ != 'as': - raise TemplateSyntaxError("Invalid syntax in widthratio tag. Expecting 'as' keyword") + if as_ != "as": + raise TemplateSyntaxError( + "Invalid syntax in widthratio tag. Expecting 'as' keyword" + ) else: raise TemplateSyntaxError("widthratio takes at least three arguments") - return WidthRatioNode(parser.compile_filter(this_value_expr), - parser.compile_filter(max_value_expr), - parser.compile_filter(max_width), - asvar=asvar) + return WidthRatioNode( + parser.compile_filter(this_value_expr), + parser.compile_filter(max_value_expr), + parser.compile_filter(max_width), + asvar=asvar, + ) -@register.tag('with') +@register.tag("with") def do_with(parser, token): """ Add one or more values to the context (inside of this block) for caching @@ -1427,8 +1480,9 @@ def do_with(parser, token): "%r expected at least one variable assignment" % bits[0] ) if remaining_bits: - raise TemplateSyntaxError("%r received an invalid token: %r" % - (bits[0], remaining_bits[0])) - nodelist = parser.parse(('endwith',)) + raise TemplateSyntaxError( + "%r received an invalid token: %r" % (bits[0], remaining_bits[0]) + ) + nodelist = parser.parse(("endwith",)) parser.delete_first_token() return WithNode(None, None, nodelist, extra_context=extra_context) diff --git a/django/template/engine.py b/django/template/engine.py index 91e503f709..9e6f1e97da 100644 --- a/django/template/engine.py +++ b/django/template/engine.py @@ -12,28 +12,39 @@ from .library import import_library class Engine: default_builtins = [ - 'django.template.defaulttags', - 'django.template.defaultfilters', - 'django.template.loader_tags', + "django.template.defaulttags", + "django.template.defaultfilters", + "django.template.loader_tags", ] - def __init__(self, dirs=None, app_dirs=False, context_processors=None, - debug=False, loaders=None, string_if_invalid='', - file_charset='utf-8', libraries=None, builtins=None, autoescape=True): + def __init__( + self, + dirs=None, + app_dirs=False, + context_processors=None, + debug=False, + loaders=None, + string_if_invalid="", + file_charset="utf-8", + libraries=None, + builtins=None, + autoescape=True, + ): if dirs is None: dirs = [] if context_processors is None: context_processors = [] if loaders is None: - loaders = ['django.template.loaders.filesystem.Loader'] + loaders = ["django.template.loaders.filesystem.Loader"] if app_dirs: - loaders += ['django.template.loaders.app_directories.Loader'] + loaders += ["django.template.loaders.app_directories.Loader"] if not debug: - loaders = [('django.template.loaders.cached.Loader', loaders)] + loaders = [("django.template.loaders.cached.Loader", loaders)] else: if app_dirs: raise ImproperlyConfigured( - "app_dirs must not be set when loaders is defined.") + "app_dirs must not be set when loaders is defined." + ) if libraries is None: libraries = {} if builtins is None: @@ -54,21 +65,21 @@ class Engine: 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>' + "<%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), + "" 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), + 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), + "" if not self.libraries else " libraries=%s" % repr(self.libraries), + "" if not self.builtins else " builtins=%s" % repr(self.builtins), repr(self.autoescape), ) @@ -93,10 +104,11 @@ class Engine: # local imports are required to avoid import loops. from django.template import engines from django.template.backends.django import DjangoTemplates + for engine in engines.all(): if isinstance(engine, DjangoTemplates): return engine.engine - raise ImproperlyConfigured('No DjangoTemplates backend is configured.') + raise ImproperlyConfigured("No DjangoTemplates backend is configured.") @cached_property def template_context_processors(self): @@ -136,7 +148,8 @@ class Engine: return loader_class(self, *args) else: raise ImproperlyConfigured( - "Invalid value in template loaders configuration: %r" % loader) + "Invalid value in template loaders configuration: %r" % loader + ) def find_template(self, name, dirs=None, skip=None): tried = [] @@ -161,7 +174,7 @@ class Engine: handling template inheritance recursively. """ template, origin = self.find_template(template_name) - if not hasattr(template, 'render'): + if not hasattr(template, "render"): # template needs to be compiled template = Template(template, origin, template_name, engine=self) return template @@ -197,4 +210,4 @@ class Engine: not_found.append(exc.args[0]) continue # If we get here, none of the templates could be loaded - raise TemplateDoesNotExist(', '.join(not_found)) + raise TemplateDoesNotExist(", ".join(not_found)) diff --git a/django/template/exceptions.py b/django/template/exceptions.py index 97edc9eba4..2a9c92f779 100644 --- a/django/template/exceptions.py +++ b/django/template/exceptions.py @@ -24,6 +24,7 @@ class TemplateDoesNotExist(Exception): encapsulate multiple exceptions when loading templates from multiple engines. """ + def __init__(self, msg, tried=None, backend=None, chain=None): self.backend = backend if tried is None: @@ -39,4 +40,5 @@ class TemplateSyntaxError(Exception): """ The exception used for syntax errors during parsing or rendering. """ + pass diff --git a/django/template/library.py b/django/template/library.py index 06ea5f1ad8..fbec9484a1 100644 --- a/django/template/library.py +++ b/django/template/library.py @@ -20,6 +20,7 @@ class Library: The filter, simple_tag, and inclusion_tag methods provide a convenient way to register callables as tags. """ + def __init__(self): self.filters = {} self.tags = {} @@ -36,6 +37,7 @@ class Library: # @register.tag('somename') or @register.tag(name='somename') def dec(func): return self.tag(name, func) + return dec elif name is not None and compile_function is not None: # register.tag('somename', somefunc) @@ -43,8 +45,8 @@ class Library: return compile_function else: raise ValueError( - "Unsupported arguments to Library.tag: (%r, %r)" % - (name, compile_function), + "Unsupported arguments to Library.tag: (%r, %r)" + % (name, compile_function), ) def tag_function(self, func): @@ -63,6 +65,7 @@ class Library: # @register.filter() def dec(func): return self.filter_function(func, **flags) + return dec elif name is not None and filter_func is None: if callable(name): @@ -72,11 +75,12 @@ class Library: # @register.filter('somename') or @register.filter(name='somename') def dec(func): return self.filter(name, func, **flags) + return dec elif name is not None and filter_func is not None: # register.filter('somename', somefunc) self.filters[name] = filter_func - for attr in ('expects_localtime', 'is_safe', 'needs_autoescape'): + for attr in ("expects_localtime", "is_safe", "needs_autoescape"): if attr in flags: value = flags[attr] # set the flag on the filter for FilterExpression.resolve @@ -88,8 +92,8 @@ class Library: return filter_func else: raise ValueError( - "Unsupported arguments to Library.filter: (%r, %r)" % - (name, filter_func), + "Unsupported arguments to Library.filter: (%r, %r)" + % (name, filter_func), ) def filter_function(self, func, **flags): @@ -103,22 +107,40 @@ class Library: def hello(*args, **kwargs): return 'world' """ + def dec(func): - params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(unwrap(func)) - function_name = (name or func.__name__) + ( + params, + varargs, + varkw, + defaults, + kwonly, + kwonly_defaults, + _, + ) = getfullargspec(unwrap(func)) + function_name = name or func.__name__ @functools.wraps(func) def compile_func(parser, token): bits = token.split_contents()[1:] target_var = None - if len(bits) >= 2 and bits[-2] == 'as': + if len(bits) >= 2 and bits[-2] == "as": target_var = bits[-1] bits = bits[:-2] args, kwargs = parse_bits( - parser, bits, params, varargs, varkw, defaults, - kwonly, kwonly_defaults, takes_context, function_name, + parser, + bits, + params, + varargs, + varkw, + defaults, + kwonly, + kwonly_defaults, + takes_context, + function_name, ) return SimpleNode(func, takes_context, args, kwargs, target_var) + self.tag(function_name, compile_func) return func @@ -140,22 +162,45 @@ class Library: choices = poll.choice_set.all() return {'choices': choices} """ + def dec(func): - params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(unwrap(func)) + ( + params, + varargs, + varkw, + defaults, + kwonly, + kwonly_defaults, + _, + ) = getfullargspec(unwrap(func)) function_name = name or func.__name__ @functools.wraps(func) def compile_func(parser, token): bits = token.split_contents()[1:] args, kwargs = parse_bits( - parser, bits, params, varargs, varkw, defaults, - kwonly, kwonly_defaults, takes_context, function_name, + parser, + bits, + params, + varargs, + varkw, + defaults, + kwonly, + kwonly_defaults, + takes_context, + function_name, ) return InclusionNode( - func, takes_context, args, kwargs, filename, + func, + takes_context, + args, + kwargs, + filename, ) + self.tag(function_name, compile_func) return func + return dec @@ -165,6 +210,7 @@ class TagHelperNode(Node): Manages the positional and keyword arguments to be passed to the decorated function. """ + def __init__(self, func, takes_context, args, kwargs): self.func = func self.takes_context = takes_context @@ -191,14 +237,13 @@ class SimpleNode(TagHelperNode): output = self.func(*resolved_args, **resolved_kwargs) if self.target_var is not None: context[self.target_var] = output - return '' + return "" if context.autoescape: output = conditional_escape(output) return output class InclusionNode(TagHelperNode): - def __init__(self, func, takes_context, args, kwargs, filename): super().__init__(func, takes_context, args, kwargs) self.filename = filename @@ -216,7 +261,7 @@ class InclusionNode(TagHelperNode): if t is None: if isinstance(self.filename, Template): t = self.filename - elif isinstance(getattr(self.filename, 'template', None), Template): + elif isinstance(getattr(self.filename, "template", None), Template): t = self.filename.template elif not isinstance(self.filename, str) and is_iterable(self.filename): t = context.template.engine.select_template(self.filename) @@ -227,32 +272,42 @@ class InclusionNode(TagHelperNode): # Copy across the CSRF token, if present, because inclusion tags are # often used for forms, and we need instructions for using CSRF # protection to be as simple as possible. - csrf_token = context.get('csrf_token') + csrf_token = context.get("csrf_token") if csrf_token is not None: - new_context['csrf_token'] = csrf_token + new_context["csrf_token"] = csrf_token return t.render(new_context) -def parse_bits(parser, bits, params, varargs, varkw, defaults, - kwonly, kwonly_defaults, takes_context, name): +def parse_bits( + parser, + bits, + params, + varargs, + varkw, + defaults, + kwonly, + kwonly_defaults, + takes_context, + name, +): """ Parse bits for template tag helpers simple_tag and inclusion_tag, in particular by detecting syntax errors and by extracting positional and keyword arguments. """ if takes_context: - if params and params[0] == 'context': + if params 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) + "have a first argument of 'context'" % name + ) args = [] kwargs = {} unhandled_params = list(params) unhandled_kwargs = [ - kwarg for kwarg in kwonly - if not kwonly_defaults or kwarg not in kwonly_defaults + kwarg for kwarg in kwonly if not kwonly_defaults or kwarg not in kwonly_defaults ] for bit in bits: # First we try to extract a potential kwarg from the bit @@ -263,13 +318,14 @@ def parse_bits(parser, bits, params, varargs, varkw, defaults, if param not in params and param not in kwonly and varkw is None: # An unexpected keyword argument was supplied raise TemplateSyntaxError( - "'%s' received unexpected keyword argument '%s'" % - (name, param)) + "'%s' received unexpected keyword argument '%s'" % (name, param) + ) elif param in kwargs: # The keyword argument has already been supplied once raise TemplateSyntaxError( - "'%s' received multiple values for keyword argument '%s'" % - (name, param)) + "'%s' received multiple values for keyword argument '%s'" + % (name, param) + ) else: # All good, record the keyword argument kwargs[str(param)] = value @@ -284,7 +340,8 @@ def parse_bits(parser, bits, params, varargs, varkw, defaults, if kwargs: raise TemplateSyntaxError( "'%s' received some positional argument(s) after some " - "keyword argument(s)" % name) + "keyword argument(s)" % name + ) else: # Record the positional argument args.append(parser.compile_filter(bit)) @@ -294,17 +351,18 @@ def parse_bits(parser, bits, params, varargs, varkw, defaults, except IndexError: if varargs is None: raise TemplateSyntaxError( - "'%s' received too many positional arguments" % - name) + "'%s' received too many positional arguments" % name + ) if defaults is not None: # Consider the last n params handled, where n is the # number of defaults. - unhandled_params = unhandled_params[:-len(defaults)] + unhandled_params = unhandled_params[: -len(defaults)] if unhandled_params or unhandled_kwargs: # Some positional arguments were not supplied raise TemplateSyntaxError( - "'%s' did not receive value(s) for the argument(s): %s" % - (name, ", ".join("'%s'" % p for p in unhandled_params + unhandled_kwargs))) + "'%s' did not receive value(s) for the argument(s): %s" + % (name, ", ".join("'%s'" % p for p in unhandled_params + unhandled_kwargs)) + ) return args, kwargs diff --git a/django/template/loader.py b/django/template/loader.py index 2492aee760..9b108f3456 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -29,9 +29,9 @@ def select_template(template_name_list, using=None): """ if isinstance(template_name_list, str): raise TypeError( - 'select_template() takes an iterable of template names but got a ' - 'string: %r. Use get_template() if you want to load a single ' - 'template by name.' % template_name_list + "select_template() takes an iterable of template names but got a " + "string: %r. Use get_template() if you want to load a single " + "template by name." % template_name_list ) chain = [] @@ -44,7 +44,7 @@ def select_template(template_name_list, using=None): chain.append(e) if template_name_list: - raise TemplateDoesNotExist(', '.join(template_name_list), chain=chain) + raise TemplateDoesNotExist(", ".join(template_name_list), chain=chain) else: raise TemplateDoesNotExist("No template names provided") diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py index 37cefaf9c7..bf4d4f0d74 100644 --- a/django/template/loader_tags.py +++ b/django/template/loader_tags.py @@ -3,14 +3,12 @@ from collections import defaultdict from django.utils.safestring import mark_safe -from .base import ( - Node, Template, TemplateSyntaxError, TextNode, Variable, token_kwargs, -) +from .base import Node, Template, TemplateSyntaxError, TextNode, Variable, token_kwargs from .library import Library register = Library() -BLOCK_CONTEXT_KEY = 'block_context' +BLOCK_CONTEXT_KEY = "block_context" class BlockContext: @@ -19,7 +17,7 @@ class BlockContext: self.blocks = defaultdict(list) def __repr__(self): - return f'<{self.__class__.__qualname__}: blocks={self.blocks!r}>' + return f"<{self.__class__.__qualname__}: blocks={self.blocks!r}>" def add_blocks(self, blocks): for name, block in blocks.items(): @@ -52,7 +50,7 @@ class BlockNode(Node): block_context = context.render_context.get(BLOCK_CONTEXT_KEY) with context.push(): if block_context is None: - context['block'] = self + context["block"] = self result = self.nodelist.render(context) else: push = block = block_context.pop(self.name) @@ -61,28 +59,30 @@ class BlockNode(Node): # Create new block so we can store context without thread-safety issues. block = type(self)(block.name, block.nodelist) block.context = context - context['block'] = block + context["block"] = block result = block.nodelist.render(context) if push is not None: block_context.push(self.name, push) return result def super(self): - if not hasattr(self, 'context'): + if not hasattr(self, "context"): raise TemplateSyntaxError( "'%s' object has no attribute 'context'. Did you use " "{{ block.super }} in a base template?" % self.__class__.__name__ ) render_context = self.context.render_context - if (BLOCK_CONTEXT_KEY in render_context and - render_context[BLOCK_CONTEXT_KEY].get_block(self.name) is not None): + if ( + BLOCK_CONTEXT_KEY in render_context + and render_context[BLOCK_CONTEXT_KEY].get_block(self.name) is not None + ): return mark_safe(self.render(self.context)) - return '' + return "" class ExtendsNode(Node): must_be_first = True - context_key = 'extends_context' + context_key = "extends_context" def __init__(self, nodelist, parent_name, template_dirs=None): self.nodelist = nodelist @@ -91,7 +91,7 @@ class ExtendsNode(Node): self.blocks = {n.name: n for n in nodelist.get_nodes_by_type(BlockNode)} def __repr__(self): - return '<%s: extends %s>' % (self.__class__.__name__, self.parent_name.token) + return "<%s: extends %s>" % (self.__class__.__name__, self.parent_name.token) def find_template(self, template_name, context): """ @@ -101,10 +101,12 @@ class ExtendsNode(Node): without extending the same template twice. """ history = context.render_context.setdefault( - self.context_key, [self.origin], + self.context_key, + [self.origin], ) template, origin = context.template.engine.find_template( - template_name, skip=history, + template_name, + skip=history, ) history.append(origin) return template @@ -113,15 +115,15 @@ class ExtendsNode(Node): parent = self.parent_name.resolve(context) if not parent: error_msg = "Invalid template name in 'extends' tag: %r." % parent - if self.parent_name.filters or\ - isinstance(self.parent_name.var, Variable): - error_msg += " Got this from the '%s' variable." %\ - self.parent_name.token + if self.parent_name.filters or isinstance(self.parent_name.var, Variable): + error_msg += ( + " Got this from the '%s' variable." % self.parent_name.token + ) raise TemplateSyntaxError(error_msg) if isinstance(parent, Template): # parent is a django.template.Template return parent - if isinstance(getattr(parent, 'template', None), Template): + if isinstance(getattr(parent, "template", None), Template): # parent is a django.template.backends.django.Template return parent.template return self.find_template(parent, context) @@ -142,8 +144,10 @@ class ExtendsNode(Node): # The ExtendsNode has to be the first non-text node. if not isinstance(node, TextNode): if not isinstance(node, ExtendsNode): - blocks = {n.name: n for n in - compiled_parent.nodelist.get_nodes_by_type(BlockNode)} + blocks = { + n.name: n + for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode) + } block_context.add_blocks(blocks) break @@ -154,16 +158,18 @@ class ExtendsNode(Node): class IncludeNode(Node): - context_key = '__include_context' + context_key = "__include_context" - def __init__(self, template, *args, extra_context=None, isolated_context=False, **kwargs): + def __init__( + self, template, *args, extra_context=None, isolated_context=False, **kwargs + ): self.template = template self.extra_context = extra_context or {} self.isolated_context = isolated_context super().__init__(*args, **kwargs) def __repr__(self): - return f'<{self.__class__.__qualname__}: template={self.template!r}>' + return f"<{self.__class__.__qualname__}: template={self.template!r}>" def render(self, context): """ @@ -173,14 +179,16 @@ class IncludeNode(Node): """ template = self.template.resolve(context) # Does this quack like a Template? - if not callable(getattr(template, 'render', None)): + if not callable(getattr(template, "render", None)): # If not, try the cache and select_template(). template_name = template or () if isinstance(template_name, str): - template_name = (construct_relative_path( - self.origin.template_name, - template_name, - ),) + template_name = ( + construct_relative_path( + self.origin.template_name, + template_name, + ), + ) else: template_name = tuple(template_name) cache = context.render_context.dicts[0].setdefault(self, {}) @@ -189,11 +197,10 @@ class IncludeNode(Node): template = context.template.engine.select_template(template_name) cache[template_name] = template # Use the base.Template of a backends.django.Template. - elif hasattr(template, 'template'): + elif hasattr(template, "template"): template = template.template values = { - name: var.resolve(context) - for name, var in self.extra_context.items() + name: var.resolve(context) for name, var in self.extra_context.items() } if self.isolated_context: return template.render(context.new(values)) @@ -201,7 +208,7 @@ class IncludeNode(Node): return template.render(context) -@register.tag('block') +@register.tag("block") def do_block(parser, token): """ Define a block that can be overridden by child templates. @@ -215,17 +222,19 @@ def do_block(parser, token): # check for duplication. try: if block_name in parser.__loaded_blocks: - raise TemplateSyntaxError("'%s' tag with name '%s' appears more than once" % (bits[0], block_name)) + raise TemplateSyntaxError( + "'%s' tag with name '%s' appears more than once" % (bits[0], block_name) + ) parser.__loaded_blocks.append(block_name) except AttributeError: # parser.__loaded_blocks isn't a list yet parser.__loaded_blocks = [block_name] - nodelist = parser.parse(('endblock',)) + nodelist = parser.parse(("endblock",)) # This check is kept for backwards-compatibility. See #3100. endblock = parser.next_token() - acceptable_endblocks = ('endblock', 'endblock %s' % block_name) + acceptable_endblocks = ("endblock", "endblock %s" % block_name) if endblock.contents not in acceptable_endblocks: - parser.invalid_block_tag(endblock, 'endblock', acceptable_endblocks) + parser.invalid_block_tag(endblock, "endblock", acceptable_endblocks) return BlockNode(block_name, nodelist) @@ -235,37 +244,36 @@ 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(('./', '../')): + new_name = relative_name.strip("'\"") + if not new_name.startswith(("./", "../")): # relative_name is a variable or a literal that doesn't contain a # relative path. return relative_name new_name = posixpath.normpath( posixpath.join( - posixpath.dirname(current_template_name.lstrip('/')), + posixpath.dirname(current_template_name.lstrip("/")), new_name, ) ) - if new_name.startswith('../'): + if new_name.startswith("../"): raise TemplateSyntaxError( "The relative path '%s' points outside the file hierarchy that " "template '%s' is in." % (relative_name, current_template_name) ) - if current_template_name.lstrip('/') == new_name: + if current_template_name.lstrip("/") == new_name: raise TemplateSyntaxError( "The relative path '%s' was translated to template name '%s', the " "same template in which the tag appears." % (relative_name, current_template_name) ) has_quotes = ( - relative_name.startswith(('"', "'")) and - relative_name[0] == relative_name[-1] + relative_name.startswith(('"', "'")) and relative_name[0] == relative_name[-1] ) return f'"{new_name}"' if has_quotes else new_name -@register.tag('extends') +@register.tag("extends") def do_extends(parser, token): """ Signal that this template extends a parent template. @@ -283,11 +291,13 @@ def do_extends(parser, token): parent_name = parser.compile_filter(bits[1]) nodelist = parser.parse() if nodelist.get_nodes_by_type(ExtendsNode): - raise TemplateSyntaxError("'%s' cannot appear more than once in the same template" % bits[0]) + raise TemplateSyntaxError( + "'%s' cannot appear more than once in the same template" % bits[0] + ) return ExtendsNode(nodelist, parent_name) -@register.tag('include') +@register.tag("include") def do_include(parser, token): """ Load a template and render it with the current context. You can pass @@ -316,22 +326,26 @@ def do_include(parser, token): option = remaining_bits.pop(0) if option in options: raise TemplateSyntaxError( - 'The %r option was specified more than once.' % option + "The %r option was specified more than once." % option ) - if option == 'with': + 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': + elif option == "only": value = True else: - raise TemplateSyntaxError('Unknown argument for %r tag: %r.' % - (bits[0], option)) + raise TemplateSyntaxError( + "Unknown argument for %r tag: %r." % (bits[0], option) + ) options[option] = value - isolated_context = options.get('only', False) - namemap = options.get('with', {}) + isolated_context = options.get("only", False) + namemap = options.get("with", {}) bits[1] = construct_relative_path(parser.origin.template_name, bits[1]) - return IncludeNode(parser.compile_filter(bits[1]), extra_context=namemap, - isolated_context=isolated_context) + return IncludeNode( + parser.compile_filter(bits[1]), + extra_context=namemap, + isolated_context=isolated_context, + ) diff --git a/django/template/loaders/app_directories.py b/django/template/loaders/app_directories.py index c9a8adf49c..0bd7dc8e25 100644 --- a/django/template/loaders/app_directories.py +++ b/django/template/loaders/app_directories.py @@ -9,6 +9,5 @@ from .filesystem import Loader as FilesystemLoader class Loader(FilesystemLoader): - def get_dirs(self): - return get_app_template_dirs('templates') + return get_app_template_dirs("templates") diff --git a/django/template/loaders/base.py b/django/template/loaders/base.py index b77ea9eca2..9168c26c54 100644 --- a/django/template/loaders/base.py +++ b/django/template/loaders/base.py @@ -2,7 +2,6 @@ from django.template import Template, TemplateDoesNotExist class Loader: - def __init__(self, engine): self.engine = engine @@ -17,17 +16,20 @@ class Loader: for origin in self.get_template_sources(template_name): if skip is not None and origin in skip: - tried.append((origin, 'Skipped to avoid recursion')) + tried.append((origin, "Skipped to avoid recursion")) continue try: contents = self.get_contents(origin) except TemplateDoesNotExist: - tried.append((origin, 'Source does not exist')) + tried.append((origin, "Source does not exist")) continue else: return Template( - contents, origin, origin.template_name, self.engine, + contents, + origin, + origin.template_name, + self.engine, ) raise TemplateDoesNotExist(template_name, tried=tried) @@ -38,7 +40,7 @@ class Loader: template name. """ raise NotImplementedError( - 'subclasses of Loader must provide a get_template_sources() method' + "subclasses of Loader must provide a get_template_sources() method" ) def reset(self): diff --git a/django/template/loaders/cached.py b/django/template/loaders/cached.py index bb47682d7f..4f40953831 100644 --- a/django/template/loaders/cached.py +++ b/django/template/loaders/cached.py @@ -12,7 +12,6 @@ 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) @@ -57,7 +56,9 @@ class Loader(BaseLoader): try: template = super().get_template(template_name, skip) except TemplateDoesNotExist as e: - self.get_template_cache[key] = copy_exception(e) if self.engine.debug else TemplateDoesNotExist + self.get_template_cache[key] = ( + copy_exception(e) if self.engine.debug else TemplateDoesNotExist + ) raise else: self.get_template_cache[key] = template @@ -80,17 +81,19 @@ class Loader(BaseLoader): y -> a -> a z -> a -> a """ - skip_prefix = '' + skip_prefix = "" if skip: - matching = [origin.name for origin in skip if origin.template_name == template_name] + matching = [ + origin.name for origin in skip if origin.template_name == template_name + ] if matching: skip_prefix = self.generate_hash(matching) - return '-'.join(s for s in (str(template_name), skip_prefix) if s) + return "-".join(s for s in (str(template_name), skip_prefix) if s) def generate_hash(self, values): - return hashlib.sha1('|'.join(values).encode()).hexdigest() + return hashlib.sha1("|".join(values).encode()).hexdigest() def reset(self): "Empty the template cache." diff --git a/django/template/loaders/filesystem.py b/django/template/loaders/filesystem.py index 2e49e3d6b3..a2474a3fad 100644 --- a/django/template/loaders/filesystem.py +++ b/django/template/loaders/filesystem.py @@ -10,7 +10,6 @@ from .base import Loader as BaseLoader class Loader(BaseLoader): - def __init__(self, engine, dirs=None): super().__init__(engine) self.dirs = dirs diff --git a/django/template/loaders/locmem.py b/django/template/loaders/locmem.py index 25d7672719..432de62b6c 100644 --- a/django/template/loaders/locmem.py +++ b/django/template/loaders/locmem.py @@ -8,7 +8,6 @@ from .base import Loader as BaseLoader class Loader(BaseLoader): - def __init__(self, engine, templates_dict): self.templates_dict = templates_dict super().__init__(engine) diff --git a/django/template/response.py b/django/template/response.py index 63d2f4a577..c38b95e9de 100644 --- a/django/template/response.py +++ b/django/template/response.py @@ -8,10 +8,18 @@ class ContentNotRenderedError(Exception): class SimpleTemplateResponse(HttpResponse): - rendering_attrs = ['template_name', 'context_data', '_post_render_callbacks'] + rendering_attrs = ["template_name", "context_data", "_post_render_callbacks"] - def __init__(self, template, context=None, content_type=None, status=None, - charset=None, using=None, headers=None): + def __init__( + self, + template, + context=None, + content_type=None, + status=None, + charset=None, + using=None, + headers=None, + ): # It would seem obvious to call these next two members 'template' and # 'context', but those names are reserved as part of the test Client # API. To avoid the name collision, we use different names. @@ -33,7 +41,7 @@ class SimpleTemplateResponse(HttpResponse): # content argument doesn't make sense here because it will be replaced # with rendered template so we always pass empty string in order to # prevent errors and provide shorter signature. - super().__init__('', content_type, status, charset=charset, headers=headers) + super().__init__("", content_type, status, charset=charset, headers=headers) # _is_rendered tracks whether the template and context has been baked # into a final response. @@ -50,7 +58,7 @@ class SimpleTemplateResponse(HttpResponse): obj_dict = self.__dict__.copy() if not self._is_rendered: raise ContentNotRenderedError( - 'The response content must be rendered before it can be pickled.' + "The response content must be rendered before it can be pickled." ) for attr in self.rendering_attrs: if attr in obj_dict: @@ -117,7 +125,7 @@ class SimpleTemplateResponse(HttpResponse): def __iter__(self): if not self._is_rendered: raise ContentNotRenderedError( - 'The response content must be rendered before it can be iterated over.' + "The response content must be rendered before it can be iterated over." ) return super().__iter__() @@ -125,7 +133,7 @@ class SimpleTemplateResponse(HttpResponse): def content(self): if not self._is_rendered: raise ContentNotRenderedError( - 'The response content must be rendered before it can be accessed.' + "The response content must be rendered before it can be accessed." ) return super().content @@ -137,9 +145,20 @@ class SimpleTemplateResponse(HttpResponse): class TemplateResponse(SimpleTemplateResponse): - rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_request'] + rendering_attrs = SimpleTemplateResponse.rendering_attrs + ["_request"] - def __init__(self, request, template, context=None, content_type=None, - status=None, charset=None, using=None, headers=None): - super().__init__(template, context, content_type, status, charset, using, headers=headers) + def __init__( + self, + request, + template, + context=None, + content_type=None, + status=None, + charset=None, + using=None, + headers=None, + ): + super().__init__( + template, context, content_type, status, charset, using, headers=headers + ) self._request = request diff --git a/django/template/smartif.py b/django/template/smartif.py index 96a1af8db0..5b15a5a476 100644 --- a/django/template/smartif.py +++ b/django/template/smartif.py @@ -13,6 +13,7 @@ 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 @@ -45,6 +46,7 @@ def infix(bp, func): Create an infix operator, given a binding power and a function that evaluates the node. """ + class Operator(TokenBase): lbp = bp @@ -70,6 +72,7 @@ def prefix(bp, func): Create a prefix operator, given a binding power and a function that evaluates the node. """ + class Operator(TokenBase): lbp = bp @@ -91,19 +94,19 @@ def prefix(bp, func): # We defer variable evaluation to the lambda to ensure that terms are # lazily evaluated using Python's boolean parsing logic. OPERATORS = { - 'or': infix(6, lambda context, x, y: x.eval(context) or y.eval(context)), - 'and': infix(7, lambda context, x, y: x.eval(context) and y.eval(context)), - 'not': prefix(8, lambda context, x: not x.eval(context)), - 'in': infix(9, lambda context, x, y: x.eval(context) in y.eval(context)), - 'not in': infix(9, lambda context, x, y: x.eval(context) not in y.eval(context)), - 'is': infix(10, lambda context, x, y: x.eval(context) is y.eval(context)), - 'is not': infix(10, lambda context, x, y: x.eval(context) is not y.eval(context)), - '==': infix(10, lambda context, x, y: x.eval(context) == y.eval(context)), - '!=': infix(10, lambda context, x, y: x.eval(context) != y.eval(context)), - '>': infix(10, lambda context, x, y: x.eval(context) > y.eval(context)), - '>=': infix(10, lambda context, x, y: x.eval(context) >= y.eval(context)), - '<': infix(10, lambda context, x, y: x.eval(context) < y.eval(context)), - '<=': infix(10, lambda context, x, y: x.eval(context) <= y.eval(context)), + "or": infix(6, lambda context, x, y: x.eval(context) or y.eval(context)), + "and": infix(7, lambda context, x, y: x.eval(context) and y.eval(context)), + "not": prefix(8, lambda context, x: not x.eval(context)), + "in": infix(9, lambda context, x, y: x.eval(context) in y.eval(context)), + "not in": infix(9, lambda context, x, y: x.eval(context) not in y.eval(context)), + "is": infix(10, lambda context, x, y: x.eval(context) is y.eval(context)), + "is not": infix(10, lambda context, x, y: x.eval(context) is not y.eval(context)), + "==": infix(10, lambda context, x, y: x.eval(context) == y.eval(context)), + "!=": infix(10, lambda context, x, y: x.eval(context) != y.eval(context)), + ">": infix(10, lambda context, x, y: x.eval(context) > y.eval(context)), + ">=": infix(10, lambda context, x, y: x.eval(context) >= y.eval(context)), + "<": infix(10, lambda context, x, y: x.eval(context) < y.eval(context)), + "<=": infix(10, lambda context, x, y: x.eval(context) <= y.eval(context)), } # Assign 'id' to each: @@ -115,6 +118,7 @@ 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. @@ -190,8 +194,9 @@ class IfParser: retval = self.expression() # Check that we have exhausted all the tokens if self.current_token is not EndToken: - raise self.error_class("Unused '%s' at end of if expression." % - self.current_token.display()) + raise self.error_class( + "Unused '%s' at end of if expression." % self.current_token.display() + ) return retval def expression(self, rbp=0): diff --git a/django/template/utils.py b/django/template/utils.py index ad7baba2f3..2b118f900e 100644 --- a/django/template/utils.py +++ b/django/template/utils.py @@ -33,31 +33,34 @@ class EngineHandler: try: # This will raise an exception if 'BACKEND' doesn't exist or # isn't a string containing at least one dot. - default_name = tpl['BACKEND'].rsplit('.', 2)[-2] + default_name = tpl["BACKEND"].rsplit(".", 2)[-2] except Exception: - invalid_backend = tpl.get('BACKEND', '<not defined>') + invalid_backend = tpl.get("BACKEND", "<not defined>") raise ImproperlyConfigured( "Invalid BACKEND for a template engine: {}. Check " - "your TEMPLATES setting.".format(invalid_backend)) + "your TEMPLATES setting.".format(invalid_backend) + ) tpl = { - 'NAME': default_name, - 'DIRS': [], - 'APP_DIRS': False, - 'OPTIONS': {}, + "NAME": default_name, + "DIRS": [], + "APP_DIRS": False, + "OPTIONS": {}, **tpl, } - templates[tpl['NAME']] = tpl - backend_names.append(tpl['NAME']) + templates[tpl["NAME"]] = tpl + backend_names.append(tpl["NAME"]) counts = Counter(backend_names) duplicates = [alias for alias, count in counts.most_common() if count > 1] if duplicates: raise ImproperlyConfigured( "Template engine aliases aren't unique, duplicates: {}. " - "Set a unique NAME for each engine in settings.TEMPLATES." - .format(", ".join(duplicates))) + "Set a unique NAME for each engine in settings.TEMPLATES.".format( + ", ".join(duplicates) + ) + ) return templates @@ -70,13 +73,14 @@ class EngineHandler: except KeyError: raise InvalidTemplateEngineError( "Could not find config for '{}' " - "in settings.TEMPLATES".format(alias)) + "in settings.TEMPLATES".format(alias) + ) # If importing or initializing the backend raises an exception, # self._engines[alias] isn't set and this code may get executed # again, so we must preserve the original params. See #24265. params = params.copy() - backend = params.pop('BACKEND') + backend = params.pop("BACKEND") engine_cls = import_string(backend) engine = engine_cls(params) |
