PythonのテンプレートエンジンJinja2を分かりやすく解説


PythonのテンプレートエンジンJinja2を徹底解説:ゼロから学ぶ実践ガイド

はじめに:なぜテンプレートエンジンが必要なのか?

Webアプリケーション開発やレポート生成、設定ファイル作成など、動的なテキストコンテンツを生成する機会は数多くあります。例えば、WebアプリケーションでユーザーごとにパーソナライズされたHTMLページを表示する場合を考えてみましょう。Pythonのコード内で直接HTMLの文字列を組み立てることも可能ですが、非常に複雑になりがちです。

“`python

Pythonコード内でHTMLを直接生成する例(非推奨)

def generate_user_profile_page(user):
html = “User Profile
html += f”

Welcome, {user.name}!


html += “

Here is your profile information:


html += “


    html += f”

  • Email: {user.email}

  • if user.is_premium:
    html += “

  • Account Type: Premium

  • else:
    html += “

  • Account Type: Free

  • html += “


html += “”
return html

class User:
def init(self, name, email, is_premium):
self.name = name
self.email = email
self.is_premium = is_premium

user = User(“Alice”, “[email protected]”, True)
print(generate_user_profile_page(user))
“`

この例のように、Pythonコード内にHTML構造とデータを混在させると、コードが読みにくく、変更しにくくなります。特に、HTMLの構造が複雑になったり、条件分岐や繰り返しが増えたりすると、保守は困難を極めます。また、WebデザイナーがHTML構造を変更したい場合にも、Pythonコードを直接編集する必要が出てきてしまい、開発者とデザイナーの分業が難しくなります。

ここでテンプレートエンジンの登場です。テンプレートエンジンは、「見た目(HTML構造など)」「データ」を分離するためのツールです。あらかじめ骨組みとなるテンプレートファイルを用意しておき、そこに後からデータを流し込んで最終的なテキストを生成します。

テンプレートの役割:
* 最終的な出力(HTML、XML、テキスト、設定ファイルなど)の静的な構造を定義する。
* データの埋め込み位置を指定する。
* 条件分岐や繰り返しなどの簡単なロジックを記述する。

テンプレートエンジンの役割:
* テンプレートファイルとデータを受け取る。
* テンプレートファイルに記述された命令(変数出力、制御構造など)を解釈する。
* データを使ってテンプレートを埋め、最終的なテキストコンテンツを生成する(この過程を「レンダリング」と呼びます)。

テンプレートエンジンを使うことで、デザイン(テンプレート)とロジック(Pythonコード)が分離され、コードの見通しが良くなり、保守性が向上します。また、デザイナーと開発者がそれぞれの役割に集中できるようになります。

Jinja2とは

Jinja2は、Pythonで最も広く使われているテンプレートエンジンの一つです。Djangoフレームワークのテンプレートシステムに影響を受けて開発されましたが、Djangoに依存せず独立して使うことができます。Flaskフレームワークでは標準のテンプレートエンジンとして採用されています。

Jinja2は高速で、セキュリティが高く(自動エスケープ機能)、非常に柔軟です。強力なテンプレート継承システム、使いやすいフィルタとテスト、マクロ機能など、動的なコンテンツ生成に必要な機能を豊富に備えています。

Jinja2の特徴とメリット

  • 高速: C言語で記述された部分もあり、高速なレンダリングが可能です。
  • 使いやすい構文: Pythonに似た直感的で分かりやすい構文を持っています。
  • 強力なテンプレート継承: 共通のレイアウトを定義し、子テンプレートで必要な部分だけを上書きすることで、効率的にテンプレートを作成できます。
  • 豊富なフィルタとテスト: 変数の値を加工したり、条件をチェックしたりするための組み込み機能が充実しています。独自のフィルタやテストを追加することも容易です。
  • マクロ: テンプレート内で繰り返し使うコードブロックを定義し、再利用できます。
  • 自動エスケープ: デフォルトでHTMLエスケープが有効になっており、クロスサイトスクリプティング(XSS)などのセキュリティリスクを低減します。
  • 柔軟な設定: ロード方法、デリミタ、空白文字の扱いなどを細かく設定できます。
  • 独立性: 特定のWebフレームワークに依存せず、単体で使用できます。

これらの特徴により、Jinja2はWeb開発だけでなく、設定ファイル生成、コード生成、静的サイトジェネレーターなど、様々な用途で利用されています。

Jinja2のインストールと準備

Jinja2を使うには、まずPythonのパッケージ管理システムpipを使ってインストールします。

bash
pip install Jinja2

インストールが完了したら、PythonスクリプトからJinja2を使ってみましょう。

基本的な使い方(Hello World)

最も基本的な使い方は、文字列としてテンプレートを定義し、それにデータを渡してレンダリングする方法です。

“`python
from jinja2 import Environment, BaseLoader

テンプレートを文字列として定義

template_string = “Hello, {{ name }}!”

Environmentオブジェクトを作成

BaseLoaderは文字列からテンプレートをロードする際に使用

env = Environment(loader=BaseLoader())

テンプレートをロード

template = env.from_string(template_string)

データを定義

data = {“name”: “World”}

テンプレートをレンダリングし、結果を出力

output = template.render(data)
print(output)

別のデータでレンダリング

data_japanese = {“name”: “Jinja2”}
output_japanese = template.render(data_japanese)
print(output_japanese)
“`

実行結果:

Hello, World!
Hello, Jinja2!

この例では、Environmentオブジェクトを作成し、BaseLoaderを使って文字列からテンプレートを読み込んでいます。env.from_string()メソッドでテンプレートオブジェクトを取得し、template.render()メソッドにデータを辞書形式で渡すことでレンダリングを実行しています。テンプレート内の {{ name }} の部分が、渡されたデータの name の値に置き換わっているのが分かります。

これは最も単純な例ですが、通常テンプレートはファイルとして管理します。ファイルからテンプレートを読み込む方法については後述します。

Jinja2の基本構文:変数はじめの一歩

Jinja2のテンプレートは、静的なテキストの中に特別な記号で囲まれた部分(デリミタ)を含みます。これらのデリミタを使って、変数を出力したり、条件分岐や繰り返しを行ったり、コメントを記述したりします。

デリミタの種類と役割

