ViewSet

Django REST framework는 ViewSet 이라는 단일 클래스에서 관련된 논리를 결합할 수 있다. ViewSet은 단순히 get()이나 post()와 같은 메소드 핸들러를 제공하지 않으며, 대신 list()create() 같은 액션을 제공한다(CBV 유형). ViewSet 의 method 핸들러는 as_view() 함수가 호출되어 view가 끝나는 시점의 해당 액션에만 바인딩된다.

바인딩(binding)

각종 값들이 확정되어 더 이상 변경할 수 없는 상태가 되는 것이다. 식별자(identifier)가 그 대상인 메모리 주소, 데이터형 또는 실제값으로 배정되는 것을 뜻한다.

일반적으로 url설정시에 view를 명시적으로 등록하는 대신 router 클래스로 등록하여 자동으로 url을 설정한다.

START VIEWSETS

from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from .serializers import UserSerializer
from rest_framework import viewsets
from rest_framework.response import Response

class UserViewSet(viewsets.ViewSet):
    def list(self, request):
        queryset = User.objects.all()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = User.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = UserSerializer(user)
        return Response(serializer.data)

필요시에는 이 ViewSet을 아래와 같이 개별 뷰 바인딩을 할 수 있다.

user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})

일반적으로 개별 뷰 바인딩을 하지 않으며, ViewSet을 라우터에 등록하여 자동으로 URLconf(url 설정파일)이 생성되도록 한다.

from .views import UserViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns - router.urls

이때 viewset을 본인이 작성하는 것보다 기본 동작을 제공해주는 기본 클래스를 사용하는 것을 권장한다.

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

장점

view 클래스를 사용하는 것보다 viewsets 클래스를 사용하면 좋은 점이 2가지 있다.

  1. 반복되는 로직을 하나의 클래스로 결합할 수 있다. 즉, viewsets을 이용하면 queryset을 단 한번만 정의하면 된다.

  2. router를 사용함으로써, URLconf(url 설정파일)을 다룰 필요가 없어진다.

하지만 아래와 같은 trade-off도 있다.

  • view와 url설정을 사용하면 보다 명확하고 상세하게 제어할 수 있다.

ViewSet 은 빠르게 실행하거나 대규모 API가 있는 경우, 또는 전체적으로 일관된 URLconf를 적용하려는 경우에 유리하다.

MARKING EXTRA ACTIONS FOR ROUTING

REST framework에 포함된 기본 router는 create / retrieve / update / destroy 스타일 작업의 기본 method를 제공한다.

class UserViewSet(viewsets.ViewSet):
    """
    Example empty viewset demonstrating the standard
    actions that will be handled by a router class.

    If you're using format suffixes, make sure to also include
    the 'format=None' keyword argument for each action.
    """

    def list(self, request):
        pass

    def create(self, request):
        pass

    def retrieve(self, request, pk=None):
        pass

    def update(self, request, pk=None):
        pass

    def partial_update(self, request, pk=None):
        pass

    def destroy(self, request, pk=None):
        pass

라우팅이 필요한 임시 메소드가 있는 경우에는 @detail_router 또는 @list_router 데코레이터를 사용하여 라우팅을 필요로 한다는 것을 표시할 수 있다.

  • @detail_router 는 URL 패턴에 pk를 포함하며 단일 인스턴스를 요구하는 메소드에 대한 것이다.

  • @list_router 는 객체 목록에서 작동하는 메소드에 대한 것이다.

rom django.contrib.auth.models import User
from rest_framework import status
from rest_framework import viewsets
from rest_framework.decorators import detail_route, list_route
from rest_framework.response import Response
from .serializers import UserSerializer, PasswordSerializer

class UserViewSet(viewsets.ModelViewSet):

    queryset = User.objects.all()
    serializer_class = UserSerializer

    @detail_route(methods=['post'])
    def set_password(self, request, pk=None):
        user = self.get_object()
        serializer = PasswordSerializer(data=request.data)
        if serializer.is_valid():
            user.set_password(serializer.data['password'])
            user.save()
            return Response({'status': 'password set'})
        else:
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

    @list_route()
    def recent_users(self, request):
        recent_users = User.objects.all().order('-last_login')

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

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

데코레이터는 오직 라우팅된 View에서만 추가적인 arguments를 받을 수 있다.

@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
  ...

해당 데코레이터는 기본적으로 GET request를 routing하지만 methods 인수를 사용하여 다른 HTTP Method로 routing할 수 있다.

@detail_route(methods=['post', 'delete'])
def unset_password(self, request, pk=None):
  ...

위의 두가지 새로운 action들은 ^users/{pk}/set_password/$, ^users/{pk}/unset_password/$ 로 사용할 수 있다.

API REFERENCE

ViewSet

ViewSet 클래스는 APIView 에서 상속받는다. permission_classes, authentication_classes 와 같은 표준 속성을 이용하여 API 정책을 통제할 수 있다.

ViewSet 클래스는 action의 구현을 제공하지 않는다. ViewSet 클래스를 사용하기 위해서는 해당 클래스를 overrice하고, 명시적으로 action을 정의해야한다.

GenericViewSet

GenericViewSet 클래스는 GenericAPIView 에서 상속 받는다. get_object, get_queryset 메소드와 다른 generic view의 기본 action을 제공하지만, 기본적으로는 어떠한 액션도 포함하고 있지 않다.

GenericViewSet 클래스를 사용하기 위해서는 해당 클래스를 override하고 필요한 mixin 클래스를 혼합하여 명시적으로 action을 정의해아한다.

ModelViewSet

ModelViewSet 클래스는 GenericAPIView 를 상속하고 다수의 mixin 클래스의 동작을 혼합하여 다양한 action에 대한 구현을 포함한다.

.list(), .retrieve(), .create(), .update(), .partial_update(), .destroy() 를 제공한다.

class AccountViewSet(viewsets.ModelViewSet):
    queryset = Account.objects.all()
    serializer_class = AccountSerializer
    permission_classes = [IsAccountAdminOrReadOnly]

GenericAPIView 를 상속받고 있기 대문에 기본적으로 querysetserializer_class 속성을 필수로 제공해야한다.

class AccountViewSet(viewsets.ModelViewSet):
    serializer_class = AccountSerializer
    permission_classes = [IsAccountAdminOrReadOnly]

    def get_queryset(self):
        return self.request.user.accounts.all()

queryset을 동적으로 결정하는 viewsets을 사용하려면 위와 같이 메소드 오버라이드를 할 수 있다.

여기서 주의해야할 점이 있다. ViewSetqueryset 속성을 제거하면 연결된 라우터가 모델의 base_name을 자동으로 파생시킬 수 없으므로 base_namekwarg를 지정해야한다. 이 클래스는 기본적으로 create / list / retrieve / update /destroy 액션을 제공하지만 표준 권한 클래스(permission)를 사용해 제한할 수 있다.

ReadOnlyModelViewSet

ReadOnlyModelViewSet 클래스는 GenericAPIView 를 상속한다. ModelViewSet과 동일하게 다양한 action애 대한 구현도 포함되지만 오직 읽기 전용 action인 .list().retrieve()만 제공한다.

class AccountViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Account.objects.all()
    serializer_class = AccountSerializer

참고링크

Last updated