テンプレート継承でDjangoブログのトップページを作る【AIと一緒に作るDjangoブログ入門 #3】

テンプレート継承でDjangoブログのトップページを作る【AIと一緒に作るDjangoブログ入門 #3】

views.pyでダミーデータを用意し、Djangoのテンプレート継承(base.html)でブログのトップページを作る章。共通の枠を base.html に集約する公式のベストプラクティスを最初から採用し、Bootstrap5・ダークテーマ・static配信(STATIC_ROOT / collectstatic)まで通します。

はじめに

前章では「URL → View → Template」の流れを学び、blog/urls.pypath('', views.index, name='index') を書いて URLの設定まで終えました。そして最後に「次章ではこの views.index 関数を実装して、画面を表示します」と予告しました。

というわけでこの章では、その予告どおり残りの View(views.pyTemplate を実装して、トップページを完成させます。今はトップページにアクセスすると views.index が呼ばれる状態なので、まずはその index 関数の中身から作っていきます。

ポイントは、テンプレートをいきなり1枚の巨大なHTMLで作らないこと。Djangoの公式が推奨する テンプレート継承(共通の枠を base.html にまとめ、各ページはその差分だけ書く)を最初から使います。

ココココ

「あとで綺麗にすればいいや」で1枚HTMLを量産すると、ヘッダーを直すのに全ページ修正…みたいな地獄になるよ!
最初から base.html に共通部分を集めておけば、1か所直すだけで全ページに反映されるって古事記にもそう書かれてる


Step1. views.pyにindex関数を書く

~/myblog/blog/views.py を開いて、以下に書き換えます。

from django.shortcuts import render


def index(request):
    # 特集バナー用のデータ
    featured = {
        'title': 'AIと一緒に作るDjangoブログ入門',
        'summary': 'DockerとDjangoを使ってブログサイトをゼロから作る方法を、AIと一緒に解説します。',
    }

    # 記事一覧用のダミーデータ
    posts = [
        {
            'title': 'Bootstrap5でデザインを整える',
            'date': '2026年5月19日',
            'author': '管理者',
            'body': 'CSSを書かなくてもBootstrap5を使えば綺麗なデザインが作れます。',
        },
        {
            'title': 'Djangoのテンプレート継承',
            'date': '2026年5月20日',
            'author': '管理者',
            'body': '共通部分を base.html にまとめると、各ページは差分だけ書けばよくなります。',
        },
    ]

    context = {
        'featured': featured,
        'posts': posts,
    }
    return render(request, 'blog/index.html', context)

views.pyの役割

views.pyデータを用意してテンプレートに渡す 役割を持ちます。

今はまだデータベースがないので、Pythonの辞書(dict)とリストでダミーデータを用意しています。第4章でデータベースのモデルを作ったら、ここをDB取得のコードに差し替えます。

render(request, 'blog/index.html', context) の意味:

引数 意味
request ブラウザからのリクエスト情報
'blog/index.html' 使うテンプレートのパス
context テンプレートに渡すデータ(辞書)
ココココ

辞書のキー名は適当でもいいけど、ここでは summary みたいに「後でDBのフィールド名になりそうな名前」を選んでるよ。第4章でDB化するとき、テンプレート側を書き換えずに済むからラクなの!


Step2. CSSファイルを作る

HTMLにCSSを直接書く(インラインスタイル)のではなく、別ファイルに分けるのが基本です。

mkdir -p ~/myblog/blog/static/blog
touch ~/myblog/blog/static/blog/blog.css

フォルダ構成:

blog/
└ static/
    └ blog/        ← テンプレートと同じく「アプリ名」で名前空間化
        └ blog.css

templates/blog/ と同じで、static/blog/ とアプリ名のフォルダを挟むのが公式ルールです。複数アプリで同名ファイルがあっても 'blog/blog.css' と一意に指定できます。

~/myblog/blog/static/blog/blog.css の中身(今はこれだけ。後の章で少しずつ増やします):

/* ロゴと見出しは上品なセリフ体に */
.blog-header-logo {
    font-family: "Playfair Display", Georgia, "Times New Roman", serif;
    font-size: 2.25rem;
}

h1, h2, h3, h4, h5, h6 {
    font-family: "Playfair Display", Georgia, "Times New Roman", serif;
}

/* 記事ブロックの余白 */
.blog-post { margin-bottom: 4rem; }
.blog-post-meta { margin-bottom: 1.25rem; color: #8a8a8a; }

Step3. 共通の枠 base.html を作る(テンプレート継承の親)

まず全ページ共通のHTML<html><head>・ヘッダー・フッターなど)を入れる親テンプレートを作ります。

touch ~/myblog/blog/templates/blog/base.html

~/myblog/blog/templates/blog/base.html の中身:

{% load static %}
<!DOCTYPE html>
<html lang="ja" data-bs-theme="dark">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}My Blog{% endblock %}</title>

    <link href="https://fonts.googleapis.com/css?family=Playfair+Display:700,900&display=swap" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="{% static 'blog/blog.css' %}" rel="stylesheet">
