ライブラリ

テンプレートエンジンの使い方(jinja2)

templateエンジン

jinja2とは「python用の、htmlを動的作成できるテンプレートエンジン」です。

Webアプリケーションを作成する際に役立つもので、Flaskという有名なWebアプリケーションフレームワークでも使われています。

ここでは「jinja2とは?」「jinja2はどうやって使うの?」といった方に向け、jinja2について解説します。

jinja2のインストール

まずはjinja2をインストールしましょう(ここではpipを使ってインストールしますが、Anacondaでも環境作成は可能です)。

次のコマンドを実行します。

$ pip install Jinja2

これでjinja2がインストールされました。

jinja2の使い方

テンプレート文字列を使う

では一番シンプルな使い方から見ていきましょう。

Template()にテンプレート文字列を渡し、render()で結果を出力します。

from jinja2 import Template
tmp_s = 'Hello {{ name }}!'
template = Template(tmp_s)
ren_s = template.render(name='佐藤')
print(ren_s)

[出力結果]

Hello 佐藤!

tmp_sがテンプレート文字列です。{{ }} で囲まれたnameはrender()のタイミングで置き変わります。

Template()にテンプレート文字列を渡した結果をtemplate変数に格納しています。templateはjinja2.environment.Templateというjinja2専用のオブジェクトです。

最後にtemplateのrender()を使ってテンプレート文字列に変数を渡し、結果を文字列で取得しています。name=’佐藤’で {{ }} で囲まれたnameを置き換えています。

用途に応じてファイルを分ける

最も基本的なjinja2の使い方を見てきましたが、上記のプログラムは「テンプレート文字列」「テンプレートに渡すパラメータ」「レンダリング」が全て同じ場所に記述されています。

これらを分割することでより管理しやすくなるので、3つのファイルに分けてみましょう。

コード(template.j2):

Hello {{ name }}!

コード(parameter.json):

{"name" : "佐藤"}

コード(rendering.py):

from jinja2 import Template, Environment, FileSystemLoader
import json

#テンプレート読み込み
env = Environment(loader=FileSystemLoader('./', encoding='utf8'))
tmpl = env.get_template('template.j2')

#設定ファイル読み込み
with open('parameter.json') as f:
    params = json.load(f)

#レンダリングして出力
rendered_s = tmpl.render(params)
print(rendered_s)

[出力結果]

Hello 佐藤!

少し長いので、一つずつ見ていきましょう。

まずtemplate.j2です。これはテンプレートファイルです。拡張子をj2にしていますが、tmplでもjinjaでもtxtでも問題ありません。

次にparameter.jsonです。これはレンダリングする際に必要なパラメータをjson形式で記述しています。今回はnameを佐藤に置き換えるので、keyを「name」、valueを「佐藤」にしています。

なお、ここではjsonを使っていますが、パラメータを管理するファイルもxmlやyaml等、別の形式でも問題ありません。

最後にrendering.pyです。これは実際にテンプレートファイルとパラメータファイルを読み込んでレンダリングして、結果を出力するメインファイルです。

FileSystemLoaderはテンプレートファイルを読み込むためのモジュール、Environmentはjinja2の環境設定用のモジュールです。

Environment()で生成したenvのget_template()でtemplate.j2ファイルを読み込みます。

次にparameter.jsonを読み込み、jsonモジュールのloadで辞書に変換します。

最後にtmplのrender()に辞書形式のパラメータを渡してレンダリングしています。

htmlを出力する

jinja2を使って動的にhtmlを生成し、ファイルに出力してみましょう。

コード(template.j2):

<html>
  <body>
    <h1>{{ shop_name }}</h1>
    <p>{{ item }}は{{ price }}円です。</p>
  </body>
</html>

コード(parameter.json):

{
	"shop_name": "雑貨屋",
	"item": "お皿",
	"price": "200"
}

コード(rendering.py):

from jinja2 import Template, Environment, FileSystemLoader
import json

#テンプレート読み込み
〜省略〜

#設定ファイル読み込み
〜省略〜

#レンダリングしてhtml出力
rendered_html = tmpl.render(params)
with open('result.html', 'w') as f:
    f.write(rendered_html)

[出力結果結果(result.html)]

<html>
  <body>
    <h1>雑貨屋</h1>
    <p>お皿は200円です。</p>
  </body>
</html>

生成されたファイルをブラウザで開いたら、htmlとして表示されます。

テンプレート内でループする

テンプレート内でループ処理は次のようにすることで実現できます。

コード(template.j2):

<html>
  <body>
    <h1>{{ shop_name }}</h1>
{% for item in items %}
    <p>{{ loop.index }} : {{ item.name }}は{{ item.price }}円です。</p>
{% endfor %}
  </body>
</html>

コード(parameter.json):

{
    "shop_name": "雑貨屋",
    "items": [
        {"name": "羊の置き物", "price": 550},
        {"name": "ねずみの貯金箱", "price": 1290},
        {"name": "猫のキャンドル", "price": 840}
    ]
}

[出力結果結果(result.html)]

<html>
  <body>
    <h1>雑貨屋</h1>

    <p>1 : 羊の置き物は550円です。</p>

    <p>2 : ねずみの貯金箱は1290円です。</p>

    <p>3 : 猫のキャンドルは840円です。</p>

  </body>
</html>

