とほほのJinja入門

目次

Jinjaとは

インストール

pip コマンドでインストールします。パッケージ名は jinja2 ですが、3.x がインストールされます。

pip3 install jinja2

プログラムの準備

最もシンプルな例

最もシンプルなサンプルです。テキストの中の {{ name }}name 変数の値で置換して表示します。

from jinja2 import Template

text = "Hello {{ name }}!"
name = "Yamada"
print(Template(text).render(name=name))     # Hello Yamada!

変数をコンテキストで渡す

下記の様に変数をコンテキストとして渡すこともできます。

text = "Hello {{ name }}!"
context = { "name": "Yamada" }
print(Template(text).render(context))       # Hello Yamada!

テンプレートをファイルから読み込む

下記の様に EnvironmentFileSystemLoader を組み込むことにより、テンプレートを /opt/myapp/templates/test.html などのテンプレートファイルから読み込むことができます。

myapp.py
from jinja2 import Environment, FileSystemLoader

TEMPLATE_DIR = "/opt/myapp/templates"
env = Environment(
    loader=FileSystemLoader(TEMPLATE_DIR),
    autoescape=True                                    # HTMLの場合は強く推奨
)

def render(template_file, context):
    template = env.get_template(template_file)
    return template.render(context)

if __name__ == "__main__":
    context = {
        "message": "Hello!",
    }
    html = render("test.html", context)
    print(html)
test.html
<h1>{{ message }}</h1>

以後の説明では、上記で作成した render() 関数を用いて説明していきます。

HTMLをサニタイジングする(autoescape)

autoescape=True を指定すると HTML や XML の <&> 等を &lt;&amp;&gt; 等にサニタイジング(エスケープ)して出力することができます。

env = Environment(
    loader=FileSystemLoader(TEMPLATE_DIR),
    autoescape=True
)
{% set message = "<h1>Hello!</h1>" %}
{{ message }}            # &lt;h1&gt;Hello!&lt;/h1&gt;

テンプレートの中で escape フィルタを用いてサニタイジングすることもできますが、XSS 脆弱性を防ぐためにも HTML や XML を扱う際は常に autoescape=True を指定しておくことを強く推奨します。

{{ message|escape }}
{{ message|e }}

autoescape=True の時にどうしても HTML を直接記述したい場合は safe フィルタを用います。

{{ message|safe }}       # <h1>Hello!</h1>

通常は *.html に対してのみ有効ですが、下記の様にすると *.xml に対しても適用することができます。

from jinja2 import Environment, FileSystemLoader, select_autoescape

env = Environment(
    loader=FileSystemLoader(TEMPLATE_DIR),
    autoescape=select_autoescape(['html', 'xml'])
)

Environment()パラメーター

パラメーター一覧

Environment() では下記の引数を使用できます。

autoescape            : 自動的にHTML/XMLエスケープを行うか(初期値:False)
block_start_string    : ブロック開始文字列(初期値:'{%')
block_end_string      : ブロック終了文字列(初期値:'%}')
variable_start_string : 値開始文字列(初期値:'{{')
variable_end_string   : 値終了文字列(初期値:'}}')
comment_start_string  : コメント開始文字列(初期値:'{#')
comment_end_string    : コメント終了文字列(初期値:'#}')
line_statement_prefix : ラインステートメント開始文字列(初期値:無し)(→ 詳細)
line_comment_prefix   : ラインコメント開始文字列(初期値:無し)(→ 詳細)
trim_blocks           : ブロックの後の改行を除去する(初期値:False)
lstrip_blocks         : ブロックの前方にあるスペースやタブを除去する(初期値:False)
newline_sequence      : 改行コード(初期値:'\n')
keep_trailing_newline : 後続の改行を維持するか(初期値:False)
extensions            : 拡張機能名のリスト(→ 詳細)
optimized             : Pythonコードの最適化を有効にするか(初期値:True)
undefined             : 未定義値に対する振る舞い(→ 詳細)
finalize              : すべての {{...}} 値に対する最終処理(→ 使用例)
loader                : ファイルシステムローダー(→ 使用例)
cache_size            : テンプレート数で数えるキャッシュサイズ(規定値:400)
auto_reload           : テンプレートを毎回自動読み込みするか(規定値:True)
bytecode_cache        : コンパイル済Pythonコードのキャッシュ
enable_async          : テンプレート内の非同期処理(await)を有効にするか(規定値:False)(→ 詳細)

ラインプレフィックス(line_*_prefix)

line_statement_prefix="%"line_comment_prefix="#" を指定すると、テンプレートを次のように記述することができます。

# Example of line prefixes
% set score = 92
% if score > 90
Good!
% endif

拡張機能(extensions)

extensions には下記などの機能を指定できます。(→ 他言語対応)

extensions=["jinja2.ext.i18n"] : 多言語対応

未定義値の振る舞い(undefined)

undefined には下記の値を指定できます。開発時には StrictUndefined を指定しておくとテンプレートの品質を高めることができます。

Undefined          : 未定義値を無視する(規定値)
ChainableUndefined : foo.baa.baz のような深い階層の未定義値でも無視する
StrictUndefined    : 未定義値があれば例外を投げる
DebugUndefined     : デバッグ用。"Undefined variable 'foo'" などを表示する

