1
0
mirror of https://github.com/django/django.git synced 2025-04-01 03:56:42 +00:00

Fixed #35899 -- Allowed multiline template tags.

This commit is contained in:
Anders Hovmöller 2024-11-23 13:40:59 +01:00
parent fc22fdd34f
commit 15e97be988
7 changed files with 93 additions and 7 deletions

View File

@ -22,6 +22,7 @@ class DjangoTemplates(BaseEngine):
options.setdefault("autoescape", True)
options.setdefault("debug", settings.DEBUG)
options.setdefault("file_charset", "utf-8")
options.setdefault("multiline", False)
libraries = options.get("libraries", {})
options["libraries"] = self.get_templatetag_libraries(libraries)
super().__init__(params)

View File

@ -86,7 +86,8 @@ 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"({%.*?%}|{{.*?}}|{#.*?#})", re.DOTALL)
tag_re_legacy = re.compile(r"({%.*?%}|{{.*?}}|{#.*?#})")
logger = logging.getLogger("django.template")
@ -180,9 +181,9 @@ class Template:
template source.
"""
if self.engine.debug:
lexer = DebugLexer(self.source)
lexer = DebugLexer(self.source, multiline=self.engine.multiline)
else:
lexer = Lexer(self.source)
lexer = Lexer(self.source, multiline=self.engine.multiline)
tokens = lexer.tokenize()
parser = Parser(
@ -338,9 +339,11 @@ class Token:
class Lexer:
def __init__(self, template_string):
def __init__(self, template_string, multiline=False):
self.template_string = template_string
self.verbatim = False
self.multiline = multiline
self.tag_re = tag_re if multiline else tag_re_legacy
def __repr__(self):
return '<%s template_string="%s...", verbatim=%s>' % (
@ -356,7 +359,7 @@ class Lexer:
in_tag = False
lineno = 1
result = []
for token_string in tag_re.split(self.template_string):
for token_string in self.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")
@ -401,7 +404,7 @@ class Lexer:
class DebugLexer(Lexer):
def _tag_re_split_positions(self):
last = 0
for match in tag_re.finditer(self.template_string):
for match in self.tag_re.finditer(self.template_string):
start, end = match.span()
yield last, start
yield start, end

View File

@ -1,9 +1,11 @@
import functools
import warnings
from django.core.exceptions import ImproperlyConfigured
from django.utils.functional import cached_property
from django.utils.module_loading import import_string
from ..utils.deprecation import RemovedInDjango60Warning
from .base import Template
from .context import Context, _builtin_context_processors
from .exceptions import TemplateDoesNotExist
@ -29,7 +31,13 @@ class Engine:
libraries=None,
builtins=None,
autoescape=True,
multiline=False,
):
if not multiline:
warnings.warn(
"Multiline tags in templates will become the default in Django 6.0",
RemovedInDjango60Warning,
)
if dirs is None:
dirs = []
if context_processors is None:
@ -61,6 +69,7 @@ class Engine:
self.template_libraries = self.get_template_libraries(libraries)
self.builtins = self.default_builtins + builtins
self.template_builtins = self.get_template_builtins(self.builtins)
self.multiline = multiline
def __repr__(self):
return (

View File

@ -46,6 +46,12 @@ else:
# Make deprecation warnings errors to ensure no usage of deprecated features.
warnings.simplefilter("error", RemovedInDjango60Warning)
warnings.simplefilter("error", RemovedInDjango61Warning)
# Ignore this message as the test suite uses the default
warnings.filterwarnings(
"ignore",
message="Multiline tags in templates will become the default in Django 6.0",
category=RemovedInDjango60Warning,
)
# Make resource and runtime warning errors to ensure no usage of error prone
# patterns.
warnings.simplefilter("error", ResourceWarning)

View File

@ -210,11 +210,21 @@ class BasicSyntaxTests(SimpleTestCase):
@setup({"basic-syntax24": "{{ moo\n }}"})
def test_basic_syntax24(self):
"""
Embedded newlines make it not-a-tag.
Embedded newlines make it not-a-tag if not in multiline mode.
"""
output = self.engine.render_to_string("basic-syntax24")
self.assertEqual(output, "{{ moo\n }}")
@setup({"basic-syntax24-multiline": "{{ \nfoo\n }}"})
def test_basic_syntax24_multiline(self):
"""
Embedded newlines are ok in multiline mode.
"""
output = self.multiline_engine.render_to_string(
"basic-syntax24-multiline", {"foo": "bar"}
)
self.assertEqual(output, "bar")
# Literal strings are permitted inside variables, mostly for i18n
# purposes.
@setup({"basic-syntax25": '{{ "fred" }}'})
@ -325,6 +335,39 @@ class BasicSyntaxTests(SimpleTestCase):
)
self.assertEqual(output, "foo bar")
@setup(
{
"basic-syntax39-multiline": """
{% if
foo
%}
a
{%
else
%}
b
{%
endif
%}
{#
comment
#}
"""
}
)
def test_basic_syntax39_multiline(self):
"""
Embedded newlines are ok in multiline mode.
"""
output = self.multiline_engine.render_to_string(
"basic-syntax39-multiline", {"foo": True}
).strip()
self.assertEqual(output, "a")
output = self.multiline_engine.render_to_string(
"basic-syntax39-multiline", {"foo": False}
).strip()
self.assertEqual(output, "b")
@setup({"template": "{% block content %}"})
def test_unclosed_block(self):
msg = "Unclosed tag on line 1: 'block'. Looking for one of: endblock."

View File

@ -45,6 +45,13 @@ class ParserTests(SimpleTestCase):
'<Lexer template_string="{% for i in 1 %}{{ a...", verbatim=False>',
)
def test_repr_multiline(self):
lexer = Lexer("{% \nfor i in 1\n %}{{ a }}\n{% \nendfor \n%}", multiline=True)
self.assertEqual(
repr(lexer),
'<Lexer template_string="{% for i in 1 %}{{...", verbatim=False>',
)
def test_filter_parsing(self):
c = {"article": {"section": "News"}}
p = Parser("", builtins=[filter_library])

View File

@ -55,6 +55,11 @@ def setup(templates, *args, test_once=False):
libraries=libraries,
loaders=loaders,
)
self.multiline_engine = Engine(
libraries=libraries,
loaders=loaders,
multiline=True,
)
func(self)
if test_once:
return
@ -65,6 +70,12 @@ def setup(templates, *args, test_once=False):
loaders=loaders,
string_if_invalid="INVALID",
)
self.multiline_engine = Engine(
libraries=libraries,
loaders=loaders,
string_if_invalid="INVALID",
multiline=True,
)
func(self)
func(self)
@ -73,6 +84,12 @@ def setup(templates, *args, test_once=False):
libraries=libraries,
loaders=loaders,
)
self.multiline_engine = Engine(
debug=True,
libraries=libraries,
loaders=loaders,
multiline=True,
)
func(self)
func(self)