ビューとテンプレート — 記事リストのレンダリング
この章では、Django ビューを使ってデータベースからデータを取得し、テンプレートと組み合わせてブログのホームページの記事カードリストをレンダリングする方法を学びます。
ビュー(View)の役割
ビューは Django の中核ロジック層であり、以下を担当します:
- ユーザーのリクエスト(
requestパラメータ) - データベースから必要なデータをクエリする
- データをテンプレートに渡して HTML をレンダリングし、ユーザーに返す
Django ビューには2つの書き方があります:関数ビュー(FBV)とクラスビュー(CBV)。
これまでの8章では FBV を優先的に使用してきました。FBV はより直感的で、初心者がリクエスト処理の流れを理解するのに適しています。
関数ビューの基本
Django のビュー関数は、requestパラメータを受け取り、HttpResponseオブジェクトを返します。
実例
from django.shortcuts import render
from .models import Post
def index(request):
"""ブログホームページ:すべての記事を表示"""
# データベースからすべての記事をクエリ(時間の降順)
posts = Post.objects.all().order_by('-created_at')
# render の3つのパラメータ:
# 1. request — リクエストオブジェクト
# 2. テンプレートパス — blog/index.html
# 3. context 辞書 — テンプレートに渡すデータ
context = {
'posts': posts,
'title': 'サンプル ブログ - ホームページ'
}
return render(request, 'blog/index.html', context)
render()は Django で最もよく使われるショートカット関数で、テンプレートのロード → データの注入 → HTML の生成 → レスポンスの返却を組み合わせています。
Django テンプレート構文
Django テンプレート構文は Vue3 のテンプレート構文と非常に似ていますが、いくつかの重要な違いがあります。
| 用途 | Django テンプレート | Vue3 テンプレート |
|---|---|---|
| 変数の出力 | {{ variable }} | {{ variable }} |
| ループ | {% for item in list %}...{% endfor %} | v-for="item in list" |
| 条件 | {% if cond %}...{% endif %} | v-if="cond" |
| URL 生成 | {% url 'name' %} | router-link to="name" |
| レイアウトの継承 | {% extends 'base.html' %} | コンポーネントのネスト |
重要な違い:Django テンプレートは
{% %}制御タグを包みます。Vue3 はv-ディレクティブを使用します。サーバーサイドでレンダリングされます。HTML が生成されてからブラウザに送信されます。Vue3 はブラウザサイドでレンダリングされます。ブラウザが JS を受け取った後に HTML を組み立てます。
テンプレート継承:base.html
複数のページで同じナビゲーションバーとフッターを共有するには、テンプレート継承を使って重複を避けます。
親テンプレートは「ブロック(block)」を定義し、子テンプレートが具体的な内容を埋め込みます。
base.html 親テンプレートを作成
実例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}サンプル ブログ{% endblock %}</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
color: #333;
}
.navbar {
display: flex; justify-content: space-between; align-items: center;
padding: 16px 40px; background: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.navbar .logo { font-size: 22px; font-weight: bold; color: #2c3e50; text-decoration: none; }
.navbar a { margin-left: 20px; text-decoration: none; color: #333; }
.container { max-width: 960px; margin: 40px auto; padding: 0 20px; }
.footer { text-align: center; padding: 20px; color: #999; border-top: 1px solid #eee; }
.section-title { font-size: 24px; margin-bottom: 20px; }
</style>
{% block extra_head %}{% endblock %}
</head>
<body>
<!-- ナビゲーションバー -->
<header class="navbar">
<a href="/" class="logo">サンプル ブログ</a>
<nav>
<a href="/">ホーム</a>
<a href="#">概要</a>
</nav>
</header>
<!-- メインコンテンツ領域(子テンプレートが埋め込み) -->
<main class="container">
{% block content %}
{% endblock %}
</main>
<!-- フッター -->
<footer class="footer">
<p>© 2024 サンプル ブログ. Powered by Django.</p>
</footer>
</body>
</html>
index.html 子テンプレートを作成
実例
<!-- extends は必ず先頭に置く必要があります -->
{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h2 class="section-title">最新の記事</h2>
{% if posts %}
<div class="article-grid">
{% for post in posts %}
<div class="article-card">
<div class="card-content">
<span class="card-category">{{ post.category.name }}</span>
<h3>{{ post.title }}</h3>
<p>{{ post.summary|truncatechars:80 }}</p>
<span class="card-date">{{ post.created_at|date:"Y-m-d" }}</span>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p class="empty-tip">まだ記事がありません。お楽しみに。</p>
{% endif %}
{% endblock %}
テンプレートで使ったいくつかの知識ポイント:
| 構文 | 意味 |
|---|---|
{% extends 'blog/base.html' %} | 親テンプレートを継承。必ず最初の行に書く |
{% block content %}...{% endblock %} | 親テンプレートで定義された content ブロックを埋め込む |
{{ post.category.name }} | 外部キーを介して関連オブジェクトの属性にアクセス |
{{ post.summary|truncatechars:80 }} | フィルター:先頭80文字を切り出す |
{{ post.created_at|date:"Y-m-d" }} | フィルター:日付をフォーマット |
Django テンプレートのフィルターはパイプ記号
|で表し、変数の後に続けて出力をフォーマットします。よく使われるフィルター:date、truncatechars、length、default。
記事リストのスタイルを追加
base.html の style タグに以下の CSS を追加します:
実例
.article-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
}
.article-card {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
transition: transform 0.2s, box-shadow 0.2s;
}
.article-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.card-content {
padding: 20px;
}
.card-category {
display: inline-block;
padding: 2px 10px;
background: #E8EEF5;
color: #3D71A6;
border-radius: 12px;
font-size: 12px;
margin-bottom: 8px;
}
.article-card h3 {
font-size: 18px;
margin-bottom: 8px;
color: #222;
}
.article-card p {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-bottom: 12px;
}
.card-date {
font-size: 12px;
color: #999;
}
.empty-tip {
text-align: center;
color: #999;
padding: 60px 0;
font-size: 16px;
}
実践:ホームページの効果を確認
以下の点が完了していることを確認してください:
- models.py で Post と Category モデルを定義し、migrate 済みである
- Admin バックエンドに少なくとも3〜5件の記事(異なるカテゴリに属する)が登録されている
- views.py の index 関数がデータベースから記事をクエリしてテンプレートに渡す
- urls.py で / ルートが index ビューを指している
サーバーを起動してホームページにアクセスすると、データベースのデータから動的にレンダリングされた記事カードリストが表示されるはずです。
本章のまとめ
この章では、Django ビューとテンプレートの核となる連携を習得しました:views.py がデータベースからデータをクエリして context に渡す、render() がテンプレートをレンダリングする、base.html 親テンプレートがレイアウト + block を定義する、子テンプレートが extends で継承して内容を埋め込む、Django テンプレート構文({{ }}、{% %}、フィルター)。
これでブログのホームページはデータベース駆動になり、ハードコーディングではなくなりました。
その他の拡張