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

010*_*101 14 python jinja2

我在我的网站上使用Jinja,我喜欢它.

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

import datetime
now = datetime.datetime.utcnow()
print now.strftime("%Y-%m-%d %H:%M")
Run Code Online (Sandbox Code Playgroud)

这篇文章说没有,但建议使用宏或过滤器?

真?我们必须诉诸于所有这一切吗?好的,在这种情况下会是什么样子?

Sea*_*ira 11

不,没有办法将Python内联到Jinja.但是,您可以通过扩展模板引擎的环境或所有模板可用的全局命名空间来添加Jinja所知的构造.或者,您可以添加一个过滤器,让您设置日期时间对象的格式.

Flask存储Jinja2 Environment app.jinja_env.您可以通过直接添加到此词典或使用@app.context_processor装饰器将新上下文注入环境.

无论您选择哪种方式,都应在设置应用程序时完成,然后才能提供任何请求.(有关如何设置过滤器的一些很好的 示例,请参阅网站的片段部分- 文档包含添加到全局变量的一个很好的示例).


Aar*_*ndt 5

当前的答案几乎适用于所有情况。但是,在极少数情况下,您希望在模板中包含 Python 代码。在我的情况下,我想用它来预处理一些乳胶文件,我更愿意将生成表值、绘图等的 python 代码保留在它自己的乳胶文件中。

所以我做了一个 Jinja2 扩展,它添加了一个新的“py”块,允许在模板中编写 python 代码。请记住,我必须做一些有问题的变通方法才能使其正常工作,所以我不能 100% 确定它在哪些情况下会失败或出现意外行为。

这是一个示例模板。

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 }}
Run Code Online (Sandbox Code Playgroud)

如果我们将模板参数设置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]]
Run Code Online (Sandbox Code Playgroud)

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

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()
Run Code Online (Sandbox Code Playgroud)