【Django Template完全ガイド】テンプレート継承・静的ファイル・高度なタグの使い方
はじめに:Web ページをもっと美しく、効率的に!
前回は、Django のビューとテンプレートの基本を学び、動的な Web ページを作ることができるようになりました。「Hello Django!」から始まって、データを表示するブログ記事一覧まで、本当によく頑張りましたね!
今回は、テンプレートの真の力を解き放ちます。テンプレートの継承を使って効率的にコードを書く方法、CSS や JavaScript、画像を使って美しいページを作る方法を学びましょう。
「えっ、また新しいことを覚えるの?」と思うかもしれません。でも安心してください。今回学ぶことは、あなたの Web ページを一気にプロフェッショナルなレベルに引き上げてくれる、とても楽しい内容です!
初めてこの記事を読む方へ:
この記事は連載の第5回目です。ビューとテンプレートの基本がまだの方は、第4回の記事をご覧ください。もしすぐにテンプレートの高度な機能を学びたい方は、前回GitHubコードを参考にしながら進めることもできます。
この記事で学べること
- テンプレートの継承システムの仕組みと使い方
- 静的ファイル(CSS、JavaScript、画像)の設定と活用
- 便利なテンプレートタグとフィルタ
- include を使った部品化
- 実践的なブログテンプレートの作成
それでは、Django テンプレートの世界をもっと深く探検していきましょう!
テンプレートの継承:DRY 原則を実現する
▶なぜテンプレートの継承が必要なの?
前回まで、各ページごとに完全な HTML を書いていました。でも、実際の Web サイトでは、ヘッダーやフッターなど、全ページで共通する部分がありますよね。
例えば、10 ページあるサイトでヘッダーを変更したい場合、10 個のファイルを全部修正する必要があります...これは大変です!😱
そこで登場するのがテンプレートの継承です。
DRY 原則とは?
DRY = Don't Repeat Yourself(同じことを繰り返すな)
プログラミングの重要な原則の一つです。同じコードを何度も書かずに、一箇所にまとめて再利用しようという考え方です。
メリット:
- 修正が一箇所で済む
- コードが読みやすい
- バグが減る
▶ベーステンプレートを作成しよう
まず、すべてのページの「型」となるベーステンプレートを作成します。
blog/templates/blog/base.html
を作成:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Blog{% endblock %}</title>
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<nav>
<h1><a href="{% url 'post_list' %}">My Blog</a></h1>
<ul>
<li><a href="{% url 'post_list' %}">ホーム</a></li>
<li><a href="#">記事一覧</a></li>
<li><a href="#">About</a></li>
</ul>
</nav>
</header>
<main>
{% block content %}
<!-- ここに各ページの内容が入ります -->
{% endblock %}
</main>
<footer>
<p>© 2025 My Blog. All rights reserved.</p>
</footer>
{% block extra_js %}{% endblock %}
</body>
</html>
重要なポイントを解説
{% block ブロック名 %}
の説明:
- blockは「ここは子テンプレートで上書きできる場所だよ」という印
- 子テンプレートで同じブロック名を使って内容を入れ替えられる
- ブロックの中に書いた内容(例:
My Blog
)はデフォルト値として使われる
例えば、{% block title %}My Blog{% endblock %}
の場合:
- 子テンプレートがこのブロックを上書きしない場合 → 「My Blog」と表示される
- 子テンプレートがこのブロックを上書きする場合 → 子テンプレートで指定した内容が表示される
デフォルト値の便利な使い方
パターン1:子テンプレートが何も指定しない場合のフォールバック
<!-- base.html -->
<title>{% block title %}My Blog{% endblock %}</title>
<!-- 子テンプレートでblockを上書きしない場合 -->
<!-- 結果:ブラウザのタブに「My Blog」と表示される -->
パターン2:メインコンテンツエリアのデフォルトメッセージ
<!-- base.html -->
<main>
{% block content %}
<div class="default-content">
<h2>ページが見つかりません</h2>
<p>お探しのページは存在しないか、移動した可能性があります。</p>
<a href="/">トップページへ戻る</a>
</div>
{% endblock %}
</main>
<!-- 通常のページ(post_list.html)で上書きする場合 -->
{% block content %}
<h2>記事一覧</h2>
<!-- 記事のリスト表示 -->
{% endblock %}
<!-- もし子テンプレートでcontentブロックを定義し忘れた場合 -->
<!-- デフォルトの「ページが見つかりません」が表示される -->
つまり、デフォルト値を使うことで:
- 開発中のミスを防げる(空白ページにならない)
- エラーページのような共通コンテンツを簡単に実装できる
- テンプレートの実装忘れがあってもユーザーに適切なメッセージを表示できる
よく使うブロック名:
title
:ページのタイトル(デフォルト値を設定することが多い)content
:メインコンテンツ(通常デフォルト値なし)extra_css
:ページ固有の CSS(通常デフォルト値なし)extra_js
:ページ固有の JavaScript(通常デフォルト値なし)
▶子テンプレートで継承を使う
では、前回作ったpost_list.html
を継承を使って書き直してみましょう。
blog/templates/blog/post_list.html
を修正:
{% extends 'blog/base.html' %}
{% block title %}記事一覧 - My Blog{% endblock %}
{% block content %}
<h2>ブログ記事一覧</h2>
{% for post in posts %}
<article class="post">
<h3>{{ post.title }}</h3>
<p class="post-date">投稿日: {{ post.created_at }}</p>
<p>{{ post.content }}</p>
</article>
{% endfor %}
{% if not posts %}
<p>まだ記事がありません。</p>
{% endif %}
{% endblock %}
なんとスッキリ!必要な部分だけを書けばいいんです。
あれ?前回の<style>
タグがなくなった?
そうです!前回は以下のように<style>
タグでスタイルを定義していました:
<style>
.post {
border: 1px solid #ddd;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
}
/* 他のスタイル... */
</style>
でも今回はclass属性(class="post"
など)だけ残して、スタイルの定義は削除しています。
心配しないでください!このあとの「静的ファイルでページを美しくする」のセクションで、CSSファイルを使ってもっとプロフェッショナルなスタイルを適用する方法を学びます。
内部スタイルシート(<style>
タグ)より外部CSSファイルを使う方が:
- 複数のページで同じスタイルを共有できる
- HTMLとCSSを分離して管理しやすい
- ブラウザがCSSファイルをキャッシュして高速化できる
という利点があります!
継承の仕組みを図解
この図を詳しく見てみましょう:
-
base.html(親テンプレート)
- HTMLの全体構造を定義
{% block title %}
と{% block content %}
で、子テンプレートが上書きできる場所を指定- ヘッダーやフッターなど、全ページ共通の部分はここに書く
-
post_list.html(子テンプレート)
{% extends 'base.html' %}
で親テンプレートを継承することを宣言- 必要なブロックだけを定義(この例では
content
ブロックのみ) title
ブロックは定義していないので、親のデフォルト値「My Blog」が使われる
-
処理後の結果
- Djangoが自動的に親と子を組み合わせて、完全なHTMLを生成
- 子で定義したブロックは上書きされ、定義していないブロックは親の内容がそのまま使われる
- 結果として、共通部分を何度も書く必要がなくなる!
▶もう一つページを作ってみよう
継承の便利さを実感するため、記事詳細ページも作ってみましょう。
blog/views.py
を修正して、共通のデータを定義し、詳細表示用の関数を追加します:
from django.shortcuts import render
# 共通の記事データ
POSTS = [
{
"id": 1,
"title": "Djangoを始めました",
"content": "Djangoの学習を始めました。楽しいです!",
"created_at": "2025-07-01",
"body": """今日からDjangoの学習を始めました。
Pythonは少し触ったことがあったけど、Webフレームワークは初めてです。
最初は難しそうだと思ったけど、チュートリアルが分かりやすくて助かります。
これからブログアプリを作っていきたいと思います!""",
},
{
"id": 2,
"title": "ビューについて学んだこと",
"content": "今日はビューについて学びました。MVTパターンの理解が深まりました。",
"created_at": "2025-07-02",
"body": """Djangoのビューは、リクエストを受け取ってレスポンスを返す関数です。
MVTパターンのVに当たる部分で、ビジネスロジックを担当します。
テンプレートにデータを渡す方法も学びました。
contextという辞書を使うのが面白いです。""",
},
{
"id": 3,
"title": "テンプレートは便利",
"content": "テンプレートを使うとHTMLが書きやすいです。継承システムが特に便利!",
"created_at": "2025-07-03",
"body": """今日はテンプレートについて学習しました。
変数の表示、forループ、if文など、基本的な機能を試しました。
特に継承システムが素晴らしいです!
共通部分を一箇所にまとめられるのは、とても効率的ですね。""",
},
]
def post_list(request):
context = {
"posts": POSTS,
}
return render(request, "blog/post_list.html", context)
def post_detail(request, post_id):
# 指定されたIDの記事を探す
post = None
for p in POSTS:
if p["id"] == post_id:
post = p
break
context = {
"post": post,
}
return render(request, "blog/post_detail.html", context)
このコードでは:
- 記事データを
POSTS
という共通の変数にまとめました post_list
関数は前回作成済みで、POSTS
を使うように修正post_detail
関数を新たに追加し、指定されたIDの記事を表示
blog/templates/blog/post_detail.html
を作成:
{% extends 'blog/base.html' %}
{% block title %}{{ post.title }} - My Blog{% endblock %}
{% block content %}
{% if post %}
<article class="post-detail">
<h2>{{ post.title }}</h2>
<p class="post-meta">投稿日: {{ post.created_at }}</p>
<div class="post-body">
{{ post.body|linebreaks }}
</div>
<p><a href="{% url 'post_list' %}">← 記事一覧に戻る</a></p>
</article>
{% else %}
<p>記事が見つかりませんでした。</p>
<p><a href="{% url 'post_list' %}">記事一覧に戻る</a></p>
{% endif %}
{% endblock %}
blog/urls.py
のURLpatternsに追加:
from django.urls import path
from . import views
urlpatterns = [
path("", views.post_list, name="post_list"),
path("post/<int:post_id>/", views.post_detail, name="post_detail"), # 追加
]
見てください!ヘッダーやフッターを書かなくても、完全なページができあがりました。これがテンプレート継承の力です!
静的ファイルでページを美しくする
▶静的ファイルとは?
静的ファイルとは、サーバー側で処理される必要がない、そのまま配信されるファイルのことです:
- CSS:スタイルシート
- JavaScript:動的な機能
- 画像:ロゴ、アイコン、写真など
- フォント:Web フォント
▶静的ファイルの設定
まず、mysite/settings.py
で静的ファイルの設定を確認しましょう:
# 静的ファイルのURL(すでに設定されているはず)
STATIC_URL = "static/"
この設定は最初から存在しているはずです。これは「静的ファイルにアクセスするときのURLのプレフィックス」を指定しています。
例えば、blog/static/blog/css/style.css
というファイルは、ブラウザからはhttp://localhost:8000/static/blog/css/style.css
でアクセスできます。
Djangoの静的ファイルの仕組み
Djangoは以下の場所から静的ファイルを自動的に探します:
-
各アプリの
static
フォルダ(自動認識)blog/static/ ← 自動的に認識される practice/static/ ← これも自動的に認識される
-
STATICFILES_DIRSで指定したフォルダ(追加設定が必要)
# プロジェクト共通の静的ファイルを置きたい場合のみ追加 STATICFILES_DIRS = [BASE_DIR / "static"]
今回は各アプリのstatic
フォルダを使うので、STATICFILES_DIRS
の設定は不要です。
もしサイト全体で使うロゴ画像などを管理したい場合は、この設定を追加して、プロジェクトルートにstatic
フォルダを作成します。
開発環境と本番環境の違い
開発中(DEBUG = True
)は、python manage.py runserver
が自動的に静的ファイルを配信してくれます。
本番環境では、パフォーマンスのためにNginxやApacheなどのWebサーバーが静的ファイルを配信します。
その際はSTATIC_ROOT
という別の設定を使いますが、今は気にする必要はありません。
▶静的ファイル用のディレクトリ構造
静的ファイルは以下の構造で配置する予定です。
blog/
├── static/
│ └── blog/
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── main.js
│ └── images/
│ └── logo.png
├── templates/
└── ...
なぜ static/blog という二重構造?
テンプレートと同じ理由です!複数のアプリケーションがある場合、名前の衝突を避けるためです。
Django は全アプリの static フォルダを一箇所にまとめるので、アプリ名のフォルダで区別します。
▶CSS ファイルを作成して適用する
blog/static/blog/css/style.css
を作成:
/* リセットとベーススタイル */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
}
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f4f4f4;
display: flex;
flex-direction: column;
}
/* ヘッダースタイル */
header {
background-color: #2c3e50;
color: white;
padding: 1rem 0;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
header nav {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
header h1 {
margin: 0;
line-height: 1;
}
header h1 a {
color: white;
text-decoration: none;
font-size: 1.8rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
header h1 img {
height: 40px;
width: auto;
display: block;
}
header ul {
list-style: none;
display: flex;
gap: 2rem;
margin: 0;
padding: 0;
}
header ul a {
color: white;
text-decoration: none;
transition: opacity 0.3s;
}
header ul a:hover {
opacity: 0.8;
}
/* メインコンテンツ */
main {
max-width: 1200px;
margin: 2rem auto;
padding: 0 2rem;
flex: 1;
width: 100%;
}
/* h2タグのマージン調整 */
main h2 {
margin-bottom: 1.5rem;
}
/* 記事スタイル */
.post {
background: white;
padding: 2rem;
margin-bottom: 2rem;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s;
}
.post:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.15);
}
.post h3 {
color: #2c3e50;
margin-bottom: 0.5rem;
}
.post-date {
color: #7f8c8d;
font-size: 0.9rem;
margin-bottom: 1rem;
}
/* フッター */
footer {
background-color: #34495e;
color: white;
text-align: center;
padding: 1rem 0;
}
▶テンプレートで CSS を読み込む
blog/templates/blog/base.html
を修正:
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Blog{% endblock %}</title>
<!-- 静的ファイルの読み込み -->
<link rel="stylesheet" href="{% static 'blog/css/style.css' %}">
{% block extra_css %}{% endblock %}
</head>
<!-- 以下省略 -->
重要なポイント:
{% load static %}
: static タグを使えるようにする(ファイルの最初に 1 回だけ){% static 'パス' %}
: 静的ファイルの URL を生成
▶動作確認してみよう
開発サーバーを起動して、ブラウザで確認してみましょう:
$ python manage.py runserver
すでにサーバーが起動している場合
前回からサーバーを起動したままの方は、一度サーバーを停止してから再起動する必要があります。
- ターミナルで
Ctrl + C
(Macの場合はControl + C
)を押してサーバーを停止 - 再度
python manage.py runserver
でサーバーを起動
これをしないと、新しく追加したCSSファイルが読み込まれません!
http://localhost:8000/blog/ にアクセスすると...
わぁ!一気にプロフェッショナルな見た目になりましたね!🎨
CSS が反映されない場合は?
- ブラウザのキャッシュをクリア(Ctrl+F5 または Cmd+Shift+R)
settings.py
のSTATICFILES_DIRS
設定を確認- ファイルパスが正しいか確認(
static/blog/css/style.css
)
▶JavaScript も追加してみよう
blog/static/blog/js/main.js
を作成:
// ページ読み込み完了時に実行
document.addEventListener('DOMContentLoaded', function() {
// 記事をクリックしたときのアニメーション
const posts = document.querySelectorAll('.post');
posts.forEach(post => {
post.addEventListener('click', function() {
// クリックアニメーション
this.style.transform = 'scale(0.98)';
setTimeout(() => {
this.style.transform = '';
}, 100);
});
});
// スムーズスクロール
const links = document.querySelectorAll('a[href^="#"]');
links.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// コンソールにメッセージ
console.log('Djangoブログへようこそ!');
});
blog/templates/blog/base.html
の body タグの閉じタグ前に追加:
<footer>
<p>© 2024 My Blog. All rights reserved.</p>
</footer>
<!-- JavaScriptの読み込み -->
<script src="{% static 'blog/js/main.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
▶画像も追加してみよう
ロゴ画像を表示する例を見てみましょう。
blog/templates/blog/base.html
のヘッダー部分を修正:
<header>
<nav>
<h1>
<a href="{% url 'post_list' %}">
<img src="{% static 'blog/images/logo.png' %}" alt="My Blog" height="40">
<span>My Blog</span>
</a>
</h1>
<ul>
<li><a href="{% url 'post_list' %}">ホーム</a></li>
<li><a href="#">記事一覧</a></li>
<li><a href="#">About</a></li>
</ul>
</nav>
</header>
画像ファイルについて
実際に表示するには、blog/static/blog/images/
フォルダに logo.png ファイルを配置する必要があります。例えば icons8.com からテスト用のロゴファイルをダウンロードしてみてください。
他の無料の画像素材サイト:
- Unsplash(https://unsplash.com)
- Pexels(https://www.pexels.com)
- Pixabay(https://pixabay.com)
- Freepik(https://www.freepik.com)
▶Faviconも追加しよう
Favicon(ファビコン) とは、ブラウザのタブやブックマークに表示される小さなアイコンのことです。Webサイトのブランディングに重要な要素で、ユーザーが複数のタブを開いているときにサイトを識別しやすくなります。
Faviconの豆知識
- Faviconは「Favorite Icon」の略称です
- 一般的なサイズは16×16、32×32、48×48ピクセル
- 最近では、より大きなサイズ(192×192など)も推奨されています
- .ico形式が伝統的ですが、.pngや.svgも使用可能です
Faviconを追加するには、blog/templates/blog/base.html
の<head>
セクションに以下を追加します:
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Blog{% endblock %}</title>
<!-- Favicon -->
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'blog/images/favicon-32x32.png' %}">
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'blog/images/favicon-16x16.png' %}">
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'blog/images/apple-touch-icon.png' %}">
<!-- 静的ファイルの読み込み -->
<link rel="stylesheet" href="{% static 'blog/css/style.css' %}">
{% block extra_css %}{% endblock %}
</head>
Faviconの作成方法
-
オンラインツールを使用する方法(推奨)
- Favicon.io:テキストや絵文字からFaviconを生成
- RealFaviconGenerator:画像から各種サイズのFaviconを一括生成
- Favicon.cc:ピクセル単位でFaviconをデザイン
-
画像編集ソフトを使用する方法
- 正方形の画像(512×512px推奨)を作成
- 必要なサイズにリサイズ
blog/static/blog/images/
フォルダに保存
Faviconが表示されない場合
- ブラウザのキャッシュをクリア(Ctrl+F5 または Cmd+Shift+R)
- 開発サーバーを再起動
- ファイルパスが正しいか確認
- ブラウザによってはico形式のみ対応している場合があるので、以下も追加:
<link rel="shortcut icon" href="{% static 'blog/images/favicon.ico' %}">
▶JavaScriptと画像の動作確認
サーバーを再起動して、すべての機能が動作しているか確認しましょう。
Faviconの動作確認方法:
- ブラウザのタブを確認 - 小さなアイコンが表示されているはず
- ページをブックマークしてみる - ブックマークリストにアイコンが表示される
- スマートフォンでホーム画面に追加 - アプリアイコンとして表示される(apple-touch-iconを設定した場合)
JavaScriptの動作確認方法:
- ブラウザの開発者ツールを開く(F12キーまたは右クリック→「検証」)
- 「Console」タブを選択
- 「Djangoブログへようこそ!」と表示されていればJavaScriptが正しく読み込まれています
- 記事のボックスをクリックすると、少し縮小されるアニメーションが動作します
上の画像では:
- ブラウザのタブにFaviconが表示されています
- ヘッダーにロゴ画像が表示されています
- CSSによるスタイリングが適用されています
- JavaScriptが正常に動作しています(コンソールで確認)
便利なテンプレートタグとフィルタ
▶よく使うテンプレートタグ
Django には便利なテンプレートタグがたくさん用意されています。
if 文:条件分岐
{% if user.is_authenticated %}
<p>ようこそ、{{ user.username }}さん!</p>
{% else %}
<p>ログインしてください</p>
{% endif %}
<!-- 複数条件 -->
{% if posts %}
<p>{{ posts|length }}件の記事があります</p>
{% elif drafts %}
<p>下書きが{{ drafts|length }}件あります</p>
{% else %}
<p>記事がありません</p>
{% endif %}
for ループの便利な変数
{% for post in posts %}
<div class="post">
<span class="number">{{ forloop.counter }}</span> <!-- 1から始まる番号 -->
<h3>{{ post.title }}</h3>
{% if forloop.first %}
<span class="badge">NEW!</span>
{% endif %}
{% if forloop.last %}
<hr>
{% endif %}
</div>
{% endfor %}
forloop の便利な変数:
forloop.counter
:1 から始まる番号forloop.counter0
:0 から始まる番号forloop.first
:最初の要素なら Trueforloop.last
:最後の要素なら True
URL の生成
<!-- 基本形 -->
<a href="{% url 'post_list' %}">記事一覧</a>
<!-- パラメータ付き -->
<a href="{% url 'post_detail' post_id=post.id %}">続きを読む</a>
▶便利なフィルタ
フィルタは、変数の値を変換するために使います。パイプ(|
)でつなげます。
文字列関連のフィルタ
<!-- 大文字に変換 -->
{{ post.title|upper }}
<!-- 小文字に変換 -->
{{ post.title|lower }}
<!-- 最初の文字を大文字に -->
{{ post.title|capfirst }}
<!-- 文字数制限(...を追加) -->
{{ post.content|truncatechars:100 }}
<!-- 単語数制限 -->
{{ post.content|truncatewords:20 }}
<!-- 改行をbrタグに変換 -->
{{ post.body|linebreaks }}
<!-- HTMLタグをエスケープしない(注意して使用) -->
{{ post.html_content|safe }}
数値関連のフィルタ
<!-- 3桁ごとにカンマ -->
{{ price|floatformat:0|intcomma }} <!-- 1,234 -->
<!-- 小数点以下の桁数指定 -->
{{ price|floatformat:2 }} <!-- 1234.50 -->
<!-- デフォルト値 -->
{{ user.age|default:"不明" }}
日付関連のフィルタ
<!-- 日付フォーマット -->
{{ post.created_at|date:"Y年m月d日" }} <!-- 2025年07月10日 -->
<!-- 時刻フォーマット -->
{{ post.created_at|time:"H:i" }} <!-- 21:30 -->
▶実践:記事一覧を改良しよう
学んだことを使って、記事一覧をもっと良くしましょう。
blog/templates/blog/post_list.html
を修正:
{% extends 'blog/base.html' %}
{% block title %}記事一覧 - My Blog{% endblock %}
{% block content %}
<div class="page-header">
<h2>ブログ記事一覧</h2>
<p class="post-count">
{% if posts %}
全{{ posts|length }}件の記事
{% endif %}
</p>
</div>
{% for post in posts %}
<article class="post">
<div class="post-header">
<h3>
<span class="post-number">#{{ forloop.counter }}</span>
{{ post.title }}
</h3>
{% if forloop.first %}
<span class="new-badge">NEW!</span>
{% endif %}
</div>
<p class="post-date">投稿日: {{ post.created_at }}</p>
<div class="post-content">
{{ post.content|truncatechars:150 }}
</div>
<a href="{% url 'post_detail' post_id=post.id %}" class="read-more">
続きを読む →
</a>
</article>
{% empty %}
<div class="no-posts">
<p>まだ記事がありません。</p>
<p>最初の記事を書いてみましょう!</p>
</div>
{% endfor %}
{% endblock %}
上記のコードは長く見えるかもしれませんが、一行ずつ見ていけば、内容はすべてこれまでに学んだものばかりです!
{% extends %}
でテンプレート継承{% block %}
でブロックの上書き{% if %}
で条件分岐{% for %}
でループ処理{{ forloop.counter }}
で番号表示{{ post.content|truncatechars:150 }}
で文字数制限{% url 'post_detail' post_id=post.id %}
でURL生成
{% empty %}タグについて
{% for %}
ループで要素が空の場合の処理を書けます。
{% for item in items %}
<!-- アイテムがある場合 -->
{% empty %}
<!-- アイテムがない場合 -->
{% endfor %}
これは{% if not items %}
を使うより簡潔です!
改良後の記事一覧ページはこのようになります:
さらに凄いのは、「続きを読む →」リンクをクリックすると、実際に記事詳細ページに遷移することです!
これこそが動的なWebアプリケーションの力です。単なるHTMLではできない、ページ間のシームレスなナビゲーションが実現できています。
include で部品を再利用する
▶include とは?
大きなテンプレートを小さな部品に分割して、再利用できるようにする機能です。
例えば、記事の表示部分を部品化してみましょう。
▶部品テンプレートを作成
blog/templates/blog/includes/post_card.html
を作成:
<article class="post">
<div class="post-header">
<h3>
<span class="post-number">#{{ number }}</span>
{{ post.title }}
</h3>
{% if is_new %}
<span class="new-badge">NEW!</span>
{% endif %}
</div>
<p class="post-date">投稿日: {{ post.created_at }}</p>
<div class="post-content">
{{ post.content|truncatechars:150 }}
</div>
<a href="{% url 'post_detail' post_id=post.id %}" class="read-more">
続きを読む →
</a>
</article>
▶include で部品を使う
blog/templates/blog/post_list.html
を修正:
{% extends 'blog/base.html' %}
{% block title %}記事一覧 - My Blog{% endblock %}
{% block content %}
<div class="page-header">
<h2>ブログ記事一覧</h2>
<p class="post-count">
{% if posts %}
全{{ posts|length }}件の記事
{% endif %}
</p>
</div>
{% for post in posts %}
{% include 'blog/includes/post_card.html' with is_new=forloop.first number=forloop.counter %}
{% empty %}
<div class="no-posts">
<p>まだ記事がありません。</p>
<p>最初の記事を書いてみましょう!</p>
</div>
{% endfor %}
{% endblock %}
with
を使って複数の変数を渡すこともできます!
includeのwith句について
{% include %}
で部品テンプレートを読み込むとき、with
を使って変数を渡せます:
{% include 'template.html' with var1=value1 var2=value2 %}
ここでは:
is_new=forloop.first
:最初の記事かどうかnumber=forloop.counter
:記事の番号
を部品テンプレートに渡しています。
▶サイドバーを作ってみよう
より実践的な例として、サイドバーを追加してみましょう。
blog/templates/blog/base.html
を修正:
<!-- mainタグ部分を修正 -->
<main>
<div class="container">
<div class="content">
{% block content %}
<!-- ここに各ページの内容が入ります -->
{% endblock %}
</div>
<aside class="sidebar">
{% block sidebar %}
{% include 'blog/includes/sidebar.html' %}
{% endblock %}
</aside>
</div>
</main>
blog/templates/blog/includes/sidebar.html
を作成:
<div class="sidebar-section">
<h3>カテゴリー</h3>
<ul class="category-list">
<li><a href="#">Python</a></li>
<li><a href="#">Django</a></li>
<li><a href="#">Web開発</a></li>
<li><a href="#">その他</a></li>
</ul>
</div>
<div class="sidebar-section">
<h3>最近の投稿</h3>
<ul class="recent-posts">
{% for post in recent_posts|default:posts|slice:":3" %}
<li>
<a href="{% url 'post_detail' post_id=post.id %}">
{{ post.title|truncatechars:30 }}
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="sidebar-section">
<h3>アーカイブ</h3>
<ul class="archive-list">
<li><a href="#">2025年7月</a></li>
<li><a href="#">2025年6月</a></li>
<li><a href="#">2025年5月</a></li>
</ul>
</div>
対応する CSS も追加しましょう:
/* コンテナとレイアウト */
.container {
display: grid;
grid-template-columns: 1fr 300px;
gap: 2rem;
}
.content {
min-width: 0; /* グリッドレイアウトのバグ対策 */
}
/* サイドバー */
.sidebar {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
height: fit-content;
position: sticky;
top: 2rem;
}
.sidebar-section {
margin-bottom: 2rem;
}
.sidebar-section:last-child {
margin-bottom: 0;
}
.sidebar h3 {
color: #2c3e50;
font-size: 1.1rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid #3498db;
}
.sidebar ul {
list-style: none;
}
.sidebar li {
margin-bottom: 0.5rem;
}
.sidebar a {
color: #7f8c8d;
text-decoration: none;
transition: color 0.3s;
}
.sidebar a:hover {
color: #3498db;
}
/* レスポンシブ対応 */
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
}
.sidebar {
position: static;
margin-top: 2rem;
}
}
詳細ページのpost_detail
関数を更新
サイドバーが「最近の投稿」を表示するためにposts
変数を使用しているので、post_detail
関数でもcontextにposts
を追加する必要があります。
blog/views.py
のpost_detail
関数を以下のように修正してください:
def post_detail(request, post_id):
# 指定されたIDの記事を探す
post = None
for p in POSTS:
if p['id'] == post_id:
post = p
break
context = {
'post': post,
'posts': POSTS, # サイドバー用に追加
}
return render(request, 'blog/post_detail.html', context)
▶サイドバーを追加した結果
すべてのコードを追加して、サーバーを再起動してみましょう。
見てください!これがinclude
の力です:
- サイドバーがすべてのページに自動的に表示される
- カテゴリー、最近の投稿、アーカイブが綺麗に整理されている
- レスポンシブ対応で、スマホではサイドバーが下に移動する
- 一つのファイル(
sidebar.html
)を修正するだけで、全ページのサイドバーが更新される
テンプレートの継承とinclude
を組み合わせることで、こんなに柔軟でメンテナンスしやすい構造が作れるんです!
カスタムテンプレートタグを作る(発展)
Django の組み込みタグだけでは足りない場合、自分でタグを作ることもできます。
▶テンプレートタグ用のディレクトリを作成
blog/
├── templatetags/
│ ├── __init__.py # 空ファイル
│ └── blog_extras.py
▶簡単なカスタムタグを作成
blog/templatetags/blog_extras.py
を作成:
from django import template
from datetime import datetime
register = template.Library()
@register.simple_tag
def current_year():
"""現在の年を返す"""
return datetime.now().year
@register.simple_tag
def greeting():
"""時間帯に応じた挨拶を返す"""
hour = datetime.now().hour
if 5 <= hour < 12:
return "おはようございます"
elif 12 <= hour < 17:
return "こんにちは"
elif 17 <= hour < 21:
return "こんばんは"
else:
return "おやすみなさい"
@register.filter
def add_mark(value, mark="!"):
"""文字列の最後にマークを追加"""
return f"{value}{mark}"
▶カスタムタグを使う
テンプレートで使用する例:
blog/templates/blog/base.html
を修正:
{% load static %}
{% load blog_extras %}
<!DOCTYPE html>
<html lang="ja">
<!-- 省略 -->
<footer>
<p>© {% current_year %} My Blog. All rights reserved.</p>
<p>{% greeting %}、訪問ありがとうございます{{ ""|add_mark }}</p>
</footer>
{% load %}タグの記述位置
{% load blog_extras %}
はテンプレートファイルの先頭に書きます。
{% load static %}
の後に追加するのが一般的{% extends %}
がある場合は、その直後に書く- 各テンプレートファイルごとに記述が必要(継承されない)
例:
{% extends 'blog/base.html' %}
{% load blog_extras %}
{% block content %}
<!-- ここでカスタムタグが使える -->
{% endblock %}
カスタムタグを作った後は
開発サーバーを再起動する必要があります!
$ python manage.py runserver
実践:完成度の高いブログテンプレートを作ろう
今まで学んだことをすべて組み合わせて、プロフェッショナルなブログを作ってみましょう。
▶ホームページを改良
blog/views.py
のpost_list
関数を改良:
from django.shortcuts import render
# 共通の記事データ
POSTS = [
{
"id": 1,
"title": "Djangoを始めました",
"content": "Djangoの学習を始めました。楽しいです!",
"created_at": "2025-07-01",
"category": "Django",
"tags": ["Python", "Django", "初心者"],
"is_featured": True,
"body": """今日からDjangoの学習を始めました。
Pythonは少し触ったことがあったけど、Webフレームワークは初めてです。
最初は難しそうだと思ったけど、チュートリアルが分かりやすくて助かります。
これからブログアプリを作っていきたいと思います!""",
},
{
"id": 2,
"title": "ビューについて学んだこと",
"content": "今日はビューについて学びました。MVTパターンの理解が深まりました。",
"created_at": "2025-07-02",
"category": "Django",
"tags": ["Django", "View", "MVT"],
"is_featured": False,
"body": """Djangoのビューは、リクエストを受け取ってレスポンスを返す関数です。
MVTパターンのVに当たる部分で、ビジネスロジックを担当します。
テンプレートにデータを渡す方法も学びました。
contextという辞書を使うのが面白いです。""",
},
{
"id": 3,
"title": "テンプレートは便利",
"content": "テンプレートを使うとHTMLが書きやすいです。継承システムが特に便利!",
"created_at": "2025-07-03",
"category": "Django",
"tags": ["Django", "Template"],
"is_featured": True,
"body": """今日はテンプレートについて学習しました。
変数の表示、forループ、if文など、基本的な機能を試しました。
特に継承システムが素晴らしいです!
共通部分を一箇所にまとめられるのは、とても効率的ですね。""",
},
]
def post_list(request):
# 注目記事を抽出
featured_posts = [p for p in POSTS if p.get("is_featured", False)]
context = {
"posts": POSTS,
"featured_posts": featured_posts,
"total_posts": len(POSTS),
}
return render(request, "blog/post_list.html", context)
def post_detail(request, post_id):
# 指定されたIDの記事を探す
post = None
for p in POSTS:
if p["id"] == post_id:
post = p
break
context = {"post": post, "posts": POSTS}
return render(request, "blog/post_detail.html", context)
▶より洗練されたテンプレート
blog/templates/blog/post_list.html
を修正:
{% extends 'blog/base.html' %}
{% load blog_extras %}
{% block title %}ホーム - My Blog{% endblock %}
{% block content %}
<!-- ヒーローセクション -->
<section class="hero">
<h1>Welcome to My Blog</h1>
<p>{% greeting %}!Djangoで作られたブログへようこそ。</p>
</section>
<!-- 注目記事 -->
{% if featured_posts %}
<section class="featured-section">
<h2>注目記事</h2>
<div class="featured-grid">
{% for post in featured_posts %}
<div class="featured-post">
<h3>{{ post.title }}</h3>
<p>{{ post.content|truncatechars:100 }}</p>
<div class="tags">
{% for tag in post.tags %}
<span class="tag">#{{ tag }}</span>
{% endfor %}
</div>
<a href="{% url 'post_detail' post_id=post.id %}" class="btn btn-primary">
読む
</a>
</div>
{% endfor %}
</div>
</section>
{% endif %}
<!-- 最新記事 -->
<section class="posts-section">
<h2>最新記事</h2>
<div class="posts-grid">
{% for post in posts %}
{% include 'blog/includes/post_card.html' %}
{% empty %}
<p class="no-posts">まだ記事がありません。</p>
{% endfor %}
</div>
</section>
{% endblock %}
blog/templates/blog/includes/post_card.html
を修正:
<article class="post-card">
<div class="post-card-header">
<span class="category">{{ post.category }}</span>
<span class="date">{{ post.created_at }}</span>
</div>
<h3 class="post-card-title">
<a href="{% url 'post_detail' post_id=post.id %}">
{{ post.title }}
</a>
</h3>
<p class="post-card-excerpt">
{{ post.content|truncatechars:120 }}
</p>
<div class="post-card-footer">
<div class="tags">
{% for tag in post.tags %}
<span class="tag">#{{ tag }}</span>
{% endfor %}
</div>
<a href="{% url 'post_detail' post_id=post.id %}" class="read-more">
続きを読む →
</a>
</div>
</article>
最終的なスタイルも追加:
CSSファイルの完全版
これまでに追加したCSSをすべて含む、最終的なblog/static/blog/css/style.css
の全体を以下に示します。
もしすでにCSSを書いている場合は、以下の内容で置き換えてください。
▶完成したブログアプリケーション
ここまでの作業で、素晴らしいブログアプリケーションが完成しました!最終的にどのような見た目になったか、確認してみましょう。
記事一覧ページ
ヒーローセクション、注目記事、記事カード、サイドバーなど、すべての要素が美しく配置されています。最初の「Hello Django!」から、ここまで成長しました!
記事詳細ページ
記事の詳細ページも、読みやすく洗練されたデザインになりました。サイドバーには関連情報が表示され、本格的なブログサイトの雰囲気が出ています。
よくあるトラブルと解決方法
▶静的ファイルが読み込まれない
症状:CSS や JavaScript が反映されない
解決方法:
{% load static %}
がテンプレートの最初にあるか確認{% static 'パス' %}
の記述が正しいか確認- ファイルが正しい場所にあるか確認(
static/blog/...
) - 開発サーバーを再起動
- ブラウザのキャッシュをクリア
▶テンプレートが見つからない
症状:TemplateDoesNotExist
エラー
解決方法:
- テンプレートのパスが正しいか確認
templates/blog/
の構造を確認settings.py
のINSTALLED_APPS
にアプリが登録されているか確認
▶カスタムタグが使えない
症状:Invalid block tag
エラー
解決方法:
{% load タグファイル名 %}
を忘れていないか確認templatetags
フォルダに__init__.py
があるか確認- 開発サーバーを再起動
まとめ
今回は、Django テンプレートの強力な機能について学びました:
- テンプレートの継承で効率的にコードを書く方法
- 静的ファイルでページを美しくする方法
- 便利なタグとフィルタで表現力を高める方法
- includeで部品を再利用する方法
- カスタムタグで機能を拡張する方法
▶現場での開発について
ここで大切なことをお伝えします。実際の現場の開発では、最初から完璧なコードを書くことはほとんどありません。
今回の記事を振り返ってみてください:
- 最初は単純なHTMLから始めました
- テンプレート継承を導入してコードを整理しました
- CSSを追加して見た目を改善しました
- JavaScriptで動きを加えました
- includeで部品化して、さらに整理しました
このように、必要最小限の機能から始めて、徐々にリファクタリング(改善)していくのが、実際の開発現場でも一般的なアプローチです。
リファクタリングとは?
リファクタリング(Refactoring)は、コードの外部的な振る舞いを変えずに、内部構造を改善することです。
今回の例:
- 各ページに同じHTMLを書いていた → テンプレート継承で共通化
- スタイルをHTMLに直接書いていた → 外部CSSファイルに分離
- 同じような構造が繰り返されていた → includeで部品化
これらはすべてリファクタリングの例です!
▶あなたへのメッセージ
最初は「覚えることが多い...」と感じたかもしれません。でも、ここまでついてきたあなたは、本当にすごいです!
思い出してください。最初は「Hello Django!」という文字を表示するだけでした。それが今では、プロフェッショナルな見た目のブログサイトを作れるようになったんです。この成長は、あなたの努力の結果です。
プログラミングは、小さな一歩の積み重ねです。今日できなかったことが、明日にはできるようになる。その喜びを感じながら、一緒に進んでいきましょう。
そして、CSS を適用した瞬間の感動はどうでしたか?地味だったページが、一気にプロフェッショナルな見た目になりましたね。これが Web デザインの醍醐味です。
完璧を目指す必要はありません。大切なのは、動くものを作り、少しずつ改善していくこと。これができるようになったあなたは、もう立派な開発者の仲間入りです!
完成したコード
もし実行でエラーが出たり、不明な点がある場合は、以下の完成後のコードと比較してみてください:
https://github.com/techarm/django-blog-tutorial/tree/05-templates
特にbase.html
の継承構造、settings.py
の静的ファイル設定、CSSファイルの配置を確認することで、問題を解決できるでしょう。
次回予告
次回は、いよいよデータベースとモデルについて学びます!
今まではビューに直接データを書いていましたが、実際の Web アプリケーションでは、データベースにデータを保存します。
- モデルの作成方法
- データベースのマイグレーション
- 管理画面でのデータ操作
- モデルとビューの連携
データベースを使えるようになると、本格的な Web アプリケーションが作れるようになります。楽しみにしていてくださいね!
ここまで一緒に学んできたあなたなら、必ずマスターできます。自信を持って進みましょう!🚀
この記事はいかがでしたか?
もしこの記事が参考になりましたら、
高評価をいただけると大変嬉しいです!
皆様からの応援が励みになります。ありがとうございます! ✨