Mixins와 GenericAPIView

API를 작업할때 CRUD는 항상 사용되는 반복적인 작업인데, mixins는 APIView에서 request의 method마다 시리얼라이저 코드를 작성하는 것을 줄이기 위해서 클래스 레벨에서 시리얼라이저를 등록하고 있다.


from rest_framework import generics
from rest_framework import mixins

# 믹스인 (generics, mixins import 하자)
class BooksAPIMixins(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
    queryset            = Book.objects.all()
    serializer_class    = BookSerializer
    
    def get(self, request, *args, **kwargs): # mixins.ListModelMixin
        return self.list(request, *args, **kwargs) # 리스트 전체 조회
    
    def post(self, request, *args, **kwargs): # mixins.CreateModelMixin
        return self.create(request, *args, **kwargs) # 생성
    
class BookAPIMixins(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
    queryset            = Book.objects.all()
    serializer_class    = BookSerializer
    lookup_field        = 'bid'
    # Django 기본 모델의 pk를 사용하는 것이 아닌 bid를 사용하고 있으므로 lookup_field 사용
    
    def get(self, request, *args, **kwargs): # mixins.RetrieveModelMixin
        return self.retrieve(request, *args, **kwargs) # 리스트 전체 조회
    
    def put(self, request, *args, **kwargs): # mixins.UpdateModelMixin
        return self.create(request, *args, **kwargs) # 생성
    
    def delete(self, request, *args, **kwargs): # 삭제
        return self.destroy(request, *args, **kwargs) # mixins.DestroyModelMixin
    
  1. ListModelMixin
  • Queryset을 리스팅하는 믹스인
  • .list(request, *args, **kwargs) 메소드로 호출하여 사용
  • GenericAPIView의 self.filter_queryset, self.get_queryset, self.get_serializer 등의 메소드를 활용해 - 데이터베이스에 저장되어 있는 데이터들을 목록 형태로 response body로 리턴
  • 성공 시, 200 OK response 리턴
  1. CreateModelMixin
  • 모델 인스턴스를 생성하고 저장하는 역할을 하는 믹스인
  • .create(request, *args, **kwargs) 메소드로 호출하여 사용
  • 성공 시, 201 Created 리턴
  • 실패 시, 400 Bad Request 리턴
  1. RetrieveModelMixin
  • 존재하는 모델 인스턴스를 리턴해 주는 믹스인
  • .retrieve(request, *args, **kwargs) 메소드로 호출하여 사용
  • 성공 시, 200 OK response 리턴
  • 실패 시, 404 Not Found 리턴
  1. UpdateModelMixin
  • 모델 인스턴스를 수정하여 저장해 주는 믹스인
  • .update(request, *args, **kwargs) 메소드로 호출하여 사용 부분만 변경하고자 할 경우, .partial_update(request, *args, **kwargs)메소드를 호출하여야 하며, 이 때 요청은 HTTP PATCH requests여야 함
  • 성공 시, 200 OK response 리턴
  • 실패 시, 404 Not Found 리턴
  1. DestoryModelMixin
  • 모델 인스턴스를 삭제하는 믹스인
  • .destroy(request, *args, **kwargs) 메소드로 호출하여 사용
  • 성공 시, 204 No Content 리턴
  • 실패 시, 404 Not Found 리턴

코드를 보면 genericsmixins를 불러와서 각각 해당 되는 기능의 클래스를 만들때 상속한다.

시리얼라이저에 일일이 데이터를 넣고 처리해서 Response로 보내고 하는 과정없이 상속받은 mixins에 연결하는 것만으로 API 구현이 끝난다.

상세 기능을 구현할때 Mixins 클래스에 존재하는 메소드나 속성을 상속받는 클래스에서 사용할때 믹스인 클래스의 메소드가 오버라이딩되어 의도치 않게 작동되는 부분은 주의하자.

GenericAPIView

DRF에는 mixins를 조합해서 미리만들어둔 일종의 mixins 세트가 있다. 하나하나 만들던 것을 세트로 만든 것으로 이애하면 될것 같다. 다음은 공식문서에 나와있는 generics APIView 리스트다.

API 이름 기능
generics.ListAPIView 전체 목록 조회(read-only) 전용 엔드포인트
generics.CreateAPIView 생성(create-only) 전용 엔드 포인트
generics.RetrieveAPIView 1개 조회 전용 엔드 포인트
generics.DestroyAPIView 1개 삭제 전용 엔드 포인트
generics.UpdateAPIView 1개 수정 전용 엔드 포인트
generics.ListCreateAPIView 전체 목록 조회/쓰기 엔드포인트
generics.RetrieveUpdateAPIView 1개 조회/수정 엔드포인트
generics.RetrieveDestroyAPIView 1개 조회/삭제 엔드포인트
generics.RetrieveUpdateDestroyAPIView 1개 조회/삭제/수정 엔드포인트
# 제네릭스
from rest_framework import generics

class BooksAPIGenerics(generics.ListCreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

class BookAPIGenerics(generics.RetrieveUpdateDestroyAPIView):
    queryset            = Book.objects.all()
    serializer_class    = BookSerializer
    lookup_field        = 'bid'

상속하는 과정은 mixins와 동일한데, 여러개의 mixins를 상속받는 방식에서 mixins가 조합된 generics를 한번 상속 받는 것으로 끝낸다.

RetrieveUpdateDestroyAPIView 클래스의 경우 기본적으로 get, put, patch, delete 를 API단에서 지원해준다.

참고로 내부 코드를 보면 결국엔 내부적으로 mixins를 구현한 코드다

Viewset

FBV/CBV 처럼 queryser/serializer_class 부분이 매번 겹치게 되는데, 하나의 클래스로 모델을 처리할 수 있으면, 겹치는 부분이 없겠지요? Viewset은 이걸 한번에 처리해줍니다.

Viewset은 말그대로 Viewset 뷰의 집합인데, 기본적인 모습은 CBV의 기본형과 똑같고, 실제로 공식문서에서도 CBV의 종류 중 하나라고 말한다.

# 뷰셋 viewsets
class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

DRF에서 Viewset 내부 코드를 보면 결국 Mixins를 사용하고 있는 것을 알수 있다.

CBV에서는 각 url을 설정할떄 .as_view()를 직접 매번 지정해줬어야 했는데, 라우터를 쓰면 다음과 같은 코드로 URL을 설정할 수 있다.

# 라우터 정의
from rest_framework import routers
from .view import BookViewSet

router = routers.SimpleRouter() # 
router.register('books', BookViewSet)

# 1 기존 include 방법
urlpatterns = [
    path('', include(router.urls))
]
# 2 
urlpatterns = router.urls

장점은 다음과 같다.

  1. 하나의 클래스로 하나의 모델에 대한 내용을 전부 작성할수 있고, 앞서 설명한 queryset이나 serializer_class 등 겹치는 부분을 최소화 가능

  2. 라우터를 통해 URL을 일일이 지정하지 않아도 일정한 규칙의 URL을 만들수 있다.

아무래도 Mixins, generics, Viewset 까지 오면서 코드가 짧아지고 DRF가 대신 만들어주는 기능이 많아졌는데, 프레임 워크가 대신해주는건 좋지만, 아무래도 커스텀을 해야할 상황이 추후에 무조건 벌어지기 때문에 무조건 사용은 안될 것이다.