Jinja2には主に3種類のデリミタがあります。

  1. 変数出力: {{ ... }}

    • テンプレートに渡された変数の値を表示します。デフォルトでは、HTMLを安全に表示するために自動的にエスケープされます。
  2. 制御構造: {% ... %}

    • テンプレートのロジック(if文、for文、ブロック定義、マクロ定義など)を記述します。
  3. コメント: {# ... #}

    • テンプレート内のコメントです。レンダリング時には出力されません。

これらのデリミタは、Environmentの設定で変更することも可能ですが、特別な理由がない限りデフォルトのまま使うことが推奨されます。

変数出力:{{ variable }}

{{ ... }} デリミタは、テンプレートに渡されたデータを表示するために使います。

“`jinja
{# template.jinja #}

User Name: {{ user.name }}

User ID: {{ user.id }}

First item: {{ my_list[0] }}

Last item: {{ my_list[-1] }}

Dictionary value: {{ my_dict[“key”] }}

Another dictionary value: {{ my_dict.another_key }}

“`

Pythonコード:

“`python
from jinja2 import Environment, FileSystemLoader

テンプレートファイルが存在するディレクトリを指定

env = Environment(loader=FileSystemLoader(‘.’)) # この例ではカレントディレクトリ

template = env.get_template(‘template.jinja’)

class User:
def init(self, id, name):
self.id = id
self.name = name

data = {
“user”: User(123, “Alice”),
“my_list”: [10, 20, 30],
“my_dict”: {“key”: “value”, “another_key”: “another_value”}
}

print(template.render(data))
“`

実行結果:

“`html

User Name: Alice

User ID: 123

First item: 10

Last item: 30

Dictionary value: value

Another dictionary value: another_value

“`

変数にアクセスするには、ドット (.) またはブラケット ([]) を使用します。
* {{ user.name }}: user オブジェクト(または辞書)の name 属性(またはキー)にアクセスします。
* {{ my_list[0] }}: my_list リストのインデックス 0 の要素にアクセスします。
* {{ my_dict["key"] }}{{ my_dict.key }}: 辞書のキーにアクセスします。ドット記法は、キー名がPythonの変数名として有効な場合に便利です。

存在しない変数へのアクセスとデフォルト値

存在しない変数や属性にアクセスした場合、デフォルトではエラーにはならず、何も表示されません(厳密には undefined オブジェクトが返ります)。しかし、意図せずデータが表示されないことを避けるため、または代替の値を表示したい場合に、default フィルタを使うことができます。

“`jinja

Optional Value: {{ optional_value | default(‘N/A’) }}

Another Optional: {{ another_optional | default(‘デフォルトです’, boolean=True) }}

“`

Pythonコード:

“`python
from jinja2 import Environment, BaseLoader

env = Environment(loader=BaseLoader())
template = env.from_string(“””

Optional Value: {{ optional_value | default(‘N/A’) }}

Another Optional: {{ another_optional | default(‘デフォルトです’, boolean=True) }}

Empty String: {{ empty_string | default(‘これは表示されない(空文字列だから)’) }}

Empty String with boolean=True: {{ empty_string | default(‘これは表示される(空文字列はFalse扱い)’, boolean=True) }}

“””)

data = {
“optional_value”: “Exists!”,
“another_optional”: None, # None は undefined 扱いされる
“empty_string”: “” # 空文字列はデフォルトでは undefined 扱いされない
}

print(template.render(data))
“`

実行結果:

“`html

Optional Value: Exists!

Another Optional: デフォルトです

Empty String:

Empty String with boolean=True: これは表示される(空文字列はFalse扱い)

“`

default フィルタは、変数が存在しないか、値が None の場合に指定したデフォルト値を返します。boolean=True オプションを指定すると、空文字列 ("") や空のリスト ([])、空の辞書 ({})、数値の 0 など、Pythonで真偽値として評価した際に False になる値も default フィルタの対象とします。

制御構造:{% ... %}

{% ... %} デリミタは、テンプレート内のロジックを記述するために使用します。

if文

条件に基づいてコンテンツを表示するかどうかを制御します。

“`jinja
{% if user.is_admin %}

Admin dashboard link here.

{% elif user.is_moderator %}

Moderator tools here.

{% else %}

Welcome, standard user!

{% endif %}

{# 真偽値として評価されるものなら何でも条件に使える #}
{% if items %}

Items available.

{% endif %}

{# not, and, or も使える #}
{% if user.is_active and user.has_permission %}

Action allowed.

{% endif %}
“`

Pythonコード:

“`python
from jinja2 import Environment, BaseLoader

env = Environment(loader=BaseLoader())
template = env.from_string(“””
{% if user.is_admin %}

Admin dashboard link here.

{% elif user.is_moderator %}

Moderator tools here.

{% else %}

Welcome, standard user!

{% endif %}

{% if items %}

Items available.

{% endif %}

{% if user.is_active and user.has_permission %}

Action allowed.

{% endif %}
“””)

class User:
def init(self, is_admin=False, is_moderator=False, is_active=True, has_permission=True):
self.is_admin = is_admin
self.is_moderator = is_moderator
self.is_active = is_active
self.has_permission = has_permission

data1 = {“user”: User(is_admin=True), “items”: [“a”, “b”]}
data2 = {“user”: User(is_moderator=True), “items”: []}
data3 = {“user”: User(), “items”: [1]}
data4 = {“user”: User(is_active=False), “items”: [1]}

print(“— Data 1 (Admin) —“)
print(template.render(data1))
print(“\n— Data 2 (Moderator) —“)
print(template.render(data2))
print(“\n— Data 3 (Standard User) —“)
print(template.render(data3))
print(“\n— Data 4 (Inactive User) —“)
print(template.render(data4))
“`

実行結果:

“`html
— Data 1 (Admin) —

<p>Admin dashboard link here.</p>



<p>Items available.</p>

<p>Action allowed.</p>

— Data 2 (Moderator) —

<p>Moderator tools here.</p>



<p>Action allowed.</p>

— Data 3 (Standard User) —

<p>Welcome, standard user!</p>


<p>Items available.</p>

<p>Action allowed.</p>

— Data 4 (Inactive User) —

<p>Welcome, standard user!</p>


<p>Items available.</p>

“`

Pythonの if, elif, else, endif に対応しています。条件式には、真偽値として評価される変数や式を使用できます。not, and, or といった論理演算子も利用可能です。

for文

リストや辞書などのシーケンスを反復処理します。

“`jinja

List items:

    {% for item in my_list %}

  • {{ loop.index }}: {{ item }}
  • {% else %}
    {# リストが空の場合に表示される内容 #}

  • No items found.
  • {% endfor %}

Dictionary items:

    {% for key, value in my_dict.items() %}

  • {{ key }}: {{ value }}
  • {% endfor %}

Loop control:

{% for num in numbers %}

{{ num }}

{# 偶数だったらループを終了 #}
{% if num is even %}
{% break %}
{% endif %}
{% endfor %}
“`

Pythonコード:

“`python
from jinja2 import Environment, BaseLoader

env = Environment(loader=BaseLoader())
template = env.from_string(“””

List items:

    {% for item in my_list %}

  • {{ loop.index }}: {{ item }}
  • {% else %}
    {# リストが空の場合に表示される内容 #}

  • No items found.
  • {% endfor %}

Dictionary items:

    {% for key, value in my_dict.items() %}

  • {{ key }}: {{ value }}
  • {% endfor %}

Loop control:

{% for num in numbers %}

{{ num }}

{# 偶数だったらループを終了 #}
{% if num is even %}
{% break %}
{% endif %}
{% endfor %}
“””)

data1 = {
“my_list”: [“apple”, “banana”, “cherry”],
“my_dict”: {“name”: “Alice”, “age”: 30},
“numbers”: [1, 3, 5, 2, 4]
}

data2 = {
“my_list”: [],
“my_dict”: {},
“numbers”: [1, 3, 5, 7, 9]
}

print(“— Data 1 —“)
print(template.render(data1))
print(“\n— Data 2 —“)
print(template.render(data2))
“`

実行結果:

“`html
— Data 1 —

List items:

  • 1: apple
  • 2: banana
  • 3: cherry

Dictionary items:

  • name: Alice
  • age: 30

Loop control:

<p>1</p>

<p>3</p>

<p>5</p>

<p>2</p>

— Data 2 —

List items:

    {# リストが空の場合に表示される内容 #}

  • No items found.

Dictionary items:

Loop control:

<p>1</p>

<p>3</p>

<p>5</p>

<p>7</p>

<p>9</p>

“`

for ... in 構文でシーケンスを反復処理できます。Pythonと同様に、リスト、タプル、セット、辞書(items(), keys(), values() を使う)などがループ可能です。
{% else %} ブロックは、ループ対象のシーケンスが空の場合に実行されます。
ループ内では、loop という特別な変数にアクセスできます。loop.index は1から始まる現在のループ回数、loop.index0 は0から始まる回数、loop.first は最初の要素かどうか、loop.last は最後の要素かどうかなど、便利な属性が提供されています。
Jinja2の loopcontrols 拡張機能を有効にすると、{% break %}{% continue %} も使用できます。

コメント:{# ... #}

{# ... #} デリミタは、テンプレート内にコメントを記述するために使用します。コメントはレンダリングされた出力には含まれません。

“`jinja
{# これはコメントです。出力には表示されません。 #}

This is visible text.

{#
これは複数行のコメントです。
これも出力には表示されません。

}

“`

コメントはテンプレートの意図を説明したり、一時的にコードを無効化したりするのに役立ちます。

Environment(環境):テンプレートのロードと設定

Environment オブジェクトは、Jinja2の心臓部です。テンプレートの読み込み方法(Loader)、自動エスケープの設定、デリミタ、グローバル変数、キャッシュなどを管理します。テンプレートを使用する際には、まず Environment を作成するのが一般的です。

python
from jinja2 import Environment, FileSystemLoader

Environmentオブジェクトの役割

Environment オブジェクトは以下の役割を担います。

  • テンプレートのロード: どこからテンプレートファイルを読み込むかを指定します。
  • 設定: デリミタ、自動エスケープ、空白文字の制御などのオプションを設定します。
  • グローバル変数の登録: テンプレート全体で利用できる変数や関数を登録します。
  • フィルタとテストの登録: カスタムフィルタやカスタムテストを登録します。
  • キャッシュ管理: テンプレートのパース結果をキャッシュし、パフォーマンスを向上させます。

Loaderの種類と使い方

Loaderは、Environmentに対して「テンプレートファイルがどこにあるか」を教える役割をします。いくつかの組み込みLoaderがあります。

FileSystemLoader

ファイルシステム上の指定したディレクトリからテンプレートを読み込みます。最もよく使われるLoaderです。複数のディレクトリを指定することも可能です。

“`python
from jinja2 import Environment, FileSystemLoader

‘templates’ ディレクトリからテンプレートをロード

env = Environment(loader=FileSystemLoader(‘templates’))

複数のディレクトリからロード(優先順位はリストの順番)

env = Environment(loader=FileSystemLoader([‘templates’, ‘other_templates’]))

templates/index.html ファイルをロード

template = env.get_template(‘index.html’)

データを渡してレンダリング

output = template.render(user_name=”Alice”)
print(output)
“`

この例では、カレントディレクトリにある templates という名前のサブディレクトリの中にテンプレートファイルがあることを想定しています。get_template() には、Loaderが設定されたディレクトリからの相対パスを指定します。

PackageLoader

Pythonパッケージ内に含まれるテンプレートを読み込みます。Webフレームワークなどで、アプリケーションの静的ファイルやテンプレートをパッケージ内に含めたい場合に便利です。

“`python
from jinja2 import Environment, PackageLoader

‘my_package’ という名前のパッケージの ‘templates’ サブディレクトリからロード

パッケージのトップレベルを指定し、サブディレクトリ名を指定する

env = Environment(loader=PackageLoader(‘my_package’, ‘templates’))

my_package/templates/page.html をロード

template = env.get_template(‘page.html’)

print(template.render())
“`

このLoaderを使うには、Pythonパッケージの構造になっている必要があります。例えば、以下のような構造です。

my_package/
├── __init__.py
└── templates/
└── page.html

DictLoader

テンプレートを辞書として保持し、そこから読み込みます。テンプレートの数が少ない場合や、ファイルとして保存したくない場合に便利です。

“`python
from jinja2 import Environment, DictLoader

templates = {
‘greeting’: ‘Hello, {{ name }}!’,
‘goodbye’: ‘Goodbye, {{ name }}.’
}

env = Environment(loader=DictLoader(templates))

‘greeting’ という名前のテンプレートをロード

template1 = env.get_template(‘greeting’)
print(template1.render(name=”Alice”))

‘goodbye’ という名前のテンプレートをロード

template2 = env.get_template(‘goodbye’)
print(template2.render(name=”Bob”))
“`

その他のLoader
  • PrefixLoader: 複数のLoaderを組み合わせ、テンプレート名にプレフィックスを付けて区別します。(例: admin/index.html をAdminLoaderで、user/index.html をUserLoaderでロード)
  • ChoiceLoader: 複数のLoaderを組み合わせ、リストの順番にテンプレートを探します。最初に見つかったLoaderでロードします。
  • FunctionLoader: カスタムの関数を使ってテンプレートのソースコードを読み込みます。データベースからテンプレートをロードしたい場合などに使用できます。

複数のLoaderを組み合わせたい場合は、ChoiceLoaderPrefixLoader を使うのが一般的です。

“`python
from jinja2 import Environment, FileSystemLoader, DictLoader, ChoiceLoader

ファイルシステムと辞書、両方からロード可能にする

loader = ChoiceLoader([
FileSystemLoader(‘templates’),
DictLoader({‘my_inline_template’: ‘This is {{ data }} from a dictionary.’})
])

env = Environment(loader=loader)

ファイルシステムからロード

file_template = env.get_template(‘page.html’) # templates/page.html を想定
print(file_template.render())

辞書からロード

dict_template = env.get_template(‘my_inline_template’)
print(dict_template.render(data=”inline template”))
“`

Environmentの主な設定オプション

Environmentを作成する際に、さまざまなオプションを設定できます。

python
env = Environment(
loader=...,
autoescape=True, # デフォルトはTrue
trim_blocks=True, # ブロックタグ前後の改行やスペースを削除
lstrip_blocks=True, # ブロックタグ行の行頭のスペースを削除
variable_start_string='((', # 変数出力デリミタを変更
variable_end_string='))',
line_comment_prefix='##', # 行コメントのプレフィックス
extensions=['jinja2.ext.loopcontrols'] # 拡張機能の有効化
)

  • autoescape: 自動エスケープを有効にするかどうか。HTMLを出力する場合は True を強く推奨します。
  • trim_blocks: {% ... %} 制御タグの直後の最初の改行を自動的に削除します。これにより、制御タグによって不要な空白行が生成されるのを防ぐことができます。
  • lstrip_blocks: {% ... %} 制御タグがある行の行頭にあるタブやスペースを削除します。これも不要な空白行を防ぐのに役立ちます。trim_blockslstrip_blocks はしばしば両方 True に設定されます。
  • variable_start_string, variable_end_string, block_start_string, block_end_string, comment_start_string, comment_end_string, line_statement_prefix, line_comment_prefix: デリミタ文字列を変更できます。テンプレートのタイプ(例: JavaScriptコード生成など)によっては、デフォルトのデリミタが衝突する場合に便利です。
  • extensions: 使用する拡張機能をリストで指定します。例えば、jinja2.ext.loopcontrolsbreakcontinue を有効にします。
  • cache_size: パースしたテンプレートをキャッシュするサイズを指定します。パフォーマンス向上に役立ちます。

これらのオプションを使うことで、特定の用途に合わせてJinja2の挙動をカスタマイズできます。

テンプレートのロードとレンダリング

Environmentが作成されたら、get_template()from_string() メソッドを使ってテンプレートオブジェクトを取得し、render() メソッドでデータを渡してレンダリングします。

“`python
from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader(‘templates’)) # templates ディレクトリを想定

templates/profile.html をロード

template = env.get_template(‘profile.html’)

データを準備

user_data = {
“name”: “Charlie”,
“age”: 25,
“city”: “London”
}

データを渡してレンダリング

output = template.render(user=user_data, title=”User Profile”)
print(output)

render() にはキーワード引数でデータを渡すこともできる

output_kw = template.render(user={“name”: “David”, “age”: 40, “city”: “Tokyo”}, title=”Profile Page”)
print(output_kw)
“`

render() メソッドには、辞書としてデータを渡すことも、キーワード引数としてデータを渡すことも可能です。キーワード引数で渡された値は、テンプレート内で変数名として直接利用できます。辞書で渡す場合は、辞書のキーがテンプレート内の変数名になります。

テンプレートの継承:共通レイアウトの設計

テンプレート継承は、Jinja2の最も強力で便利な機能の一つです。Webサイトのように、複数のページでヘッダー、フッター、サイドバーなどの共通部分を持つ場合に、コードの重複をなくし、メンテナンスを容易にします。

基本的な考え方は、親テンプレート(基盤となるレイアウト)と子テンプレート(親テンプレートの一部を上書きする)を作成することです。

継承の目的と仕組み

  • 目的: サイト全体のデザインや構造を親テンプレートで定義し、各ページのコンテンツは子テンプレートで埋めることで、一貫性のあるデザインを保ちつつ、効率的にページを作成する。
  • 仕組み: 親テンプレートは {% block ... %} タグを使って、子テンプレートが上書きできる領域(ブロック)を定義します。子テンプレートは {% extends 'parent_template.html' %} タグで親テンプレートを指定し、{% block ... %} タグを使って親の同名のブロックの内容を置き換えます。

親テンプレートの作成 ({% block ... %})

まず、基盤となるレイアウトを持つ親テンプレートを作成します。このテンプレートには、共通のHTML構造と、子テンプレートが内容を挿入または上書きするためのブロックを定義します。

templates/base.html:

“`jinja





{% block title %}My Website{% endblock %}

{% block header_title %}Welcome{% endblock %}


{% block content %}
{# このブロックは子テンプレートで上書きされます #}

This is default content.

{% endblock %}

© 2023 My Website. All rights reserved.


“`

この親テンプレートでは、title, header_title, content という名前のブロックを定義しています。各ブロックの中にはデフォルトの内容を記述することもできます。子テンプレートがこれらのブロックを上書きしない場合、デフォルトの内容が表示されます。

子テンプレートの作成 ({% extends ... %}, {% block ... %})

次に、親テンプレートを継承する子テンプレートを作成します。子テンプレートの最初のタグは必ず {% extends ... %} でなければなりません。その後、親テンプレートで定義されているブロックと同じ名前のブロックを定義し、その中にそのページ固有の内容を記述します。

templates/index.html:

“`jinja
{% extends “base.html” %}

{% block title %}Homepage – My Website{% endblock %}

{% block header_title %}Homepage{% endblock %}

{% block content %}

Hello, {{ user_name }}!

This is the content specific to the homepage.

<h3>Latest Articles</h3>
<ul>
    {# articles は Python から渡される変数と仮定 #}
    {% for article in articles %}
        <li><a href="{{ article.url }}">{{ article.title }}</a></li>
    {% endfor %}
</ul>

{% endblock %}
“`

templates/about.html:

“`jinja
{% extends “base.html” %}

{% block title %}About Us – My Website{% endblock %}

{# header_title ブロックは上書きしない -> 親のデフォルト(Welcome)が使われる #}

{% block content %}

About Our Company

Details about the company go here.

{% endblock %}
“`

index.htmlabout.html はどちらも base.html を継承しています。それぞれのファイルで block タグを使って、親テンプレートの同名のブロックの内容を上書きしています。about.html では header_title ブロックを上書きしていないため、親テンプレートのデフォルト値 “Welcome” が使われます。

super() 関数による親ブロックの内容の利用

子テンプレートで親テンプレートのブロックの内容を完全に置き換えるのではなく、親の内容に追加したい場合があります。その場合は、子テンプレートのブロック内で {{ super() }} を使うと、親テンプレートの同名のブロックの内容を取得できます。

templates/base_with_footer.html:

“`jinja
{% extends “base.html” %} {# さらに別の親テンプレートを継承することも可能 #}

{% block content %}
{# 親テンプレートの content ブロックの内容を先に表示 #}
{{ super() }}

<p>This text is added after the parent content.</p>

{% endblock %}

{% block footer %}
{# 親テンプレートに footer ブロックがあればそれも利用できる #}
{{ super() }}

Additional footer information.

{% endblock %}
“`

この例では、base_with_footer.htmlbase.html を継承し、さらに content ブロックで {{ super() }} を使って親の content ブロックの内容を取り込んでいます。

継承のPythonコードでの利用

Pythonコード側では、親テンプレートをロードするのではなく、子テンプレートをロードしてレンダリングします。Jinja2が自動的に親テンプレートを解決して組み合わせます。

“`python
from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader(‘templates’))

index.html をロード (これは base.html を継承している)

index_template = env.get_template(‘index.html’)

articles_data = [
{“title”: “First Post”, “url”: “/articles/1”},
{“title”: “Second Post”, “url”: “/articles/2”},
]

print(“— Rendering index.html —“)
print(index_template.render(user_name=”Bob”, articles=articles_data))

about.html をロード (これも base.html を継承している)

about_template = env.get_template(‘about.html’)

print(“\n— Rendering about.html —“)
print(about_template.render()) # about ページに特別なデータは渡さない
“`

実行結果(一部抜粋):

“`html
— Rendering index.html —





Homepage – My Website

Homepage

Hello, Bob!

This is the content specific to the homepage.

Latest Articles

© 2023 My Website. All rights reserved.


— Rendering about.html —





About Us – My Website

Welcome

{# header_title ブロックは子で上書きされていないので親のデフォルトが使われる #}

About Our Company

Details about the company go here.

© 2023 My Website. All rights reserved.


“`

このようにテンプレート継承を使うことで、共通部分の管理が容易になり、テンプレートファイルの構造化が進みます。

フィルタ:変数出力を加工する

フィルタは、変数出力を加工するために使用します。変数名の後にパイプ記号 | を付けてフィルタ名を記述します。複数のフィルタをチェーンすることも可能です。

jinja
{{ variable | filter_name(argument, another_argument) | another_filter }}

よく使われる組み込みフィルタの詳細

Jinja2には便利な組み込みフィルタが多数用意されています。

テキスト処理
  • upper: 文字列を大文字に変換します。
    {{ "Hello World" | upper }} -> HELLO WORLD
  • lower: 文字列を小文字に変換します。
    {{ "Hello World" | lower }} -> hello world
  • capitalize: 文字列の最初の文字だけを大文字にし、残りを小文字にします。
    {{ "hello world" | capitalize }} -> Hello world
  • title: 各単語の最初の文字を大文字に変換します。
    {{ "hello world" | title }} -> Hello World
  • trim: 文字列の先頭と末尾の空白文字を削除します。
    {{ " Hello World " | trim }} -> Hello World
  • striptags: HTMLタグを取り除きます。
    {{ "<p>Hello <strong>World</strong>!</p>" | striptags }} -> Hello World!
  • truncate(length=255, killwords=False, end='...'): 文字列を指定した長さに切り詰めます。killwords=True だと単語の途中でも切り、False だと単語の区切りで切ります。
    {{ "これは長い文章です" | truncate(length=10) }} -> これは長い文...
    {{ "これは長い文章です" | truncate(length=10, killwords=True) }} -> これは長い...
  • wordwrap(width=79, break_long_words=True, wrapstring=None): 文字列を折り返します。
リスト/シーケンス処理
  • length: シーケンス(文字列、リスト、辞書など)の長さを返します。
    {{ my_list | length }} -> 3 (my_listが3要素の場合)
  • first: シーケンスの最初の要素を返します。
    {{ my_list | first }} -> apple (my_listが['apple', 'banana']の場合)
  • last: シーケンスの最後の要素を返します。
    {{ my_list | last }} -> banana (my_listが['apple', 'banana']の場合)
  • join(separator=''): シーケンスの要素を区切り文字で連結した文字列を返します。
    {{ my_list | join(', ') }} -> apple, banana, cherry
  • sort(reverse=False, attribute=None): シーケンスをソートします。reverse=True で降順、attribute で要素の特定の属性やキーでソートできます。
    {{ numbers | sort }} -> [1, 2, 3, 4]
    {{ users | sort(attribute='name') }} -> name属性でソートされたUserオブジェクトのリスト
  • reverse: シーケンスの要素の順序を反転させます。
    {{ my_list | reverse }} -> ['cherry', 'banana', 'apple']
  • groupby(attribute): シーケンスの要素を指定した属性やキーでグループ化します。これは非常に便利で、ループと組み合わせて使います。
    {% for group, items in users | groupby('city') %} のように使用。group はグループ化に使われた属性の値、items はそのグループに属する要素のリストになります。
数値処理
  • abs: 数値の絶対値を返します。
    {{ -5 | abs }} -> 5
  • round(precision=0, method='common'): 数値を四捨五入します。precision で小数点以下の桁数を指定、methodcommon (標準の四捨五入), ceil (切り上げ), floor (切り捨て) が指定できます。
    {{ 3.14159 | round(2) }} -> 3.14
    {{ 3.14159 | round(2, 'ceil') }} -> 3.15
  • int, float: 値を整数または浮動小数点数に変換します。
    {{ "123" | int }} -> 123
    {{ "3.14" | float }} -> 3.14
  • format(format_string): 文字列の format() メソッドのように値をフォーマットします。(Pythonの str.format() と同じ構文)
    {{ "%0.2f" | format(value) }}
    {{ "Hello {0}!" | format(name) }}
HTMLエスケープ
  • escape または e: HTMLの特殊文字 (<, >, ", ', &) を安全な表現に置き換えます。Jinja2はデフォルトで自動エスケープが有効なため、通常は明示的に指定する必要はありませんが、自動エスケープを無効にしている場合などに使用します。
    {{ user_input | escape }}
  • safe: このフィルタが適用された変数の内容は、自動エスケープの対象外となります。信頼できるソースからのHTMLを出力する場合などに使用します。
    {{ trusted_html | safe }}
その他
  • default(value, boolean=False): 変数が未定義または None の場合に指定した値を返します。boolean=True の場合は、空文字列、空リストなども対象になります(前述)。
    {{ username | default('ゲスト') }}
  • urlencode: URLエンコードします。辞書を渡すとクエリ文字列形式に変換します。
    {{ "検索ワード" | urlencode }} -> %E6%A4%9C%E7%B4%A2%E3%83%AF%E3%83%BC%E3%83%89
    {{ {'q': '検索', 'page': 1} | urlencode }} -> q=%E6%A4%9C%E7%B4%A2&page=1

datetimeformat (例): datetime オブジェクトをフォーマットするフィルタは組み込みではありませんが、多くのWebフレームワークで提供されていたり、カスタムフィルタとして簡単に追加できたりします。

jinja
{# 組み込みではないことが多いが、よく使われる概念 #}
{# assuming 'post.published_date' is a datetime object #}
{{ post.published_date | datetimeformat('%Y-%m-%d %H:%M') }}

複数のフィルタを組み合わせる方法

複数のフィルタを適用するには、パイプ記号 | でつなげて記述します。左から順番に適用されます。

“`jinja
{# HTMLタグを取り除いてから大文字にする #}
{{ “

Hello World!

” | striptags | upper }}
-> HELLO WORLD!

{# リストを連結して、先頭末尾の空白を削除 #}
{{ [” apple “, ” banana “] | join(‘,’) | trim }}
-> apple , banana
“`

カスタムフィルタの作成と登録

独自のフィルタが必要な場合は、Python関数を作成し、Environmentに登録することでカスタムフィルタとして利用できます。

“`python
from jinja2 import Environment, BaseLoader

def reverse_string_filter(s):
return s[::-1]

def make_bold_filter(text):
# 信頼できるソースからの入力として、safeにする例
return f”{text}

env = Environment(loader=BaseLoader())

フィルタを登録

env.filters[‘reverse’] = reverse_string_filter
env.filters[‘bold’] = make_bold_filter

template = env.from_string(“””

Reversed: {{ my_string | reverse }}

Bold: {{ another_string | bold | safe }}

{# safe を忘れずに #}
“””)

data = {
“my_string”: “hello”,
“another_string”: “important”
}

print(template.render(data))
“`

実行結果:

“`html

Reversed: olleh

Bold: important

“`

カスタムフィルタ関数は、フィルタの左側にある変数の値(または直前のフィルタの出力)を最初の引数として受け取ります。フィルタに引数を渡したい場合は、関数定義で引数を追加し、テンプレート側でフィルタ名の後の括弧内に指定します。

“`python
def repeat_filter(text, count):
return text * count

env.filters[‘repeat’] = repeat_filter

template = env.from_string(“{{ ‘-‘ | repeat(10) }}”) # フィルタに引数 10 を渡す
print(template.render())

実行結果: ———-

“`

テスト:条件分岐で変数の状態をチェック

テストは、変数が特定の状態にあるか、特定の特性を持っているかなどをチェックするために使用します。主に if 文やフィルタ内で使われます。テストは is キーワードの後にテスト名を記述します。

jinja
{% if variable is test_name(argument) %}
{# 変数がテストに合格した場合 #}
{% endif %}

よく使われる組み込みテストの詳細

  • defined: 変数が定義されているか(存在するか)どうかをチェックします。
    {% if user_name is defined %}
  • undefined: 変数が定義されていないかどうかをチェックします。
    {% if user_name is undefined %}
  • none: 変数の値が None であるかをチェックします。
    {% if item.price is none %}
  • boolean: 値がブーリアン型であるかをチェックします。
  • callable: 値が呼び出し可能(関数やメソッドなど)であるかをチェックします。
    {% if my_function is callable %}
  • iterable: 値が反復可能(リスト、タプル、辞書、文字列など)であるかをチェックします。
    {% if my_list is iterable %}
  • number: 値が数値型(整数、浮動小数点数など)であるかをチェックします。
  • string: 値が文字列型であるかをチェックします。
  • list: 値がリスト型であるかをチェックします。
  • dict: 値が辞書型であるかをチェックします。
  • mapping: 値がマッピング型(辞書など)であるかをチェックします。
  • sequence: 値がシーケンス型(リスト、タプル、文字列など。ジェネレーターは含まない)であるかをチェックします。
  • odd: 数値が奇数であるかをチェックします。
    {% if loop.index is odd %}
  • even: 数値が偶数であるかをチェックします。
    {% if loop.index is even %}
  • divisibleby(num): 数値が指定した数で割り切れるかをチェックします。
    {% if loop.index is divisibleby(3) %}
  • equalto(value): 値が指定した値と等しいかをチェックします。(== 演算子を使うのとほぼ同じですが、テスト形式でも書けます)
    {% if status is equalto('active') %}
  • in(container): 値が指定したコンテナ(リスト、文字列など)に含まれているかをチェックします。
    {% if user.role is in(['admin', 'moderator']) %}
  • lower: 文字列が全て小文字であるかをチェックします。
  • upper: 文字列が全て大文字であるかをチェックします。
  • empty: 値が空であるかをチェックします。(空文字列 ""、空リスト []、空辞書 {}None など)

“`jinja
{% if items is not empty %}

Items count: {{ items | length }}

{% endif %}

{% if user.email is defined and user.email is not none %}

Email: {{ user.email }}

{% else %}

Email not provided.

{% endif %}

{# テストに引数を渡す例 #}
{% if user.age is divisibleby(10) %}

Age is a multiple of 10.

{% endif %}

{% if username is in([‘admin’, ‘root’]) %}

Welcome, privileged user!

{% endif %}
“`

Pythonコード:

“`python
from jinja2 import Environment, BaseLoader

env = Environment(loader=BaseLoader())
template = env.from_string(“””
{% if items is not empty %}

Items count: {{ items | length }}

{% else %}

No items.

{% endif %}

{% if user.email is defined and user.email is not none %}

Email: {{ user.email }}

{% else %}

Email not provided.

{% endif %}

{% if user.age is divisibleby(10) %}

Age is a multiple of 10.

{% endif %}

{% if username is in([‘admin’, ‘root’]) %}

Welcome, privileged user!

{% endif %}
“””)

data1 = {
“items”: [1, 2, 3],
“user”: {“email”: “[email protected]”, “age”: 30},
“username”: “admin”
}

data2 = {
“items”: [],
“user”: {“email”: None, “age”: 25},
“username”: “guest”
}

data3 = {
# items は定義されていない
“user”: {“email”: “[email protected]”, “age”: 40},
“username”: “root”
# username は定義されている
}

print(“— Data 1 —“)
print(template.render(data1))
print(“\n— Data 2 —“)
print(template.render(data2))
print(“\n— Data 3 —“)
print(template.render(data3))
“`

実行結果(一部抜粋):

“`html
— Data 1 —

<p>Items count: 3</p>


<p>Email: [email protected]</p>


<p>Age is a multiple of 10.</p>


<p>Welcome, privileged user!</p>

— Data 2 —

<p>No items.</p>


<p>Email not provided.</p>

— Data 3 —

<p>No items.</p>


<p>Email: [email protected]</p>


<p>Age is a multiple of 10.</p>


<p>Welcome, privileged user!</p>

“`

カスタムテストの作成と登録

カスタムフィルタと同様に、独自のテストもPython関数として作成し、Environmentに登録できます。

“`python
from jinja2 import Environment, BaseLoader

def is_prime_test(n):
if n < 2:
return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True

env = Environment(loader=BaseLoader())

テストを登録

env.tests[‘prime’] = is_prime_test

template = env.from_string(“””
{% for num in numbers %}
{% if num is prime %}

{{ num }} is a prime number.

{% else %}

{{ num }} is not a prime number.

{% endif %}
{% endfor %}
“””)

data = {“numbers”: [2, 3, 4, 5, 6, 7, 11]}

print(template.render(data))
“`

実行結果(一部抜粋):

“`html

2 is a prime number.

<p>3 is a prime number.</p>

<p>4 is not a prime number.</p>

<p>5 is a prime number.</p>

<p>6 is not a prime number.</p>

<p>7 is a prime number.</p>

<p>11 is a prime number.</p>

“`

カスタムテスト関数は、テストの対象となる変数の値を最初の引数として受け取ります。テストに引数を渡したい場合は、関数定義で引数を追加し、テンプレート側でテスト名の後の括弧内に指定します。

マクロ:再利用可能なテンプレート部品

マクロは、テンプレート内で繰り返し使われるHTMLのスニペットなどを定義し、関数のように呼び出せる機能です。コードの重複を減らし、テンプレートの可読性と保守性を向上させます。

マクロの定義:{% macro macro_name(...) %}

マクロは {% macro ... %}{% endmacro %} タグで定義します。引数を定義することもできます。

“`jinja
{# templates/macros.jinja #}
{% macro render_input(name, label, type=’text’, value=”, size=20) %}


{% endmacro %}

{% macro render_submit_button(text=’送信’) %}

{% endmacro %}

{% macro card(title) %}

{{ title }}
{# マクロ呼び出し時に content をブロックとして渡せる #}
{{ caller() }}

{% endmacro %}
“`

この例では、フォームの <input> タグや <button> タグを生成するマクロを定義しています。render_input マクロは複数の引数を受け取り、デフォルト値も設定しています。render_submit_button は引数無しで定義されています。

card マクロのように、caller() を使うと、マクロ呼び出し時にタグ内に記述されたコンテンツをマクロ内で利用できます。これは少し高度な使い方です。

マクロの呼び出し:{{ macro_name(...) }}

定義したマクロは、{{ macro_name(...) }} という構文で呼び出します。引数は定義順に渡すか、キーワード引数で渡します。

“`jinja
{# templates/form.jinja #}
{# macros.jinja をインポートしてマクロを使えるようにする #}
{% import ‘macros.jinja’ as forms %}

{{ forms.render_input(‘username’, ‘ユーザー名’) }} {# type, value, size はデフォルト値 #}
{{ forms.render_input(‘password’, ‘パスワード’, type=’password’) }} {# type を上書き #}
{{ forms.render_input(name=’email’, label=’メールアドレス’, value=’[email protected]’, size=30) }} {# キーワード引数 #}

{{ forms.render_submit_button() }} {# 引数なしの呼び出し #}
{{ forms.render_submit_button(‘登録’) }} {# 引数を渡す #}

{# caller() を使うマクロの呼び出し例 #}
{% from ‘macros.jinja’ import card %}

{% call card(‘ユーザー情報’) %}

名前: {{ user.name }}

年齢: {{ user.age }}

{% endcall %}
“`

この例では、import タグを使って macros.jinja ファイルをインポートし、forms というエイリアスを付けています。マクロを呼び出す際には forms.マクロ名 のように記述します。caller() を使う card マクロは、call タグを使って呼び出します。call タグの本体に記述された内容が、マクロ内の {{ caller() }} の位置に挿入されます。

引数(必須引数、デフォルト値を持つ引数)

Python関数と同様に、マクロの引数には必須引数とデフォルト値を持つ引数を定義できます。デフォルト値を持つ引数は、デフォルト値を持たない引数の後に記述する必要があります。

“`jinja
{% macro greet(name, greeting=”Hello”) %}

{{ greeting }}, {{ name }}!

{% endmacro %}

{{ greet(‘Alice’) }} {# greeting はデフォルト値 “Hello” #}
{{ greet(‘Bob’, greeting=”Good Morning”) }} {# greeting を指定 #}
“`

可変長引数 (*args, **kwargs)

Python関数と同様に、*args**kwargs を使って可変長の位置引数とキーワード引数を受け取るマクロを定義することも可能です。

“`jinja
{% macro list_items(*items) %}

    {% for item in items %}

  • {{ item }}
  • {% endfor %}

{% endmacro %}

{% macro show_details(**details) %}

{% for key, value in details.items() %}

{{ key | capitalize }}:
{{ value }}

{% endfor %}

{% endmacro %}

{{ list_items(‘Apple’, ‘Banana’, ‘Cherry’) }}
{{ show_details(name=’Alice’, age=30, city=’Tokyo’) }}
“`

マクロのスコープ

マクロは定義されたテンプレート内でのみ直接利用可能です。他のテンプレートから利用するには、import または from ... import ... タグを使ってインポートする必要があります。

インクルードとインポート:テンプレートを組み合わせる

テンプレートを部品化して組み合わせるための機能として、マクロとは別にインクルードとインポートがあります。

{% include '...' %}

include タグは、別のテンプレートの内容を現在のテンプレートに挿入します。HTMLの共通部品(例: ヘッダー、フッター、特定のウィジェットなど)を複数のテンプレートで再利用したい場合に便利です。

“`jinja
{# templates/header.html #}

{# templates/footer.html #}

© 2023 Example Site

{# templates/page.html #}




My Page


{% include ‘header.html’ %}

Page Content

This is the main content of the page.

{% include ‘footer.html’ %}

“`

page.html をレンダリングすると、header.htmlfooter.html の内容がそれぞれの位置に挿入されます。

インクルードにおけるコンテキストの扱い

デフォルトでは、インクルードされるテンプレートは親テンプレートの現在のコンテキスト(利用可能な変数)にアクセスできます。

“`jinja
{# templates/greeting.html #}

Hello, {{ user_name }}!

{# templates/main_page.html #}
{% include ‘greeting.html’ %} {# main_page に渡された user_name を使える #}
“`

もし、インクルードされるテンプレートが親テンプレートのコンテキストにアクセスしないようにしたい場合は、without context を付けます。逆に、明示的に現在のコンテキストを使いたい場合は with context を付けます(デフォルトの挙動ですが、明示することで意図を明確にできます)。

“`jinja
{# templates/no_context_greeting.html #}

Hello, {{ user_name | default(‘Guest’) }} (no context)!

{# templates/main_page.html #}
{% include ‘no_context_greeting.html’ without context %} {# user_name は利用できない #}
“`

また、インクルードする際に、特定の変数のみを渡すことも可能です。

“`jinja
{# templates/only_user_greeting.html #}

Hello, {{ user.name }}! (user context)

{# templates/main_page.html #}
{% include ‘only_user_greeting.html’ with user=user only %} {# user 変数のみを渡す。他の変数はアクセス不可 #}
“`

only キーワードを使うと、指定した変数のみが渡され、親テンプレートの他の変数は利用できなくなります。

条件付きインクルード (ignore missing)

指定したテンプレートファイルが存在しない場合でもエラーにせず、何も挿入しないようにしたい場合は、ignore missing を付けます。

“`jinja
{# templates/page.html #}


{% include ‘optional_sidebar.html’ ignore missing %} {# optional_sidebar.html がなくてもエラーにならない #}

“`

{% import '...' as ... %}

import タグは、主にマクロが定義されたテンプレートを読み込み、そのマクロを現在のテンプレートから利用できるようにします。インクルードと異なり、インポートされたテンプレートの内容が直接挿入されるわけではありません。

“`jinja
{# templates/forms.jinja (マクロ定義ファイル) #}
{% macro input(name) %}

{% endmacro %}

{# templates/page.html #}
{% import ‘forms.jinja’ as form_macros %}

{{ form_macros.input(‘username’) }}
{{ form_macros.input(‘password’) }}

“`

import タグで指定したテンプレートは、指定したエイリアス(例: form_macros)を通じてアクセスできるようになります。インポートされたテンプレート内で定義されたマクロや変数にアクセスするには、エイリアス.名前 の形式で記述します。

{% from '...' import ... %}

import タグの別の形式として、特定の変数やマクロだけを直接インポートすることもできます。この場合、エイリアスを介さずに直接名前で呼び出せます。

“`jinja
{# templates/forms.jinja (マクロ定義ファイル) #}
{% macro input(name) %}

{% endmacro %}

{% macro submit_button(text=’Submit’) %}

{% endmacro %}

{# templates/page.html #}
{% from ‘forms.jinja’ import input, submit_button as btn %} {# input と submit_button をインポート、submit_button は btn という名前で利用 #}

{{ input(‘username’) }}
{{ input(‘password’) }}
{{ btn(‘Send’) }} {# submit_button は btn として呼び出す #}

“`

複数のテンプレートから多くのマクロをインポートする場合、名前の衝突に注意が必要です。衝突を避けるためや、どのマクロがどこから来たかを分かりやすくするために、import ... as ... 形式でエイリアスを付けるのが推奨されることが多いです。

グローバル変数と関数:テンプレート全体で利用可能にする

Environmentにグローバル変数やグローバル関数を登録することで、どのテンプレートからでも特別なインポートなしにそれらを利用できるようになります。サイト名やアプリケーションのバージョン情報など、テンプレート全体で共通して使われる情報を登録するのに便利です。

“`python
from jinja2 import Environment, BaseLoader

def current_year():
from datetime import datetime
return datetime.now().year

env = Environment(loader=BaseLoader())

グローバル変数を登録

env.globals[‘site_name’] = “My Awesome Site”
env.globals[‘version’] = “1.0”

グローバル関数を登録

env.globals[‘get_year’] = current_year

template = env.from_string(“””

© {{ get_year() }} {{ site_name }}. Version: {{ version }}

Welcome to {{ site_name }}!

“””)

print(template.render())
“`

実行結果:

“`html

© 2023 My Awesome Site. Version: 1.0

Welcome to My Awesome Site!

“`

env.globals は辞書のようなオブジェクトで、キー名がテンプレート内で利用できる名前になります。値にはPythonの変数や関数を指定できます。登録された関数は、テンプレート内で {{ function_name(...) }} のように呼び出すことができます。

Webフレームワーク(Flaskなど)では、これらのグローバル変数が自動的に設定されている場合もあります(例: Flaskの g オブジェクトやURL生成関数 url_for など)。

ホワイトスペース制御:生成されるHTMLの整形

デフォルトでは、Jinja2はテンプレート内のすべての空白文字(スペース、タブ、改行)をそのまま出力に含めます。これは正確なテキスト生成には便利ですが、特にHTMLテンプレートの場合、制御タグ {% ... %} の周りの改行などが不要な空白行として出力されてしまうことがあります。

“`jinja
{% if users %}

    {% for user in users %}

  • {{ user.name }}
  • {% endfor %}

{% endif %}
“`

もし users リストに要素がある場合、上記は以下のような出力になる可能性があります(trim_blockslstrip_blocks の設定によりますが、デフォルトでは制御タグの改行が残ります)。

“`html

  • Alice
  • Bob

“`

不要な空白行を削除するために、いくつかの方法があります。

ブロックタグ内の制御 ({%- ... -%}, {{- ... -}}, {#- ... -#})

デリミタの開始タグまたは終了タグの内側にハイフン (-) を付けると、そのタグに隣接する空白文字を削除できます。

  • {%- ... %}: 開始タグの前の空白文字を削除
  • {% ... -%}: 終了タグの後の空白文字を削除
  • {{- ... }}: 開始タグの前の空白文字を削除
  • {{ ... -}}: 終了タグの後の空白文字を削除
  • {#- ... #}: 開始タグの前の空白文字を削除
  • {# ... -#}: 終了タグの後の空白文字を削除

上記の例に適用すると:

“`jinja
{%- if users -%}

    {%- for user in users -%}

  • {{ user.name }}
  • {%- endfor -%}

{%- endif -%}
“`

このテンプレートは、以下のような整形されたHTMLを出力します(users に要素がある場合)。

“`html

  • Alice
  • Bob

“`

これはタグごとに細かく空白を制御したい場合に便利です。

Statementレベルの制御 (lstrip_blocks, trim_blocks)

Environmentの設定で、lstrip_blockstrim_blocksTrue にすると、ブロックタグ行全体の空白文字の扱いや、ブロックタグ直後の改行を自動的に削除できます。(詳細は Environment の設定オプションのセクションを参照)。

python
env = Environment(
loader=...,
lstrip_blocks=True,
trim_blocks=True
)

これらのオプションを有効にすると、テンプレート内の多くの制御タグの周りの不要な空白が自動的に削除されるため、テンプレートを記述する際に - を毎回付ける手間が省けます。ただし、意図的に空白を保持したい場合は注意が必要です。

ラインステートメント (#)

Environmentの line_statement_prefix オプションを設定すると、特定の文字で行が始まる場合に、その行を制御構造として扱うことができます。これは、設定ファイルなどのテンプレートで、波括弧デリミタを使いたくない場合に便利です。

“`python
env = Environment(
loader=BaseLoader(),
line_statement_prefix=’#’ # 行頭が # の場合を制御構造とみなす
)

template = env.from_string(“””

for item in items

{{ item }}

endfor

“””)

print(template.render(items=[1, 2, 3]))
“`

実行結果:

1
2
3

同様に line_comment_prefix を設定すると、行頭がその文字で始まる行をコメントとして扱えます(詳細は Environment の設定オプションのセクションを参照)。

これらの空白制御機能を使いこなすことで、生成される出力の整形を細かく調整できます。通常は lstrip_blocks=True, trim_blocks=True をEnvironmentで設定し、特定の箇所でさらに調整が必要な場合に - を使うのが効率的です。

高度な機能とカスタマイズ

Jinja2はそのままでも強力ですが、さらに高度なカスタマイズや機能拡張が可能です。

カスタムローダーの作成

標準のLoaderでは対応できない方法でテンプレートを読み込みたい場合(例: データベース、リモートストレージなど)は、独自のLoaderクラスを作成できます。カスタムLoaderは jinja2.BaseLoader またはそのサブクラスを継承し、少なくとも get_source(environment, template_name) メソッドを実装する必要があります。

“`python
from jinja2 import Environment, BaseLoader, TemplateNotFound

class DatabaseLoader(BaseLoader):
def init(self, connection):
self.connection = connection

def get_source(self, environment, template_name):
    # データベースからテンプレートのソースコードを取得するロジック
    cursor = self.connection.cursor()
    cursor.execute("SELECT source, filename, mtime FROM templates WHERE name = ?", (template_name,))
    row = cursor.fetchone()

    if row is None:
        raise TemplateNotFound(template_name)

    source, filename, mtime = row

    # テンプレートが変更されたかどうかをチェックする関数
    # キャッシュが有効な場合に使用される
    def uptodate():
        # データベースの更新時刻と現在の mtime を比較するなどのロジック
        # 簡単のため常に True を返す例
        return True

    return source, filename, uptodate

データベース接続オブジェクトを仮定

db_connection = … # ここに実際のデータベース接続オブジェクトを渡す

env = Environment(loader=DatabaseLoader(db_connection))

‘mytemplate_from_db’ という名前のテンプレートをデータベースからロードしてレンダリング

template = env.get_template(‘mytemplate_from_db’)
print(template.render(…))
“`

get_source メソッドは、テンプレートのソースコード、ファイル名(デバッグやキャッシュに使われる)、そしてテンプレートが最後に更新されたかをチェックする関数をタプルで返す必要があります。

拡張機能 (Extensions) の利用

Jinja2は拡張機能を通じて機能を追加できます。いくつかの組み込み拡張機能があり、また独自の拡張機能を作成することも可能です。拡張機能はEnvironment作成時に extensions リストで指定して有効化します。

組み込み拡張機能の例
  • jinja2.ext.i18n: 国際化(i18n)と地域化(l10n)をサポートするためのタグ({% trans %}, {% endtrans %})とフィルタを提供します。
  • jinja2.ext.do: ステートメントとして式を実行するための {% do ... %} タグを提供します。例えば、リストに要素を追加するなどの副作用をテンプレート内で行いたい場合に便利です(ただし、テンプレート内での複雑なロジックは避けるべきです)。
    {% do my_list.append(new_item) %}
  • jinja2.ext.loopcontrols: {% break %}{% continue %} タグを for ループ内で使用できるようにします。

python
env = Environment(
loader=...,
extensions=['jinja2.ext.do', 'jinja2.ext.loopcontrols']
)

バイトコードキャッシュ

Jinja2はテンプレートをパースしてバイトコードにコンパイルし、それをキャッシュすることでレンダリング速度を向上させます。Environmentの cache_size オプションでキャッシュするテンプレートの数を指定できます。デフォルトは400です。0に設定するとキャッシュが無効になります。

スレッドセーフ性

Jinja2の Environment および Template オブジェクトはスレッドセーフです。複数のスレッドから同じEnvironmentオブジェクトを使ってテンプレートをロードしたり、同じTemplateオブジェクトをレンダリングしたりできます。ただし、カスタムLoaderやカスタム拡張機能を作成する場合は、それらがスレッドセーフであることを確認する必要があります。

デバッグとエラーハンドリング

テンプレートに構文エラーがあったり、テンプレート内で例外が発生したりした場合、Jinja2は詳細なエラー情報を含む例外を発生させます。

テンプレートエラーの種類

  • jinja2.exceptions.TemplateSyntaxError: テンプレートの構文が間違っている場合に発生します。
  • jinja2.exceptions.UndefinedError: 存在しない変数や属性にアクセスした場合に発生します(自動エスケープが無効な場合など、設定による)。
  • Pythonの他の例外: テンプレート内で呼び出されたPython関数やフィルタ内で例外が発生した場合など。

これらの例外には、エラーが発生したテンプレートファイル名、行番号、詳細なメッセージが含まれています。これにより、問題の箇所を特定しやすくなります。

“`python
from jinja2 import Environment, BaseLoader, TemplateSyntaxError

env = Environment(loader=BaseLoader())

構文エラーがあるテンプレート

bad_template_string = “””

    {% for item in items %}

  • {{ item.name }
  • {# ここに閉じ波括弧が無い #}
    {% endfor %}

“””

try:
template = env.from_string(bad_template_string)
print(template.render(items=[{“name”: “A”}]))
except TemplateSyntaxError as e:
print(f”Template Syntax Error: {e.message}”)
print(f”Line: {e.lineno}, Name: {e.name}”)
except Exception as e:
print(f”An unexpected error occurred: {e}”)
“`

実行結果:

Template Syntax Error: unexpected '}'
Line: 4, Name: <template>

デバッグテクニック

  • 例外情報: 発生した例外のトレースバックを確認するのが第一歩です。Jinja2のエラーメッセージは比較的詳細です。
  • pprint フィルタ: 変数の内容を整形して表示したい場合に、pprint フィルタ(jinja2.ext.debug 拡張機能に含まれる)や、Pythonの pprint モジュールを呼び出すカスタム関数を利用できます。
    jinja
    {# デバッグ拡張機能を有効にしてから #}
    <pre>{{ my_complex_variable | pprint }}</pre>
  • カスタム関数: デバッグ目的で、テンプレート内でPythonのデバッグツール(例: pdb.set_trace())を呼び出すカスタム関数をEnvironmentに登録することもできます。
    “`python
    import pdb

    def debug_trace():
    pdb.set_trace()
    return ” # テンプレートに出力しないように空文字列を返す

    env.globals[‘debug’] = debug_trace

    テンプレート内でブレークポイントを置きたい場所に

    {{ debug() }}

    “`
    ただし、これは開発環境でのみ使用し、本番環境には含めないように十分注意が必要です。

実践的な利用例

Jinja2はWeb開発以外にも様々な用途で活用できます。

簡単なWebアプリケーションでの利用例(Flaskなどと連携)

FlaskなどのWebフレームワークでは、Jinja2は標準で組み込まれており、簡単に利用できます。通常、テンプレートは templates フォルダに配置し、Flaskの render_template 関数を使ってレンダリングします。

“`python

app.py (Flaskの例)

from flask import Flask, render_template

app = Flask(name)

@app.route(‘/’)
def index():
user = {“name”: “Alice”, “is_admin”: True}
articles = [{“title”: “News 1”, “url”: “/news/1”}, {“title”: “News 2”, “url”: “/news/2”}]
return render_template(‘index.html’, user=user, articles=articles) # templates/index.html をレンダリング

@app.route(‘/about’)
def about():
return render_template(‘about.html’) # templates/about.html をレンダリング

if name == ‘main‘:
app.run(debug=True)
“`

jinja
{# templates/index.html (base.html を継承) #}
{% extends "base.html" %}
{% block title %}Homepage{% endblock %}
{% block content %}
<h1>Welcome, {{ user.name }}</h1>
{% if user.is_admin %}
<p>Admin panel link here.</p>
{% endif %}
<h2>Latest Articles</h2>
<ul>
{% for article in articles %}
<li><a href="{{ article.url }}">{{ article.title }}</a></li>
{% endfor %}
</ul>
{% endblock %}

この例のように、フレームワークがJinja2のEnvironmentやLoaderの設定を管理してくれるため、開発者はテンプレートの記述とPythonコードからのデータ受け渡しに集中できます。

設定ファイル(YAML/INIなど)の自動生成

Jinja2はHTMLだけでなく、あらゆるテキスト形式のファイルを生成するのに使えます。アプリケーションの設定ファイル、サーバーの設定、デプロイスクリプトなどをテンプレート化し、動的な値(ホスト名、ポート番号、APIキーなど)をPythonコードから埋め込むことができます。

“`python

generate_config.py

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader(‘.’)) # テンプレートはカレントディレクトリに配置

template = env.get_template(‘config.yaml.jinja’)

config_data = {
“app_name”: “MyApp”,
“database”: {
“host”: “localhost”,
“port”: 5432,
“user”: “admin”,
“password”: “secure_password”
},
“api_keys”: [
“key123”,
“key456”
],
“debug_mode”: True
}

output = template.render(config_data)

with open(‘config.yaml’, ‘w’) as f:
f.write(output)

print(“config.yaml generated successfully.”)
“`

“`yaml
{# config.yaml.jinja #}

Application Configuration

app:
name: {{ app_name }}
debug: {{ debug_mode | lower }} # ブーリアンを文字列に変換

database:
host: {{ database.host }}
port: {{ database.port }}
user: {{ database.user }}
password: {{ database.password }}

api_keys:
{% for key in api_keys %}
– {{ key }}
{% endfor %}
“`

このスクリプトを実行すると、config.yaml ファイルが生成され、テンプレートに指定されたデータが埋め込まれます。

静的サイトジェネレーター

Jinja2を使って、Markdownファイルやデータファイルから静的なHTMLサイトを生成するツールを作成することも可能です。各ページのコンテンツをデータとして持ち、共通のレイアウトテンプレートと組み合わせてHTMLファイルを生成します。

まとめ:Jinja2を使いこなすために

Jinja2はPythonで動的なテキストコンテンツを生成するための非常に強力で柔軟なツールです。本記事では、Jinja2の基本的な構文から、テンプレート継承、フィルタ、テスト、マクロ、インクルード、インポート、グローバル変数、空白制御、カスタム機能、デバッグ、そして実践的な利用例までを網羅的に解説しました。

Jinja2を使いこなす上でのポイントは以下の通りです。

  • デザインとロジックの分離: テンプレートには表示に関するロジック(条件分岐や繰り返し)のみを記述し、複雑なデータ処理やビジネスロジックはPythonコード側で行うように心がけましょう。
  • テンプレート継承の活用: 共通するレイアウトを持つテンプレートは積極的に継承を活用し、メンテナンス性を高めましょう。
  • フィルタとテストの理解: 組み込みのフィルタとテストを効果的に使い、テンプレート内のデータ加工や条件チェックを簡潔に記述しましょう。必要に応じてカスタムフィルタやテストも活用します。
  • マクロの活用: テンプレート内で繰り返し出現する要素はマクロとして定義し、再利用性を高めましょう。
  • 適切なLoaderの選択とEnvironmentの設定: アプリケーションの構造に合わせて適切なLoaderを選択し、自動エスケープや空白制御などのEnvironment設定を適切に行いましょう。

Jinja2はPythonエコシステムで広く使われており、FlaskのようなWebフレームワークだけでなく、Ansible(自動化ツール)やMkDocs(ドキュメントジェネレーター)など、様々なプロジェクトで採用されています。その習得は、Pythonでの開発においてきっと役立つはずです。

本記事が、あなたがJinja2を理解し、使い始めるための一助となれば幸いです。さらに詳しい情報や最新の情報については、Jinja2公式ドキュメントを参照してください。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール