web-dev-qa-db-ja.com

Jinjaテンプレートでインラインコードを使用できますか?

私は Jinjaを使用 私のサイトにいます。私はそれが好きです。

私は単純なニーズに出くわしました。今日の日付を表示するには? いくつかのPython Jinjaテンプレートのコードをインライン化する方法はありますか?

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

この記事はノーと言っています ですが、マクロまたはフィルターの使用を提案していますか?

本当に?私たちはそれらすべてに頼らなければなりませんか? OK、この場合はどのようになりますか?

15
010110110101

いいえ、PythonをJinjaにインライン化する方法はありません。ただし、テンプレートエンジンの Environment または-を拡張することで、Jinjaが認識している構造に追加できます。 グローバル名前空間 すべてのテンプレートで使用できます。または、日時オブジェクトをフォーマットできるフィルターを追加することもできます。

FlaskはJinja2環境を app.jinja_env に保存します。このディクショナリに直接追加するか、 @app.context_processor デコレータを使用して、環境に新しいコンテキストを挿入できます。

どのパスを選択する場合でも、これは、アプリケーションのセットアップ中に、要求を処理する前に実行する必要があります。 (フィルターの設定方法のいくつかについては、Webサイトのスニペットセクションを参照してください goodexamplesdocs に追加する良い例が含まれていますグローバル変数)。

11
Sean Vieira

Jinjaテンプレートからアクセスできる グローバル変数 に追加できます。そこに独自の関数定義を配置して、必要なことをすべて実行できます。

3
Vinay Sajip

現在の答えは、ほとんどすべての状況で正しいです。ただし、テンプレート内にpythonコードが必要になる非常にまれなケースがいくつかあります。私の場合、これを使用していくつかのラテックスファイルを前処理したいので、pythonコードを生成するテーブル値、プロットなどをラテックスファイル自体の中に保持したいと思います。

そこで、テンプレート内にpythonコードを記述できるようにする新しい「py」ブロックを追加するJinja2拡張機能を作成しました。これを機能させるには、いくつかの疑わしい回避策を実行する必要があったことを覚えておいてください。そのため、どのような状況で失敗したり、予期しない動作をしたりするかは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 }}

テンプレートパラメータを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]]

例を実行するための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()
1
Aaron de Windt