DRFチュートリアル要旨3

## APIクラスベースビューについて

#### 基本

前回までは関数ベースでのAPIビューを見てきたが、今回からクラスベースビューを見ていく。
関数ベースでは`@api_view[]`を用いたが、クラスベースビューでAPIビューを書きたいときは`APIView`を継承する。

<details><summary>要旨2の内容をクラスベースビューに置き換え</summary><div>

```python

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)


```

</div></details>

また、クラスベースビューへリファクタリングしたので`snippets/urls.py`も書き換える必要がある。


```python

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の利用は通常のクラスベースビューのようにフレームワーク側が用意する汎用ビューを継承して使うことができる。
汎用ビューを使うと例えば先程のコードが以下のように圧縮されることになる。

<details><summary>汎用ビューを使ってリファクタリング</summary><div>


```python

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


```

</div></details>

genericsモジュールから各汎用ビューを継承していく(実際に書く際にはfrom rest_framework.generics import 各汎用ビュー……と読み込んでいく)。
`ListCreateAPIView`はList、Createの機能を持ったビューで、`RetrieveUpdateDestroyAPIView`はUpdate、PUT、Deleteの機能を持ったビューになる。
これらはそれぞれ`ListAPIView(一覧取得) + CreateAPIView(登録)`、`RetrieveAPIView(取得) + UpdateAPIView(更新) + DestroyAPIView(削除)`といったようにそれぞれの機能を持った汎用ビューを統合したものである。
こうやって書くとリクエストの内容に応じてフレームワーク側が自動的に役割を切り替えてくれるという寸法。
例えば、`SnippetList`クラスに対してGETのリクエストが来た場合はList、つまりは一覧表示の処理を行ってくるということになる。

そして、これの前段階としてmixinsでのクラスベースの書き方があるので理解を深めるためにも確認してみる。


<details><summary>mixinsで書いたクラスベースAPI</summary><div>


```python

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)



```


</div></details>

このコードをみると、先程の汎用ビューがどのような働きをしているのかというのがわかりやすくなると思う。
ポイントとしては実は先程の汎用ビューを含めて`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`を用いる際には必ず

```python

    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

```

`queryset ＝ モデルのインスタンス`及び`serializer_class = モデルのシリアライザー`を明示しておかないといけない。
もちろん継承先のクラスにも同じことが言えるので忘れないようにしたい。

## 参考

[DRFのGeneric viewの使い方](https://qiita.com/murapon/items/8a6a1715712ddd1a4ac1)
[DRF公式より ViewSets](https://www.django-rest-framework.org/api-guide/viewsets/)
[DRF公式より Generic views](https://www.django-rest-framework.org/api-guide/generic-views/#generic-views)