3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-26

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

関数ベースのView(一番簡単)

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

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的に呼ぶと"ハンドラメソッド")。

                          # 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を使ってみましょう。

from rest_framework import generics

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

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

ビューセット

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

from rest_framework import viewsets

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

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

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にアクセスすることで任意の処理が実行できます。

$ 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 変数に登録します。

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 をつけたりして、アクセスができます。

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/

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?