mirror of
https://github.com/django/django.git
synced 2024-12-22 09:05:43 +00:00
Fixed #35535 -- Added template tag decorator simple_block_tag().
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
This commit is contained in:
parent
9543c605c3
commit
4c452cc377
@ -153,6 +153,90 @@ class Library:
|
||||
else:
|
||||
raise ValueError("Invalid arguments provided to simple_tag")
|
||||
|
||||
def simple_block_tag(self, func=None, takes_context=None, name=None, end_name=None):
|
||||
"""
|
||||
Register a callable as a compiled block template tag. Example:
|
||||
|
||||
@register.simple_block_tag
|
||||
def hello(content):
|
||||
return 'world'
|
||||
"""
|
||||
|
||||
def dec(func):
|
||||
nonlocal end_name
|
||||
|
||||
(
|
||||
params,
|
||||
varargs,
|
||||
varkw,
|
||||
defaults,
|
||||
kwonly,
|
||||
kwonly_defaults,
|
||||
_,
|
||||
) = getfullargspec(unwrap(func))
|
||||
function_name = name or func.__name__
|
||||
|
||||
if end_name is None:
|
||||
end_name = f"end{function_name}"
|
||||
|
||||
@wraps(func)
|
||||
def compile_func(parser, token):
|
||||
tag_params = params.copy()
|
||||
|
||||
if takes_context:
|
||||
if len(tag_params) >= 2 and tag_params[1] == "content":
|
||||
del tag_params[1]
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
f"{function_name!r} is decorated with takes_context=True so"
|
||||
" it must have a first argument of 'context' and a second "
|
||||
"argument of 'content'"
|
||||
)
|
||||
elif tag_params and tag_params[0] == "content":
|
||||
del tag_params[0]
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
f"'{function_name}' must have a first argument of 'content'"
|
||||
)
|
||||
|
||||
bits = token.split_contents()[1:]
|
||||
target_var = None
|
||||
if len(bits) >= 2 and bits[-2] == "as":
|
||||
target_var = bits[-1]
|
||||
bits = bits[:-2]
|
||||
|
||||
nodelist = parser.parse((end_name,))
|
||||
parser.delete_first_token()
|
||||
|
||||
args, kwargs = parse_bits(
|
||||
parser,
|
||||
bits,
|
||||
tag_params,
|
||||
varargs,
|
||||
varkw,
|
||||
defaults,
|
||||
kwonly,
|
||||
kwonly_defaults,
|
||||
takes_context,
|
||||
function_name,
|
||||
)
|
||||
|
||||
return SimpleBlockNode(
|
||||
nodelist, func, takes_context, args, kwargs, target_var
|
||||
)
|
||||
|
||||
self.tag(function_name, compile_func)
|
||||
return func
|
||||
|
||||
if func is None:
|
||||
# @register.simple_block_tag(...)
|
||||
return dec
|
||||
elif callable(func):
|
||||
# @register.simple_block_tag
|
||||
return dec(func)
|
||||
else:
|
||||
raise ValueError("Invalid arguments provided to simple_block_tag")
|
||||
|
||||
def inclusion_tag(self, filename, func=None, takes_context=None, name=None):
|
||||
"""
|
||||
Register a callable as an inclusion tag:
|
||||
@ -243,6 +327,23 @@ class SimpleNode(TagHelperNode):
|
||||
return output
|
||||
|
||||
|
||||
class SimpleBlockNode(SimpleNode):
|
||||
def __init__(self, nodelist, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.nodelist = nodelist
|
||||
|
||||
def get_resolved_arguments(self, context):
|
||||
resolved_args, resolved_kwargs = super().get_resolved_arguments(context)
|
||||
|
||||
# Restore the "content" argument.
|
||||
# It will move depending on whether takes_context was passed.
|
||||
resolved_args.insert(
|
||||
1 if self.takes_context else 0, self.nodelist.render(context)
|
||||
)
|
||||
|
||||
return resolved_args, resolved_kwargs
|
||||
|
||||
|
||||
class InclusionNode(TagHelperNode):
|
||||
def __init__(self, func, takes_context, args, kwargs, filename):
|
||||
super().__init__(func, takes_context, args, kwargs)
|
||||
|
@ -498,6 +498,195 @@ you see fit:
|
||||
{% current_time "%Y-%m-%d %I:%M %p" as the_time %}
|
||||
<p>The time is {{ the_time }}.</p>
|
||||
|
||||
.. _howto-custom-template-tags-simple-block-tags:
|
||||
|
||||
Simple block tags
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 5.2
|
||||
|
||||
.. method:: django.template.Library.simple_block_tag()
|
||||
|
||||
When a section of rendered template needs to be passed into a custom tag,
|
||||
Django provides the ``simple_block_tag`` helper function to accomplish this.
|
||||
Similar to :meth:`~django.template.Library.simple_tag()`, this function accepts
|
||||
a custom tag function, but with the additional ``content`` argument, which
|
||||
contains the rendered content as defined inside the tag. This allows dynamic
|
||||
template sections to be easily incorporated into custom tags.
|
||||
|
||||
For example, a custom block tag which creates a chart could look like this::
|
||||
|
||||
from django import template
|
||||
from myapp.charts import render_chart
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def chart(content):
|
||||
return render_chart(source=content)
|
||||
|
||||
The ``content`` argument contains everything in between the ``{% chart %}``
|
||||
and ``{% endchart %}`` tags:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% chart %}
|
||||
digraph G {
|
||||
label = "Chart for {{ request.user }}"
|
||||
A -> {B C}
|
||||
}
|
||||
{% endchart %}
|
||||
|
||||
If there are other template tags or variables inside the ``content`` block,
|
||||
they will be rendered before being passed to the tag function. In the example
|
||||
above, ``request.user`` will be resolved by the time ``render_chart`` is
|
||||
called.
|
||||
|
||||
Block tags are closed with ``end{name}`` (for example, ``endchart``). This can
|
||||
be customized with the ``end_name`` parameter::
|
||||
|
||||
@register.simple_block_tag(end_name="endofchart")
|
||||
def chart(content):
|
||||
return render_chart(source=content)
|
||||
|
||||
Which would require a template definition like this:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% chart %}
|
||||
digraph G {
|
||||
label = "Chart for {{ request.user }}"
|
||||
A -> {B C}
|
||||
}
|
||||
{% endofchart %}
|
||||
|
||||
A few things to note about ``simple_block_tag``:
|
||||
|
||||
* The first argument must be called ``content``, and it will contain the
|
||||
contents of the template tag as a rendered string.
|
||||
* Variables passed to the tag are not included in the rendering context of the
|
||||
content, as would be when using the ``{% with %}`` tag.
|
||||
|
||||
Just like :ref:`simple_tag<howto-custom-template-tags-simple-tags>`,
|
||||
``simple_block_tag``:
|
||||
|
||||
* Validates the quantity and quality of the arguments.
|
||||
* Strips quotes from arguments if necessary.
|
||||
* Escapes the output accordingly.
|
||||
* Supports passing ``takes_context=True`` at registration time to access
|
||||
context. Note that in this case, the first argument to the custom function
|
||||
*must* be called ``context``, and ``content`` must follow.
|
||||
* Supports renaming the tag by passing the ``name`` argument when registering.
|
||||
* Supports accepting any number of positional or keyword arguments.
|
||||
* Supports storing the result in a template variable using the ``as`` variant.
|
||||
|
||||
.. admonition:: Content Escaping
|
||||
|
||||
``simple_block_tag`` behaves similarly to ``simple_tag`` regarding
|
||||
auto-escaping. For details on escaping and safety, refer to ``simple_tag``.
|
||||
Because the ``content`` argument has already been rendered by Django, it is
|
||||
already escaped.
|
||||
|
||||
A complete example
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Consider a custom template tag that generates a message box that supports
|
||||
multiple message levels and content beyond a simple phrase. This could be
|
||||
implemented using a ``simple_block_tag`` as follows:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``testapp/templatetags/testapptags.py``
|
||||
|
||||
from django import template
|
||||
from django.utils.html import format_html
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def msgbox(context, content, level):
|
||||
format_kwargs = {
|
||||
"level": level.lower(),
|
||||
"level_title": level.capitalize(),
|
||||
"content": content,
|
||||
"open": " open" if level.lower() == "error" else "",
|
||||
"site": context.get("site", "My Site"),
|
||||
}
|
||||
result = """
|
||||
<div class="msgbox {level}">
|
||||
<details{open}>
|
||||
<summary>
|
||||
<strong>{level_title}</strong>: Please read for <i>{site}</i>
|
||||
</summary>
|
||||
<p>
|
||||
{content}
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
"""
|
||||
return format_html(result, **format_kwargs)
|
||||
|
||||
When combined with a minimal view and corresponding template, as shown here:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``testapp/views.py``
|
||||
|
||||
from django.shortcuts import render
|
||||
|
||||
|
||||
def simpleblocktag_view(request):
|
||||
return render(request, "test.html", context={"site": "Important Site"})
|
||||
|
||||
|
||||
.. code-block:: html+django
|
||||
:caption: ``testapp/templates/test.html``
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load testapptags %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% msgbox level="error" %}
|
||||
Please fix all errors. Further documentation can be found at
|
||||
<a href="http://example.com">Docs</a>.
|
||||
{% endmsgbox %}
|
||||
|
||||
{% msgbox level="info" %}
|
||||
More information at: <a href="http://othersite.com">Other Site</a>/
|
||||
{% endmsgbox %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
The following HTML is produced as the rendered output:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<div class="msgbox error">
|
||||
<details open>
|
||||
<summary>
|
||||
<strong>Error</strong>: Please read for <i>Important Site</i>
|
||||
</summary>
|
||||
<p>
|
||||
Please fix all errors. Further documentation can be found at
|
||||
<a href="http://example.com">Docs</a>.
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="msgbox info">
|
||||
<details>
|
||||
<summary>
|
||||
<strong>Info</strong>: Please read for <i>Important Site</i>
|
||||
</summary>
|
||||
<p>
|
||||
More information at: <a href="http://othersite.com">Other Site</a>
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
.. _howto-custom-template-tags-inclusion-tags:
|
||||
|
||||
Inclusion tags
|
||||
|
@ -323,7 +323,9 @@ Signals
|
||||
Templates
|
||||
~~~~~~~~~
|
||||
|
||||
* ...
|
||||
* The new :meth:`~django.template.Library.simple_block_tag` decorator enables
|
||||
the creation of simple block tags, which can accept and use a section of the
|
||||
template.
|
||||
|
||||
Tests
|
||||
~~~~~
|
||||
|
@ -20,6 +20,16 @@ def make_data_div(value):
|
||||
return '<div data-name="%s"></div>' % value
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def div(content, id="test"):
|
||||
return format_html("<div id='{}'>{}</div>", id, content)
|
||||
|
||||
|
||||
@register.simple_block_tag(end_name="divend")
|
||||
def div_custom_end(content):
|
||||
return format_html("<div>{}</div>", content)
|
||||
|
||||
|
||||
@register.filter
|
||||
def noop(value, param=None):
|
||||
"""A noop filter that always return its first argument and does nothing with
|
||||
@ -51,6 +61,12 @@ def one_param(arg):
|
||||
one_param.anything = "Expected one_param __dict__"
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def one_param_block(content, arg):
|
||||
"""Expected one_param_block __doc__"""
|
||||
return f"one_param_block - Expected result: {arg} with content {content}"
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=False)
|
||||
def explicit_no_context(arg):
|
||||
"""Expected explicit_no_context __doc__"""
|
||||
@ -60,6 +76,12 @@ def explicit_no_context(arg):
|
||||
explicit_no_context.anything = "Expected explicit_no_context __dict__"
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=False)
|
||||
def explicit_no_context_block(content, arg):
|
||||
"""Expected explicit_no_context_block __doc__"""
|
||||
return f"explicit_no_context_block - Expected result: {arg} with content {content}"
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def no_params_with_context(context):
|
||||
"""Expected no_params_with_context __doc__"""
|
||||
@ -72,6 +94,15 @@ def no_params_with_context(context):
|
||||
no_params_with_context.anything = "Expected no_params_with_context __dict__"
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def no_params_with_context_block(context, content):
|
||||
"""Expected no_params_with_context_block __doc__"""
|
||||
return (
|
||||
"no_params_with_context_block - Expected result (context value: %s) "
|
||||
"(content value: %s)" % (context["value"], content)
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def params_and_context(context, arg):
|
||||
"""Expected params_and_context __doc__"""
|
||||
@ -84,6 +115,20 @@ def params_and_context(context, arg):
|
||||
params_and_context.anything = "Expected params_and_context __dict__"
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def params_and_context_block(context, content, arg):
|
||||
"""Expected params_and_context_block __doc__"""
|
||||
return (
|
||||
"params_and_context_block - Expected result (context value: %s) "
|
||||
"(content value: %s): %s"
|
||||
% (
|
||||
context["value"],
|
||||
content,
|
||||
arg,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def simple_two_params(one, two):
|
||||
"""Expected simple_two_params __doc__"""
|
||||
@ -93,16 +138,48 @@ def simple_two_params(one, two):
|
||||
simple_two_params.anything = "Expected simple_two_params __dict__"
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def simple_two_params_block(content, one, two):
|
||||
"""Expected simple_two_params_block __doc__"""
|
||||
return "simple_two_params_block - Expected result (content value: %s): %s, %s" % (
|
||||
content,
|
||||
one,
|
||||
two,
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def simple_keyword_only_param(*, kwarg):
|
||||
return "simple_keyword_only_param - Expected result: %s" % kwarg
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def simple_keyword_only_param_block(content, *, kwarg):
|
||||
return (
|
||||
"simple_keyword_only_param_block - Expected result (content value: %s): %s"
|
||||
% (
|
||||
content,
|
||||
kwarg,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def simple_keyword_only_default(*, kwarg=42):
|
||||
return "simple_keyword_only_default - Expected result: %s" % kwarg
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def simple_keyword_only_default_block(content, *, kwarg=42):
|
||||
return (
|
||||
"simple_keyword_only_default_block - Expected result (content value: %s): %s"
|
||||
% (
|
||||
content,
|
||||
kwarg,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def simple_one_default(one, two="hi"):
|
||||
"""Expected simple_one_default __doc__"""
|
||||
@ -112,6 +189,16 @@ def simple_one_default(one, two="hi"):
|
||||
simple_one_default.anything = "Expected simple_one_default __dict__"
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def simple_one_default_block(content, one, two="hi"):
|
||||
"""Expected simple_one_default_block __doc__"""
|
||||
return "simple_one_default_block - Expected result (content value: %s): %s, %s" % (
|
||||
content,
|
||||
one,
|
||||
two,
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def simple_unlimited_args(one, two="hi", *args):
|
||||
"""Expected simple_unlimited_args __doc__"""
|
||||
@ -123,6 +210,15 @@ def simple_unlimited_args(one, two="hi", *args):
|
||||
simple_unlimited_args.anything = "Expected simple_unlimited_args __dict__"
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def simple_unlimited_args_block(content, one, two="hi", *args):
|
||||
"""Expected simple_unlimited_args_block __doc__"""
|
||||
return "simple_unlimited_args_block - Expected result (content value: %s): %s" % (
|
||||
content,
|
||||
", ".join(str(arg) for arg in [one, two, *args]),
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def simple_only_unlimited_args(*args):
|
||||
"""Expected simple_only_unlimited_args __doc__"""
|
||||
@ -134,6 +230,18 @@ def simple_only_unlimited_args(*args):
|
||||
simple_only_unlimited_args.anything = "Expected simple_only_unlimited_args __dict__"
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def simple_only_unlimited_args_block(content, *args):
|
||||
"""Expected simple_only_unlimited_args_block __doc__"""
|
||||
return (
|
||||
"simple_only_unlimited_args_block - Expected result (content value: %s): %s"
|
||||
% (
|
||||
content,
|
||||
", ".join(str(arg) for arg in args),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def simple_unlimited_args_kwargs(one, two="hi", *args, **kwargs):
|
||||
"""Expected simple_unlimited_args_kwargs __doc__"""
|
||||
@ -146,6 +254,38 @@ def simple_unlimited_args_kwargs(one, two="hi", *args, **kwargs):
|
||||
simple_unlimited_args_kwargs.anything = "Expected simple_unlimited_args_kwargs __dict__"
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def simple_unlimited_args_kwargs_block(content, one, two="hi", *args, **kwargs):
|
||||
"""Expected simple_unlimited_args_kwargs_block __doc__"""
|
||||
return (
|
||||
"simple_unlimited_args_kwargs_block - Expected result (content value: %s): "
|
||||
"%s / %s"
|
||||
% (
|
||||
content,
|
||||
", ".join(str(arg) for arg in [one, two, *args]),
|
||||
", ".join("%s=%s" % (k, v) for (k, v) in kwargs.items()),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def simple_block_tag_without_context_parameter(arg):
|
||||
"""Expected simple_block_tag_without_context_parameter __doc__"""
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def simple_tag_without_content_parameter(arg):
|
||||
"""Expected simple_tag_without_content_parameter __doc__"""
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def simple_tag_with_context_without_content_parameter(context, arg):
|
||||
"""Expected simple_tag_with_context_without_content_parameter __doc__"""
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def simple_tag_without_context_parameter(arg):
|
||||
"""Expected simple_tag_without_context_parameter __doc__"""
|
||||
@ -157,6 +297,12 @@ simple_tag_without_context_parameter.anything = (
|
||||
)
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def simple_tag_takes_context_without_params_block():
|
||||
"""Expected simple_tag_takes_context_without_params_block __doc__"""
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def simple_tag_takes_context_without_params():
|
||||
"""Expected simple_tag_takes_context_without_params __doc__"""
|
||||
@ -168,24 +314,52 @@ simple_tag_takes_context_without_params.anything = (
|
||||
)
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def simple_block_tag_without_content():
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def simple_block_tag_with_context_without_content():
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def escape_naive(context):
|
||||
"""A tag that doesn't even think about escaping issues"""
|
||||
return "Hello {}!".format(context["name"])
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def escape_naive_block(context, content):
|
||||
"""A block tag that doesn't even think about escaping issues"""
|
||||
return "Hello {}: {}!".format(context["name"], content)
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def escape_explicit(context):
|
||||
"""A tag that uses escape explicitly"""
|
||||
return escape("Hello {}!".format(context["name"]))
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def escape_explicit_block(context, content):
|
||||
"""A block tag that uses escape explicitly"""
|
||||
return escape("Hello {}: {}!".format(context["name"], content))
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def escape_format_html(context):
|
||||
"""A tag that uses format_html"""
|
||||
return format_html("Hello {0}!", context["name"])
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def escape_format_html_block(context, content):
|
||||
"""A block tag that uses format_html"""
|
||||
return format_html("Hello {0}: {1}!", context["name"], content)
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def current_app(context):
|
||||
return str(context.current_app)
|
||||
|
@ -243,6 +243,343 @@ class SimpleTagTests(TagTestCase):
|
||||
)
|
||||
|
||||
|
||||
class SimpleBlockTagTests(TagTestCase):
|
||||
def test_simple_block_tags(self):
|
||||
c = Context({"value": 42})
|
||||
|
||||
templates = [
|
||||
(
|
||||
"{% load custom %}{% div %}content{% enddiv %}",
|
||||
"<div id='test'>content</div>",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% one_param_block 37 %}inner"
|
||||
"{% endone_param_block %}",
|
||||
"one_param_block - Expected result: 37 with content inner",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% explicit_no_context_block 37 %}inner"
|
||||
"{% endexplicit_no_context_block %}",
|
||||
"explicit_no_context_block - Expected result: 37 with content inner",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% no_params_with_context_block %}inner"
|
||||
"{% endno_params_with_context_block %}",
|
||||
"no_params_with_context_block - Expected result (context value: 42) "
|
||||
"(content value: inner)",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% params_and_context_block 37 %}inner"
|
||||
"{% endparams_and_context_block %}",
|
||||
"params_and_context_block - Expected result (context value: 42) "
|
||||
"(content value: inner): 37",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% simple_two_params_block 37 42 %}inner"
|
||||
"{% endsimple_two_params_block %}",
|
||||
"simple_two_params_block - Expected result (content value: inner): "
|
||||
"37, 42",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% simple_keyword_only_param_block kwarg=37 %}thirty "
|
||||
"seven{% endsimple_keyword_only_param_block %}",
|
||||
"simple_keyword_only_param_block - Expected result (content value: "
|
||||
"thirty seven): 37",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% simple_keyword_only_default_block %}forty two"
|
||||
"{% endsimple_keyword_only_default_block %}",
|
||||
"simple_keyword_only_default_block - Expected result (content value: "
|
||||
"forty two): 42",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% simple_keyword_only_default_block kwarg=37 %}"
|
||||
"thirty seven{% endsimple_keyword_only_default_block %}",
|
||||
"simple_keyword_only_default_block - Expected result (content value: "
|
||||
"thirty seven): 37",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% simple_one_default_block 37 %}inner"
|
||||
"{% endsimple_one_default_block %}",
|
||||
"simple_one_default_block - Expected result (content value: inner): "
|
||||
"37, hi",
|
||||
),
|
||||
(
|
||||
'{% load custom %}{% simple_one_default_block 37 two="hello" %}inner'
|
||||
"{% endsimple_one_default_block %}",
|
||||
"simple_one_default_block - Expected result (content value: inner): "
|
||||
"37, hello",
|
||||
),
|
||||
(
|
||||
'{% load custom %}{% simple_one_default_block one=99 two="hello" %}'
|
||||
"inner{% endsimple_one_default_block %}",
|
||||
"simple_one_default_block - Expected result (content value: inner): "
|
||||
"99, hello",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% simple_one_default_block 37 42 %}inner"
|
||||
"{% endsimple_one_default_block %}",
|
||||
"simple_one_default_block - Expected result (content value: inner): "
|
||||
"37, 42",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% simple_unlimited_args_block 37 %}thirty seven"
|
||||
"{% endsimple_unlimited_args_block %}",
|
||||
"simple_unlimited_args_block - Expected result (content value: thirty "
|
||||
"seven): 37, hi",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% simple_unlimited_args_block 37 42 56 89 %}numbers"
|
||||
"{% endsimple_unlimited_args_block %}",
|
||||
"simple_unlimited_args_block - Expected result "
|
||||
"(content value: numbers): 37, 42, 56, 89",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% simple_only_unlimited_args_block %}inner"
|
||||
"{% endsimple_only_unlimited_args_block %}",
|
||||
"simple_only_unlimited_args_block - Expected result (content value: "
|
||||
"inner): ",
|
||||
),
|
||||
(
|
||||
"{% load custom %}{% simple_only_unlimited_args_block 37 42 56 89 %}"
|
||||
"numbers{% endsimple_only_unlimited_args_block %}",
|
||||
"simple_only_unlimited_args_block - Expected result "
|
||||
"(content value: numbers): 37, 42, 56, 89",
|
||||
),
|
||||
(
|
||||
"{% load custom %}"
|
||||
'{% simple_unlimited_args_kwargs_block 37 40|add:2 56 eggs="scrambled" '
|
||||
"four=1|add:3 %}inner content"
|
||||
"{% endsimple_unlimited_args_kwargs_block %}",
|
||||
"simple_unlimited_args_kwargs_block - Expected result (content value: "
|
||||
"inner content): 37, 42, 56 / eggs=scrambled, four=4",
|
||||
),
|
||||
]
|
||||
|
||||
for entry in templates:
|
||||
with self.subTest(entry[0]):
|
||||
t = self.engine.from_string(entry[0])
|
||||
self.assertEqual(t.render(c), entry[1])
|
||||
|
||||
def test_simple_block_tag_errors(self):
|
||||
errors = [
|
||||
(
|
||||
"'simple_one_default_block' received unexpected keyword argument "
|
||||
"'three'",
|
||||
"{% load custom %}"
|
||||
'{% simple_one_default_block 99 two="hello" three="foo" %}'
|
||||
"{% endsimple_one_default_block %}",
|
||||
),
|
||||
(
|
||||
"'simple_two_params_block' received too many positional arguments",
|
||||
"{% load custom %}{% simple_two_params_block 37 42 56 %}"
|
||||
"{% endsimple_two_params_block %}",
|
||||
),
|
||||
(
|
||||
"'simple_one_default_block' received too many positional arguments",
|
||||
"{% load custom %}{% simple_one_default_block 37 42 56 %}"
|
||||
"{% endsimple_one_default_block %}",
|
||||
),
|
||||
(
|
||||
"'simple_keyword_only_param_block' did not receive value(s) for the "
|
||||
"argument(s): 'kwarg'",
|
||||
"{% load custom %}{% simple_keyword_only_param_block %}"
|
||||
"{% endsimple_keyword_only_param_block %}",
|
||||
),
|
||||
(
|
||||
"'simple_keyword_only_param_block' received multiple values for "
|
||||
"keyword argument 'kwarg'",
|
||||
"{% load custom %}"
|
||||
"{% simple_keyword_only_param_block kwarg=42 kwarg=37 %}"
|
||||
"{% endsimple_keyword_only_param_block %}",
|
||||
),
|
||||
(
|
||||
"'simple_keyword_only_default_block' received multiple values for "
|
||||
"keyword argument 'kwarg'",
|
||||
"{% load custom %}{% simple_keyword_only_default_block kwarg=42 "
|
||||
"kwarg=37 %}{% endsimple_keyword_only_default_block %}",
|
||||
),
|
||||
(
|
||||
"'simple_unlimited_args_kwargs_block' received some positional "
|
||||
"argument(s) after some keyword argument(s)",
|
||||
"{% load custom %}"
|
||||
'{% simple_unlimited_args_kwargs_block 37 40|add:2 eggs="scrambled" 56 '
|
||||
"four=1|add:3 %}{% endsimple_unlimited_args_kwargs_block %}",
|
||||
),
|
||||
(
|
||||
"'simple_unlimited_args_kwargs_block' received multiple values for "
|
||||
"keyword argument 'eggs'",
|
||||
"{% load custom %}"
|
||||
"{% simple_unlimited_args_kwargs_block 37 "
|
||||
'eggs="scrambled" eggs="scrambled" %}'
|
||||
"{% endsimple_unlimited_args_kwargs_block %}",
|
||||
),
|
||||
(
|
||||
"Unclosed tag on line 1: 'div'. Looking for one of: enddiv.",
|
||||
"{% load custom %}{% div %}Some content",
|
||||
),
|
||||
(
|
||||
"Unclosed tag on line 1: 'simple_one_default_block'. Looking for one "
|
||||
"of: endsimple_one_default_block.",
|
||||
"{% load custom %}{% simple_one_default_block %}Some content",
|
||||
),
|
||||
(
|
||||
"'simple_tag_without_content_parameter' must have a first argument "
|
||||
"of 'content'",
|
||||
"{% load custom %}{% simple_tag_without_content_parameter %}",
|
||||
),
|
||||
(
|
||||
"'simple_tag_with_context_without_content_parameter' is decorated with "
|
||||
"takes_context=True so it must have a first argument of 'context' and "
|
||||
"a second argument of 'content'",
|
||||
"{% load custom %}"
|
||||
"{% simple_tag_with_context_without_content_parameter %}",
|
||||
),
|
||||
]
|
||||
|
||||
for entry in errors:
|
||||
with self.subTest(entry[1]):
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, entry[0]):
|
||||
self.engine.from_string(entry[1])
|
||||
|
||||
def test_simple_block_tag_escaping_autoescape_off(self):
|
||||
c = Context({"name": "Jack & Jill"}, autoescape=False)
|
||||
t = self.engine.from_string(
|
||||
"{% load custom %}{% escape_naive_block %}{{ name }} again"
|
||||
"{% endescape_naive_block %}"
|
||||
)
|
||||
self.assertEqual(t.render(c), "Hello Jack & Jill: Jack & Jill again!")
|
||||
|
||||
def test_simple_block_tag_naive_escaping(self):
|
||||
c = Context({"name": "Jack & Jill"})
|
||||
t = self.engine.from_string(
|
||||
"{% load custom %}{% escape_naive_block %}{{ name }} again"
|
||||
"{% endescape_naive_block %}"
|
||||
)
|
||||
self.assertEqual(
|
||||
t.render(c), "Hello Jack & Jill: Jack &amp; Jill again!"
|
||||
)
|
||||
|
||||
def test_simple_block_tag_explicit_escaping(self):
|
||||
# Check we don't double escape
|
||||
c = Context({"name": "Jack & Jill"})
|
||||
t = self.engine.from_string(
|
||||
"{% load custom %}{% escape_explicit_block %}again"
|
||||
"{% endescape_explicit_block %}"
|
||||
)
|
||||
self.assertEqual(t.render(c), "Hello Jack & Jill: again!")
|
||||
|
||||
def test_simple_block_tag_format_html_escaping(self):
|
||||
# Check we don't double escape
|
||||
c = Context({"name": "Jack & Jill"})
|
||||
t = self.engine.from_string(
|
||||
"{% load custom %}{% escape_format_html_block %}again"
|
||||
"{% endescape_format_html_block %}"
|
||||
)
|
||||
self.assertEqual(t.render(c), "Hello Jack & Jill: again!")
|
||||
|
||||
def test_simple_block_tag_missing_context(self):
|
||||
# The 'context' parameter must be present when takes_context is True
|
||||
msg = (
|
||||
"'simple_block_tag_without_context_parameter' is decorated with "
|
||||
"takes_context=True so it must have a first argument of 'context'"
|
||||
)
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load custom %}{% simple_block_tag_without_context_parameter 123 %}"
|
||||
"{% endsimple_block_tag_without_context_parameter %}"
|
||||
)
|
||||
|
||||
def test_simple_block_tag_missing_context_no_params(self):
|
||||
msg = (
|
||||
"'simple_tag_takes_context_without_params_block' is decorated with "
|
||||
"takes_context=True so it must have a first argument of 'context'"
|
||||
)
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load custom %}{% simple_tag_takes_context_without_params_block %}"
|
||||
"{% endsimple_tag_takes_context_without_params_block %}"
|
||||
)
|
||||
|
||||
def test_simple_block_tag_missing_content(self):
|
||||
# The 'content' parameter must be present when takes_context is True
|
||||
msg = (
|
||||
"'simple_block_tag_without_content' must have a first argument of 'content'"
|
||||
)
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load custom %}{% simple_block_tag_without_content %}"
|
||||
"{% endsimple_block_tag_without_content %}"
|
||||
)
|
||||
|
||||
def test_simple_block_tag_with_context_missing_content(self):
|
||||
# The 'content' parameter must be present when takes_context is True
|
||||
msg = "'simple_block_tag_with_context_without_content' is decorated with "
|
||||
"takes_context=True so it must have a first argument of 'context' and a "
|
||||
"second argument of 'content'"
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load custom %}{% simple_block_tag_with_context_without_content %}"
|
||||
"{% endsimple_block_tag_with_context_without_content %}"
|
||||
)
|
||||
|
||||
def test_simple_block_gets_context(self):
|
||||
c = Context({"name": "Jack & Jill"})
|
||||
t = self.engine.from_string("{% load custom %}{% div %}{{ name }}{% enddiv %}")
|
||||
self.assertEqual(t.render(c), "<div id='test'>Jack & Jill</div>")
|
||||
|
||||
def test_simple_block_capture_as(self):
|
||||
c = Context({"name": "Jack & Jill"})
|
||||
t = self.engine.from_string(
|
||||
"{% load custom %}{% div as div_content %}{{ name }}{% enddiv %}"
|
||||
"My div is: {{ div_content }}"
|
||||
)
|
||||
self.assertEqual(t.render(c), "My div is: <div id='test'>Jack & Jill</div>")
|
||||
|
||||
def test_simple_block_nested(self):
|
||||
c = Context({"name": "Jack & Jill"})
|
||||
t = self.engine.from_string(
|
||||
"{% load custom %}Start{% div id='outer' %}Before{% div id='inner' %}"
|
||||
"{{ name }}{% enddiv %}After{% enddiv %}End"
|
||||
)
|
||||
self.assertEqual(
|
||||
t.render(c),
|
||||
"Start<div id='outer'>Before<div id='inner'>Jack & Jill</div>After"
|
||||
"</div>End",
|
||||
)
|
||||
|
||||
def test_different_simple_block_nested(self):
|
||||
c = Context({"name": "Jack & Jill"})
|
||||
t = self.engine.from_string(
|
||||
"{% load custom %}Start{% div id='outer' %}Before"
|
||||
"{% simple_keyword_only_default_block %}Inner"
|
||||
"{% endsimple_keyword_only_default_block %}"
|
||||
"After{% enddiv %}End"
|
||||
)
|
||||
self.assertEqual(
|
||||
t.render(c),
|
||||
"Start<div id='outer'>Before"
|
||||
"simple_keyword_only_default_block - Expected result (content value: "
|
||||
"Inner): 42After</div>End",
|
||||
)
|
||||
|
||||
def test_custom_end_tag(self):
|
||||
c = Context({"name": "Jack & Jill"})
|
||||
t = self.engine.from_string(
|
||||
"{% load custom %}{% div_custom_end %}{{ name }}{% divend %}"
|
||||
)
|
||||
self.assertEqual(t.render(c), "<div>Jack & Jill</div>")
|
||||
|
||||
with self.assertRaisesMessage(
|
||||
TemplateSyntaxError,
|
||||
"'enddiv_custom_end', expected 'divend'. Did you forget to register or "
|
||||
"load this tag?",
|
||||
):
|
||||
self.engine.from_string(
|
||||
"{% load custom %}{% div_custom_end %}{{ name }}{% enddiv_custom_end %}"
|
||||
)
|
||||
|
||||
|
||||
class InclusionTagTests(TagTestCase):
|
||||
def test_inclusion_tags(self):
|
||||
c = Context({"value": 42})
|
||||
|
@ -120,6 +120,47 @@ class SimpleTagRegistrationTests(SimpleTestCase):
|
||||
self.assertTrue(hasattr(func_wrapped, "cache_info"))
|
||||
|
||||
|
||||
class SimpleBlockTagRegistrationTests(SimpleTestCase):
|
||||
def setUp(self):
|
||||
self.library = Library()
|
||||
|
||||
def test_simple_block_tag(self):
|
||||
@self.library.simple_block_tag
|
||||
def func(content):
|
||||
return content
|
||||
|
||||
self.assertIn("func", self.library.tags)
|
||||
|
||||
def test_simple_block_tag_parens(self):
|
||||
@self.library.simple_tag()
|
||||
def func(content):
|
||||
return content
|
||||
|
||||
self.assertIn("func", self.library.tags)
|
||||
|
||||
def test_simple_block_tag_name_kwarg(self):
|
||||
@self.library.simple_block_tag(name="name")
|
||||
def func(content):
|
||||
return content
|
||||
|
||||
self.assertIn("name", self.library.tags)
|
||||
|
||||
def test_simple_block_tag_invalid(self):
|
||||
msg = "Invalid arguments provided to simple_block_tag"
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
self.library.simple_block_tag("invalid")
|
||||
|
||||
def test_simple_tag_wrapped(self):
|
||||
@self.library.simple_block_tag
|
||||
@functools.lru_cache(maxsize=32)
|
||||
def func(content):
|
||||
return content
|
||||
|
||||
func_wrapped = self.library.tags["func"].__wrapped__
|
||||
self.assertIs(func_wrapped, func)
|
||||
self.assertTrue(hasattr(func_wrapped, "cache_info"))
|
||||
|
||||
|
||||
class TagRegistrationTests(SimpleTestCase):
|
||||
def setUp(self):
|
||||
self.library = Library()
|
||||
|
Loading…
Reference in New Issue
Block a user