はじめに
自分用の個人開発のメモ/備忘録として記録していきます。
やりたいこと
Django REST framework(DRF)のModelViewSetを使いたい
使い方
viwes.pyで下記のように書く
※Serializerについては省略する
from rest_framework import viewsets
class MyModelViewSet(viewsets.ModelViewSet):
serializer_class = MyModelSerializer
queryset = MyModel.objects.all()
urls.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だけ使うこともできる
class ModelViewSet(mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet):
GenericAPIViewを確認
一緒に継承しているviewsets.GenericViewSet
を見ていくと、GenericAPIView
を継承していた。
その中でよく使うqueryset
と serializer_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を確認
インポートしているもの
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()
おわりに
・必要なところだけ、カスタマイズして使うと少ない記述量で最低限の機能を実装できるので便利
参考