Django REST framework는 단일 클래스에 관련 있는 view들을 결합한 ViewSet 기능을 제공한다. 즉, ViewSet는 여러 가지 API의 기능을 통합해서 하나의 API Set로 제공하는 것이다.
하나의 모델을 가지고 list, detail 등 각각의 API를 만들어 보면, 중복되는 로직이 많다. 이런 경우, ViewSet을 쓰게 되면 중복되는 로직의 코드를 줄일 수 있어 코드의 효율성을 높일 수 있다.
기존에 사용자 Profile 정보를 List로 보여주는 API가 있었다.
이 코드를 list뿐만 아니라 detail도 처리할 수 있는 ViewSet API로 아래와 같이 수정하여 만들 수 있다.
[변경 전]
class ProfileListView(generics.ListView):
queryset = Profile.objects.all()
serializer_class = ProfileStatusSerializer
permission_classes = [IsAuthenticated]
[변경 후]
from rest_framework.viewsets import ReadOnlyModelViewSet # ViewSet 임포트
class ProfileViewSet(ReadOnlyViewSet):
queryset = Profile.objects.all()
serializer_class = ProfileStatusSerializer
permission_classes = [IsAuthenticated]
#from profiles.api.views import ProfileList
from profiles.api.views import ProfileViewSet
profile_list = ProfileViewSet.as_view({'get' : 'list'})
profile_detail = ProfileViewSet.as_view({'get' : 'retrieve'})
urlpatterns = [
path('profiles/', profile_list, name='profile-list'),
path('profiles/<int:pk>', profile_detail, name='profile-list'),
]
앞에서는 urls.py에서 viewset의 경로를 직접 다 지정해 주었다. 하지만 router를 이용하면 경로 지정을 DRF에서 자동화해 주기에 통상 viewset의 url은 router로 설정을 해 준다.
이를 위해 아래와 같이 urls.py
파일을 수정한다.
[urls.py]
from django.urls import include, path
from profiles.api.views import ProfileViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r"profiles", ProfileViewSet)
urlpatterns = [
path("", include(router.urls))
]
그리고 Django REST API에서 확인을 해 보면 아래와 같이 동일한 결과가 출력됨을 확인할 수 있다. 다만, API 명칭이 변경되어서 Profile List, Profile Instance로 출력됨을 알 수 있다.
그리고 현재까지는 허용하는 메소드가 GET임을 알 수 있다.
[결과 보기]
위의 ProfileViewSet
은 List, Detail 기능을 수행하는 View 클래스였다. 이 클래스를 update까지 할 수 있는 ViewSet으로 재작성해 보자.
Profile은 작성한 본인만 수정할 수 있어야 함으로 먼저 permissions.py
에서 Owner가 아니면 읽기만 가능한 IsOwnerOrReadOnly
클래스를 만든다.
[permissions.py]
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.user_profile == request.user.profile
그 다음 수정까지 되도록 views.py를 수정하자.
앞에서는 ReadOnlyViewSet
클래스를 상속 받았지만, 이번에는 딱 우리는 원하는 ViewSet이 없음으로 필요한 기능들만 Mixins으로 상속 받아서 사용한다. 이를 위해 viewsets
과 mixins
을 상속 받는다.
그리고 permission_classes에 IsOwnerOrReadOnly
를 추가한다.
[views.py]
from rest_framework import viewsets
from rest_framework import mixins
class ProfileViewSet(mixins.UpdateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
permission_classes = [IsAuthenticated, IsOwnProfileOrReadOnly]
API 호출 결과를 보면 PUT, PATCH가 추가되었음을 확인할 수 있다.
pk 2번 프로파일은 내가 작성한게 아니므로 PUT 옵션이 뜨지 않는다.
pk1 번은 본인이 작성한 프로파일임으로 수정할 수 있도록 옵션이 나온다.
ProfileStatusViewSet
은 profile status를 Create, Read, Update, Detail를 다 할 수 있는 ViewSet으로 만드려고 한다. 이를 위해 ProfileStatusViewSet
은 ModelViewSet
을 상속 받아서 작성한다.
ModelViewSet에 대한 설명 필요!
[views.py]
from rest_framework.viewsets import ModelViewSet
class ProfileStatusViewSet(ModelViewSet):
queryset = ProfileStatus.objects.all()
serializer_class = ProfileStatusSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
def perform_create(self, serializer):
user_profile = self.request.user.profile
serializer.save(user_profile=user_profile)
urls.py에 아래 내용을 추가한다.
[urls.py]
from profiles.api.views import ProfileStatusViewSet
router = DefaultRouter()
#router.register(r"profiles", ProfileViewSet)
router.register(r"status", ProfileStatusViewSet)
[views.py]
from profiles.api.serializers import ProfileAvatarSerializer
class AvatarUpdateView(generics.UpdateAPIView):
serializer_class = ProfileAvatarSerializer
permission_classes = [IsAuthenticated]
def get_object(self):
profile_object = self.request.user.profile
return profile_object
[urls.py]
from profiles.api.views import AvatarUpdateView
urlpatterns = [
path("", include(router.urls)),
path('avatar/', AvatarUpdateView.as_view(), name='avatar-update')
]