1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

株式会社ネオシステムAdvent Calendar 2024

Day 4

Jinja2 変数内を再帰的に render するフィルタ処理

Last updated at Posted at 2024-12-03

概要

  • jinja2 を使って文字列をレンダリングする際に、テンプレートに対して渡したデータ自体が jinja2 のテンプレート形式であるときに、その内容もテンプレートとしてレンダリングを行いたい。
      • テンプレート: Hello, {{ sample_var }}
      • データ: {"sample_var": "{{ nested_var }}!", "nested_var": "world"}
      • 期待する出力: "Hello, world!"
  • 影響範囲を限定するため、jinja2 の filter で実現する。
  • ※ユーザが入力するデータに対して使用する場合にはセキュリティ上の注意が必要です。xss (クロスサイトスクリプティング), SQLインジェクション等の脆弱性になることが考えられるため。
    • 以下の例では、管理者が作成するマスタデータに対して行う処理とします。

実装

filter の実装

from jinja2 import Template, Environment, FileSystemLoader, pass_context

@pass_context
def recursive_render(context, value, max_iterations=10, **kwargs):
    template = Template(value)
    result = template.render(**context, **kwargs)

    for _ in range(max_iterations):
        new_result = Template(result).render(**context, **kwargs)
        if new_result == result:
            break
        result = new_result

    return result

使用方法

サンプル

env = Environment(loader=FileSystemLoader("."))
env.filters['recursive_render'] = recursive_render

template_str = """
{{ var1 | recursive_render }} is {{ var2 }}
"""

template = env.from_string(template_str)
context = {
    "var1": "{{ nested_var }}",
    "var2": "awesome",
    "nested_var": "Python"
}

output = template.render(**context)
print(output)

出力

Python is awesome

テンプレート内で定義される変数は filter に引数として渡す

for ループなどによって、テンプレート内で定義される変数については、filter 使用時に引数として渡す必要がある。
@pass_context デコレータによって context 変数に渡される内容は、render() の引数のみであるため。

失敗例

template_str = """\
{% for member in members %}
{{ member.name }}
{{ member.introduction | recursive_render }}
{% endfor %}
"""

template = env.from_string(template_str)
context = {
    "members": [
        {"name": "テスト1", "introduction": "はじめまして、{{ member.name }} です。"},
        {"name": "テスト2", "introduction": "はじめまして、{{ member.name }} です。"},
    ]
}

output = template.render(**context)
print(output)

-> jinja2.exceptions.UndefinedError: 'member' is undefined 例外が発生

成功例 (recursive_render フィルタに引数を渡すように変更)

template_str = """\
{% for member in members %}
{{ member.name }}
{{ member.introduction | recursive_render(member=member) }}
{% endfor %}
"""

template = env.from_string(template_str)
context = {
    "members": [
        {"name": "テスト1", "introduction": "はじめまして、{{ member.name }} です。"},
        {"name": "テスト2", "introduction": "はじめまして、{{ member.name }} です。"},
    ]
}

output = template.render(**context)
print(output)

出力

テスト1
はじめまして、テスト1 です。

テスト2
はじめまして、テスト2 です。

最後に

概要に記載しましたが改めて、ユーザが入力するデータに対して使用する際にはセキュリティ上の注意が必要です。
少なくとも上記の実装では context で渡されるデータに、recursive_render する変数の中身がアクセスできてしまいます。その際には、@pass_context デコレータを外しアクセスできる変数を制限するなどの対応が必要になると考えられます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?