본문 바로가기
Django/Django REST framework

5. ViewSets

by hyun-am 2021. 6. 26.

라우팅이 요청에 사용할 컨트롤러를 결정하면, 컨트롤러는 요청을 이해하고 적절한 출력을 생성할 책임이 있습니다.


DRF를 사용하면 VeiwSet라고 하는 단일 클래스의 연관된 viewset에 대한 논리를 결합할 수 있습니다.

다른 프레임워크에서는 개념적으로 유사한 것으로는 'Resources' 또는 'Controllers'를 찾을 수도 있습니다.

ViewSet 클래스는 단순히 .get() 또는 .post()와 같은 메서드 핸들러를 제공하지 않고 .list() 및 .create()과 같은 작업을 제공하는 클래스 기반 View의 한 유형입니다.

ViewSet의 메서드 핸들러는 as_view() 메서드를 사용하여 View를 완료하는 시점에만 해당 작업에 바인딩 됩니다.

일반적으로, urlconf의 viewSet에 view를 명시적으로 등록하는 대신, 자동으로 urlconf를 결정하는 라우터 클래스에 viewSet을 등록합니다.

예시

시스템의 모든 사용자를 나열하거나 검색하는데 사용할 수 있는 간단한 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)

필요한 경우 이 viewSet을 다음과 같은 두 개의 개별 view로 바인딩 할 수 있습니다.

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

일반적으로는 이런식으로 사용하지 않고 대신 라우터에 viewset을 등록하고 urlconf가 자동으로 생성되도록 허용합니다.

from myapp.views import UserViewSet
from rest_framework.routers import DefaultRouter

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

viewwset을 직접 작성하는 대신 기본 default 동작 set을 제공하는 기존 base class를 사용하는 경우가 많습니다. 예를 들면,

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

Viewset클래스를 사용하면 View 클래스를 사용할 때 보다 두 가지 주요 이점이 있습니다.

  1. 반복되는 로직을 단일 클래스로 결합될 수 있습니다. 위의 예시에서는 쿼리셋을 한 번만 지정하면 됩니다. 그러면 여러 view에 걸쳐 쿼리가 사용됩니다.
  2. 라우터를 사용함으로써 더 이상 직접 URL conf를 연결할 필요가 없습니다.

두 개다 장단점이 있습니다. 일반적인 view 및 url 연결을 사용하는 것이 보다 명확하고 제어 능력을 높여줍니다. ViewSet은 빠르게 시작하고 실행하려는 경우 또는 대규모 API가 있고 일관된 URL 구성을 적용하려는 경우에 유용합니다.

 

Viewset actions

REST 프레임워크에 포함된 기본 라우터는 아래와 같이 표준 create/retrieve/update/destroy style actions를 위한 라우트를 제공합니다.

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
       

Introspecting ViewSet actions

디스패치 중에 ViewSet에서 다음 특성을 사용할 수 있습니다.

  • besename : 생성된 URL 이름에 사용할 기준입니다.
  • action : 현재 action의 이름(ex : list, create)
  • detail : 현재 action이 list 또는 detail view에 대해 구성되었는지 여부를 나타내는 boolean입니다.
  • suffix : viewset 유형의 display suffix는 detail 특성을 반영합니다.
  • name : viewset의 표시 이름. 이 아규먼트는 suffix에 대해 상호 배타적입니다.
  • description : viewset의 개별 view에 대한 display 설명

이러한 특성을 검사하여 현재 action을 기준으로 동작을 조정할 수 있습니다. 예를 들어 다음과 유사한 list action을 제외한 모든 항목으로 사용 permission을 제한할 수 있습니다.

 

def get_permissions(self):
    """
    Instantiates and returns the list of permissions that this view requires.
    """
    if self.action == 'list':
        permission_classes = [IsAuthenticated]
    else:
        permission_classes = [IsAdmin]
    return [permission() for permission in permission_classes]

Marking extra actions for routing

라우팅할 수 있는 임시 메서드가 있는 경우 @action 데코레이터를 사용하여 해당 메서드를 display할 수 있습니다. 일반 action과 마찬가지로 추가 action은 단일 개체 또는 전체 컬렉션에 대해 수행될 수 있습니다. 이를 나타내려면 detail 아규먼트를 True 또는 False로 설정합니다. 라우터는 그에 따라 URL 패턴을 구성합니다. 예를 들어, DefaultRouter는 URL 패턴에 pk를 포함하도록 detail action을 구성합니다.

extra actions의 더 정확한 예시 :

