Jinja模板中是否允许内联代码?

2024-04-26 05:57:15 发布

您现在位置:Python中文网/ 问答频道 /正文

我在我的网站上,我喜欢它。

我遇到了一个简单的需要。如何显示今天的日期?有没有办法在Jinja模板中内嵌一些Python代码?

import datetime
now = datetime.datetime.utcnow()
print now.strftime("%Y-%m-%d %H:%M")

This article says no,但建议使用宏或筛选器?

真的吗?我们一定要这么做吗?好吧,在这种情况下会是什么样子?


Tags: no代码import模板datetime网站articlethis
3条回答

您可以添加到global variables,可以从Jinja模板访问它。您可以将自己的函数定义放在其中,它可以做您需要的任何事情。

目前的答案在几乎所有情况下都是正确的。但是,在一些非常罕见的情况下,您希望在模板中包含python代码。在我的例子中,我想用它来预处理一些latex文件,我更愿意让python代码生成表值、图等,在latex文件中。

所以我做了一个Jinja2扩展,它添加了一个新的“py”块,允许在模板中编写python代码。请记住,我必须做一些有问题的工作来让这个工作,所以我不能百分之百确定在什么情况下,它失败或行为出乎意料。

这是一个示例模板。

Foo was given to the template
foo: {{ foo }}

Bar was not, so it is missing
bar is missing: {{ bar == missing }}

{% py %}
    # Normal python code in here
    # Excess indentation will be removed.
    # All template variables are accessible and can be modified.
    import numpy as np
    a = np.array([1, 2])
    m = np.array([[3, 4], [5, 6]])
    bar = m @ a * foo

    # It's also possible to template the python code.
    {% if change_foo %}
    foo = 'new foo value'
    {% endif %}

    print("Stdio is redirected to the output.")
{% endpy %}

Foo will have the new value if you set change_foo to True
foo: {{ foo }}

Bar will now have a value.
bar: {{ bar }}

{% py %}
    # The locals from previous blocks are accessible.
    m = m**2
{% endpy %}
m:
{{ m }}

如果将模板参数设置为foo=10, change_foo=True,则输出为:

Foo was given to the template
foo: 10

Bar was not, so it is missing
bar is missing: True

Stdio is redirected to the output.


Foo will have the new value if you set change_foo to True
foo: new foo value

Bar will now have a value.
bar: [110 170]


m:
[[ 9 16]
 [25 36]]

带有运行示例的主函数的扩展。

from jinja2 import Environment, PackageLoader, nodes
from jinja2.ext import Extension
from textwrap import dedent
from io import StringIO
import sys
import re
import ctypes


def main():
    env = Environment(
        loader=PackageLoader('python_spike', 'templates'),
        extensions=[PythonExtension]
    )

    template = env.get_template('emb_py2.txt')
    print(template.render(foo=10, change_foo=True))


var_name_regex = re.compile(r"l_(\d+)_(.+)")


class PythonExtension(Extension):
    # a set of names that trigger the extension.
    tags = {'py'}

    def __init__(self, environment: Environment):
        super().__init__(environment)

    def parse(self, parser):
        lineno = next(parser.stream).lineno
        body = parser.parse_statements(['name:endpy'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_exec_python',
                                                [nodes.ContextReference(), nodes.Const(lineno), nodes.Const(parser.filename)]),
                               [], [], body).set_lineno(lineno)

    def _exec_python(self, ctx, lineno, filename, caller):
        # Remove access indentation
        code = dedent(caller())

        # Compile the code.
        compiled_code = compile("\n"*(lineno-1) + code, filename, "exec")

        # Create string io to capture stdio and replace it.
        sout = StringIO()
        stdout = sys.stdout
        sys.stdout = sout

        try:
            # Execute the code with the context parents as global and context vars and locals.
            exec(compiled_code, ctx.parent, ctx.vars)
        except Exception:
            raise
        finally:
            # Restore stdout whether the code crashed or not.
            sys.stdout = stdout

        # Get a set of all names in the code.
        code_names = set(compiled_code.co_names)

        # The the frame in the jinja generated python code.
        caller_frame = sys._getframe(2)

        # Loop through all the locals.
        for local_var_name in caller_frame.f_locals:
            # Look for variables matching the template variable regex.
            match = re.match(var_name_regex, local_var_name)
            if match:
                # Get the variable name.
                var_name = match.group(2)

                # If the variable's name appears in the code and is in the locals.
                if (var_name in code_names) and (var_name in ctx.vars):
                    # Copy the value to the frame's locals.
                    caller_frame.f_locals[local_var_name] = ctx.vars[var_name]
                    # Do some ctypes vodo to make sure the frame locals are actually updated.
                    ctx.exported_vars.add(var_name)
                    ctypes.pythonapi.PyFrame_LocalsToFast(
                        ctypes.py_object(caller_frame),
                        ctypes.c_int(1))

        # Return the captured text.
        return sout.getvalue()

if __name__ == "__main__":
    main()

不,没有办法把Python内联到Jinja中。但是,您可以通过扩展模板引擎的Environment或对所有模板可用的global namespace来添加Jinja知道的构造。或者,您可以添加一个过滤器,让我们格式化datetime对象。

烧瓶将Jinja2环境存储在^{}上。您可以通过直接添加到此字典或使用^{}装饰器将新上下文插入到环境中。

无论您选择哪种路径,都应该在设置应用程序时完成,然后再处理任何请求。(有关如何设置筛选器的一些goodexamples信息,请参见网站的“代码片段”部分-the docs包含添加到全局变量的一个很好的示例)。

相关问题 更多 >