template.j2ファイルの{% for item in items %}が重要な点です。

itemsはparameter.jsonのitemsキーを指していますので、itemにはnameとpriceの要素のあるオブジェクトが、ループごとに一つずつ入っていきます。

あとは{{ item.name }}の形でjson内(正確には辞書内)の値と置き換えます。

また、loop.indexはループ中のインデックス番号を取得できます。

最後に{% endfor %}でループを終了します。

※話は少しそれますが、今回rendering.pyは変更していません。これはファイルを用途に応じて3つに分けたからです。

修正の必要な箇所がファイル単位で切り出せるのは利点です。

テンプレート内で条件分岐する

テンプレート内での条件分岐は次のようにすることで実現できます。

コード(template.j2):

<html>
  <body>
    <h1>{{ shop_name }}</h1>
{% if user.birth_month == 11 %}
    <p>{{ item.name }}は10%引きで{{ item.price|int * 0.9 }}円です。</p>
{% else %}
    <p>{{ item.name}}は通常価格で{{ item.price }}円です。</p>
{% endif %}
  </body>
</html>

コード(parameter.json):

{
    "shop_name": "雑貨屋",
    "item": {"name": "お皿", "price":"200"},
    "user": {"id":1, "name":"鈴木", "birth_month": 11}
}

[出力結果結果(result.html)]

<html>
  <body>
    <h1>雑貨屋</h1>

    <p>お皿は10%引きで180.0円です。</p>

  </body>
</html>

{% if user.birth_mondh %}と{% else %}と{% endif %}で、テンプレート内でif〜else文を表現しています。

endifは忘れやすいので注意しましょう。

また、item.priceは文字列なので、そのまま計算式に使うとエラーになってしまいます。

そこでフィルターのintを使い、数値に変換しています。フィルターはpythonの関数のようなものです。

jinja2のテンプレートでpythonの関数を使う場合は「|(パイプ記号)」で区切ってフィルター名を記述します。

こちらにjinja2で使えるフィルターの一覧を載せておきます。trimやreplaceなど、文字列に対して使うフィルターも多数存在します。

Template Designer Documentation — Jinja Documentation (2.11.x)

テンプレートの継承

jinja2の便利な使い方にテンプレートの継承があります。テンプレートの継承は「ベーステンプレートと子テンプレートに分けて、子テンプレートがベーステンプレートをオーバーライドできる」機能です。

ためしに使ってみましょう。まずはベーステンプレートを作ります。

コード(base.j2):

<html>
<head>
  {% block head %}
  <link rel="stylesheet" href="style.css" />
  <title>Sample - Web Page</title>
  {% endblock %}
</head>
<body>
  <div id="content">{% block content %}{% endblock %}</div>
  <div id="footer">{% block footer %}{% endblock %}</div>
</body>
</html>

htmlタグやhead、bodyタグなどのよく使うタグが配置され、さらにbody内にcontentやfooterのdivを記述しており、よくあるページレイアウトとなっています。

重要なのは{% block %}と{% endblock %}です。この2つに挟まれた箇所は子テンプレート側でコンテンツが埋め込まれます。

次は子テンプレートを見てみましょう。

コード(child.j2):

{% extends "base.j2" %}
{% block head %}
  {{ super() }}
  <style type="text/css">
    .important { color: #336699; }
    #footer { font-size: 10px; }
  </style>
{% endblock %}
{% block content %}
  <h1>テンプレート継承テスト</h1>
  <p class="important">テンプレートが継承されています。</p>
{% endblock %}
{% block footer %}
  <p>Copyright 2020</p>
{% endblock %}

まず{% extends "base.j2" %}でbase.j2を読み込んでいます。

次に「base.j2側で定義したblockと同じ名前のblock」でベーステンプレートにコンテンツを埋めていきます。

例えば{% block content %}では<h1>タグや<p>タグでコンテンツを埋めています。

また、{% block header %}では{{ super() }}という記述があります。これはベーステンプレートの内容を継承するという意味です。

それではrendering.pyを実行して、結果を見てみましょう(child.j2を読み込んでレンダリングましょう)。

[出力結果結果(result.html)]

<html>
<head>
  
  
  <link rel="stylesheet" href="style.css" />
  <title>Sample - Web Page</title>
  
  <style type="text/css">
    .important { color: #336699; }
    #footer { font-size: 10px; }
  </style>

</head>
<body>
  <div id="content">
  <h1>テンプレート継承テスト</h1>
  <p class="important">テンプレートが継承されています。</p>
</div>
  <div id="footer">
  <p>Copyright 2020</p>
</div>
</body>
</html>

ベーステンプレートのレイアウトに子テンプレートで追加したコンテンツが埋め込まれているのがわかります。

headerで{{ super() }}を使っているので、ベーステンプレート側で定義していた<link>タグや<title>タグもレンダリングされています。

テンプレート内でコメントアウトする

最後にコメントアウトの方法を見てみましょう。

コメントアウトは{# #}で括ります。

コード(template.j2):

<html>
  <body>
    <h1>{{ shop_name }}</h1>
    {# <p>{{ item }}は{{ price }}円です。</p> #}
  </body>
</html>

[出力結果]

<html>
  <body>
    
  </body>
</html>

{# #}で括った箇所がコメントアウトされ、結果に出力されなくなりました。

タイトルとURLをコピーしました