【備忘録】Django Rest Framework のViewについて

DjangoのRest Frameworkについて学んでいます。
学んだことをアウトプットするため、備忘録を書いていきます。
今回はViewに焦点を当てました。
間違った点がありましたら、教えていただけますと幸いです。

# 関数ベースのView（一番簡単）

Responseを返却する関数型のビューです。
第一仮引数に request を受取るように定義するだけです。

```ruby:
from rest_framework.response import Response
from rest_framework.decorators import api_view # ここにapi_viewというデコレータを記述することにより、DRFの設定が、下記ビューに引き継がれるようになります。


@api_view(['GET', 'POST']) # 引数としてHTTPメソッドを指定
def hello_world(request):
    if request.method == 'POST':
        return Response({"message": "Got some data!", "data": request.data})
    return Response({"message": "Hello, world!"})
```

単純な機能は、上記のように関数ベースで書いたほうが可読性がよいですが、設定項目が増えてきたら見栄えが悪くなるため、後述するクラスベースにしたほうが良い。

# クラスベースのビュー

クラスベースのビューは、HTTPメソッドごとのインスタンスメソッドを定義できます。
また、他にも後述する様々な設定を記述することができます。
関数ベースだとデコレータで連ねて書く必要があり可読性が悪くなってしまうため、
クラスベースのビューのメリットであると言えます。

クラスベースのビューは、APIViewクラスを継承して定義します。
対象のHTTPメソッドがそのままインスタンスメソッドになります（DRF的に呼ぶと"ハンドラメソッド"）。

```ruby:
                          # APIViewクラスを継承
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from django.contrib.auth.models import User

　　　　　　　　　　　# APIViewクラスを継承
class UserNameAPI(APIView):
    authentication_classes = (authentication.TokenAuthentication,)
    permission_classes = (permissions.IsAdminUser,)

    # ↓対象のHTTPメソッドがそのままハンドラメソッドになっている
    def get(self, request, format=None):
        usernames = [user.username for user in User.objects.all()]
        return Response(usernames)

　　 # ↓対象のHTTPメソッドがそのままハンドラメソッドになっている
    def post(self, request):
        # 普通こんなことはしないが...
        users = [User(username=name) for name in request.POST.getlist('name')]
        User.objects.bulk_create(users)
        return Response({'succeeded': True})
```

このように、モデルに紐付かない処理はクラスベースのビューを記述するのが適切です。
ちなみに後述のジェネリックビュークラスは、APIViewを継承して定義されています。

# ジェネリックビュー

DjangoやDRFのお作法に従うなら、ジェネリックビューがおすすめ。便利で種類も多いです。

ジェネリックビューはモデル（クエリセット）に紐づくため、簡略化した書き方ができるのが特徴です（処理を書かなくてよいことも多々あり）。

種類がとても多いように感じそうですが、作成, 一覧, 取得, 削除, 更新 の用途によって使い分ければいいだけの話なので、身構えることはありません。

手始めにListAPIViewを使ってみましょう。

```ruby:
from rest_framework import generics

class UserList(generics.ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
```

たったこれだけでユーザ一覧を返却するAPIビューの出来上がりです。 各ユーザレコードは UserSerializer に従ってシリアライズされます。


# ビューセット

ビューセットは 取得, 一覧, 登録, 更新, 削除 をまとめて管理するビューです。 
自分で書かなくてもこのViewSetが肩代わりしてくれるので楽ですね。

```ruby:
from rest_framework import viewsets

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

たったこれだけで、取得, 一覧, 登録, 更新, 削除 ができるようになりました。
なお、ViewSet は ハンドラメソッドではなく アクションメソッドで定義するべきとの記述を読みました（それに対してジェネリックビューはハンドラメソッド）。

```ruby:ViewSetの定義
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    pagination_class = MyCursorPagination

    @action(methods=['get'], detail=False)
    def fullnames(self, request):
        return Response([
            '{user.first_name} {user.last_name}'.format(user=user)
            for user in self.get_queryset()
        ])

    @action(methods=['get'], detail=True)
    def fullname(self, request, pk=None):
        user = self.get_object()
        return Response('{user.first_name} {user.last_name}'.format(user=user))

# Router については後述
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns = [url(r'^api/', include(router.urls))]
```

下記のように、インスタンスメソッド名でURLにアクセスすることで任意の処理が実行できます。

```ruby:
$ curl -X GET 'http://127.0.0.1:8000/api/users/fullnames/'
["takayuki shimizukawa","takanory suzuki","teruhiko teruya"]
$ curl -X GET 'http://127.0.0.1:8000/api/users/1/fullname/'
"takayuki shimizukawa"
$ curl -X GET 'http://127.0.0.1:8000/api/users/2/fullname/'
"takanory suzuki"
$ curl -X GET 'http://127.0.0.1:8000/api/users/3/fullname/'
"teruhiko teruya"
```

- methods は どの HTTP メソッド を走らせたいのかどうか
- detailの True or False は 一覧APIを取得するのか、詳細API を取得するのか
- detailがTrueの場合は、詳細APIを取得することになるので、pk引数が必要。


# 個人的に考えた使い分け

- クエリセットを使わない API は クラスベースビュー(APIView) か 関数ビュー
- モデルに対する処理をまとめて一つのクラスで定義したい場合は View set
- それ以外は Generic view

# ビューに関する補足情報

ビューは、ルーティングの紐付けをしないとページとして認識されません。
そのためurls.py の urlpatterns 変数に登録します。

```ruby:urls.py
from django.conf.urls import url, include

urlpatterns = [
  url(r'^hello/$', hello_world),
  url(r'^user/$', UserList.as_view()),
]
```

- 関数のビュー: そのまま登録
- クラスのビュー: as_views() メソッドの結果を url 関数でマッピングすることで登録
- ビューセット: Routerを使って登録


# Router

Routerというものを使って、urls.pyにルーティングルールを追加していきます。
やること自体は簡単なんですが、残念なことにいくつかの種類があります。。。

ここでは DefaultRouter だけ説明します。 SimpleRouter は DefaultRouter と大体同じものです。
ですが DefaultRouter は Router のルート画面にアクセスしたときに API のリンク一覧を見せてくれるので、SimpleRouterより少しだけ便利です。

じゃあブラウザから直接APIを見たいときはどうするのかと言いますと、
.json の拡張子をつけたり、クエリストリングとして ?format=json をつけたりして、アクセスができます。

```ruby:urls.py
from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'users', UserViewSet)

urlpatterns = [
    url(r'^api/', include(router.urls)),
]
```

これにより /api/ 配下にビューが登録されます。 The Browsable API (私の場合は http://localhost:8080/api/) にアクセスすると、上記 register で登録されたビューをたどることができるようになります。
（例えば、　http://localhost:8080/api/users/　）

# 補足情報
- Routerで登録できるのは、ビューセットだけです。ジェネリックビューなど他の種類のビューを登録しようとすると、
AttributeError: type object 'UserListView' has no attribute 'get_extra_actions' のようにエラーが発生します。 get_extra_actions メソッドは viewsets.py にしか定義されていません。


- Router は 詳細API を users/{pk}/$ のように pk を自動的に付加してURL登録してくれるいい子です。

# request オブジェクト

クライアントからのrequest オブジェクトは、ビューから参照することができます。

簡単ですが、今回はここまでにします。

▼参考にさせていただいた記事
https://note.crohaco.net/2018/django-rest-framework-view/