クラスビュー(CBV)リファクタリング
本章では、Djangoのクラスビュー(CBV)を学び、より簡潔なコードでホームページと詳細ページをリファクタリングします。
FBV vs CBV
これまでのビューはすべて関数で書いてきました(Function-Based View、FBV)。
Djangoにはさらにクラスビュー(Class-Based View、CBV)、クラスを用いてビューのロジックをカプセル化します。
| 特徴 | FBV(関数ビュー) | CBV(クラスビュー) |
|---|---|---|
| コード量 | 柔軟だが、よく使うロジックは繰り返し書く必要がある | 汎用ビューを継承すれば数行で済む |
| 可読性 | 直線的なフローで、ロジックが明確 | 継承チェーンを追う必要があるが、慣れれば一目瞭然 |
| 再利用性 | 関数でカプセル化 | Mixinの組み合わせ(Djangoの特徴) |
| 適用シーン | ロジックが単純で、フローが明確なページ | CRUDのようなパターン化されたページ |
| HTTPメソッドの振り分け | 手動で if request.method == 'POST' | 自動的に get() / post() メソッドへ振り分け |
両者に絶対的な優劣はなく、Djangoプロジェクトでは通常FBVとCBVを混在させる:複雑なロジックにはFBV、標準的なCRUDにはCBVを使用します。
ListView — リストページのリファクタリング
ホームページのindexビューは本質的に:記事リストの取得 → テンプレートの描画。
このようなパターン化されたロジックは、DjangoのListViewがすでに実装してくれています。
例
from django.views.generic import ListView
from django.db.models import Q
from .models import Post, Category
class PostListView(ListView):
"""ブログのホームページ:ListViewを継承し、記事リストを表示"""
model = Post # モデルを指定
template_name = 'blog/index.html' # テンプレートを指定(デフォルト:blog/post_list.html)
context_object_name = 'posts' # テンプレートで使用する変数名(デフォルト:object_list)
paginate_by = 12 # 1ページ12件(ページネーション)
ordering = ['-created_at'] # 並び替え
def get_queryset(self):
"""クエリメソッドをオーバーライド:カテゴリ絞り込み + キーワード検索に対応"""
queryset = super().get_queryset()
# カテゴリ絞り込み
self.category_slug = self.request.GET.get('category', '')
if self.category_slug:
queryset = queryset.filter(category__slug=self.category_slug)
# キーワード検索
self.keyword = self.request.GET.get('q', '')
if self.keyword:
queryset = queryset.filter(
Q(title__icontains=self.keyword) |
Q(summary__icontains=self.keyword)
)
return queryset
def get_context_data(self, **kwargs):
"""コンテキストメソッドをオーバーライド:テンプレートに追加データを渡す"""
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
context['category_slug'] = self.category_slug
context['keyword'] = self.keyword
context['title'] = 'サンプル ブログ - ホームページ'
return context
リファクタリング前後のコード量を比較すると:FBV 約40行 → CBV 約30行になり、しかもロジックが異なるメソッドに明確に分割されています。
DetailView — 詳細ページのリファクタリング
例
from django.views.generic import DetailView
class PostDetailView(DetailView):
"""記事詳細ページ:DetailViewを継承"""
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
# pk_url_kwarg:URL内で主キーを渡すパラメータ名(デフォルトは pk、ここでは明示的に宣言)
pk_url_kwarg = 'pk'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = f'{self.object.title} - サンプル ブログ'
return context
ルーティング設定の更新
CBVのルーティングの書き方はFBVとは少し異なります——呼び出す必要があります.as_view()。
例
from django.urls import path
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
# CBV:クラスの後に .as_view() を付けてビュー関数に変換する
path('', views.PostListView.as_view(), name='index'),
path('post/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),
# 他のビューは変更なし
path('register/', views.register, name='register'),
path('login/', auth_views.LoginView.as_view(
template_name='blog/login.html',
redirect_authenticated_user=True
), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
path('post/<int:pk>/favorite/', views.toggle_favorite, name='toggle_favorite'),
]
なぜCBVでは
.as_view()を使うのか?DjangoのURL設定は呼び出し可能オブジェクト(関数)を期待します。.as_view()クラスビューをDjangoのビュー関数シグネチャに適合する呼び出し可能オブジェクトに変換し、内部でHTTPメソッド(GET/POST)に応じてクラスの get() または post() メソッドに自動的に振り分けます。
よく使う汎用ビュー一覧
| ビュー | テンプレートのデフォルト名 | 用途 |
|---|---|---|
| ListView | モデル名_list.html | オブジェクトのリストを表示 |
| DetailView | モデル名_detail.html | 単一オブジェクトの詳細を表示 |
| CreateView | モデル名_form.html | 新しいオブジェクトを作成(フォームを含む) |
| UpdateView | モデル名_form.html | 既存のオブジェクトを更新 |
| DeleteView | モデル名_confirm_delete.html | オブジェクト削除の確認 |
| TemplateView | 指定が必要 | 静的テンプレートを描画(データ取得なし) |
Djangoの汎用ビューはWeb開発の約80%のシーンをカバーしています。要件を満たさない場合はFBVに戻ります。
LoginRequiredMixin
CBVでは@login_requiredデコレータ(それは関数用のもの)は使えません。代わりにLoginRequiredMixin。
例
from django.views.generic import ListView
class FavoriteListView(LoginRequiredMixin, ListView):
"""お気に入りリスト:ログインが必要"""
model = Post
template_name = 'blog/favorites.html'
context_object_name = 'posts'
ordering = ['-created_at']
# LoginRequiredMixin 未ログインユーザーは自動的に settings.LOGIN_URL へリダイレクトされる
def get_queryset(self):
# 現在のユーザーがお気に入りした記事のみ表示
return self.request.user.favorite_posts.all().order_by('-created_at')
Mixinの継承順序は重要です:
LoginRequiredMixin必ずListView前に書く必要があります。Pythonの多重継承の解決順序は左から右で、LoginRequiredMixinが先にログイン状態をチェックし、通過した後にListViewのロジックを実行します。
CBVはいつ使い、FBVはいつ使うのか?
経験則:
- FBV:ロジックが複雑で、条件分岐が多く、標準的なフローではないビュー(例:toggle_favorite)
- CBV:標準的なCRUD操作、ロジックを get/post に明確に分割できるビュー(例:リストページ、詳細ページ)
toggle_favorite(お気に入り切り替え)のような操作は、ロジックは単純ですがフローが通常のCRUDとは異なるため、FBVの方が適しています。
index や post_detail のような標準的なデータ表示ページでは、CBVがボイラープレートコードを大幅に削減できます。
本章のまとめ
本章では、Djangoのクラスビューの核心的な使い方を学びました:ListViewでリストを表示、DetailViewで詳細を表示、get_queryset/get_context_dataでクエリとコンテキストをカスタマイズ、as_view() でルーティングに適用、LoginRequiredMixin で login_required を代替します。
リファクタリング後のホームページと詳細ページのコードはより短く、構造がより明確になりました。
その他の拡張