</head>
<body>

{# 共通ヘッダー #}
<div class="container">
    <header class="border-bottom lh-1 py-3">
        <div class="row justify-content-center">
            <div class="col-4 text-center">
                <a class="blog-header-logo text-body-emphasis text-decoration-none"
                   href="{% url 'blog:index' %}">My Blog</a>
            </div>
        </div>
    </header>
</div>

{# 各ページの中身がここに入る #}
<main class="container">
    {% block content %}{% endblock %}
</main>

{# 共通フッター #}
<footer class="py-5 text-center text-body-secondary bg-body-tertiary">
    <p class="mb-0">My Blog - AIと一緒に作るDjango入門</p>
</footer>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

</body>
</html>

ポイント解説

{% load static %}
1行目の {% load static %} は、Djangoの静的ファイル機能({% static %} タグ)を使うためのおまじないです。これがないと下の {% static 'blog/blog.css' %} がエラーになります。

{% block title %}{% block content %}
{% block %} は「子テンプレートが上書きできる穴」です。title はページごとのタイトル、content はページごとの本文が入ります。共通の枠だけ base.html に書いて、中身は各ページに任せる――これがテンプレート継承です。

data-bs-theme="dark"
Bootstrap5.3の機能で、これ1つでサイト全体がダークテーマになります。bg-body-tertiarytext-body-emphasis といった「テーマに追従する色クラス」を使えば、明/暗の切り替えも自動です。

{% url 'blog:index' %}
前章で blog/urls.py に付けた名前(app_name='blog' + name='index')を使って、トップページのURLを自動生成しています。URLを直書き(href="/")せずに名前で参照すると、あとでURL構成を変えても全ページが自動で追従します。

📚 参考URLDjango チュートリアル Part 3 - テンプレートの継承(公式も base.html を親にする継承を推奨しています)

ココココ

今の base.html はロゴだけのシンプルなヘッダーだけど、第5章で「検索フォーム」と「カテゴリナビ」、サイドバーを足していくよ。SEOタグ(OGPとか)は第7章でこの base.html に追加する予定。今は『共通の枠を1か所にまとめる』感覚を掴めばOK!


Step4. index.html を作る(base.html を継承する子)

次に、トップページ専用の中身だけを書きます。

touch ~/myblog/blog/templates/blog/index.html

~/myblog/blog/templates/blog/index.html の中身:

{% extends 'blog/base.html' %}

{% block title %}My Blog - トップ{% endblock %}

{% block content %}

    {# 特集バナー #}
    <div class="p-4 p-md-5 mb-4 rounded text-body-emphasis bg-body-secondary">
        <div class="col-lg-6 px-0">
            <h1 class="display-4 fst-italic">{{ featured.title }}</h1>
            <p class="lead my-3">{{ featured.summary }}</p>
        </div>
    </div>

    {# 記事一覧 #}
    {% for post in posts %}
    <article class="blog-post">
        <h2 class="display-5 mb-1">{{ post.title }}</h2>
        <p class="blog-post-meta">{{ post.date }} by {{ post.author }}</p>
        <p>{{ post.body }}</p>
    </article>
    {% endfor %}

{% endblock %}

たったこれだけ なのがテンプレート継承の威力です。

  • 1行目 {% extends 'blog/base.html' %} で「base.html を土台にする」と宣言
  • {% block title %}…{% endblock %} で base.html のタイトルを上書き
  • {% block content %}…{% endblock %} の中だけが、base.html の <main> に差し込まれる

<html><head>、ヘッダー・フッターは base.html 側にあるので、ここでは一切書かなくていい。ページが増えても同じ枠を使い回せます。

ココココ

{{ featured.title }}featured は、Step1のviews.pyで context に入れた辞書だよ。featured.title でその中の 'title' を取り出してる。{% for post in posts %} も同じで、views.pyから渡したリストを1件ずつ回してるの!


Step5. settings.pyにSTATIC_ROOTを追加する

CSSなどの静的ファイルを本番で配信できるように、集約先を設定します。~/myblog/myblog/settings.pySTATIC_URL の下に追記:

STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'  # ← 追加

STATIC_URLとSTATIC_ROOTの違い

設定 役割
STATIC_URL HTMLに出力されるURLのプレフィックス(/static/blog/blog.css/static/ 部分)
STATIC_ROOT collectstatic コマンドでファイルを集める場所のパス

Step6. collectstaticを実行する

cd ~/myblog
docker compose run --rm web python manage.py collectstatic --noinput

collectstatic は各アプリの static/ フォルダからファイルを集めて staticfiles/ にまとめるコマンドです。

blog/static/blog/blog.css
         ↓ collectstatic
staticfiles/blog/blog.css   ← Nginx がここを配信する

Nginx は staticfiles/ フォルダを直接配信しています(docker/nginx.dev.conflocation /static/alias /app/staticfiles/; を設定済み)。

⚠️ 注意:CSSや画像ファイルを追加・変更したときは、毎回この collectstatic を実行する必要があります。「CSSを直したのに反映されない」ときはこれを忘れていることが多いです。


Step7. 起動確認

cd ~/myblog
docker compose up

ブラウザで http://localhost を開いて、ダークテーマのトップページ(特集バナー+記事2件)が表示されれば成功です!


まとめ

この章で、Djangoの基本の流れがひと通り完成しました。

URL(urls.py)         ← 第2章で設定
  ↓
処理(views.py)        ← ダミーデータを用意(この章)
  ↓
表示(テンプレート)     ← base.html(共通の枠)+ index.html(差分)

ポイントは、最初から テンプレート継承(base.html) で組んだこと。共通部分が1か所にまとまっているので、これから章を進めても

  • 第5章:base.html に検索フォーム・カテゴリナビ・サイドバーを追加
  • 第7章:base.html にSEO(OGP等)を追加

…と、同じ base.html を育てていくだけで済みます。

次章では MySQL にデータを保存する モデル を作り、この章のダミーデータをDBのデータに置き換えます。

関連記事

Markdownで記事を綺麗に表示する — コードハイライトと目次【AIと一緒に作るDjangoブログ入門 #6.1】
Django
Markdownで記事を綺麗に表示する — コードハイライトと目次【AIと一緒に作るDjangoブログ入門 #6.1】

第5章まで素のテキストだった記事本文を、Markdownで整形して表示できるようにする章。Python-Markdow…

Django記事詳細・カテゴリ・キーワード検索ページの実装【AIと一緒に作るDjangoブログ入門 #5】
Django
Django記事詳細・カテゴリ・キーワード検索ページの実装【AIと一緒に作るDjangoブログ入門 #5】

slug方式の記事詳細ページ、カテゴリ別の一覧、キーワード検索を実装。base.htmlでの共通レイアウト化、F()式…

Djangoのモデル設計とfixtureでブログのデータベースを作る【AIと一緒に作るDjangoブログ入門 #4】
Django
Djangoのモデル設計とfixtureでブログのデータベースを作る【AIと一緒に作るDjangoブログ入門 #4】

Post・Category・DisplaySlotの3つのモデルを設計し、管理画面で記事を管理できるようにする章。fi…