DRF CBV에는 APIView, GenericAPIView, Mixin, GenericView, ViewSet가 있습니다.
지난번에는 APIView를 공부해보았는데요.
그러다 보니 자연스레 다른 CBV에도 관심이 생기게 되었습니다.
그 중에서도 왠지 관심이 간 게 ViewSet…!
이번 포스트에서는 “DRF ViewSet” 은 무엇이며 어떻게 사용되는지 APIView 방법과 비교하며 정리해보았습니다.

APIView

View에서 APIView 사용하기

APIView는 들어오는 HTTP 요청을 처리하고 적절한 HTTP 응답을 반환하는 뷰의 기본 클래스입니다.

위의 FBV 코드를 APIView로 만들면 아래와 같습니다.

from rest_framework.views import APIView

class ArticleListAPIView(APIView):
    def get(self, request):
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)


class ArticleDetailAPIView(APIView):

        # 두 번 이상 반복되는 로직은 함수로 빼면 좋습니다
    def get_object(self, pk):
        return get_object_or_404(Article, pk=pk)

    def get(self, request, pk):
        article = self.get_object(pk)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

    def put(self, request, pk):
        article = self.get_object(pk)
        serializer = ArticleSerializer(article, data=request.data, partial=True)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)

    def delete(self, request, pk):
        article = self.get_object(pk)
        article.delete()
        data = {"pk": f"{pk} is deleted."}
        return Response(data, status=status.HTTP_200_OK)

URL 패턴 정의하기

URL에서 어떤 view로 라우팅 할 것인지를 변경해줘야 합니다.

from django.urls import path
from . import views

app_name = "articles"
urlpatterns = [
    # 기존
    # path("", views.article_list, name="article_list"),
    # 변경
    path("", views.ArticleListAPIView.as_view(), name="article_list"),
]

ViewSets

View에서 ViewSet 사용하기

ViewSet은 여러 뷰들에 대한 논리를 단일 클래스로 결합하는 상위 수준의 추상화입니다.

여러 엔드포인트(endpoint)를 단일 클래스에서 관리할 수 있게 해줍니다.

이는 RESTful 원칙을 준수하고 일관된 URL 패턴이 필요한 API를 구축하는 데 특히 유용합니다.

ViewSet은 각 HTTP 메서드에 대해 자체적으로 액션 (action)을 정의합니다.

액션은 기본적인 CRUD 같은 프로세스를 함수로 정의했다고 생각하면 되는데요.

이렇게 함으로써 코드가 더 간결하게 만들 수 있습니다.

기본적인 ViewSet 사용법은 다음과 같습니다.

class ExampleViewSet(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

위에서 APIView로 구현했던 로직을 그대로 ViewSet으로 변경하면 아래와 같습니다.

# views.py
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import action
from .serializers import ArticleSerializer
from .models import Articles


class ArticleViewSet(ViewSet):
    def get_object(self, pk):
        return get_object_or_404(Articles, pk=pk)

    def list(self, request):
        articles = Articles.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)

    def create(self, request):
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

    def retrieve(self, request, pk=None):
        article = self.get_object(pk)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

    def update(self, request, pk=None):
        article = self.get_object(pk)
        serializer = ArticleSerializer(
            article, data=request.data, partial=True)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)

    def destroy(self, request, pk=None):
        article = self.get_object(pk)
        article.delete()
        data = {"pk": f"{pk} is deleted."}
        return Response(data, status=status.HTTP_200_OK)

URL 패턴 정의하기

Viewset은 하나의 클래스로 추상화 했기 때문에 urlpattern도 간단하게 입력할 수 있습니다.

update, detail, delete와 같이 pk가 필요한 경우에는 urls.py에서 지정한 urlpattern에서 뒤에 /pk/ 붙여주는 식으로 자동으로 urlpattern이 등록 됩니다.

# urls.py
from rest_framework.routers import DefaultRouter
from . import views

app_name = "articles"

# ViewSet을 Router에 등록
router = DefaultRouter()
router.register("", views.ArticleViewSet, basename='article')

# 추가 ViewSet이 있는 경우 해당 Viewset만 register해주면 됨
# router.register("comments", views.CommentViewSet, basename='comment')

# Router의 URL 패턴을 가져와서 urlpatterns에 추가
urlpatterns = router.urls

Note. PK가 아닌 Variable Routing이 필요한 경우

ViewSet에서 router를 사용하면 pk에 대한 variable routing까지 해주지만 그 외의 값은 추가적인 코드를 작성해야 하는 것 같습니다.

예를 들어 url에 username이 추가로 입력된 경우 해당 사용자의 모든 글을 가져오는 기능을 구현 하고 싶을 때 다음과 같이 urlpattern을 추가해줄 수 있습니다.

urlpatterns += [path('<str:username>/', views.ArticleViewSet.as_view({'get': 'retrieve'}), name='article'),]

@action : 사용자 지정 액션 추가

@action 데코레이터는 ViewSet 클래스의 메서드를 추가적인 액션으로 변환하는 데 사용됩니다.

이 데코레이터를 사용하면 기본적인 CRUD(Create, Retrieve, Update, Delete) 이외의 사용자 정의 액션을 ViewSet에 추가할 수 있습니다.

예를 들어 likeunlike과 같은 특정 기능을 @action을 통해서 구현할 수 있습니다.

from rest_framework.decorators import action

class ArticleViewSet(ViewSet):
    ...

    @action(detail=True, methods=['post'])
    def like(self, request, pk=None):
        article = self.get_object(pk)
        article.likes += 1
        article.save()
        return Response({'message': 'Article liked successfully'}, status=status.HTTP_200_OK)

위의 코드에서 @action 데코레이터는 like 메서드를 like 액션으로 변환합니다.

pk 값을 받고 작업하는 상세 작업

요청 method 정의

마치며

한번에 FBV, CBV 방법을 익히니 머리가 복잡해지는 것 같습니다… ㅎㅎ

CBV 방식은 상위 수준의 추상화를 해주다보니 점점 더 간단해지는 것 같으면서도…. 동시에 방법론이 여러개다 보니 복잡해지는 것 같기도 합니다…

참고하면 좋은 글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다


목차