LoginSignup
0
0

[Django]DRFのModelViewSetを使ってみる

Posted at

はじめに

自分用の個人開発のメモ/備忘録として記録していきます。

やりたいこと

Django REST framework(DRF)のModelViewSetを使いたい

使い方

viwes.pyで下記のように書く

※Serializerについては省略する

views.py
from rest_framework import viewsets

class MyModelViewSet(viewsets.ModelViewSet):
    serializer_class = MyModelSerializer
    queryset = MyModel.objects.all()

urls.pyで下記のように書く

views.py
from rest_framework.routers import DefaultRouter
from .views import MyModelViewSet

router = DefaultRouter()
router.register(r'mymodels', MyModelViewSet, basename='mymodel')
urlpatterns = router.urls

一覧

これだけで下記のようなURL一覧が作成される

例:router.register(r'mymodels', MyModelViewSet) としてルーターに登録した場合のURLパターンと名前を表示

カテゴリ メソッド URL 名前 説明
リスト(list) GET /mymodels/ mymodel-list すべての MyModel インスタンスのリストを取得します。
詳細(retrieve) GET /mymodels/{id}/ mymodel-detail 指定された id の MyModel インスタンスの詳細を取得します。
作成(create) POST /mymodels/ mymodel-list 新しい MyModel インスタンスを作成します。
全体更新(update) PUT /mymodels/{id}/ mymodel-detail 指定された id の MyModel インスタンスを全体更新します。
部分更新(partial_update) PATCH /mymodels/{id}/ mymodel-detail 指定された id の MyModel インスタンスを部分的に更新します。
削除(destroy) DELETE /mymodels/{id}/ mymodel-detail 指定された id の MyModel インスタンスを削除します。

中身について

ModelViewSetは下記のミックスインクラスと GenericViewSet を継承している
・ CreateModelMixin
・ RetrieveModelMixin
・ UpdateModelMixin
・ DestroyModelMixin
・ ListModelMixin

特定のMixinだけ使うこともできる

views.py
class ModelViewSet(mixins.CreateModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    viewsets.GenericViewSet):

GenericAPIViewを確認

一緒に継承しているviewsets.GenericViewSetを見ていくと、GenericAPIViewを継承していた。
その中でよく使うquerysetserializer_classについて確認。

冒頭の注釈にて下記のように書かれています。

`get_queryset()` / `get_serializer_class()` をオーバーライドする必要があります。  
ビューメソッドをオーバーライドする場合は、 `queryset` プロパティに直接アクセスするのではなく、
`get_queryset()` を呼び出すことが重要です。  
なぜなら、 `queryset` が評価されるのは一度だけであり、その結果は
それ以降のリクエストに対してキャッシュされるからである。

get_queryset() と get_serializer_class() を抜粋

class GenericAPIView(views.APIView):
    """
    Base class for all other generic views.
    """
    # You'll need to either set these attributes,
    # or override `get_queryset()`/`get_serializer_class()`.
    # If you are overriding a view method, it is important that you call
    # `get_queryset()` instead of accessing the `queryset` property directly,
    # as `queryset` will get evaluated only once, and those results are cached
    # for all subsequent requests.
    queryset = None
    serializer_class = None

    def get_queryset(self):
        """
        Get the list of items for this view.
        This must be an iterable, and may be a queryset.
        Defaults to using `self.queryset`.

        This method should always be used rather than accessing `self.queryset`
        directly, as `self.queryset` gets evaluated only once, and those results
        are cached for all subsequent requests.

        You may want to override this if you need to provide different
        querysets depending on the incoming request.

        (Eg. return a list of items that is specific to the user)
        """
        assert self.queryset is not None, (
            "'%s' should either include a `queryset` attribute, "
            "or override the `get_queryset()` method."
            % self.__class__.__name__
        )

        queryset = self.queryset
        if isinstance(queryset, QuerySet):
            # Ensure queryset is re-evaluated on each request.
            queryset = queryset.all()
        return queryset

    def get_serializer_class(self):
        """
        Return the class to use for the serializer.
        Defaults to using `self.serializer_class`.

        You may want to override this if you need to provide different
        serializations depending on the incoming request.

        (Eg. admins get full serialization, others get basic serialization)
        """
        assert self.serializer_class is not None, (
            "'%s' should either include a `serializer_class` attribute, "
            "or override the `get_serializer_class()` method."
            % self.__class__.__name__
        )

        return self.serializer_class

List や Retrieve は 下記のように get_queryset() / get_serializer_class() をオーバーライドして使うことが多い
<例>
querysetでフィルターを使ったり
retrieveとそれ以外で使うSerializerを分けたりできる

def get_queryset(self):
    return MyModel.objects.filter(user=self.request.user)

def get_serializer_class(self):
    if self.action == 'retrieve':
        return MyModelDetailSerializer
    return MyModelSerializer

mixins.pyを確認してみる

Create や Update や Destroy は一部をオーバーライドしてカスタマイズして使うことが多い
元のmixins.pyを確認

インポートしているもの

mixins.py
from django.db.models.query import prefetch_related_objects

from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings

CreateModelMixin

オーバーライドする場所としてはsave前のperform_create

class CreateModelMixin:
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

ListModelMixin

class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

RetrieveModelMixin

class RetrieveModelMixin:
    """
    Retrieve a model instance.
    """
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

UpdateModelMixin

オーバーライドする場所としてはsave前のperform_update

class UpdateModelMixin:
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        queryset = self.filter_queryset(self.get_queryset())
        if queryset._prefetch_related_lookups:
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance,
            # and then re-prefetch related objects
            instance._prefetched_objects_cache = {}
            prefetch_related_objects([instance], *queryset._prefetch_related_lookups)

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

DestroyModelMixin

オーバーライドする場所としてはsave前のperform_destroy

class DestroyModelMixin:
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()

おわりに

・必要なところだけ、カスタマイズして使うと少ない記述量で最低限の機能を実装できるので便利

参考

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