[DRF] 책 정보 검색 API 개선하기 w/ mixins, generics, Viewset

mia·2024년 8월 5일
0

✏️ DRF의 심화 개념: mixins, generics, Viewset을 알아보자

클라이언트가 API로 보내는 요청은 URL과 Method의 조합이다.
나올 수 있는 URL과 Method의 조합은 5가지이다.

(앞서 만들었던 책 정보 검색 API를 참고)

  • 전체 책 목록 : GET /books/ (list)
  • 책 1권 정보 등록 : POST /books/ (create)
  • 책 1권 정보 조회 : GET /book/1 (retrieve)
  • 책 1권 정보 수정 : PUT /book/1 (update)
  • 책 1권 정보 삭제 : DELETE /book/1 (destroy)

앞서 만들었던 책 정보 검색 API의 클래스형 뷰를 다시 살펴보자

class BooksAPI(APIView):
    def get(self, request):
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    def post(self, request):
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
class BookAPI(APIView):
    def get(self, request, bid):
        book = get_object_or_404(Book, bid=bid)
        serializer = BookSerializer(book)
        return Response(serializer.data, status=status.HTTP_200_OK)
  • 하나의 클래스 내에서도 books, book과 같은 모델로부터 가져온 데이터 & BookSerializer와 같은 시리얼라이저가 여러 번 사용된다
  • 이러한 중복을 없애기 위해 DRF에서는 mixins을 정의하고 있다.

mixins

  • 반복적인 기능을 하나의 클래스로 제공
  • 각 method에 시리얼라이저 코드를 일일이 작성X ➡️ 상속받은 mixin에 연결하여 사용

위 클래스형 뷰를 mixins로 다시 작성해보자

  • queryset : 모델에 질문을 보내 받아온 데이터
    • 일반적으로 그냥 모든 데이터를 불러온다
  • serializer_class : 해당 API에서 사용할 시리얼라이저를 설정
  • *args, **kwargs : 함수와 함께 전달될 데이터를 담는 인자로, 상황에 따라 쓰일 수도 있고 안 쓰일 수도 있는 인자
from rest_framework import generics
from rest_framework import mixins

class BooksAPIMixins(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class BookAPIMixins(mixins.RetrieveModelMixin, generics.GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = 'bid'
    # Django 기본 모델 pk가 아닌 bid를 pk로 사용하고 있기에 따로 설정해줌
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)
  • 함수의 내용이 없어지고 바로 return
    • 각 method별 처리하는 기능(list, create, retrieve)에 따라 각각 다른 것을 리턴한다

그렇다면 수정(update), 삭제(destroy) 기능도 써보자
수정 & 삭제 기능은 책 한 권마다 있어야 하므로,
retrieve 기능이 있는 두 번째 클래스를 수정!

class BookAPIMixins(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = 'bid'
    # Django 기본 모델 pk가 아닌 bid를 pk로 사용하고 있기에 따로 설정해줌
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)
    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

함수 전체가 두 줄씩으로 줄었다... 놀랍다😱

이제 URL을 등록해보자
/mixin/ URL로 추가!

path("mixin/books/", BooksAPIMixins.as_view()),
path("mixin/book/<int:bid>/", BookAPIMixins.as_view()),

example/mixin/books로 들어가보면 정상작동~

이런 편리한 폼도 뿅 생겼다
아까는 빈 Content란에 JSON을 통째로 입력했어야 했는데 엄청난 발전이다
mixin을 사용하며 DRF가 함께 제공해주는 템플릿이라고 한다

example/mixin/book/1도 확인해보자

위에는 DELETE 버튼, 아래는 PUT 버튼이 새로 생겼다
수정, 삭제 기능 모두 잘 작동한다
굿



generics

  • 위 mixins을 간단히 조합해서 만들어둔 9가지 세트
  • generics.ListAPIView : 전체 목록
  • generics.CreateAPIView : 생성
  • generics.RetrieveAPIView : 1개 조회
  • generics.UpdateAPIView : 1개 수정
  • generics.DestroyAPIView : 1개 삭제
  • generics.ListCreateAPIView : 전체 목록+생성
  • generics.RetrieveUpdateAPIView : 1개 조회+수정
  • generics.RetrieveDestroyAPIView : 1개 조회+삭제
  • generics.RetreiveUpdateDestroyAPIView : 1개 조회+수정+삭제

위에서 mixins로 고쳤던 클래스형 뷰를 generics로 다시 고쳐보자
전체 목록+생성과 1개 조회+수정+삭제 조합이므로
generics.ListCreateAPIViewgenerics.RetrieveUpdateDestroyAPIView를 사용하면 될 듯

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를 상속받지 않고 하나의 generics만 상속받는다
  • return 구문 필요 여부
    • 여러 개의 mixins를 상속받았을 때는 각 mixin을 메소드에 매칭시켜야 했어서 필요 O
    • generics는 어차피 mixins의 조합이기에, list+create 조합이면 get+post에 들어간다는 것을 이미 알고 있으므로 필요 X




지금까지는 URL마다 클래스를 만들었기 때문에, queryset이나 serializer_class 부분이 계속해서 겹치게 되었다.
이러한 중복을 최소화하기 위해 사용하는 것이 바로 Viewset이다.


Viewset

  • mixin을 기반으로 작성된, 하나의 클래스로 하나의 모델을 전부 처리해주는 뷰의 집합
  • 5가지의 mixins 기능을 한 번에 구현

Viewset으로 뷰를 간단하게 작성해보자!

from rest_framework import viewsets

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

단 4줄, 하나의 클래스로 아까 만들었던 클래스 두 개를 대신할 수 있다.

URL도 아주 간단하게 연결해주었다

from rest_framework import routers
from .views import BookViewSet

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

urlpatterns = router.urls.

Router

  • 라우터를 통해 URL을 일일이 지정하지 않아도 일정한 규칙의 URL을 만들 수 있다
  • 라우터 객체를 만들고 라우터에 Viewset을 등록한다
    • 'books'는 API의 엔드포인트를 의미하며, BookViewSet에 대한 URL 경로이다
      즉, BookViewSet에 대한 CRUD(생성, 읽기, 수정, 삭제) 작업을 수행하기 위한 URL 경로
  • GET /books/: 모든 책 목록을 가져오는 요청
  • POST /books/: 새로운 책을 추가하는 요청
  • GET /books/<int:pk>/: 특정 ID를 가진 책의 세부 정보를 가져오는 요청
  • PUT /books/<int:pk>/: 특정 ID를 가진 책을 수정하는 요청
  • DELETE /books/<int:pk>/: 특정 ID를 가진 책을 삭제하는 요청

각 path를 일일이 등록하지 않고 router.urls를 include하며 프로젝트의 urls.py에서 각 앱의 url을 불러오는 것처럼 작성하는 효과가 있다.


마무리

mixins, generics, Viewset으로 오면서 점점 코드가 짧아지고 DRF가 대신 만들어주는 기능들이 늘어난다.
하지만 Viewset과 라우터가 가장 강력한 형태라고 해서 그것만이 정답은 아니다.
상황에 따라 적절히 잘 사용해보자!

profile
바보

0개의 댓글