一覧画面と詳細画面は Web アプリケーションで最も頻繁に登場するパターンです。Django の汎用クラスベースビュー ListView と DetailView を使えば、この 2 つの画面をわずか数行で実装できます。
ListView で一覧画面を作る
ListView はモデルのレコード一覧を取得してテンプレートに渡すビューです。
from django.views.generic import ListView from .models import Article class ArticleListView(ListView): model = Article template_name = 'articles/list.html' context_object_name = 'articles'
これだけで、Article テーブルの全レコードを取得し、テンプレート変数 articles として渡す処理が完成します。
template_name を省略すると、Django は アプリ名/モデル名_list.html(例: articles/article_list.html)を自動的に探しにいきます。context_object_name を省略した場合は object_list という名前になります。明示的に指定しておく方が、テンプレート側で何のデータかわかりやすくなります。
テンプレートはシンプルなループで書けます。
{% extends "base.html" %}
{% block content %}
<h1>記事一覧</h1>
{% for article in articles %}
<div>
<h2><a href="{% url 'article_detail' article.pk %}">{{ article.title }}</a></h2>
<p>{{ article.body|truncatechars:100 }}</p>
</div>
{% empty %}
<p>記事がありません。</p>
{% endfor %}
{% endblock %}
{% empty %} は for ループの結果が 0 件だったときに表示されるブロックです。
queryset で表示対象を絞り込む
全件ではなく条件を絞りたい場合は queryset を上書きします。
class ArticleListView(ListView): model = Article template_name = 'articles/list.html' context_object_name = 'articles' queryset = Article.objects.filter(is_draft=False).order_by('-published_at')
さらに動的な条件(URL パラメータに応じた絞り込みなど)が必要なら、get_queryset メソッドをオーバーライドします。
class ArticleListView(ListView): model = Article template_name = 'articles/list.html' context_object_name = 'articles' def get_queryset(self): qs = Article.objects.filter(is_draft=False) category = self.request.GET.get('category') if category: qs = qs.filter(category__name=category) return qs
ページネーション
paginate_by を設定するだけで、自動的にページ分割されます。
class ArticleListView(ListView): model = Article template_name = 'articles/list.html' context_object_name = 'articles' paginate_by = 10
テンプレート側では page_obj を使ってページ送りの UI を構築します。
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">前へ</a>
{% endif %}
<span>{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">次へ</a>
{% endif %}
ページネーションの処理を自分で書くと、境界値のチェックやクエリパラメータの処理でコードが膨らみがちですが、ListView なら paginate_by の 1 行で済みます。
DetailView で詳細画面を作る
DetailView は URL から主キー(pk)やスラッグを受け取り、該当する 1 件のレコードをテンプレートに渡します。
from django.views.generic import DetailView class ArticleDetailView(DetailView): model = Article template_name = 'articles/detail.html' context_object_name = 'article'
URL の設定で <int:pk> を使い、主キーをビューに渡します。
from django.urls import path from .views import ArticleListView, ArticleDetailView urlpatterns = [ path('articles/', ArticleListView.as_view(), name='article_list'), path('articles/<int:pk>/', ArticleDetailView.as_view(), name='article_detail'), ]
該当するレコードが存在しない場合、DetailView は自動的に 404 ページを返します。get_object_or_404 を自分で呼ぶ必要はありません。
モデルの一覧を取得してテンプレートに渡す。paginate_by でページ分割も可能
URL パラメータから 1 件を特定してテンプレートに渡す。存在しなければ自動で 404
get_context_data で追加のコンテキストを渡す
テンプレートにモデルのデータ以外の情報も渡したい場合は get_context_data をオーバーライドします。
class ArticleDetailView(DetailView): model = Article template_name = 'articles/detail.html' context_object_name = 'article' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['related_articles'] = Article.objects.filter( category=self.object.category ).exclude(pk=self.object.pk)[:5] return context
super().get_context_data(**kwargs) で元のコンテキスト(article オブジェクト等)を引き継ぎ、そこに related_articles を追加しています。self.object で現在表示中のレコードにアクセスできる点も覚えておくと便利です。
ListView と DetailView のまとめ
ListView と DetailView は、Django アプリケーションで最も使用頻度の高い汎用ビューです。基本的な使い方はモデルとテンプレートを指定するだけ、カスタマイズが必要になったら get_queryset・get_context_data・get_object といったメソッドをオーバーライドする——この段階的なアプローチが汎用ビューの設計思想になっています。
まずはシンプルな設定で動かし、要件に応じてメソッドを上書きしていくのが、汎用ビューを効率よく使いこなすコツです。