ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Django REST framework] ViewSet
    언어/파이썬 & 장고 2016. 8. 3. 14:22

    ViewSets

    Django REST framework는 단일 클래스에서 관련있는 view들의 집합을 위해 logic의 결합을 허용합니다. 이를 ViewSet이라 합니다. 또한 다른 framework에서 resource 또는 controller같이 이름이 개념적으로 유사한 implementation을 찾을 수 있습니다.

    클래스는 단순하게 class 기반 view 타입이며  이는 .get() 또는 .post()와 같은 모든 method 핸들러를 제공하지 못하지만 대신 .list()와 .create()같은 액션을 제공합니다.

    ViewSet을 위한 method 핸들러들은 as_view()함수를 사용해 view가 끝나는 시점에 해당하는 행동이 취할 때, 바인딩 합니다.

    일반적으로 url설정의 viewset안에서 view들을 명시적으로 등록하는 것 보다 router 클래스를 가지고 있는 viewset을 등록할 것이고, 이 것은 자동으로 사용자를 위해 url 설정을 결정합니다.

    Example

    시스템 내 모든 사용자를 retrieve 하거나 list하는 viewset을 정의한 코드입니다.

    from django.contrib.auth.models import User
    from django.shortcuts import get_object_or_404
    from myapps.serializers import UserSerializer
    from rest_framework import viewsets
    from rest_framework.response import Response
    
    class UserViewSet(viewsets.ViewSet):
        """
        A simple ViewSet for listing or retrieving users.
        """
        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)


    직접 할 경우, 두 개로 나눠진 view들의 viewset을 바인딩할 수 있습니다.

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


    일반적으로 우리는 이것 을 할수 없지만 대신 router를 가지는 viewset을 등록하고 자동으로 url설정파일이 생성되는 것을 허용합니다.

    from myapp.views import UserViewSet
    from rest_framework.routers import DefaultRouter
    
    router = DefaultRouter()
    router.register(r'users', UserViewSet)
    urlpatterns = router.urls


    스스로 viewset을 쓰는 것보다 이미 존재하는 기본 클래스를 사용하고자 할 때가 있다. 그래서 viewset은 기본적인 동작들의 집합을 제공합니다.

    class UserViewSet(viewsets.ModelViewSet):
        """
        A viewset for viewing and editing user instances.
        """
        serializer_class = UserSerializer
        queryset = User.objects.all()


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

    1. 반복되는 로직은 단일 클래스 안에 결합될 수가 있습니다. 따라서 viewset을 사용하면 queryset을 단 한번만 정의하는 된다는 점이 있고 view는 다수의 view들을 사용해야 합니다.
    2. 라우터를 사용함으로서, 더이상 사용자 스스로 URL 설정파일을 다룰 필요가 없습니다. 

    아래와 같은 두 가지의 trade-off가 존재합니다.

    기본 view와 URL 설정을 사용하면 더 명확해지고 상세한 제어를 할 수 있습니다.

    viewset을 사용하면 빠르게 실행하는 경우에 유리하거나 거대한 API를 사용자가 가지고 있을 때, 전체 프로젝트(viewset을 사용하고 있는) 내에 일관된 URL 구성을 적용할 수 있습니다.

    routing을 위한 추가 action 표시

    아래와 같이 REST framework가 포함된 기본 router는 create/retrieve/update/destroy 명령의 기본 함수들을 제공합니다.

    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

     

    라우팅 할 필요가 있는 임시 method가 있는 경우에 @detail_route또는 @list_route 데코레이터를 사용하여 라우팅을 필요로 한 다는 것을 표시 할 수 있습니다.

     

    @detail_route데코레이터는 URL 패턴에 pk를 포함하고 단일 인스턴스를 요구하는 method에 대한 것입니다.

    @list_route데코레이터는 object의 list에서 작동하는 method에 대한 것입니다.

    from 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 myapp.serializers import UserSerializer, PasswordSerializer
    
    class UserViewSet(viewsets.ModelViewSet):
        """
        A viewset that provides the standard actions
        """
        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를 위한 추가적인 argument들을 받을 수 있습니다.

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


    해당 데코레이터는 기본으로 GET request를 라우팅 하지만 methods argument를 사용하면 다른 HTTP method들 또한 받을 수 있습니다. 

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

    두 개의 새로운 action들은 ^users/{pk}/set_password/$ 와 ^users/{pk}/unset_password/$ url로 사용할 수 있습니다.

    API Reference

    ViewSet

    ViewSet 클래스는 APIView를 상속받습니다. 사용자는 viewset에 있는 API 정책을 통제하기 위해 permission_classesauthentication_classes와 같은 표준 attribute를 사용할 수 있습니다.

    ViewSet 클래스는 모든 action의 implementation을 제공할 수 없습니다. ViewSet 클래스를 사용하기 위해 사용자는 해당 클래스를 override하고 명시적으로 action을 정의해야 합니다.

    GenericViewSet

    GenericViewSet 클래스는 GenericAPIView를 상속하고 get_object, get_queryset method들의 기본 종류들과 다른 generic view의 기본 행동들을 제공하지만 기본적으로는 어떠한 행동에 대한 action을 제공하지는 않습니다.

    GenericViewSet 클래스를 사용하기 위해 사용자는 해당 클래스를 override하고 mixin 클래스를 요구하는 mixin을 override 하거나 명시적으로 action implementation을 정의해야 합니다. 

    ModelViewSet

    ModelViewSet 클래스는 GenericAPIView를 상속하고 다수의 mixin class들의 행동들을 섞음으로써 다양한 action을 위한 implementation들을 포함합니다.

    ModelViewSet 클래스에서 제공되는 action들은 .list(), .retrieve(), .create(), .update(), .partial_update(), .destroy()입니다.

    Example

    ModelViewSet은 GenericAPIView의 확장했기 때문에 일반적으로 queryset serializer_class attribute들을 제공하는 것이 필요합니다. 

    class AccountViewSet(viewsets.ModelViewSet):
        """
        A simple ViewSet for viewing and editing accounts.
        """
        queryset = Account.objects.all()
        serializer_class = AccountSerializer
        permission_classes = [IsAccountAdminOrReadOnly]


    사용자가 GenericAPIView에서 제공하는 기본 attribute 또는 method를 override하는 것 중 하나를 사용할 수 있습니다. 예를 들어, queryset을 동적으로 결정하는 ViewSet을 사용할 수 있습니다.

    class AccountViewSet(viewsets.ModelViewSet):
        """
        A simple ViewSet for viewing and editing the accounts
        associated with the user.
        """
        serializer_class = AccountSerializer
        permission_classes = [IsAccountAdminOrReadOnly]
    
        def get_queryset(self):
            return self.request.user.accounts.all()


    참고할점으로 사용자의 ViewSet으로부터 queryset 속성의 삭제할 때, 모든 연관된 router는 자동적으로 사용자 모델의 base_name을 빼앗깁니다. 그래서 사용자는 base_name의 kwarg를 사용자의 router registration의 부분에 구체화해야 합니다. 또한 이 클래스는 기본적으로 create/list/retrieve/update/destroy actions을 완벽하게 제공할지라도, 사용자는 사용가능한 명령들을 표준 permission 클래스를 사용하여 제한할 수 있습니다.

    ReadOnlyModelViewSet

    ReadOnlyModelViewSet 클래스는 GenericAPIView를 상속합니다. ModelViewSet과 마찬가지로 다양한 action을 위한 implementation들을 포함합니다. 그러나 ModelViewSet와 같지 않게 오직 'read-only' action인 .list() .retrieve()를 제공합니다.

    example

    ModelViewSet과 마찬가지로 일반적으로 queryset serializer_class attribute를 제공하는 것을 필요로 합니다.

     

    class AccountViewSet(viewsets.ReadOnlyModelViewSet):
        """
        A simple ViewSet for viewing accounts.
        """
        queryset = Account.objects.all()
        serializer_class = AccountSerializer

    Custom ViewSet base classes

    사용자는 ModelViewSet action의 전체 집합들을 가지지 않거나(필요한 부분만 정의하여 사용) 다른 방법으로 행동들을 customizing한 custom ViewSet 클래스를 제공하기를 원할 수 있습니다.

    Example

    createlistretrieve 명령어를 제공하는 기본 viewset 클래스를 생성하기 위해, GenericViewSet를 상속하고 요구하는 action들을 mixin합니다.

    class CreateListRetrieveViewSet(mixins.CreateModelMixin,
                                    mixins.ListModelMixin,
                                    mixins.RetrieveModelMixin,
                                    viewsets.GenericViewSet):
        """
        A viewset that provides `retrieve`, `create`, and `list` actions.
    
        To use it, override the class and set the `.queryset` and
        `.serializer_class` attributes.
        """
        pass

    사용자만의 ViewSet 클래스를 생성함으로써 사용자는 API 간 다수의 viewset에서 재사용 될 수 있는 공통 행동들을 제공할 수 있습니다.

    댓글