Django クラスベースビュー
(CBV)完全ガイド
クラスベースビューを使えば、CRUDや一覧表示をわずか数行で実装できます。
こんな人向けの記事です
- Django のビューをクラスで書きたい人
- ListView、DetailView などの汎用ビューを使いたい人
- 関数ビューとの違いを理解したい人
Step 1関数ビュー vs クラスベースビュー
views.py
# 関数ビュー(FBV)
from django.shortcuts import render, get_object_or_404
from .models import Article
def article_list(request):
articles = Article.objects.filter(is_published=True)
return render(request, 'blog/article_list.html', {'articles': articles})
def article_detail(request, pk):
article = get_object_or_404(Article, pk=pk)
return render(request, 'blog/article_detail.html', {'article': article})
# クラスベースビュー(CBV) — 同じことをより簡潔に
from django.views.generic import ListView, DetailView
class ArticleListView(ListView):
model = Article
template_name = 'blog/article_list.html'
context_object_name = 'articles'
queryset = Article.objects.filter(is_published=True)
class ArticleDetailView(DetailView):
model = Article
template_name = 'blog/article_detail.html'
context_object_name = 'article'urls.py
from django.urls import path
from . import views
urlpatterns = [
# 関数ビュー
path('articles/', views.article_list),
# クラスベースビュー(.as_view() が必要)
path('articles/', views.ArticleListView.as_view()),
path('articles/<int:pk>/', views.ArticleDetailView.as_view()),
]Step 2TemplateViewとListView
views.py
from django.views.generic import TemplateView, ListView
# 静的ページ
class HomeView(TemplateView):
template_name = 'home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'ホームページ'
return context
# 一覧表示(ページネーション付き)
class ArticleListView(ListView):
model = Article
template_name = 'blog/article_list.html'
context_object_name = 'articles'
paginate_by = 10 # 10件ずつ表示
ordering = ['-published_at'] # 新しい順
def get_queryset(self):
qs = super().get_queryset().filter(is_published=True)
# 検索キーワードがあればフィルタ
keyword = self.request.GET.get('q')
if keyword:
qs = qs.filter(title__icontains=keyword)
return qsページネーション
paginate_byを設定するだけで、テンプレートでpage_objを使ったページ送りが可能です。Step 3DetailViewとCreateView
views.py
from django.views.generic import DetailView, CreateView
from django.urls import reverse_lazy
from .models import Article
from .forms import ArticleForm
# 詳細表示
class ArticleDetailView(DetailView):
model = Article
template_name = 'blog/article_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['related'] = Article.objects.filter(
classification=self.object.classification
).exclude(pk=self.object.pk)[:5]
return context
# 新規作成
class ArticleCreateView(CreateView):
model = Article
form_class = ArticleForm
template_name = 'blog/article_form.html'
success_url = reverse_lazy('article-list')
def form_valid(self, form):
form.instance.author = self.request.user # ログインユーザーを設定
return super().form_valid(form)Step 4UpdateViewとDeleteView
views.py
from django.views.generic import UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin
# 更新
class ArticleUpdateView(LoginRequiredMixin, UpdateView):
model = Article
form_class = ArticleForm
template_name = 'blog/article_form.html'
def get_success_url(self):
return reverse_lazy('article-detail', kwargs={'pk': self.object.pk})
def get_queryset(self):
# 自分の記事のみ編集可能
return super().get_queryset().filter(author=self.request.user)
# 削除
class ArticleDeleteView(LoginRequiredMixin, DeleteView):
model = Article
template_name = 'blog/article_confirm_delete.html'
success_url = reverse_lazy('article-list')
def get_queryset(self):
return super().get_queryset().filter(author=self.request.user)urls.py(CRUD全体)
urlpatterns = [
path('', ArticleListView.as_view(), name='article-list'),
path('<int:pk>/', ArticleDetailView.as_view(), name='article-detail'),
path('create/', ArticleCreateView.as_view(), name='article-create'),
path('<int:pk>/edit/', ArticleUpdateView.as_view(), name='article-update'),
path('<int:pk>/delete/', ArticleDeleteView.as_view(), name='article-delete'),
]Step 5Mixinでカスタマイズ
views.py
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
# ログイン必須
class MyView(LoginRequiredMixin, ListView):
login_url = '/login/'
model = Article
# 権限チェック
class AdminArticleView(PermissionRequiredMixin, UpdateView):
permission_required = 'blog.change_article'
model = Article
# カスタムMixin
class AuthorRequiredMixin:
"""投稿者のみアクセス可能にするMixin"""
def get_queryset(self):
return super().get_queryset().filter(author=self.request.user)
class MyArticleUpdateView(LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
model = Article
form_class = ArticleForm
# JSON レスポンス用 Mixin
from django.http import JsonResponse
class JsonResponseMixin:
def render_to_response(self, context, **kwargs):
return JsonResponse(self.get_json_data(context))
def get_json_data(self, context):
return {'object': str(context['object'])}
class ArticleJsonView(JsonResponseMixin, DetailView):
model = ArticleStep 6実践パターン
views.py
from django.views.generic import FormView
from django.contrib import messages
# FormView: フォーム処理に特化
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = reverse_lazy('contact-done')
def form_valid(self, form):
form.send_email()
messages.success(self.request, '送信しました')
return super().form_valid(form)
# 複数のHTTPメソッドを処理
from django.views import View
class ArticleAPIView(View):
def get(self, request, pk):
article = get_object_or_404(Article, pk=pk)
return JsonResponse({'title': article.title})
def post(self, request):
# 作成処理
pass
def delete(self, request, pk):
article = get_object_or_404(Article, pk=pk)
article.delete()
return JsonResponse({'status': 'deleted'})CBVの注意点
汎用ビューの内部動作(メソッド呼び出し順序)を理解していないと、カスタマイズ時に混乱します。ccbv.co.uk でソースコードを確認しましょう。Step 7redirect() によるページ遷移
ビュー内でフォーム処理やデータ保存後に別ページへ遷移させたい場合は、redirect() を使います。
views.py
from django.shortcuts import redirect, render
class ArticleCreateView(View):
def get(self, request):
form = ArticleForm()
return render(request, 'blog/article_form.html', {'form': form})
def post(self, request):
form = ArticleForm(request.POST)
if form.is_valid():
form.save()
# urls.py の name で指定したページへリダイレクト
return redirect('article-list')
else:
# バリデーション失敗時は元のページに戻す
return render(request, 'blog/article_form.html', {'form': form})redirect() のポイント
redirect() の引数には urls.py の name を渡します。URLを直接書くのではなく、name を使うことで URL 構成を変更しても影響を受けません。なお、CBV の
CreateView や UpdateView では success_url 属性で同様のリダイレクトを宣言的に設定できます(Step 3 参照)。Step 8include() と namespace によるURL分割
アプリが増えると、すべての URL をプロジェクトの urls.py に書くのは管理しづらくなります。include() を使ってアプリごとに urls.py を分割し、namespace で名前空間を付けましょう。
プロジェクトの urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# include() でアプリの urls.py を読み込み、namespace を設定
path('blog/', include(('blog.urls', 'blog'), namespace='blog')),
path('shop/', include(('shop.urls', 'shop'), namespace='shop')),
]アプリの urls.py(blog/urls.py)
from django.urls import path
from . import views
urlpatterns = [
path('', views.ArticleListView.as_view(), name='article-list'),
path('<int:pk>/', views.ArticleDetailView.as_view(), name='article-detail'),
]テンプレートやビューでの使い方
# ビューで namespace 付きの name を使う
return redirect('blog:article-list')
# テンプレートで namespace 付きの name を使う
# <a href="{% url 'blog:article-detail' article.pk %}">記事を見る</a>namespace のメリット
複数アプリで同じ name(例: list)を使っても、blog:list、shop:list のように区別できます。include() の第1引数はタプル ('アプリ.urls', 'app_name') の形式で指定します。まとめ
- ListView, DetailView でCRUD処理を簡潔に記述
- paginate_by でページネーションを自動化
- LoginRequiredMixin で認証チェックを追加
- カスタムMixinで共通ロジックを再利用
- URLパターンでは
.as_view()を忘れずに redirect()でname指定のページ遷移include()とnamespaceでURL管理を整理