Django

[DRF] APIView vs ViewSet

r잡초처럼 2023. 2. 20. 20:47

DRF는 RESTful API를 만들기 위한 뷰를 정의하는 CBV 형태의 두 가지 방법이 있다.  APIView와 ViewSet이다. 두 가지의 특징과 차이점을 살펴보자

APIView

APIView는 커스텀하기 편한 기본 클래스이다. API View를 사용하면 각 HTTP 메서드(GET, POST, PUT, DELETE 등)를 별도로 정의하고 요청/응답을 수동으로 처리해야 한다. API View를 사용하면 API 로직과 응답 생성을 완벽하게 제어할 수 있지만 더 많은 상용 코드가 필요하다. 

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from django.contrib.auth.models import User

class ListUsers(APIView):
    """
    View to list all users in the system.

    * Requires token authentication.
    * Only admin users are able to access this view.
    """
    authentication_classes = [authentication.TokenAuthentication]
    permission_classes = [permissions.IsAdminUser]

    def get(self, request, format=None):
        """
        Return a list of all users.
        """
        usernames = [user.username for user in User.objects.all()]
        return Response(usernames)

 

ViewSet

ViewSet은 모델에서 CRUD(작성, 읽기, 업데이트, 삭제) 작업을 처리하기 위해 일반적으로 사용되는 일련의 작업을 제공하는 클래스 기반 뷰이다.

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)

또한 CRUD에 해당하는 url을 자동으로 생성해준다.

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

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

각 action에 대해 사용권한을 제한할 수 있다.

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 = [IsAdminUser]
    return [permission() for permission in permission_classes]

또한 라우팅을 통해 추가적인 작업을 할 경우 action decorator를 사용하여 해당 메서드를 표현할 수 있다.

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)

detail을 True로 설정한 경우 pk를 이용할 수 있으며 url은 다음과 같다.

users/{pk}/set_password/$