基本

Djangoテンプレート応用ガイド|継承・タグ・フィルターを使いこなす

Django テンプレート Python

Djangoテンプレート応用ガイド
継承・タグ・フィルターを使いこなす

Djangoテンプレートの継承、テンプレートタグ、フィルター、カスタムタグの作成まで、実践的なテンプレート技術を解説します。

こんな人向けの記事です

  • Djangoテンプレートの継承を理解したい
  • テンプレートタグやフィルターを使いこなしたい
  • カスタムテンプレートタグを作りたい

Step 1テンプレートの継承(extends / block)

Djangoテンプレートの最大の特徴は継承です。共通レイアウトを「ベーステンプレート」に定義し、各ページで必要な部分だけを上書きできます。

templates/base.html(ベーステンプレート)
<!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>&copy; 2026 My Site</p>
    </footer>

    {% block extra_js %}{% endblock %}
</body>
</html>
templates/blog/article_list.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.counter1から始まるカウンター
forloop.counter00から始まるカウンター
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_noneNoneのときだけ代替{{ value|default_if_none:"未設定" }}未設定
lengthリストや文字列の長さ{{ items|length }}5
linebreaksbr改行をbrタグに変換{{ text|linebreaksbr }}行1<br>行2
safeHTMLエスケープを無効化{{ html_content|safe }}HTMLがそのまま表示
slugifyスラッグ形式に変換{{ "Hello World"|slugify }}hello-world
joinリストを結合{{ tags|join:", " }}Django, Python, Web
add値を加算{{ page|add:1 }}2
yesnoTrue/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
  ...

カスタムフィルターの作成

myapp/templatetags/custom_filters.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 #}

カスタムテンプレートタグの作成

myapp/templatetags/custom_tags.py
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>&copy; {% current_year %} My Site</footer>
種類用途デコレータ
カスタムフィルター変数の表示加工@register.filter
シンプルタグ値を返す処理@register.simple_tag
インクルージョンタグテンプレート付きの部品@register.inclusion_tag

__init__.py を忘れずにtemplatetags/ ディレクトリに __init__.py がないとDjangoがモジュールとして認識できず、{% load %} でエラーになります。

Step 5コンテキストプロセッサ

コンテキストプロセッサは、すべてのテンプレートで共通して使える変数を自動的に追加する仕組みです。サイト名やグローバルな設定値を毎回ビューで渡す必要がなくなります。

カスタムコンテキストプロセッサの作成

myapp/context_processors.py
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への登録

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組み込みのコンテキストプロセッサ

プロセッサ提供する変数
debugdebug(DEBUG設定値), sql_queries
requestrequest(HttpRequestオブジェクト)
authuser, perms(認証情報)
messagesmessages(フラッシュメッセージ)
mediaMEDIA_URL
staticSTATIC_URL
i18nLANGUAGES, LANGUAGE_CODE

パフォーマンスに注意:コンテキストプロセッサはすべてのリクエストで実行されます。重いDBクエリを含めると全ページの表示速度に影響します。キャッシュの活用や、必要なページだけで使うならビューで渡す方が適切です。

Step 6静的ファイルの読み込み(static)

CSS、JavaScript、画像などの静的ファイルは {% static %} タグで読み込みます。パスをハードコードするとデプロイ時に問題が起きるため、必ずこのタグを使いましょう。

settings.pyの設定

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_ROOTcollectstaticの出力先本番

開発サーバーの自動配信DEBUG=True のときは Django が静的ファイルを自動配信しますが、本番(DEBUG=False)では Nginx や WhiteNoise 等で配信する設定が別途必要です。

まとめチェックリスト

  • {% extends %}{% block %} でレイアウトの共通化ができた
  • {% for %}, {% if %}, {% include %} でロジックと部品化ができた
  • date, truncatechars, default 等のフィルターで表示を加工できた
  • templatetags/ でカスタムフィルター・タグを作成できた
  • コンテキストプロセッサでグローバル変数を提供できた
  • {% static %} で静的ファイルを正しく読み込めた