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:
parent
fc22fdd34f
commit
15e97be988
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 (
|
||||
|
@ -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)
|
||||
|
@ -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."
|
||||
|
@ -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])
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user