Django REST Frameworkを理解する

## 初めに
Dango REST Framework = DRFを活用するにあたり、
メリットたくさんあるけどかなり複雑だなと感じたので、
学習したないようをアウトプットしたいと思います。

## 目次
- DRFとは？
- 全体像をつかもう
- シリアライザーを理解する
- ビューを理解しよう
- まとめ

## DRFとは？
DRFはREST APIを作成するにあたり、多様なニーズに応えるためのDjangoのパッケージです。
Djangoだけでは満たせないたくさんの機能を補完・提供してくれます。

**REST APIについても解説します。**
REST APIはスマホやSPA（シングルアプリケーション）のバックエンドとしてよく利用されています。そして、URIでリソースを特定し、HTTPメソッドでリソースに対して操作するという直感的で理解しやすい設計となっていることが特徴的です。

**DRFが最も得意とするのが単一リソースのCRUDを処理するREST APIです。**
GET,POST,PUT,DELETEなどのHTTPメソッドに意味を持たせて、リソースをURIと組み合わせ『リソースの一覧取得』、『リソースの詳細取得』、『リソースの更新』などのAPIを作成します。

**しかも、単一リソース以外の扱いもできるます。**
上記の通り、単一リソースを扱うAPIの作成以外にも、
- リソースがネストしたAPI
- 独自のアクションを追加したAPI
- リソースに紐づいてないAPI
の作成もできます。

**次はDRFを使うメリットについてです。**
まず私が思ったのは、非常に人気が高いことから知見や解説情報が多いので、学習コストが低いなと感じました。わからないことあればだいたい出てきます。
さらに、以下のように機能がかなり充実しています。
- Cookie認証・トークン認証・JWT認証などの認証
- パーミッション（アクセス権）
- フィルタリング
- ページネーション
- スロットリング（利用回数制限）
など（実際まだまだあると思います）

**ただ、複雑なのがデメリットです。**

## 全体像をつかもう

#### Django プロジェクトの全体像

まず比較のために、Djangoプロジェクトの全体像と登場人物について説明します。図の左側から来たリクエストを、URLディスパッチャと呼ばれる Django のコアコンポーネントがハンドリングし、URLconf で登録したビュー関数を呼び出して、ビューの中でフォームやモデル、テンプレートを使って、レスポンスを作成して返し、URLディスパッチャがレスポンスを処理するという流れになっています。ミドルウェアは、ビューが呼び出される前や後で何らかの処理をするために利用されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3895655/083c9f6c-7bea-710d-14b9-5e738d64a27c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3895655/c6c37470-9dbe-8529-11d2-4a98dccf29ca.png)

####  DRFプロジェクト
REST API を構築するためのDRFプロジェクトでは、（HTML のフォーム要素を扱わないし、レスポンスとして画面をレンダリングした HTML を返さないので）基本的にフォームとテンプレートは使いません（後述しますが、Django を共存させる場合にはその限りではありません）。その代わり、DRFでは「シリアライザ」というコンポーネントを使います。シリアライザは入出力の JSON（およびモデルとの連携）定義とバリデーションを担当します。シリアライザはフォームと似たように使うことができます。（感想）

**起点となるのは「DRF 用ビュー」です。図では「✕」で示していますが、通常の Django のビューを URLconf に登録しておくことも可能です。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3895655/655b7b42-9a79-4172-858b-8d533f3fbe05.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3895655/436133fe-2505-d5cb-5afc-f2665598dc74.png)


## シリアライザを理解する


#### シリアライザはフォームぽく使える

登録API、更新API、取得API のそれぞれでシリアライザの使いどころが異なります。
例えば登録 API だと、リクエストされた JSON データを元にしてシリアライザオブジェクトを作成し、シリアライザオブジェクトのバリデーションメソッドを実行し、シリアライザの永続化メソッドを実行して、最後にレスポンスオブジェクトの引数にシリアライザのデータを渡すという流れになります。
このように、ほとんどの場合でシリアライザを使うことになるため、
**DRF を使うときにはシリアライザは避けて通ることができません。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3895655/819ca960-e3b7-ea64-7a09-c782f7180fb1.png)

シリアライザの実装イメージは次のようになります。

