APIクラスベースビューについて
基本
前回までは関数ベースでのAPIビューを見てきたが、今回からクラスベースビューを見ていく。
関数ベースでは@api_view[]
を用いたが、クラスベースビューでAPIビューを書きたいときはAPIView
を継承する。
要旨2の内容をクラスベースビューに置き換え
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):
"""
List all snippets, or create a new 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)
class SnippetDetail(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
# インスタンス単体を取得
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)
mixinsについて
クラスベースビューでのAPIの利用は通常のクラスベースビューのようにフレームワーク側が用意する汎用ビューを継承して使うことができる。
汎用ビューを使うと例えば先程のコードが以下のように圧縮されることになる。
汎用ビューを使ってリファクタリング
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
genericsモジュールから各汎用ビューを継承していく(実際に書く際にはfrom rest_framework.generics import 各汎用ビュー……と読み込んでいく)。
ListCreateAPIView
はList、Createの機能を持ったビューで、RetrieveUpdateDestroyAPIView
はUpdate、PUT、Deleteの機能を持ったビューになる。
これらはそれぞれListAPIView(一覧取得) + CreateAPIView(登録)
、RetrieveAPIView(取得) + UpdateAPIView(更新) + DestroyAPIView(削除)
といったようにそれぞれの機能を持った汎用ビューを統合したものである。
こうやって書くとリクエストの内容に応じてフレームワーク側が自動的に役割を切り替えてくれるという寸法。
例えば、SnippetList
クラスに対してGETのリクエストが来た場合はList、つまりは一覧表示の処理を行ってくるということになる。
そして、これの前段階としてmixinsでのクラスベースの書き方があるので理解を深めるためにも確認してみる。
mixinsで書いたクラスベースAPI
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)
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
というビューが大本でそれを継承した派生クラスであるというところである。
当のGenericAPIView
自身もAPIView
を継承したクラスである……ということはおいておいて、GenericAPIView
は自身はDjangoにおけるGenericView
のように枠組みは用意してあげるからあとは自分で作ってねとフレームワーク側から渡される白図面のようなものです。
自作する場合はGenericAPIView
にmixins
の機能を1つ以上組み合わせて作っていくということになり、それをやっているのがこのコードということになります。
また当然、この場合処理にあたるメソッドも自分で定義する必要があります。
上記の例だと例えばRetrieveModelMixin
を継承した場合は、.retrieve()
と1件取得のメソッドが使えるようになるのでそれを使って何か取得するような処理を書かないといけない。
対して先程のListCreateAPIView
などはフレーム側が「まあよくある組み合わせだよね」と用意してくれたいわばプリセットのようなものになる。
なので、用途がはっきり決まっているので先述のように処理を書かなくてもフレームワーク側で判断してくれるということになる。
こうみるとそれでは最初から汎用ビューだけでいいのでは? と思うところだが、mixins
という仕組みが何故あるのかというのを自分なりに考えると、CRUDという仕組みにその要因があるのだと思う。
CRUDはシンプルに機能を実装できる反面、複雑な処理のハンドリングをやるのに向いてないという弱点があると私は思っている。
例えばここでは詳しく触れないが、GenericAPIView
を継承したModelViewSet
というものがある。
これを継承するとmixins
のすべての機能を持ったビューを作ることができる。
で、そのビューでユーザー登録をCRUDで作ろうとした時、ソーシャルログインを導入するからCreateの部分だけはdjango-allauth
で作りたいということを思ったとする。
ところがCRUDでビュー(コントローラー)を作るときの決まりごとのようなものとして使わないメソッド(文脈の都合上、以後ここではmixin
とする)は削除するというものがある。余計なものは残しておかないということだ。
その決まり事に従おうとするとこのケースでは詰んでしまうので、そういうときのためにmixins
を用いてビューをカスタマイズできるようにしているのだろう。
余談ではあるが、実際にはビューをカスタマイズする他にもHTTPリクエストを制限したりするなど他にも方法はあるようだ。
閑話休題、最後にGenericAPIView
を用いる際には必ず
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
queryset = モデルのインスタンス
及びserializer_class = モデルのシリアライザー
を明示しておかないといけない。
もちろん継承先のクラスにも同じことが言えるので忘れないようにしたい。