from django.contrib.auth.models import User
from rest_framework import status, viewsets
from rest_framework.decorators import action
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

    @action(detail=True, 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.validated_data['password'])
            user.save()
            return Response({'status': 'password set'})
        else:
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

    @action(detail=False)
    def recent_users(self, request):
        recent_users = User.objects.all().order_by('-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)

action 데코레이터는 기본적으로 GET 요청을 라우팅하지만 methods 아규먼트를 설정하여 다른 HTTP 메서드를 허용할 수 있습니다. 예시는 다음과 같습니다.

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

데코레이터를 사용하면 permission_classes, serializer_class, filter_backends와 같은 모든 viewset-level 구성을 재정의할 수 있습니다.

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

그런 다음 두 가지 새로운 action을 ^users/{pk}/set_password/$ 그리고 ^users/{pk}/unset_password/$ 에서 사옹할 수 있습니다. URL 세그먼트와 역방향 URL 이름을 변경하려면 url_path 및 url_name 매개 변수를 사용합니다.

모든 extra actions를 보려면 .get_extra_actions()메서드를 호출하면됩니다.

Routing additional HTTP methods for extra actions

extra actions은 별도의 ViewSet 메서드에 추가 HTTP메서드를 연결할 수 있습니다. 예를 들어 위의 암호 set/unset 방법을 단일 라우트로 통합할 수 있습니다. 추가 매핑은 아규먼트를 허용하지 않습니다.

@action(detail=True, methods=['put'], name='Change Password')
    def password(self, request, pk=None):
        """Update the user's password."""
        ...

    @password.mapping.delete
    def delete_password(self, request, pk=None):
        """Delete the user's password."""
        ...

Reversing action URLs

action의 URL을 가져오려면 .reverse_action() 메서드를 사용하면 됩니다. reverse()를 위한 편리한 wrapper로, view의 요청 개체를 자동으로 전달하며 url_name 앞에 .basename 어트리뷰트를 붙입니다.

basename은 viewSet registartion 중에 라우터에 의해 제공됩니다. 라우터를 사용하지 않는 경우 basename 아규먼트를 .as_view()메서드에 제공해야합니다.

예시는 다음과 같습니다.

>>> view.reverse_action('set-password', args=['1'])
'http://localhost:8000/api/users/1/set_password'

또는 @action 데코레이터에서 설정한 url_name 속성을 사용할 수 있습니다.

>>> view.reverse_action(view.set_password.url_name, args=['1'])
'http://localhost:8000/api/users/1/set_password'

.reverse_action()에 대한 url_name 아규먼트는 동일한 인수와 @action 데코레이터를 일치시켜야 합니다. 또한 이 메서드를 사용하여 list 및 create과 같은 기본 작업을 되돌릴 수 있습니다.

API Reference

ViewSet

ViewSet 클래스는 API View에서 상속됩니다. permission_classes, authentication_classes등의 표준 애트리뷰트를 사용하여 viewSet의 API 정책을 제어할 수 있습니다.

ViewSet 클래스는 action 구현을 제공하지 않습니다. viewSet 클래스를 사용하려면 클래스를 재정의하고 action 구현을 명시적으로 정의해야 합니다.

GenericViewSet

GenericViewSet은 GenericAPIView에서 상속되는 클래스입니다. 그리고 기본 세트의 get_object, get_queryset 메서드 및 기타 generic view 기본 동작을 제공하지만 기본적으로 작업은 포함하지 않습니다.

GenericViewSet을 사용하려면 클래스를 재정의하고 필요한 mixin 클래스를 mixin하거나 action 구현(implementations)을 명시적으로 정의합니다.

ModelViewSet

GenericAPIView에서 상속된 ModelViewSet은 은 다양한 mixin클래스의 동작을 mixin하여 다양한 action에 대한 구현을 검토하고 포함합니다.

ModelView에서 제공하는 action설정 클래스는 .list(), .retrieve(), .create(), .update(), .partial_update() 및 .destroy()입니다.

예시

GenericAPIView를 확장한 ModelViewSet가 있기 때문에, 일반적으로 적어도 쿼리셋과 serializer_class 특성을 제공해야 합니다.

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

GenericAPIView에서 제공하는 모든 표준 애트리뷰트 또는 메서드 재정의를 사용할 수 있습니다. 예를들면, 작동해야 하는 쿼리셋을 동적으로 결정하는 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에서 쿼리셋 속성을 제거하면 연관된 라우터가 모델의 basename을 가져올 수 없으므로 라우터 등록의 특별하게 basename kwarg를 지정해야 합니다.

또한 이 클래스는 기본적으로 전체 create/list/retrieve/update/destroy 작업 셋을 제공하지만 표준 permission classes를 사용하여 사용 가능한 작업을 제한할 수 있습니다.

ReadOnlyModelViewSet

ReadOnlyModelViewSet 클래스 또한 GerericAPIView에서 상속됩니다. ModelView와 마찬가지로 다양한 action에 대한 구현을 포함하지만 '읽기 전용' 작업인 .list()와 .retrieve()만 제공합니다.

예시

ModelViewSet와 마찬가지로 쿼리셋과 serializer_class 애트리뷰트를 제공해야 합니다.

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

ModelViewSet와 마찬가지로 GenericAPIView에서 사용할 수 있는 표준 애트리뷰트 및 메서드 재정의를 사용할 수 있습니다.

Custom ViewSet base classes

전체 ModelViewSet가 없는 사용자 정의 ViewSet 클래스를 제공하애 할 수 있습니다. action들을 설정하거나 다른 방식으로 동작을 사용자 정의합니다.

예시

create, list 및 retrieve 작업을 제공하고 GenericViewSet에서 상속하여 필요한 작업을 mixin하는 GenericViewSet을 생성하는 과정은 다음과 같습니다.

from rest_framework import mixins

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

고유한 base ViewSet 클래스를 생성하려면 API 전체에서 여러 viewsets에서 재사용할 수 있는 일반적인 동작을 제공할 수 있습니다.

 

참고 :

https://www.django-rest-framework.org/api-guide/viewsets/#custom-viewset-base-classes

 

Viewsets - Django REST framework

viewsets.py After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output. — Ruby on Rails Documentation Django REST framework allows you to combine

www.django-rest-framework.org

 

'Django > Django REST framework' 카테고리의 다른 글

7. Parsers  (0) 2021.06.29
6. Routers  (0) 2021.06.28
4-2 Generic Views (Mixin,Concrete)  (0) 2021.06.23
4-1. Generic Views (GenericAPIView)  (2) 2021.06.07
3. DRF-Views  (0) 2021.06.06

댓글