Django REST framework（DRF）公式チュートリアル③ Class-based Views

# Tutorial 3: Class-based Views

### はじめに
本記事は、Django REST framework の公式チュートリアル[Tutorial 3: Class-based Views](https://www.django-rest-framework.org/tutorial/3-class-based-views/)の内容をもとに、和訳および補足解説を行ったものです。

基本的にはチュートリアルの翻訳を軸に構成しており、`✏️補足`の箇所では、各コードセクションの補足や内部処理の解説を備忘録的に記載しています。

他のチュートリアル記事もあわせて読みたい方は、以下のまとめ記事からご覧いただけます👇  
[DRF（Django REST framework）公式チュートリアルを日本語でまとめてみた【全6回】](https://qiita.com/yutty-kov/items/1850f3e865ec1a50a7bb)

それでは、本チュートリアルを始めましょう。
- - -

関数ベースビューの代わりに、クラスベースビューを使ってAPIビューを書くこともできます。
これから見ていくように、これは共通の機能を再利用できる強力なパターンであり、コードを<font color="#d14">DRY</font>に保つのに役立ちます。

### クラスベースビューを使って、APIを書き直す

まずは、ルートビューをクラスベースビューとして書き直すことから始めましょう。それには、<font color="#d14">`views.py`</font> を少しリファクタリングするだけで済みます。

```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):
    """
    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メソッドごとの処理がより明確に分離されています。また、<font color="#d14">`views.py`</font> にあるインスタンスビューもあわせて更新する必要があります。

```python:
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)
```

見た目は良好です。ただし、現時点では関数ベースビューとまだかなり似ています。
また、クラスベースビューを使用しているため、<font color="#d14">`snippets/urls.py`</font> も少しリファクタリングする必要があります。

```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)
```

これで完了です。開発サーバーを起動すれば、これまでと同様に全てが正常に動作するはずです。

## ✏️補足

今回は[パート2](https://qiita.com/yutty-kov/items/f5c33b15f6a7fdacf4ef)で書いた関数ベースビューをクラスベースビューでリファクタリングしています。
パート2で書いた関数ベースビュー `@api_view()` も `APIView `クラスを継承した `WrappedAPIView` クラス内で `dispatch()` メソッドを使用しているため、実は `dispatch()` メソッドを呼んで `Request` オブジェクトを生成するところまでは同じ処理を行なっています。

```python:
# 関数ベースビューの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()`）を実行するようにオーバーライドされています。
```python:
# 関数ベースビュー
def dispatch(self, request, *args, **kwargs):
    request = self.initialize_request(request, *args, **kwargs)
    ...
    return func(request, *args, **kwargs)
```
<br>

以下のクラスベースビューの `dispatch()` メソッドでは、オーバーライドしないもともとの `dispatch()`メソッドの挙動として、Requestオブジェクトを生成した後、以下を実行しています。
1. `getattr()` 関数を使用して、ビューインスタンスからHTTPメソッド名（例: "get", "post"）に応じたクラス内メソッドを取得

する
2. 取得したビューインスタンスメソッドを実行する
```python:
# クラスベースビュー
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クラスで定義した下記２つのメソッドが実行され、Responseオブジェクトが返されます。
```python:
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 を構成していきましょう。以下が対象の<font color="#d14">`views.py`</font>になります。

```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)
```

このコードで何が起こっているのか、少し立ち止まって確認してみましょう。
現在は <font color="#d14">`GenericAPIView`</font> を基盤にビューを構築し、さらに <font color="#d14">`ListModelMixin`</font> と <font color="#d14">`CreateModelMixin`</font> を組み合わせています。

ベースクラス（ `GenericAPIView` ）は中核となる機能を提供し、mixinクラスは <font color="#d14">`.list()`</font> および <font color="#d14">`.create()`</font> の操作を提供します。
そのうえで、<font color="#d14">`get()`</font> メソッドと <font color="#d14">`post()`</font> メソッドを、それぞれ適切な操作に明示的に結びつけています。
ここまでは比較的シンプルな内容です。

```python:
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)
```

よく似た構成です。今回も中核となる機能を提供するために <font color="#d14">`GenericAPIView`</font> を使用し、<font color="#d14">`.retrieve()`</font>、<font color="#d14">`.update()`</font>、<font color="#d14">`.destroy()`</font> の操作を実現するために 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オブジェクトを削除する。
<br>

`APIView` クラス と `GenericView` クラス + mixinクラスを比較して見てみましょう。


```python:
# 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 が組み込まれたジェネリックビューのセットを提供しており、それを使うことで <font color="#d14">`views.py`</font> モジュールをさらに簡潔にできます。

```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
```

かなり簡潔ですね。
多くの機能をほとんど手間なく手に入れられましたし、コードも綺麗で、Djangoらしい書き方になっています。

次は [<font color="#d14">チュートリアルのパート4</font>](https://qiita.com/yutty-kov/items/2ddd38f4cbc4eb4ec00f) に進み、API の認証と権限をどのように扱うか見ていきましょう。


## ✏️補足
**ListCreateAPIView**
このクラスは、`ListModelMixin`、`CreateModelMixin`、`GenericAPIView` の3つを継承しており、モデルの一覧取得（GET）と 新規作成（POST）の処理を簡潔に実装できます。

`queryset` と `serializer_class` を指定するだけで、リクエストの種類に応じて自動で以下の処理を行い、JSONレスポンスを返してくれます。

・GET リクエスト：`ListModelMixin` の `list()` メソッドにより、Model の一覧データを取得  
・POST リクエスト：`CreateModelMixin` の `create()` メソッドにより、新規オブジェクトを作成

```python
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()` メソッドにより、モデルインスタンスを削除