본문 바로가기
카테고리 없음

[장고] DRF - APIView와 ModelViewSet의 차이 및 사용 예시

by 샘오리 2024. 6. 26.
728x90
반응형

https://samori.tistory.com/101

 

[장고] DRF 함수형 vs 클래스형의 차이 및 사용 예시

DRF  = (a.k.a. Django Rest Framework)  장고는 MTP =  Model, Template, View의 형태로 이루어져 있는데이 View가 사실상 장고의 핵심으로 request를 받아서 연산 혹은 db 작업을 하고response까지 하는 모든 작업을

samori.tistory.com

위 포스팅에 이어서

 

REST API처리를 할 때 

사용됐던 APIView와 이 APIView의 단점을 보완한 ModelViewSet의 차이 및 사용예시를 다루려고 한다.

 

 

APIView 와 ModelViewSet의 차이

 

1. 함수의 네이밍 차이

 

APIView는 함수 네이밍이

REST api와 동일하다. (get,post,put,patch,delete)

 

ModelViewSet의 함수 네이밍은

좀 더 목적에 맞게끔 지어졌다.

이미 REST에 익숙한 사람들에겐 이게 오히려 불편할 수도 있지만

나는 충분히 적절하고 납득이 가능한 네이밍이라고 생각한다.

마치 인터프리터 언어같달까?

 

예를 들어 post 대신 create를 쓰고

patch 대신 partial_update를 쓴다.

물론 post와 patch를 써도 되고 그렇게 써도 매핑이 된다.

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    
    # GET Method ( list)
    def list(self, request):
        pass

    # POST Method
    def create(self, request):
        pass

    # GET Method ( 하나의 객체 )
    def retrieve(self, request, pk=None):
        pass

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

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

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

 

2. Url Mapping 차이

APIView를 사용하면 .as_view()라는 빌트인 함수를 사용하게 되고 url포맷이 같다면

REST api는 모두 하나의 url로 처리가 가능했다.

 

허나 url 포맷이 하나는 my-template, 하나는 my-template/<pk> 라면 둘다 같은 클래스의 .as_view()를 호출하지만 두개를 직접 각각 선언해야했다.

 

예컨데 아래와 같다.

from django.urls import path
from .views import MyTemplateAPIView

urlpatterns = [
    path('my-template', MyTemplateAPIView.as_view(), name='my_template'),
    path('my-template/<int:pk>', MyTemplateAPIView.as_view(), name='my_template'),
]

 

단순히 pk만 하나 넣어줘도 같은 클래스를 매핑하는데도 path를 달리 적어줘야 한다는 것은 개발자 입장에서 피곤해지는 것이라고 본 것이다.

그래서 ModelViewSet은 기본적으로 아래와 같이 Router라는 것을 사용한다.

#. urls.py

from django.urls import path, include
from . import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'my-template', views.MyTemplateAPIView)

urlpatterns = [
    path('',include(router.urls)),
]

 

 

위와 같이 선언한다면 my-template으로 시작하는 웬만한 url 패턴은 다 자동으로 만들어지고 라우터를 통해 path 한줄에 추가된다.

예를 들어 아래와 같이 자동으로 생성되고 추가되는 원리이다.

 

  • ^my-template/$ (GET: list, POST: create)
  • ^my-template/{pk}/$ (GET: retrieve, PUT: update, PATCH: partial update, DELETE: destroy)

만약 커스텀으로 url 패턴을 바꾸고 싶다면?

예를 들어 my-template/sample/edit/pk 로 request를 날리고 싶다면?

 

view 함수 네이밍을 커스텀으로 하고 @action decorator를 사용하면 된다.

    @action(detail=True, methods=['get', 'post'], url_path='sample/edit/(?P<pk>[^/.]+)')
    def sample_edit(self, request, pk=None):
        if request.method == 'GET':
            instance = self.get_object()
            serializer = self.get_serializer(instance)
            return Response(serializer.data)
        elif request.method == 'POST':
            instance = self.get_object()
            serializer = self.get_serializer(instance, data=request.data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data)
            return Response(serializer.errors, status=400)

 

Detail 이란?

 

Detail이라는 것은 하나의 행 (특정 인스턴스)을 다룰 것인지, 전체 모델을 다룰 것인지 구분하는 것이고

pk를 잡는 다는 것은 하나의 행을 특정해서 가져오든,조작하든 한다는 뜻이기에 Detail을 True로 잡는 것이다.

이렇게 되면 @action decorator는 pk를 기대하게 된다. 반대로 True로 잡으면 생성되는 url 패턴은 pk를 추가하지 않게 된다.

 

 

이렇게 되면 url은 변경하지 않아도 된다.

한 클래스에 하나의 router만 register 하면 되니 매우 간결해진다.

참고로 여러개의 router가 register 될 때는 basename이라는 것을 명시해줘야한다.

아래는 예시이다.

router = DefaultRouter(trailing_slash=False)
router.register(r'menu_object', view.MenuObjectInfoView, basename='menu_object')
router.register(r'user_object', view.UserObjectInfoView, basename='user_object')

#trailing_slash=False 는 url 마지막 슬래시를 안받겠다는 뜻이다.

 

3. 클래스 변수 사용 유무

APIView는 queryset과 serializer를 각 method마다 선언해줘야 했다.

이는 귀찮기도 귀찮지만 코드의 중복이 꽤 많아질 수 밖에 없는 태생적인 한계이다.

ViewSets 중 ModelViewSet의 경우 이를 해결하는데 클래스 변수라는 것을 사용한다.

클래스 상단에 한번만 선언해주고 각 method에서는 self.queryset, self.serialiazer_class 등으로 호출할 수 있기 때문에

상당히 간결해질 수 있는 것이다. ModelViewSet이라고 반드시 저렇게 해야한다는 것은 아니기 때문에

필요에 의해 커스터마이징이 필요하다면 따로 선언해줘도 무방하다.

from rest_framework import viewsets
from .models import MyModel
from .serializers import MyModelSerializer

class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()  # Class-level queryset definition
    serializer_class = MyModelSerializer  # Class-level serializer definition

 

결론 

앞서 다룬 3가지의 차이가 APIView 와 ModelViewSet 의 대표적인 차이이다. 

글을 쭉 읽었다면 ModelViewSet이 무조건 더 좋아보이는데 꼭 그런것은 아닌게

아주 디테일하게 커스터마이징이 필요한 경우에는 APIView가 더 나은 방법일 수 있다.

 

일단 커스텀 ULR 패턴을 사용한다면

@action decorator에 url_path를 일일히 달아주는 것도 매우 귀찮고

처음에는 router를 통해서 생성되는 url의 경우 명시적이지 않고 내부적으로 자동으로 생성되다보니

답답함이 있다.

 

허나 레거시가 아니라 새로 코드를 짜는거라면 위 언급한 여러가지 장점을 고려했을 때

웬만해서는 ModelViewSet을 추천하는 바이다.

 

다음 포스팅은 리액트를 장고 서버에서 한번에 돌리는 법,

즉 서버사이드렌더링으로 구동하는 방법에 대해서 다뤄보려고 한다.

728x90
반응형