```python
from rest_framework import serializers
from.models import Tweet,Comment,Like
from django.contrib.auth.models import User
from bookmarks.models import Bookmark

class TweetSerializer(serializers.ModelSerializer):
    user = UserSerializer(read_only=True)
    comments = serializers.SerializerMethodField()
    likes_count = serializers.SerializerMethodField()
    user_has_liked = serializers.SerializerMethodField()
    is_bookmarked = serializers.SerializerMethodField()

    class Meta:
        model = Tweet
        fields = ('id', 'content', 'user', 'created_at', 'comments', 'likes_count', 'user_has_liked', 'is_bookmarked')

```

## ビューを理解しよう
DRF には『APIVie』というクラスが用意されているのですが、DRFの起点となる基底ビュークラスで、これを継承することで、さまざまな前処理と後処理をやってくれます。

1. リクエストオブジェクトをDRF用に変換
2. レンダラクラスとメディアタイプを決定
3. バージョニング
4. 認証チェック
5. パーミッションチェック
6. スロットリング
7. 例外ハンドリング

2～6には代表的な処理クラスがいくつか用意されていて、デフォルトの設定を設定ファイル/settings.py/の『REST_FRAMEWORK』で上書きして全体的な設定をしたり、クラス変数をオーバーライドしてビュークラス単位での個別設定をしたりすることができます。

### DRF 用のビューは大きく分けて3種類あり、用途に応じて

1. rest_framework.views.APIView
2. rest_framework.generics.CreateAPIView などの 汎用 APIView
3. rest_framework.viewsets.ModelViewSet 系ビュー

のいずれかを継承して作成します。 
違いはカスタム性やコード量が異なってきますが、
この後で具体的な実装を見ながら説明していきます。

ちなみに以下のようにAPIViewもDjangoのViewクラスの子クラスです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3895655/4873a67c-5b9b-8314-7e9f-27bb65a7aa43.png)

### APIViewの記述例

```python

class LikeAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request, tweet_id):
        try:
            tweet = Tweet.objects.get(pk=tweet_id)
            like, created = Like.objects.get_or_create(user=request.user, tweet=tweet)

            if not created:
                return Response({'message': 'You already liked this tweet.'}, status=status.HTTP_200_OK)
            
            return Response({'message': 'Tweet liked successfully.'}, status=status.HTTP_201_CREATED)
        except Tweet.DoesNotExist:
            return Response({'error': 'Tweet not found'}, status=status.HTTP_404_NOT_FOUND)
        
    def delete(self, request, tweet_id):
        try:
            tweet = Tweet.objects.get(pk=tweet_id)
            like = Like.objects.filter(user=request.user, tweet=tweet)

            if not like.exists():
                return Response({'error': 'You have not liked this tweet.'}, status=status.HTTP_200_OK)
            
            like.delete()
            return Response({'message': 'Like removed successfully.'}, status=status.HTTP_200_OK)
        except Tweet.DoesNotExist:
            return Response({'error': 'Tweet not found.'}, status=status.HTTP_404_NOT_FOUND)
            
```

### 汎用APIViewの対応表
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3895655/b66f32cf-0b9d-6003-eaf1-6fe558ce156c.png)

### 汎用APIViewの記述例

```python

class UserCreateView(generics.CreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.AllowAny]

    authentication_classes = []

class UserDetailView(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_object(self):
        return self.request.user

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

```

### ModelViewSetの対応表

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3895655/18df99e4-c1ca-82c4-48b0-bf102700cb56.png)

### ModelViewSetの記述例

```python

class TweetViewSet(viewsets.ModelViewSet):
    queryset = Tweet.objects.all().order_by('-created_at')
    serializer_class = TweetSerializer
    permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]

```

## まとめ

DjangoにDRFの要素、コンポーネントが加わることで複雑になります。
なのでまずは、全体像を把握しましょう。
一見複雑にみえますが、新しい要素は「シリアライザ」と「DRF用ビュー」です。

「シリアライザ」は入力データのバリデーション、
内部に保持したモデルオブジェクトの永続化、
出力データの作成を担当するクラスで、
Django のフォームと同じような使い方ができます。

「DRF用ビュー」は大きく3種類あり、まるっと API 全部を実装できてしまうものもあれば、複雑なカスタム API にも対応できるものもあるので、用途に合わせて親クラスと書き方を使い分けてください。
個人的に、APIViewだけでいいのでは？とも思っています。

ありがとうございました。