Tutorial 3: Class-based Views
はじめに
本記事は、Django REST framework の公式チュートリアルTutorial 3: Class-based Viewsの内容をもとに、和訳および補足解説を行ったものです。
基本的にはチュートリアルの翻訳を軸に構成しており、✏️補足
の箇所では、各コードセクションの補足や内部処理の解説を備忘録的に記載しています。
他のチュートリアル記事もあわせて読みたい方は、以下のまとめ記事からご覧いただけます👇
DRF(Django REST framework)公式チュートリアルを日本語でまとめてみた【全6回】
それでは、本チュートリアルを始めましょう。
関数ベースビューの代わりに、クラスベースビューを使ってAPIビューを書くこともできます。
これから見ていくように、これは共通の機能を再利用できる強力なパターンであり、コードをDRYに保つのに役立ちます。
クラスベースビューを使って、APIを書き直す
まずは、ルートビューをクラスベースビューとして書き直すことから始めましょう。それには、views.py
を少しリファクタリングするだけで済みます。
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
"""
Snippetの一覧を取得、もしくは新しいSnippetを作成する。
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
今のところ順調です。見た目は前のケース(関数ベースビューのコード)とよく似ていますが、HTTPメソッドごとの処理がより明確に分離されています。また、views.py
にあるインスタンスビューもあわせて更新する必要があります。
class SnippetDetail(APIView):
"""
Snippetインスタンスを取得・更新・削除する。
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
見た目は良好です。ただし、現時点では関数ベースビューとまだかなり似ています。
また、クラスベースビューを使用しているため、snippets/urls.py
も少しリファクタリングする必要があります。
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
path('snippets/', views.SnippetList.as_view()),
path('snippets/<int:pk>/', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
これで完了です。開発サーバーを起動すれば、これまでと同様に全てが正常に動作するはずです。
✏️補足
今回はパート2で書いた関数ベースビューをクラスベースビューでリファクタリングしています。
パート2で書いた関数ベースビュー @api_view()
も APIView
クラスを継承した WrappedAPIView
クラス内で dispatch()
メソッドを使用しているため、実は dispatch()
メソッドを呼んで Request
オブジェクトを生成するところまでは同じ処理を行なっています。
# 関数ベースビューのRequestオブジェクトを生成する内部処理
def api_view(http_method_names):
def decorator(func):
class WrappedAPIView(APIView):
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
# クラスベースビューのRequestオブジェクトを生成する内部処理
class WrappedAPIView(APIView):
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
両者の違いは、dispatch()
メソッドをオーバーライドして関数を実行するか、元の処理をそのまま利用するか にあります。
以下の関数ベースビューの dispatch()
メソッドでは、initialize_request()
メソッドでRequestオブジェクトを生成した後、return func(request, *args, **kwargs)
で任意の関数(例: snippet_list()
)を実行するようにオーバーライドされています。
# 関数ベースビュー
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
...
return func(request, *args, **kwargs)
以下のクラスベースビューの dispatch()
メソッドでは、オーバーライドしないもともとの dispatch()
メソッドの挙動として、Requestオブジェクトを生成した後、以下を実行しています。
-
getattr()
関数を使用して、ビューインスタンスからHTTPメソッド名(例: "get", "post")に応じたクラス内メソッドを取得
する
2. 取得したビューインスタンスメソッドを実行する
# クラスベースビュー
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
...
# ここでHTTPメソッド名に応じたクラス内メソッドを取得
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
# get() や post() などが呼ばれる
response = handler(request, *args, **kwargs)
return self.finalize_response(request, response, *args, **kwargs)
この1と2の処理により、SnippetListクラスで定義した下記2つのメソッドが実行され、Responseオブジェクトが返されます。
class SnippetList(APIView):
def get(self, request, format=None):
...
return Response(serializer.data)
def post(self, request, format=None):
...
if serializer.is_valid():
...
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
mixinを利用する
クラスベースビューを使う大きな利点の一つは、再利用可能な振る舞いをするパーツを簡単に組み合わせて構築できる点にあります。
今まで使ってきた create
・retrieve
・update
・delete
操作は、どのModelを利用したAPIビューでも非常に似通ったものになります。これらの共通の振る舞いは、REST framework の mixin
クラスで実装されています。
それでは mixin
クラスを使って、View を構成していきましょう。以下が対象のviews.py
になります。
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
このコードで何が起こっているのか、少し立ち止まって確認してみましょう。
現在は GenericAPIView
を基盤にビューを構築し、さらに ListModelMixin
と CreateModelMixin
を組み合わせています。
ベースクラス( GenericAPIView
)は中核となる機能を提供し、mixinクラスは .list()
および .create()
の操作を提供します。
そのうえで、get()
メソッドと post()
メソッドを、それぞれ適切な操作に明示的に結びつけています。
ここまでは比較的シンプルな内容です。
class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
よく似た構成です。今回も中核となる機能を提供するために GenericAPIView
を使用し、.retrieve()
、.update()
、.destroy()
の操作を実現するために mixin クラスを組み合わせています。
✏️補足
ここでは GenericAPIView クラス と 各mixinクラスを説明します。
GenericAPIViewクラス
GenericAPIViewクラスは View が対象とする Model や Serializer を定義するベースクラスです。
このクラスは、対象とするModel( queryset
)や Serializer( serializer_class
)を簡単に定義できる仕組みを提供します。
このクラスはAPIViewを継承しており、これ単体では get()
や post()
などのHTTPメソッドには対応していませんが、後述する ListModelMixin
や CreateModelMixin
、RetrieveModelMixin
、UpdateModeMixin
、DestroyModeMixin
の各種mixinクラスと組み合わせることで、API処理を簡潔に実装できるようになります。
組み合わせのイメージとしては以下の形です。
APIView
↑
GenericAPIView + mixins → CRUD APIビューが完成
ListModelMixin
Modelオブジェクトの一覧取得(GET)処理を提供するクラス。
get()
メソッドの中で self.list()
を組み合わせて呼ぶと、Querysetを取得してシリアライズされた一覧データを返す。
CreateModelMixin
Modelオブジェクトの作成(POST)処理を提供するクラス。
post()
メソッドの中で self.create()
を組み合わせて呼ぶと、Modelオブジェクトを作成して返す。
RetrieveModelMixin
特定のModelオブジェクトの取得(GET / pk)処理を提供するクラス。
get()
メソッドの中で self.retrieve()
を組み合わせて呼ぶと、対象のModelオブジェクトを取得してシリアライズされたデータを返す。
UpdateModelMixin
特定のModelオブジェクトの更新(PUT / PATCH)処理を提供するクラス。
put()
や patch()
の中で self.update()
を組み合わせて呼ぶと、対象のModelオブジェクトを更新する。
DestroyModelMixin
特定のModelオブジェクトの削除(DELETE)処理を提供するクラス。
delete()
の中で self.destroy()
を組み合わせて呼ぶと、対象のModelオブジェクトを削除する。
APIView
クラス と GenericView
クラス + mixinクラスを比較して見てみましょう。
# APIViewを使用(自力で処理を書く)
class SnippetList(APIView):
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
# GenericAPIView + mixin(ListModelMixin)を使用
class SnippetList(mixins.ListModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
APIViewクラスでは、クエリ取得からシリアライズ、レスポンス生成までの処理を全て明示的に書く必要がありますが、GenericAPIView
クラスでは Model と Serializer の指定だけで済み、後はmixinクラスのメソッドでCRUD処理(このコードではGETの一覧取得処理)を呼ぶだけで、簡潔に実装できます。
ジェネリッククラスベースビューを利用する
mixin クラスを使うことで、以前よりも少ないコードで View を書き直すことができましたが、さらにもう一歩進めることができます。
Django REST framework は、すでに必要な mixin が組み込まれたジェネリックビューのセットを提供しており、それを使うことで views.py
モジュールをさらに簡潔にできます。
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
かなり簡潔ですね。
多くの機能をほとんど手間なく手に入れられましたし、コードも綺麗で、Djangoらしい書き方になっています。
次は チュートリアルのパート4 に進み、API の認証と権限をどのように扱うか見ていきましょう。
✏️補足
ListCreateAPIView
このクラスは、ListModelMixin
、CreateModelMixin
、GenericAPIView
の3つを継承しており、モデルの一覧取得(GET)と 新規作成(POST)の処理を簡潔に実装できます。
queryset
と serializer_class
を指定するだけで、リクエストの種類に応じて自動で以下の処理を行い、JSONレスポンスを返してくれます。
・GET リクエスト:ListModelMixin
の list()
メソッドにより、Model の一覧データを取得
・POST リクエスト:CreateModelMixin
の create()
メソッドにより、新規オブジェクトを作成
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
RetrieveUpdateDestroyAPIView
このクラスは、RetrieveModelMixin
、UpdateModelMixin
、DestroyModelMixin
、GenericAPIView
の4つを継承しており、特定のモデルインスタンスの取得(GET)、更新(PUT / PATCH)、削除(DELETE)の処理を簡潔に実装できます。
ListCreateAPIView
クラスと同じように queryset
と serializer_class
を指定するだけで、リクエストの種類に応じて自動で以下の処理を行い、JSONレスポンスを返してくれます。
・GET リクエスト:RetrieveModelMixin
の retrieve()
メソッドにより、特定のモデルインスタンスを取得
・PUT / PATCH リクエスト:UpdateModelMixin
の update()
メソッドにより、モデルインスタンスを更新
・DELETE リクエスト:DestroyModelMixin
の destroy()
メソッドにより、モデルインスタンスを削除