如何确保 Jinja 自定义标签只输出一次?

发布于 2024-09-07 11:19:41 字数 767 浏览 5 评论 0原文

我在 Jinja2 中有一个自定义标签,我只想在第一次调用它时输出一些内容。假设我有以下模板:

1. {% only_once %}
2. {% only_once %}
3. {% only_once %}

我希望输出为:

1. "I only get printed once!"
2.
3.

我猜最好的方法是在模板的上下文中设置一个标志来跟踪我是否已经打印了某些内容。这是一个代码示例,但这对吗?

class OnlyOnceExtension(Extension):
    tags = set(['only_once'])

    @contextfunction
    def parse(self, context, parser):
        if hasattr(context, 'my_flag') and context.my_flag:
            return Output("")
        else:
            return Output("I only get printed once!")

这是正确的吗?我读到一些关于上下文是不可变的东西,所以这不起作用吗? (参见 http://jinja.pocoo.org/2/documentation/api 和搜索不可变)

I have a custom tag in Jinja2 that I want to output something only the first time that it is called. So say I have the following template:

1. {% only_once %}
2. {% only_once %}
3. {% only_once %}

I want the output to be:

1. "I only get printed once!"
2.
3.

I'm guessing the best way to do this is to set a flag in the context of the template to track whether I've already printed something or not. Here's a code sample, but is this right?

class OnlyOnceExtension(Extension):
    tags = set(['only_once'])

    @contextfunction
    def parse(self, context, parser):
        if hasattr(context, 'my_flag') and context.my_flag:
            return Output("")
        else:
            return Output("I only get printed once!")

Is that correct? I read some stuff about the context is immutable, so will this not work? (see http://jinja.pocoo.org/2/documentation/api and search for immutable)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

风透绣罗衣 2024-09-14 11:19:41

如果你想纯粹用 Jinja 来做,你可以以这种方式检查loop.index变量,

{% for bar in bars %}
    {% if loop.index == 1 %}
        Print me once
    {% endif %}
    Print me every time
{% endfor %}

If you want to do it purely with Jinja you can just check the loop.index variable in that fashion,

{% for bar in bars %}
    {% if loop.index == 1 %}
        Print me once
    {% endif %}
    Print me every time
{% endfor %}
夜清冷一曲。 2024-09-14 11:19:41

我的建议是在 Python 代码中实现这一点:

class OnlyOnce(object):
    def __init__(self, data):
        self.data = data
        self.printed = False

    def __str__(self):
        if self.printed is False:
            self.printed = True
            return self.data
        return ''

在 Python 代码中创建一个 OnlyOnce 实例并将其传递给模板,然后每次要使用它时,只需使用 {{ only_once }}

我注意到很多使用 Jinja 的人都想以 Django 式的方式做事,即编写扩展。但是 Jinja 的表达式/导入/任何东西都足够强大,您不必为所有事情使用扩展。

是的,使用 context.my_flag 是个坏主意。只有模板可以修改上下文。 曾经。

My suggestion is to implement this in Python code:

class OnlyOnce(object):
    def __init__(self, data):
        self.data = data
        self.printed = False

    def __str__(self):
        if self.printed is False:
            self.printed = True
            return self.data
        return ''

Create an OnlyOnce instance in your Python code and pass it to the template, and then every time you want to use it, just use {{ only_once }}.

One thing I notice about a lot of people using Jinja is that they want to do things in a Django-ish way, that is, writing extensions. But Jinja's expressions/importing/whatever is powerful enough that you don't have to use extensions for everything.

And yes, using the context.my_flag thing is a bad idea. Only the template gets to modify the context. EVER.

堇色安年 2024-09-14 11:19:41

我会定义一个布尔值和一个宏,然后从那里开始。您可以打印宏的结果,而不是打印变量,该宏使用 if 语句和布尔值来决定是否应该打印。所以,你会得到以下结果:

{{ set was_printed = false }}
{% macro print_once(to_print) %}
    {% if was_printed is sameas false %}
        {{ to_print }}
        {% set was_printed = true %}
    {% endif %}
{% endmacro %}
1. {% print_once(only_once) %}
2. {% print_once(only_once) %}
3. {% print_once(only_once) %}

I would define a boolean and a macro and go from there. Instead of printing the variable, you print the result of the macro which uses an if statement and the boolean to decide if it should print. So, you get the following:

{{ set was_printed = false }}
{% macro print_once(to_print) %}
    {% if was_printed is sameas false %}
        {{ to_print }}
        {% set was_printed = true %}
    {% endif %}
{% endmacro %}
1. {% print_once(only_once) %}
2. {% print_once(only_once) %}
3. {% print_once(only_once) %}
桃扇骨 2024-09-14 11:19:41

Jinja 并不总是重新评估宏,因此设置宏变量不会阻止值被创建两次。可接受的解决方案有效,但它要求您在烧瓶视图级别对对象进行硬编码。
我通过创建一个自定义 jinja 过滤器解决了这个问题,该过滤器每个请求只返回一次值。

过滤器:

from flask import g    

def onetime(val, atr):
    """
    this filter will return the given value only once per request

    :param val: the value to return 1 time
    :type val: str
    :param atr: the attribute to remember this value by
    :type atr: str
    :return: returns True for this attribute once per request
    :rtype: str
    """
    # has this attribute been set for this request?
    if getattr(g, atr, False):
        # the value has already been returned for this request
        return ""
    # set the flag
    setattr(g, atr, True)
    # return the value, this is the first call
    return val

将过滤器添加到您的 Flask 应用

app.jinja_env.filters['onetime'] = onetime

创建一个包含以下内容的 jinja 宏:您只想输出一次的文本:
Macros.html

{% macro my_macro() %}
This text should only be created once
{% endmacro %}

并使用它:

{{ my_macro() | onetime('a_name') }}
{{ my_macro() | onetime('a_name') }}
{{ my_macro() | onetime('a_name') }}

Jinja doesn't always re-evaluate macros, so setting a macro variable won't prevent a value from being created twice. The accepted solution works, but it requires you to hard code your object at the flask view level.
I solved this by creating a custom jinja filter that will only return the value once per request.

The filter:

from flask import g    

def onetime(val, atr):
    """
    this filter will return the given value only once per request

    :param val: the value to return 1 time
    :type val: str
    :param atr: the attribute to remember this value by
    :type atr: str
    :return: returns True for this attribute once per request
    :rtype: str
    """
    # has this attribute been set for this request?
    if getattr(g, atr, False):
        # the value has already been returned for this request
        return ""
    # set the flag
    setattr(g, atr, True)
    # return the value, this is the first call
    return val

Add the filter to your flask app

app.jinja_env.filters['onetime'] = onetime

Create a jinja macro containing the text your only want to output once:
macros.html

{% macro my_macro() %}
This text should only be created once
{% endmacro %}

And to use it:

{{ my_macro() | onetime('a_name') }}
{{ my_macro() | onetime('a_name') }}
{{ my_macro() | onetime('a_name') }}
玩世 2024-09-14 11:19:41

以前的答案实际上在它们自己的上下文中相当不错,但我最近不得不解决这个问题,并且我不得不稍微偏离,这导致了我认为更“通用”的解决方案。

在我看来,使用“循环变量”是执行此操作的正确/优雅的方法,但这仅在您开始使用循环时才有效。

就我而言,我有一个递归宏(我有需要遍历的复杂嵌套结构),因此仅在宏第一次实际渲染某些内容时渲染“foo”并不是非常简单。

我尝试使用 解决方案 的变体来完成此任务 近乎整体,使用 命名空间,尽管它有效,但结果非常丑陋,因为我必须多次重复同一个块:

{% set ns = namespace(printed=false) %}
{% if ns.printed == false%}
   {% set ns.printed = true %}
{{ print foo }}
{% endif %}

我最终所做的类似于 user2682863 在他们的解决方案中建议。

定义并实例化一个 Once 对象,其中将存储属性以供将来参考;然后,在过滤器本身中,我们检查该属性是否已存储,以决定是否应该渲染它。

    class Once(object):
        pass

    once = Once()

    # This is a "weird" filter, in the sense that it actually checks current
    # state of the "once" global variable to determine whether it should
    # return the requested value or not. In order to distinguish calls to the
    # filter, we use the current "context" as a way to namespace the calls to
    # the filter, otherwise this could only be used "once" per run, regardless
    # of the number of files generated. We also hash the value in order to
    # simplify the call to the filter (no arguments needed).
    @jinja2.pass_context
    def only_once(ctx, val):
        ctx_hash = hash(ctx)
        val_hash = hash(val)
        namespaced_attr = f"{ctx_hash}_{val_hash}"
        if getattr(once, namespaced_attr, False):
            # The value has already been returned for this attribute
            return ""
        # Set the attribute and return the value, this is the first call
        setattr(once, namespaced_attr, True)
        return val

env.filters["only_once"] = only_once

这稍微好一些,因为值本身用作要存储的属性(其名称空间哈希),因此使用过滤器更干净:

定义宏(您可以使用输入):

{% macro custom_macro(input) %}
//
// {{ input }}
//
{% endmacro %}

使用它:

{{ custom_macro("foo") | only_once }}
{{ custom_macro("foo") | only_once }}

Previous answers are actually pretty good in their own context, but I recently had to tackle this, and I had to deviate slightly, which led into what I think is a more "generic" solution.

Using "loop variables" is IMO the right/elegant way to do this, but this only works if you are using loops to begin with.

In my case, I have a recursive macro (I have complex nested structures I need to traverse), so rendering "foo" only the first time the macro actually renders something was not super straightforward.

I tried to accomplish this with a variation of the solution suggested by nearlymonolith, using namespaces, and even though it worked, the result was really ugly, as I had to duplicate the same block multiple times:

{% set ns = namespace(printed=false) %}
{% if ns.printed == false%}
   {% set ns.printed = true %}
{{ print foo }}
{% endif %}

What I ended up doing was something similar to what user2682863 suggested in their solution.

Define and instantiate a Once object where attributes will be stored for future reference; then, in the filter itself we check if the attribute has already been stored or not to decide whether it should be rendered.

    class Once(object):
        pass

    once = Once()

    # This is a "weird" filter, in the sense that it actually checks current
    # state of the "once" global variable to determine whether it should
    # return the requested value or not. In order to distinguish calls to the
    # filter, we use the current "context" as a way to namespace the calls to
    # the filter, otherwise this could only be used "once" per run, regardless
    # of the number of files generated. We also hash the value in order to
    # simplify the call to the filter (no arguments needed).
    @jinja2.pass_context
    def only_once(ctx, val):
        ctx_hash = hash(ctx)
        val_hash = hash(val)
        namespaced_attr = f"{ctx_hash}_{val_hash}"
        if getattr(once, namespaced_attr, False):
            # The value has already been returned for this attribute
            return ""
        # Set the attribute and return the value, this is the first call
        setattr(once, namespaced_attr, True)
        return val

env.filters["only_once"] = only_once

This is slightly better because the value itself is used as the attribute to store (its name-spaced hash), so using the filter is cleaner:

Define the macro (you can use inputs):

{% macro custom_macro(input) %}
//
// {{ input }}
//
{% endmacro %}

Use it:

{{ custom_macro("foo") | only_once }}
{{ custom_macro("foo") | only_once }}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文