こんにちは、かつコーチです。
前回はビュー(View)の書き方を解説しました。
ビューで用意したデータを、実際に画面のHTMLへ表示するのがテンプレートの役割です。
今回はDjango 5系を前提に、テンプレートの基本構文とよく使うテンプレートタグをまとめて解説します。
テンプレートとは?
HTMLに「Pythonの値」を差し込む仕組み
テンプレートとは、HTMLの中にPythonの値やちょっとしたロジックを埋め込めるファイルのことです。
拡張子は.htmlのままですが、中身にはDjango独自の記法が混ざります。
たとえば、ビューから渡されたnameという変数を画面に表示したい場合、こう書きます。
<p>こんにちは、{{ name }}さん!</p>
{{ }}で囲んだ部分が変数として展開され、実際の値に置き換わります。
テンプレートファイルの置き場所
Djangoはデフォルトで、各アプリのtemplates/フォルダの中を探しに行きます。
アプリ名がblogなら、次のような構成が一般的です。
blog/
├── templates/
│ └── blog/
│ └── index.html
├── views.py
わざわざblog/index.htmlのようにアプリ名のフォルダを一段挟むのがポイントです。
これを名前空間化と呼びます。
複数のアプリに同じindex.htmlという名前のファイルがあっても、テンプレート名をblog/index.htmlのように指定すれば、Djangoがどちらのファイルか正しく区別できるようになります。
筆者は最初この一段深いフォルダ構成を省略してしまい、別アプリの同名テンプレートが誤って読み込まれるという事故に遭いました。
TemplateDoesNotExistとは逆に、こちらはエラーにならず静かに違うHTMLが表示されるので、実は結構気づきにくいトラブルです。
テンプレートの基本構文
変数の表示:{{ }}
先ほど紹介した{{ 変数名 }}が最も基本的な構文です。
ビュー側でrender()にコンテキスト(辞書データ)を渡すことで、テンプレートから参照できるようになります。
# views.py
from django.shortcuts import render
def index(request):
context = {"name": "かつコーチ"}
return render(request, "blog/index.html", context)
タグの実行:{% %}
ifやforのような処理を書くときは、{% %}を使います。
{% if user_count > 0 %}
<p>{{ user_count }}人が登録しています。</p>
{% else %}
<p>まだ誰も登録していません。</p>
{% endif %}
{{ }}が「値を表示する」ためのもの、{% %}が「処理を実行する」ためのもの、と役割で覚えると混同しにくいです。
よく使うテンプレートタグ・フィルタ
{% for %}で一覧表示する
一覧ページを作るときによく使うのがforタグです。
<ul>
{% for post in posts %}
<li>{{ post.title }}</li>
{% empty %}
<li>投稿がありません</li>
{% endfor %}
</ul>
{% empty %}は、postsが空のリストだった場合にだけ表示される特別なブロックです。
「データが0件のときの表示」を用意し忘れると、本番で空白の画面が出てユーザーを不安にさせてしまうので、忘れずに書く習慣をつけましょう。
{% url %}でリンクをハードコーディングしない
リンク先を<a href="/blog/1/">のように直接書いてしまうと、後でURLの構造を変えたときに全ページを書き換える羽目になります。
{% url %}タグを使えば、urls.pyで設定したnameからURLを自動生成できます。
<a href="{% url 'blog:detail' post.id %}">{{ post.title }}</a>
フィルタで表示形式を整える
値を加工して表示したいときは、パイプ|でフィルタをつなげます。
<p>{{ post.created_at|date:"Y年n月j日" }}</p>
<p>{{ post.content|truncatewords:30 }}</p>
<p>{{ post.title|default:"タイトル未設定" }}</p>
dateで日付の表示形式を整えたり、truncatewordsで本文を途中まで省略表示したりと、実務でも頻出のフィルタです。
つまずきやすいポイント
変数名のスペルミスに気づきにくい
❌ Before:変数名を間違えているのにエラーが出ない
<p>こんにちは、{{ nmae }}さん!</p>
Djangoのテンプレートは、存在しない変数を参照してもエラーを出さず、単に空文字として扱います。
{{ nmae }}のようにスペルミスをしても、画面が真っ白になるだけでコンソールにも何も出ません。
筆者はこれで30分ほど「なぜ名前が表示されないんだ」と原因を探し回った経験があります。
✅ After:settings.pyのTEMPLATES設定でDEBUG=Trueのまま開発し、目視で丁寧に確認する
<p>こんにちは、{{ name }}さん!</p>
対策として決定的な仕組みはありませんが、開発中はコンテキストに渡す変数名とテンプレート側の変数名を、コピー&ペーストで揃える癖をつけると事故を減らせます。
テンプレートの再利用:継承とinclude
{% extends %}で共通レイアウトを使い回す
ヘッダーやフッターを毎回コピーするのは非効率です。
base.htmlという共通テンプレートを用意し、各ページはそれを継承する形にします。
<!-- base.html -->
<html>
<body>
<header>共通ヘッダー</header>
{% block content %}{% endblock %}
<footer>共通フッター</footer>
</body>
</html>
<!-- index.html -->
{% extends "blog/base.html" %}
{% block content %}
<p>ここがページごとの中身です。</p>
{% endblock %}
{% block %}で「差し替え可能な範囲」を宣言しておき、子テンプレート側で中身を上書きするイメージです。
{% include %}で部品化する
ナビゲーションメニューのような小さな部品は、includeで切り出すと管理しやすくなります。
{% include "blog/_navbar.html" %}
継承は「ページ全体の骨組みを共有する」場合、includeは「一部の小さなパーツを再利用する」場合、と使い分けると設計が整理しやすいです。
まとめ
この記事のポイント
- テンプレートは
{{ }}で変数表示、{% %}でタグ処理を行う - テンプレートファイルはアプリ名で名前空間を分けて配置する
{% url %}とフィルタを活用すると、保守しやすいテンプレートになる- 存在しない変数は空文字扱いになり、エラーが出ない点に注意する
{% extends %}と{% include %}で共通部分を使い回す
次に読むべき記事
- クラスベースビュー(CBV)の基本と使いどころ
- urls.pyでルーティングを設定する基本
- 関数ベースビュー(FBV)の書き方
タグ: Django, 初心者向け, フレームワーク基礎