Djangoテンプレート応用ガイド
継承・タグ・フィルターを使いこなす
Djangoテンプレートの継承、テンプレートタグ、フィルター、カスタムタグの作成まで、実践的なテンプレート技術を解説します。
こんな人向けの記事です
- Djangoテンプレートの継承を理解したい
- テンプレートタグやフィルターを使いこなしたい
- カスタムテンプレートタグを作りたい
Step 1テンプレートの継承(extends / block)
Djangoテンプレートの最大の特徴は継承です。共通レイアウトを「ベーステンプレート」に定義し、各ページで必要な部分だけを上書きできます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{% block title %}My Site{% endblock %}</title>
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<nav>共通ナビゲーション</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2026 My Site</p>
</footer>
{% block extra_js %}{% endblock %}
</body>
</html>
{% extends "base.html" %}
{% block title %}記事一覧 | My Site{% endblock %}
{% block content %}
<h1>記事一覧</h1>
{% for article in articles %}
<div class="article-card">
<h2>{{ article.title }}</h2>
<p>{{ article.excerpt }}</p>
</div>
{% endfor %}
{% endblock %}
| タグ | 役割 | 使用場所 |
|---|---|---|
{% block 名前 %} | 上書き可能な領域を定義 | ベーステンプレート |
{% extends "base.html" %} | ベーステンプレートを継承 | 子テンプレート(先頭行) |
{{ block.super }} | 親のblock内容を保持しつつ追加 | 子テンプレートのblock内 |
多段継承も可能:base.html → section_base.html → page.html のように3段階以上の継承もできます。サイト全体の共通部分、セクション共通部分、個別ページと分けると管理しやすくなります。
注意:{% extends %} は必ずテンプレートの先頭行に書く必要があります。前に空白行があっても動作しますが、他のHTMLやタグを書くとエラーになります。
Step 2テンプレートタグ(for, if, include等)
テンプレートタグは {% %} で囲んで使います。ロジックの制御やテンプレートの部品化に不可欠です。
forループ
{% for article in articles %}
<div class="article">
<span class="number">{{ forloop.counter }}.</span>
<h3>{{ article.title }}</h3>
{% if forloop.last %}
<hr>
{% endif %}
</div>
{% empty %}
<p>記事がありません。</p>
{% endfor %}
| forloop変数 | 説明 |
|---|---|
forloop.counter | 1から始まるカウンター |
forloop.counter0 | 0から始まるカウンター |
forloop.first | 最初のループならTrue |
forloop.last | 最後のループならTrue |
forloop.revcounter | 末尾からのカウンター(1始まり) |
forloop.parentloop | 入れ子ループの親ループ情報 |
if / elif / else
{% if user.is_authenticated %}
<p>ようこそ、{{ user.username }}さん</p>
{% elif user.is_anonymous %}
<p>ゲストユーザーです</p>
{% else %}
<p>不明なユーザーです</p>
{% endif %}
{# 複合条件 #}
{% if article.is_published and article.category == "tech" %}
<span class="badge">公開中・技術記事</span>
{% endif %}
{# in 演算子 #}
{% if "python" in article.tags.all %}
<span>Python関連</span>
{% endif %}
include(部品化)
{# 別テンプレートを埋め込む #}
{% include "components/pagination.html" %}
{# 変数を渡して埋め込む #}
{% include "components/card.html" with title=article.title image=article.thumbnail %}
{# 親の変数を渡さない(スコープを限定) #}
{% include "components/sidebar.html" with items=menu_items only %}
その他の便利なタグ
| タグ | 用途 | 例 |
|---|---|---|
{% url %} | URLの逆引き | {% url 'article_detail' slug=article.slug %} |
{% csrf_token %} | CSRF対策トークン | フォーム内に記述 |
{% comment %} | 複数行コメント | {% comment %}...{% endcomment %} |
{% with %} | 変数のエイリアス | {% with total=items|length %}...{% endwith %} |
{% now %} | 現在日時の表示 | {% now "Y年m月d日" %} |
{% cycle %} | 値を順番に切替 | {% cycle "odd" "even" %} |
Step 3フィルター(date, truncatechars, default等)
フィルターは {{ 変数|フィルター }} の形式で変数の表示を加工します。パイプ(|)でチェーンすることも可能です。
よく使うフィルター一覧
| フィルター | 説明 | 使用例 | 出力例 |
|---|---|---|---|
date | 日時フォーマット | {{ article.published_at|date:"Y年m月d日" }} | 2026年02月18日 |
truncatechars | 文字数で切り詰め | {{ article.content|truncatechars:50 }} | この記事では... |
truncatewords | 単語数で切り詰め | {{ text|truncatewords:10 }} | 最初の10単語... |
default | 値がFalsyなら代替表示 | {{ user.name|default:"匿名" }} | 匿名 |
default_if_none | Noneのときだけ代替 | {{ value|default_if_none:"未設定" }} | 未設定 |
length | リストや文字列の長さ | {{ items|length }} | 5 |
linebreaksbr | 改行をbrタグに変換 | {{ text|linebreaksbr }} | 行1<br>行2 |
safe | HTMLエスケープを無効化 | {{ html_content|safe }} | HTMLがそのまま表示 |
slugify | スラッグ形式に変換 | {{ "Hello World"|slugify }} | hello-world |
join | リストを結合 | {{ tags|join:", " }} | Django, Python, Web |
add | 値を加算 | {{ page|add:1 }} | 2 |
yesno | True/False/Noneを変換 | {{ is_active|yesno:"有効,無効,不明" }} | 有効 |
フィルターのチェーン
{# 複数のフィルターを連結 #}
{{ article.content|striptags|truncatechars:100 }}
{# HTMLタグを除去してから文字数制限 #}
{{ article.title|lower|slugify }}
{# 日付フォーマット + デフォルト値 #}
{{ article.published_at|date:"Y/m/d"|default:"未公開" }}
safeフィルターに注意:{{ value|safe }} はHTMLエスケープを無効にします。ユーザー入力をsafeで表示するとXSS攻撃のリスクがあります。信頼できるデータにのみ使用してください。
Step 4カスタムテンプレートタグ・フィルターの作成
組み込みのタグやフィルターで足りない場合、自分で作成できます。アプリ内に templatetags/ ディレクトリを作り、Pythonファイルを配置します。
ディレクトリ構成
myapp/
templatetags/
__init__.py # 空ファイル(必須)
custom_filters.py # カスタムフィルター定義
models.py
views.py
...
カスタムフィルターの作成
from django import template
from django.utils.timesince import timesince
register = template.Library()
@register.filter(name='currency')
def currency(value):
"""数値を日本円表記にする"""
try:
return f"{int(value):,}円"
except (ValueError, TypeError):
return value
@register.filter(name='time_ago')
def time_ago(value):
"""日時を『〇〇前』の形式で表示"""
return f"{timesince(value)}前"
@register.filter(name='mask_email')
def mask_email(value):
"""メールアドレスの一部をマスク"""
if '@' not in str(value):
return value
local, domain = str(value).split('@')
masked = local[:2] + '***'
return f"{masked}@{domain}"
{% load custom_filters %}
<p>価格: {{ product.price|currency }}</p>
{# 出力: 価格: 1,500円 #}
<p>投稿: {{ article.created_at|time_ago }}</p>
{# 出力: 投稿: 3日前 #}
<p>連絡先: {{ user.email|mask_email }}</p>
{# 出力: 連絡先: ta***@example.com #}
カスタムテンプレートタグの作成
from django import template
from myapp.models import Article
register = template.Library()
@register.simple_tag
def recent_articles(count=5):
"""最新記事を取得するシンプルタグ"""
return Article.objects.filter(
is_published=True
).order_by('-published_at')[:count]
@register.inclusion_tag('components/tag_list.html')
def show_tags(article):
"""記事のタグ一覧を表示するインクルージョンタグ"""
return {'tags': article.tags.all()}
@register.simple_tag(takes_context=True)
def current_year(context):
"""現在の年を返す(コンテキスト対応)"""
from datetime import datetime
return datetime.now().year
{% load custom_tags %}
{# simple_tag: 結果を変数に格納 #}
{% recent_articles 3 as latest %}
{% for article in latest %}
<a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
{% endfor %}
{# inclusion_tag: テンプレートごと描画 #}
{% show_tags article %}
{# コンテキスト対応タグ #}
<footer>© {% current_year %} My Site</footer>
| 種類 | 用途 | デコレータ |
|---|---|---|
| カスタムフィルター | 変数の表示加工 | @register.filter |
| シンプルタグ | 値を返す処理 | @register.simple_tag |
| インクルージョンタグ | テンプレート付きの部品 | @register.inclusion_tag |
__init__.py を忘れずに:templatetags/ ディレクトリに __init__.py がないとDjangoがモジュールとして認識できず、{% load %} でエラーになります。
Step 5コンテキストプロセッサ
コンテキストプロセッサは、すべてのテンプレートで共通して使える変数を自動的に追加する仕組みです。サイト名やグローバルな設定値を毎回ビューで渡す必要がなくなります。
カスタムコンテキストプロセッサの作成
from django.conf import settings
def site_info(request):
"""サイト共通情報をテンプレートに渡す"""
return {
'site_name': 'My Django Site',
'site_version': '1.0.0',
'contact_email': 'info@example.com',
}
def user_permissions(request):
"""ユーザー権限情報をテンプレートに渡す"""
if request.user.is_authenticated:
return {
'is_admin': request.user.is_staff,
'user_groups': list(
request.user.groups.values_list('name', flat=True)
),
}
return {
'is_admin': False,
'user_groups': [],
}
settings.pyへの登録
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
# Django組み込み
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
# カスタム
'myapp.context_processors.site_info',
'myapp.context_processors.user_permissions',
],
},
},
]
{# ビューで渡さなくても自動的に使える #}
<title>{{ site_name }}</title>
{% if is_admin %}
<a href="/admin/">管理画面</a>
{% endif %}
<p>所属グループ: {{ user_groups|join:", " }}</p>
Django組み込みのコンテキストプロセッサ
| プロセッサ | 提供する変数 |
|---|---|
debug | debug(DEBUG設定値), sql_queries |
request | request(HttpRequestオブジェクト) |
auth | user, perms(認証情報) |
messages | messages(フラッシュメッセージ) |
media | MEDIA_URL |
static | STATIC_URL |
i18n | LANGUAGES, LANGUAGE_CODE 等 |
パフォーマンスに注意:コンテキストプロセッサはすべてのリクエストで実行されます。重いDBクエリを含めると全ページの表示速度に影響します。キャッシュの活用や、必要なページだけで使うならビューで渡す方が適切です。
Step 6静的ファイルの読み込み(static)
CSS、JavaScript、画像などの静的ファイルは {% static %} タグで読み込みます。パスをハードコードするとデプロイ時に問題が起きるため、必ずこのタグを使いましょう。
settings.pyの設定
import os
# 静的ファイルのURL
STATIC_URL = '/static/'
# 開発時に探索する追加ディレクトリ
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
# collectstatic の出力先(本番用)
STATIC_ROOT = BASE_DIR / 'staticfiles'
テンプレートでの使用
{% load static %}
<!DOCTYPE html>
<html>
<head>
{# CSS読み込み #}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{# ファビコン #}
<link rel="icon" href="{% static 'images/favicon.ico' %}">
</head>
<body>
{# 画像 #}
<img src="{% static 'images/logo.png' %}" alt="ロゴ">
{# JavaScript #}
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
ディレクトリ構成の推奨パターン
project/
static/ # STATICFILES_DIRSで指定(プロジェクト共通)
css/
style.css
js/
main.js
images/
logo.png
myapp/
static/ # アプリ固有(APP_DIRS=Trueで自動探索)
myapp/ # 名前空間を切る(重要)
css/
app.css
js/
app.js
名前空間を切る理由:複数アプリで同じファイル名(例: style.css)があると衝突します。myapp/static/myapp/css/style.css のようにアプリ名のサブディレクトリを作ることで回避できます。テンプレートでは {% static 'myapp/css/style.css' %} と指定します。
本番環境でのcollectstatic
# 全静的ファイルをSTATIC_ROOTに集約
python manage.py collectstatic
# 確認なしで実行
python manage.py collectstatic --noinput
| 設定 | 用途 | 環境 |
|---|---|---|
STATIC_URL | ブラウザからアクセスするURL | 共通 |
STATICFILES_DIRS | 追加の静的ファイル探索パス | 開発 |
STATIC_ROOT | collectstaticの出力先 | 本番 |
開発サーバーの自動配信:DEBUG=True のときは Django が静的ファイルを自動配信しますが、本番(DEBUG=False)では Nginx や WhiteNoise 等で配信する設定が別途必要です。
まとめチェックリスト
{% extends %}と{% block %}でレイアウトの共通化ができた{% for %},{% if %},{% include %}でロジックと部品化ができたdate,truncatechars,default等のフィルターで表示を加工できたtemplatetags/でカスタムフィルター・タグを作成できた- コンテキストプロセッサでグローバル変数を提供できた
{% static %}で静的ファイルを正しく読み込めた