非同期関数の呼び出し(enable_async)

enable_async=True を指定すると非同期関数を呼び出すことが可能となります。例として指定した URL のコンテンツを取得する fetch() 非同期関数を準備して呼び出してみます。まずは aiohttp をインストールします。

pip3 install aiohttp

fetch() 非同期関数を作成し、グローバルオブジェクトとしてテンプレートに渡します。

import aiohttp

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()

env = Environment(
    loader=FileSystemLoader(TEMPLATE_DIR),
    enable_async=True
)
env.globals["fetch"] = fetch

これをテンプレートから呼び出します。

{{ fetch("https://www.example.com/") }}

テンプレートの書き方

変数を埋め込む({{...}})

{{...}} で変数を表示します。

context = {
    "name": "Yamada"
}
print(render("test.html", context))
<p>Hello {{ name }}!</p>

コメントを記述する({#...#})

{#...#} はコメントとみなされます。

{# This line is not displayed #}
<p>Hello {{ name }}!</p>

条件判断を行う({% if, elif, else %})

{% ... %} の中で if, elif, else, endif を用いて条件判断を行うことができます。

context = {
    "score": 82
}
print(render("test.html", context))
{% if score >= 90 %}
  <p>Good!</p>
{% elif score >= 65 %}
  <p>Normal</p>
{% else %}
  <p>Bad!</p>
{% endif %}

リストをループ表示する({% for in %})

for, in, endfor を用いてリストをループ表示することができます。

context = {
    "users": ["Yamada", "Tanaka", "Suzuki"]
}
print(render("test.html", context))
<ul>
{% for user in users %}
<li>{{ user }}
{% endfor %}
</ul>

下記の様にオブジェクトのリストを渡すこともできます。

context = {
    "users": [
        { "name": "Yamada", "age": 36 },
        { "name": "Tanaka", "age": 42 },
        { "name": "Suzuki", "age": 54 },
    ]
}
print(render("test.html", context))
<ul>
{% for user in users %}
  <li>{{ user.name }}({{ user.age }})
{% endfor %}
</ul>

辞書をループ表示する({% for in %})

下記の様にして辞書データの各項目に対してループすることができます。

context = {
    "user": {"name": "Yamada", "age": 36}
}
print(render("test.html", context))
<ul>
{% for key in user %}             # user.keys() と同等
<li>{{ key }}
{% endfor %}
</ul>

<ul>
{% for key in user.keys() %}
<li>{{ key }}
{% endfor %}
</ul>

<ul>
{% for value in user.values() %}
<li>{{ value }}
{% endfor %}
</ul>

<ul>
{% for key, value in user.items() %}
<li>{{ key }}: {{ value }}
{% endfor %}
</ul>

ループパラメーター(loop)

loop.index は特殊変数でループのインデックス(1~)を示します。下記の様にしてリストに連番を表示することができます。

<ul>
{% for user in users %}
<li>{{ loop.index }}: {{ user }}
{% endfor %}
</ul>

loop パラメーターでは下記の値を参照できます。

loop.index          # 先頭から数えたインデックス番号(1開始)
loop.index0         # 先頭から数えたインデックス番号(0開始)
loop.revindex       # 末尾から数えたインデックス番号(1開始)
loop.revindex0      # 末尾から数えたインデックス番号(0開始)
loop.first          # ループの最初を判断する真偽値
loop.last           # ループの最後を判断する真偽値
loop.length         # 配列の要素数
loop.cycle(list)    # ループ毎にlistを参照(※1)
loop.depth          # ループの階層の深さ(1開始)
loop.depth0         # ループの階層の深さ(0開始)
loop.previtem       # ひとつ前の要素
loop.nextitem       # ひとつ後の要素
loop.changed(val)   # 前回のループと値が変わっていれば(※2)

※1 loop.cycle(list) はループ毎に list 内の要素をサイクリックに参照します。下記の例では、偶数行には even が、奇数行には odd が指定されます。

{% for user in users %}
  <tr class="{{ loop.cycle("odd", "even") }}">...</tr>
{% endfor %}

※2 loop.changed(val) は、val の値が前回のループ時から変わっていれば、True を返します。下記の例では、city の値が変わる度に <h3>city</h3> を表示します。

context = {
    "users": [
        {"city": "Tokyo", "name": "Yamada"},
        {"city": "Tokyo", "name": "Tanaka"},
        {"city": "Osaka", "name": "Suzuki"}
    ]
}
print(render("test.html", context))
{%- for user in users %}
  {% if loop.changed(user.city) %}<h3>{{ user.city }}</h3>{% endif %}
  <div>{{ user.name }}</div>
{%- endfor %}

変数に代入する({% set ... %})

set を用いてテンプレートの中で変数に値を指定することができます。

{% set score = 92 %}
{% if score >= 70 %}
  <p>Good!</p>
{% else %}
  <p>Bad!</p>
{% endif %}

本文自体を変数に設定することもできます。

{% set copyright %}
<div>Copyright (C) 2026 Example.com</div>
{% endset %}

{{ copyright }}

ループ内で設定した変数は、ループ外にその値を持ち出すことはできません。持ち出したい場合は namespace() を使用してください。

スコープ変数に代入する({% with %})

with を用いると、指定した変数のスコープを withendwith の間に制限することができます。

{% with score = 78 %}
  {% if score >= 70 %}
    <p>Good!</p>
  {% else %}
    <p>Bad!</p>
  {% endif %}
{% endwith %}

演算子を使用する(+, -, *, /...)

{{...}}{%...%} の中では下記の演算子を使用できます。

{{ x + y }}                   # 加算
{{ x - y }}                   # 減算
{{ x * y }}                   # 乗算
{{ x / y }}                   # 除算
{{ x // y }}                  # 除算の整数部
{{ x % y }}                   # 除算の余り
{{ x ** n }}                  # 累乗
{{ s1 ~ s2 }}                 # 文字列の連結
{{ s1 * n }}                  # 文字列の繰り返し

条件判断でよく使用される演算子には下記のものがあります。

{% if x == y %}               # 等しければ
{% if x != y %}               # 異なれば
{% if x > y %}                # 大なり
{% if x >= y %}               # 大なりイコール
{% if x < y %}                # 小なり
{% if x <= y %}               # 小なりイコール
{% if x > 5 and y > 5 %}      # かつ(AND)
{% if x > 5 or y > 5 %}       # または(OR)
{% if not x > 5 %}            # でなければ(NOT)
{% if x in y %}               # リスト中に存在すれば
{% if x is string %}          # テスト判断 (→ ビルトインテスト)

Pythonメソッドを呼び出す({{ variable.method() }})

{%...%}{{...}} の中で変数を使用する際は .upper() などの Python メソッドを呼び出すことができます。

{%- set name = "Yamada" %}
Hello {{ name.upper() }}!

テンプレートの中で {% %}{{ }} を記述する({% raw %})

テンプレートの中で {% %}{{ }} を記述するには {% raw %}...{% endraw %} を使用します。

{% raw %}
変数 name の値を表示するには {{ name }} と記述します。
{% endraw %}

もしくは、下記の様に文字列として書き出すこともできます。

変数 name の値を表示するには {{ '{{' }} name {{ '}}' }} と記述します。

ホワイトスペース制御({%-...-%})

下記のテンプレートでリストを書き出してみます。

{%- set users = ["Yamada", "Tanaka", "Suzuki"] %}
<ul>
{% for user in users %}
<li>{{ user }}
{% endfor %}
</ul>

下記の様に無駄な空行が発生します。

<ul>

<li>Yamada

<li>Tanaka

<li>Suzuki

</ul>

{% の後に - を追記すると、{%- の前の連続する空白や改行をすべて削除してくれます。

<ul>
{%- for user in users %}
<li>{{ user }}
{%- endfor %}
</ul>
<ul>
<li>Yamada
<li>Tanaka
<li>Suzuki
</ul>

%} の前に - を記述すると、-%} の後ろの連続する空白や空行をすべて削除してくれます。

<ul>
{% for user in users -%}
<li>{{ user }}
{% endfor -%}
</ul>

HTML のタグも下記の様に記述することで、前後の空白や改行を削除して表示することができます。

{%- set value="yamada" %}
{%- set placeholder="Your name" %}
<input type="text"
  {%- if value %} value="{{ value }}"{% endif -%}
  {%- if placeholder %} placeholder="{{ placeholder }}"{% endif -%}
>
<input type="text" value="yamada" placeholder="Your name">

同様に {{---}} も前後の空白や改行を削除してくれます。

<p>
{{- message -}}
</p>

親テンプレートの中に子テンプレートを埋め込む({% block %}, {% extends %})

下記の様に {% block ... %} を使用することで、親テンプレート(layout.html)の一部に子テンプレート(child.html)の内容を埋め込むことができます。render() には子テンプレートのファイル名を指定します。

layout.html
<!DOCTYPE html>
<html lang="ja">
<head>
<title>{{ title }}</title>
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
child.html
{% extends "layout.html" %}
{% block body %}
<p>{{ message }}</p>
{% endblock %}
Python
context = {
    "title": "MY TITLE",
    "message": "Hello world!",
}
print(render("child.html", context))

テンプレートに他テンプレートを埋め込む({% include %})

{% include %} を用いて他のテンプレートを埋め込むことができます。

{% include "header.html" %}
...
{% include "footer.html" %}

without context をつけると、他のテンプレートにコンテキストを渡しません。

{% include "header.html" without context %}

ignore missing をつけると、ファイルが存在しなくてもエラーとしません。

{% include "header.html" ignore missing %}

マクロを呼び出す({% macro %})

{% macro %} はマクロを定義します。下記の例では input() マクロを定義し、それを呼び出しています。

{% macro input(name, value="", type="text") %}
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}
{{ input("username") }}
{{ input("password", type="password") }}

マクロに本文を渡す({% call %}, caller())

マクロを {% call %} で呼び出すと、マクロに本文を渡すことができます。マクロ側ではこれを caller() で受け取ります。下記の例では "TITLE"{{ title }} に、body...{{ caller() }} に渡されます。

{% macro section(title) %}
<h3>{{ title }}</h3>
<p>{{ caller() }}</p>
{% endmacro %}
{% call section("TITLE") %}
body...
{% endcall %}

別ファイルのマクロを呼び出す({% import %})

下記の様にして、別ファイルに記述されたマクロを呼び出すことができます。

forms.html
{% macro input(name, value="", type="text") %}
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}
main.html
{% import "forms.html" as forms %}
{{ forms.input("username") }}
main.html
{% from "forms.html" import input as input %}
{{ input("username") }}

フィルターをかける({% filter %})

{% filter %} で本文にフィルターをかけることができます。下記の例では本文をすべて大文字にします。使用可能なフィルターは、フィルター を参照してください。

{% filter upper %}
  This is Japan.
{% endfilter %}

処理を実行する({% do %})

{% do ... %}{{ ... } と同じように動作しますが、結果を表示しません。下記の様に .append() 処理は行いたいけど、その結果を表示したくない場合などに利用します。Environment()extensionsjinja2.ext.do を指定しておく必要があります。

env = Environment(
    loader=FileSystemLoader(TEMPLATE_DIR),
    extensions=["jinja2.ext.do"]
)
{%- set users = [] %}
{%- do users.append("Yamada") %}
{%- for user in users %}
  <div>{{ user }}</div>
{%- endfor %}

フィルター

テンプレートの中でフィルターを使用することができます。例えば upper は文字列を大文字にするフィルターです。

{%- set name = "Yamada" %}
{{ name|upper }}             # YAMADA

ビルトインフィルター

文字列系

safe()
safe()

値が安全なものである(悪意の利用者から受け取ったものではない)ことを示します。Environment()autoescape=True が指定されていても、値は安全なものと見なして HTML のサニタイジングを行いません。

{{ "<b>Hello!</b>"|safe }}
escape()
escape()

文字列の <, &, >&lt;, &amp;, &gt; にエスケープします。e() というエイリアス名も定義されています。Environment()autoescape=True を指定しておくとエスケープするのがデフォルト動作となります。

{%- set s = "<b>Hello!</b>" %}
{{ s|escape }}
{{ s|e }}
forceescape()
forceescape()

文字列の <, &, >&lt;, &amp;, &gt; に強制的にエスケープします。昔サポートされていた {% safe %}...{% endsafe %} の中でも強制的にエスケープするフィルタとして実装されました。現在では safe に後続する場合のみに意味を持ちます。

{{ "<b>Hello!</b>"|safe|escape }}          # <b>Hello!</b>
{{ "<b>Hello!</b>"|safe|forceescape }}     # &lt;b&gt;Hello!&lt;/b&gt;
urlencode()
urlencode()

文字列をURLエンコードします。

{%- set query = "Big banana" %}
https://example.com?q={{ query|urlencode }}      # https://example.com?q=Big%20banana
upper()
upper()

文字列を大文字に変換します。

{{ "Yamada"|upper }}             # YAMADA
lower()
lower()

文字列を小文字化します。

{{ "Yamada"|lower }}             # yamada
capitalize()
capitalize()

文字列の先頭の1文字を大文字にします。

{{ "yamada taro"|capitalize }}   # Yamada taro
title()
title()

各単語の先頭文字を大文字に、残りの文字を小文字にします。

{{ "yamada taro"|title }}        # Yamada Taro
length()
length()

値の要素数を返します。文字列の場合は文字数を返します。

{{ ["A", "B", "C"]|length }}        # 3
{{ "ABC"|length }}                  # 3
wordcount()
wordcount()

単語数を数えます。

{{ "This is Japan."|wordcount }}     # 3
join()
join(d='', attribute=None)

d を結合子として配列を結合します。

{{ ["A", "B", "C"]|join("-") }}     # A-B-C

attribute を指定すると辞書リストの属性値のみを連結します。

{%- set users = [
  {"name": "Yamada", "age": 36},
  {"name": "Tanaka", "age": 42},
  {"name": "Suzuki", "age": 58}
] %}
{{ users|join(", ", "name") }}    # Yamada, Tanaka, Suzuki
replace()
replace(old, new, count=None)

old にマッチする文字列を new に置換します。count を指定すると最大何回置換するかを指定できます。

{{ "A.B.C.D.E"|replace(".", "-") }}         # A-B-C-D-E
{{ "A.B.C.D.E"|replace(".", "-", 2) }}      # A-B-C.D.E
indent()
indent(width=4, first=False, blank=False)

改行された文字列の各行の先頭に width 個のスペースを入れてインデントします。デフォルトでは先頭行や空行にはインデントをつけませんが、first=True を指定すると先頭行もインデントし、blank=True を指定すると空行もインデントします。

{%- set message = "FOO\nBAA\n\nBAZ" %}
{{ message|indent(2, True, True) }}
truncate()
truncate(length=255, killwords=False, end='...', leeway=None)

長い文字列を end と合わせて length 文字以下になるように切り詰めます。通常は単語単位に切り詰めますが、killwords=True を指定すると単語の途中であっても切り詰めます。切り詰めた後に end を付加します。leeway を指定すると、文章全体が length + leeway 以下であれば全文を表示し、さもなくば length で切り詰めを行います。

{%- set msg = "This is long long long long message." %}
{{ msg|truncate(18) }}                 # This is long...
{{ msg|truncate(18, killwords=True) }} # This is long lo...
{{ msg|truncate(18, end="---") }}      # This is long---
{{ msg|truncate(18, leeway=18) }}      # This is long long long message.
{{ msg|truncate(18, leeway=17) }}      # This is long...
format()
format(*args, **kwargs)

printf-style フォーマットで値をフォーマットします。

{{ "%s, %s!"|format("Hello", "Yamada") }}             # Hello, Yamada!
center()
center(width)

文字列を width 文字幅の中央に表示します。

{%- set msg = "OK" %}
[{{ msg|center(10) }}]              # [    OK    ]
string()
string()

オブジェクトを文字列に変換します。

{%- set number = 123 %}
{%- set unit = "px" %}
{{ number|string + unit }}            # 123px
striptags()
striptags()

HTML タグを除去します。

{{ "<h1>Hello!</h1>"|striptags }}     # Hello!
urlize()
urlize(trim_url_limit=None, nofollow=False, target=None, rel=None, extra_schemes=None)

文字列の中から URL と認識される文字列を <a href="...">...</a> の形式に変換します。

{%- set message = "See https://example.com" %}
{{ message|urlize }}     # See <a href="https://example.com" rel="noopener">https://example.com</a>

trim_url_limit=10 を指定すると表示する URL を最大10文字に制限します。nofollow=True を指定すると rel="nofollow" を追加します。targetrel を指定すると target 属性や rel 属性にその値を追加します。extra_schema を指定すると、http, https, mailto の他にも URL として認識するスキーマを増やすことができます。

{{ $url|urlize(trim_url_limit=10, nofollow=True, target="_blank", rel="contents", extra_schema=["ftp"]) }}
xmlattr()
xmlattr(autospace=True)

辞書を HTML/XML の属性値に展開します。

{%- set attrs = {"class": "my-list", "id": "list-42"} %}
<ul {{ attrs|xmlattr }}>            # <ul  class="my-list" id="list-42">
...
</ul>

数値系

int()
int(default=0, base=10)

整数に変換します。変換が失敗すると default の値を返します。base には進数(2, 8, 16)を指定できます。

{{ "123.45"|int }}                 # 123
{{ "ABC"|int(default=-1) }}        # -1
{{ "0b11111111"|int(base=2) }}     # 255
{{ "0o377"|int(base=8) }}          # 255
{{ "0xff"|int(base=16) }}          # 255
float()
float(default=0.0)

値を浮動小数点数に変換します。

{%- set number = "123.45" %}
{{ number|float + 12.3 }}            # 135.75
min()
min(case_sensitive=False, attribute=None)

最小値を取得します。

{{ [22, 33, 11]|min }}               # 11

attribute を指定すると辞書の属性値を参照します。

{%- set users = [
  {"name": "Yamada", "age": 36},
  {"name": "Tanaka", "age": 42},
  {"name": "Suzuki", "age": 52},
] %}
{{ users|min(attribute="age") }}    # {'name': 'Yamada', 'age': 36}
max()
max(case_sensitive=False, attribute=None)

最大値を取得します。

{{ [22, 33, 11]|max }}               # 33

attribute を指定すると辞書の属性値を参照します。

{%- set users = [
  {"name": "Yamada", "age": 36},
  {"name": "Tanaka", "age": 42},
  {"name": "Suzuki", "age": 52},
] %}
{{ users|max(attribute="age") }}    # {'name': 'Suzuki', 'age': 52}
random()
random()

リストの中からランダムな要素を取り出します。

{{ ["A", "B", "C"]|random }}          # A or B or C
round()
round(precision=0, method='common')

数値を四捨五入、切上げ、切り捨てします。precision には桁数を指定します。

{{ 1234.567|round }}                # 1235.0  - 小数を四捨五入
{{ 1234.567|round(2) }}             # 1234.57 - 小数部2桁目で四捨五入
{{ 1234.567|round(-2) }}            # 1200.0  - 整数部2桁目で四捨五入
{{ 1234.567|round(0, 'common') }}   # 1235.0  - 四捨五入
{{ 1234.567|round(0, 'ceil') }}     # 1235.0  - 切上げ
{{ 1234.567|round(0, 'floor') }}    # 1234.0  - 切捨て
abs()
abs()

数値の絶対値を返します。

{%- set number = -123 %}
{{ number|abs }}              # 絶対値(123)
sum()
sum(attribute=None, start=0)

リストの合計値を求めます。attribute を指定すると辞書の属性値の合計を求めます。start は初期値を指定します。

{%- set nums = [1, 2, 3, 4, 5] %}
{{ nums|sum }}                     # 15
{{ nums|sum(start=100) }}          # 115

{%- set objs = [
  {"name": "Yamada", "score": 93},
  {"name": "Tanaka", "score": 84},
  {"name": "Suzuki", "score": 73},
] %}
{{ objs|sum(attribute="score") }}  # 250
filesizeformat()
filesizeformat(binary=False)

数値を 12 Bytes, 12.3 kB, 12.3 MB などのファイルサイズ形式の文字列に変換します。k は 1000倍、M は 1000×1000倍を意味します。binaryTrue を指定すると KiB, Mebi, Gibi などと表示され 1024倍系の単位となります。

{%- set size = 10000 %}
{{ size|filesizeformat }}            # 10.0 kB
{{ size|filesizeformat(True) }}      # 9.8 KiB

リスト系

list()
list()

値をリスト化したものを返します。

{{ "ABC"|list }}                     # ['A', 'B', 'C']
first()
first()

リストの先頭の要素を返します。

{{ [1, 2, 3]|first }}                # 1
last()
last()

リストの末尾の要素を返します。

{{ [1, 2, 3]|last }}                 # 3
sort()
sort(reverse=False, case_sensitive=False, attribute=None)

リストをソートします。reverse=True を指定すると逆順にソートします。case_sensitive=True を指定すると大文字・小文字を区別します。attribute は辞書配列を attribute 属性の値でソートします。

{{ [3, 1, 2]|sort }}                 # [1, 2, 3]
reverse()
reverse()

リストを逆順にします。

{{ [1, 2, 3]|reverse|list }}         # [3, 2, 1]
unique()
unique(case_sensitive=False, attribute=None)

リストから重複する要素を除外します。case_sensitive=True を指定すると大文字・小文字を区別します。attribute を指定すると辞書リストの中の属性値で判断します。

{{ [1, 2, 1, 3]|unique|list }}     # [1, 2, 3]
select()
select(*args, **kwargs)

リストから条件に合致するものを抜き出します。条件には ビルトインテスト などのテスト関数を指定できます。

{{ [1, 2, 3, 4, 5, 6]|select("gt", 3)|list }}   # [4, 5, 6]
selectattr()
selectattr(*args, **kwargs)

辞書のリストから属性名を指定して、条件に合致するものを抜き出します。条件には ビルトインテスト などのテスト関数を指定できます。下記の例では age が 50 より大きなエントリを抜き出しています。

{%- set users = [
  {"name": "Yamada", "age": 36},
  {"name": "Tanaka", "age": 42},
  {"name": "Suzuki", "age": 52},
] %}
{%- for user in users|selectattr("age", "gt", 50) %}
  {{ user.name }} : {{ user.age }}
{%- endfor %}
reject()
reject(*args, **kwargs)

リストから条件に合致するものを除外します。条件には ビルトインテスト などのテスト関数を指定できます。

{{ [1, 2, 3, 4, 5, 6]|reject("gt", 3)|list }}   # [1, 2, 3]
slice()
slice(slices, fill_with=None)

リストを slices 行にグルーピングします。fill_with は最後の行の項目数が不足する場合の代替値を指定します。下記の例では 1行目が A, B, C, D、2行目が E, F, G, - の2行のテーブルとなります。

{%- set items = ["A", "B", "C", "D", "E", "F", "G"] %}
<table>
{%- for row in items|slice(2, '-') %}
  <tr>
  {%- for item in row %}
    <td>{{ item }}</td>
  {%- endfor %}
  </tr>
{%- endfor %}
</table>
batch()
batch(linecount, fill_with=None)

リストを linecount 列にグルーピングします。fill_with は最後の行の項目数が不足する場合の代替値を指定します。下記の例では 1行目が A, B, C、2行目が D, E, F、3行目が G, -, - の3列のテーブルとなります。

{%- set items = ["A", "B", "C", "D", "E", "F", "G"] %}
<table>
{%- for row in items|batch(3, '-') %}
  <tr>
  {%- for column in row %}
    <td>{{ column }}</td>
  {%- endfor %}
  </tr>
{%- endfor %}
</table>

辞書系

items()
items()

辞書の値を key, value ペアのリストとして返します。

{%- set mydict = {"foo": "FOO", "baa": "BAA", "baz": "BAZ"} %}
{%- for key, value in mydict|items %}
  {{ key }} : {{ value }}
{%- endfor %}
dictsort()
dictsort(case_sensitive=False, by='key', reverse=False)

辞書をソートして key, value を返します。case_sensitiveTrue を指定すると大文字・小文字を区別します。by'key' でソートするか 'value' でソートするかを指定します。reverseTrue を指定すると逆順にソートします。

{%- set user = {"name": "Yamada", "age": 36, "city": "Tokyo"} %}
{%- for key, value in user|dictsort(True, 'key', True) %}
  {{ key }} : {{ value }}
{%- endfor %}
groupby()
groupby(attribute, default=None, case_sensitive=False)

辞書を attribute 属性の値でグルーピングします。default にはエントリが attribute 属性を持たない場合のデフォルト値を指定します。case_sensitiveTrue を指定すると大文字・小文字を無視します。

{%- set users = [
  {"city": "Tokyo",  "name": "Yamada"},
  {"city": "Osaka",  "name": "Tanaka"},
  {"city": "Nagoya", "name": "Suzuki"},
  {"city": "Tokyo",  "name": "Fujita"},
  {                  "name": "Kaneko"},
] %}
{%- for city, users in users|groupby("city", default="(Unknown)") %}
<h3>{{ city }}</h3>
  {%- for user in users %}
  <div>{{ user.name }}</div>
  {%- endfor %}
{%- endfor %}
rejectattr()
rejectattr(*args, **kwargs)

辞書のリストから属性名を指定して、条件に合致するものを除外します。条件には ビルトインテスト などのテスト関数を指定できます。下記の例では age が 50 より大きなエントリを除外しています。

{%- set users = [
  {"name": "Yamada", "age": 36},
  {"name": "Tanaka", "age": 42},
  {"name": "Suzuki", "age": 52},
] %}
{%- for user in users|rejectattr("age", "gt", 50) %}
  {{ user.name }} : {{ user.age }}
{%- endfor %}
map()
map(*args, **kwargs)

attribute を指定すると辞書リストに対して属性値を抜き出します。

{%- set users = [
  {"name": "Yamada", "age": 36},
  {"name": "Tanaka", "age": 42},
  {"name": "Suzuki", "age": 58}
] %}
{{ users|map(attribute="age")|list }}   # [36, 42, 58]

各要素に適用するフィルター関数名を変数で指定することもできます。

{%- set filter = "lower" %}
{{ ["A", "B", "C"]|map(filter)|list }}    # ['a', 'b', 'c']

その他系

attr()
attr(name)

クラスインスタンスの属性(インスタンス変数)を参照します。

class Person():
    def __init__(self, name):
        self.name = name

context = {
    "user": Person("Yamada")
}
print(render("test.html", context))
{# user = Person(name="Yamada") #}
{{ user|attr("name") }}          # Yamada
default()
default(default_value='', boolean=False)

値が未定義の時 default_value を表示します。boolean は省略可能で、True を指定すると値が未定義や False と判断される値(0, None, "" など)の時にも default_value を表示します。

{{ message|default("(No message)", True) }}
pprint()
pprint()

値を書き出します。デバッグで利用されます。

{{ value|pprint }}

カスタムフィルター

自作のフィルター関数を追加することもできます。下記の例では、文字列をタグ名で囲む自作フィルターを作成して使用しています。

from jinja2 import Environment, FileSystemLoader

TEMPLATE_DIR = "/opt/myapp/templates"

def tag(value, tagname):
    return f"<{tagname}>{value}</{tagname}>"

env = Environment(loader=FileSystemLoader(TEMPLATE_DIR))
env.filters["tag"] = tag

def render(template_file, context):
    template = env.get_template(template_file)
    return template.render(context)

if __name__ == "__main__":
    context = { "message": "Hello!"}
    print(render("test.html", context))
{{ message|tag("p")|safe }}        # <p>Hello!</p>

ビルトインテスト

フィルターの is 演算子や、select(), selectattr(), reject(), rejectattr() では、下記のテスト関数を使用できます。

{% if value is eq("ABC") %}...{% endif %}
{{ [v1, v2, v3, v4] | select("eq", "ABC") | list }}
eq(b)             : 等しい(equal)
ne(b)             : 等しくない(not equal)
ge(b)             : 以上(greater equal)
le(b)             : 以下(less equal)
gt(b)             : 大きい(greater than)
lt(b)             : 小さい(less than)
in(seq)           : 含んでいる
sameas(obj)       : objと同値であれば(==ではなくis演算子で判定)
upper()           : 大文字であれば
lower()           : 小文字であれば
string()          : 文字列であれば
boolean()         : 真偽値であれば
number()          : 数値であれば(真偽値を含む)
integer()         : 整数であれば
float()           : 浮動小数点数であれば
iterable()        : イテラブルであれば
sequence()        : シーケンスであれば
mapping()         : マッピングオブジェクト(辞書など)であれば
true()            : 真値であれば
false()           : 偽値であれば
even()            : 偶数であれば
odd()             : 奇数であれば
divisibleby(num)  : numで割り切れれば
none()            : None値であれば
defined()         : 定義値であれば
undefined()       : 未定義であれば
callable()        : 呼び出し可能な関数やメソッドであれば
filter()          : フィルター名であれば
test()            : テスト名であれば
escaped()         : HTML/XML的にエスケープ済みであれば

グローバル関数

range()

range([start,] stop[, step])

start から stop - 1 まで step ずつインクリメントするリストを返します。start を省略すると 0step を省略すると 1 とみなします。

{% for n in range(5) %}{{ n }} {% endfor %}         # 0, 1, 2, 3, 4
{% for n in range(2, 5) %}{{ n }} {% endfor %}      # 2, 3, 4
{% for n in range(1, 8, 2) %}{{ n }} {% endfor %}   # 1, 3, 5, 7

lipsum()

lipsum(n=5, html=True, min=20, max=100)

lorem ipsum と呼ばれるダミーテキストを生成します。n は生成するパラグラフ数、html は HTML として生成するか、min は最低ワード数、max は最大ワード数を指定します。

{{ lipsum(n=1) }}      # <p>Lorem ipsum dolor sit amet, ...

dict()

dict(**items)

関数への引数を属性とする辞書オブジェクトを生成します。

{%- for key, value in dict(name="Yamada", age=36).items() %}
  {{ key }} = {{ value }}
{%- endfor %}

cycler()

cycler(*item)

サイクルオブジェクトを生成します。cycler.next() を呼び出すたびに次の値がサイクリックに返却されます。cycler.current は現在の値、cycler.prev() はひとつ前の値を参照します。

{%- set colors = cycler("blue", "red", "green") %}
{%- set items = ["A", "B", "C", "D", "E", "F", "G"] %}
{%- for item in items %}
  <div style="color:{{ colors.next() }}">{{ item }}</div>
{%- endfor %}

joiner()

joiner(sep=", ")

1回目は空文字を返し、2回目以降は sep を返すジョイナー関数を生成します。単純に join() で連結できないリストで一番先頭の要素の前にセパレーターを表示したくない場合に利用できます。

{%- set users = [
  { "name": "Yamada", "status": "active" },
  { "name": "Tanaka", "status": "inactive" },
  { "name": "Suzuki", "status": "active" },
] %}
{%- set comma = joiner(", ") %}
{%- for user in users %}
  {%- if user.status == "active" %}
    {{- comma() }}{{ user.name -}}
  {%- endif %}
{%- endfor %}                        # Yamada, Suzuki

namespace()

namespace(...)

通常の {% set %} はループの中で設定されてもループ外に持ち出すことができません。下記の例では 1 から 5 の合計 total を計算していますが、結果は 0 となります。

{%- set numbers = [1, 2, 3, 4, 5] %}
{%- set total = 0 %}
{%- for n in numbers %}
  {%- set total = total + n %}
{%- endfor %}
total={{ total }}

下記の様にネームスペースを作成することで、ループ内の計算結果をループ外に持ち出すことが可能となります。

{%- set numbers = [1, 2, 3, 4, 5] %}
{%- set ns = namespace(total=0) %}
{%- for n in numbers %}
  {%- set ns.total = ns.total + n %}
{%- endfor %}
total={{ ns.total }}

ノウハウ

Python関数を呼び出す

env.globals に変数や関数を指定することでテンプレート側から呼び出すことができます。

def hello(name):
    return f"Hello {name}!"

env.globals["hello"] = hello
{{ hello("Yamada") }}            # Hello Yamada!

テンプレートの中から実行できる関数は、必要最小限にとどめてください。特にファイル操作や外部コマンド実行などの関数をそのまま公開すると、テンプレートインジェクションの脆弱性につながる可能性があります。

多言語対応

pybabel コマンドを使用するために babel をインストールします。

pip3 install Babel

下記のコンフィグファイル(babel.cfg)を作成しておきます。

[python: **.py]

[jinja2: templates/**.html]
extensions=jinja2.ext.i18n

Python プログラムに下記を追記します。

import gettext
from jinja2 import Environment, FileSystemLoader

TEMPLATE_DIR = "/opt/myapp/templates"
LOCALE_DIR = "/opt/myapp/locale"

env = Environment(
    loader=FileSystemLoader(TEMPLATE_DIR),
    autoescape=True,
    extensions=["jinja2.ext.i18n"]
)

def render(template_file, context, lang="en"):
    trans = gettext.translation(
        "messages",
        localedir=LOCALE_DIR,
        languages=[lang],
        fallback=True
    )
    env.install_gettext_callables(gettext=trans.gettext, ngettext=trans.ngettext)
    template = env.get_template(template_file)
    return template.render(context)

テンプレートで翻訳したい箇所を _(...) で囲みます。

{{ _("Hello!") }}

pybabel コマンドで、*.py*.html ファイルから翻訳対象の文字列を抽出します。

pybabel extract -F babel.cfg -o messages.pot .

messages.pot ファイルを言語毎の ./locale/ja/LC_MESSAGES/messages.po ファイルにコピーします。

pybabel init -D messages -l ja -i messages.pot -d ./locale

./locale/ja/LC_MESSAGES/messages.po ファイルを編集し、msgid に対する翻訳 msgstr を記述します。

msgid: "Hello!"
msgstr: "こんにちは!"

*.po ファイルを *.mo ファイルにコンパイルします。

pybabel compile -D messages -d ./locale

render()lang="ja" を指定することで、日本語のメッセージを表示することができます。

print(render("test.html", context, lang="ja"))

「こんにちは!」と表示されれば成功です。

None 値を "None" ではなく空文字で出力する

None 値の変数を出力すると "None" という文字列が表示されますが、これを空文字("")で表示するには、下記の様にします。

{%- set value = none %}
{{ value }}                                  # None
{{ value | default("", true) }}     # 空文字
{{ value or "" }}                   # 空文字

あるいは下記の様なファイナライズ関数を指定する方法もあります。すべての {{...}} に対してファイナライズ関数が呼ばれ、None を空文字で表示します。

def my_finalize(value):
    return "" if value is None else value

env = Environment(
    loader=FileSystemLoader(TEMPLATE_DIR),
    autoescape=True,
    finalize=my_finalize
)

数値をカンマ(,)区切りで表示する

下記の様なフィルタを準備すると便利です。

def comma(value):
    if isinstance(value, str):
        value = float(value) if "." in value else int(value)
    return f"{value:,}"

env = Environment(
    loader=FileSystemLoader(TEMPLATE_DIR),
    autoescape=True
)
env.filters["comma"] = comma
{{ 1234.567|comma }}             # 1,234.567
{{ "1234.567"|comma }}           # 1,234.567
{{ 1234567|comma }}              # 1,234,567
{{ "1234567"|comma }}            # 1,234,567