클라이언트 측에서 우리 API로 요청을 보낼 때에는 URL과 Method를 조합하여 요청을 보내는데, 이때 같은 URL 내의 여러 Method를 처리하는 방법으로 함수형과 클래스형이 구분되었다.
그렇다면 URL과 Method의 조합은 몇 가지 정도 있을까? 일반적인 구조로는 다음의 5개의 조합이 존재한다.
DRF로 API를 개발할 때 만들어야 하는 기능은 GET(list)
POST(create)
GET(retrieve)
PUT(update)
DELETE(destroy)
의 5가지가 전부이다. 이들은 모델 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)
앞서 작성했던 CBV를 보면 books, book과 같은 모델로부터 가져온 데이터나 BookSerializer와 같은 시리얼라이저가 여러 번 사용되고 있음을 확인할 수 있다. 이 부분의 중복을 제거하기 위해 DRF에서는 mixins
라는 것을 미리 정의하고 있다.
mixins
는 APIView에서 request의 method마다 시리얼라이저 코드를 작성하는 것을 줄이기 위해 클래스 레벨에서 시리얼라이저를 등록하고 있다. 각 method에서는 시리얼라이저 코드를 작성하는 대신 각 method별 mixin에 연결하여 사용하기만 하면 된다.
from rest_framework import generics # generics 불러오기
from rest_framework import mixins # mixins 불러오기
from .models import Book
from .serializers import BookSerializer
class BooksAPIMixins(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): # ListModelMixin, CreateModelMixin 상속 + GenericAPIView는 공통적으로 상속
queryset = Book.objects.all()
serializer = BookSerializer
def get(self, request, *args, **kwargs): # GET - 전체 도서 조회
return self.list(request, *args, **kwargs) # ListModelMixin과 연결
def post(self, request, *args, **kwargs): # POST - 도서 1권 등록
return self.create(request, *args, **kwargs) # CreateModelMixin과 연결
class BookAPIMixins(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView): # RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin 상속 + GenericAPIView는 공통적으로 상속
queryset = Book.objects.all()
serializer = BookSerializer()
lookup_field = 'bid' # Django 기본 모델 pk가 아닌 bid를 pk로 사용하고 있으니 lookup_field로 설정한다.
def get(self, request, *args, **kwargs): # 도서 1권 조회
return self.retrieve(request, *args, **kwargs) # RetrieveModelMixin과 연결
# 도서 1권 수정, 도서 1권 삭제 기능 추가!
def put(self, request, *args, **kwargs): # 도서 1권 수정
return self.update(request, *args, **kwargs) # UpdateModelMixin과 연결
def delete(self, request, *args, **kwargs): # 도서 1권 삭제
return self.destroy(request, *args, **kwargs) # DestroyModelMixin과 연결
from django.urls import path, include
from .views import helloAPI, BooksAPIMixins, BookAPIMixins
urlpatterns = [
path("hello/", helloAPI),
path("mixin/books/", BooksAPIMixins.as_view()),
path("mixins/book/<int:bid>/", BookAPIMixins.as_view()),
]
CBV와 동일하게 .as_view()
도 작성해야 한다.
mixins
보다 간단하게 REST API의 5가지 메인 기능을 수행할 수 있다.
DRF generics
는mixins
를 조합해 일종의mixins 세트
를 미리 만들어둔다.
from rest_framework import generics # generics 불러오기
from .models import Book
from .serializers import BookSerializer
class BooksAPIGenerics(generics.ListCreateAPIView): # 전체 도서 조회 + 도서 1권 등록
queryset = Book.objects.all()
serializer_class = BookSerializer
class BookAPIGenerics(generics.RetrieveUpdateDestroyAPIView): # 도서 1권 조회 + 도서 1권 수정 + 도서 1권 삭제
queryset = Book.objects.all()
serializer_class = BookSerializer
lookup_field = 'bid'
from django.urls import path, include
from .views import helloAPI, BooksAPIMixins, BookAPIMixins
urlpatterns = [
path("hello/", helloAPI),
path("mixin/books/", BooksAPIMixins.as_view()),
path("mixins/book/<int:bid>/", BookAPIMixins.as_view()),
]
CBV와 동일하게 .as_view()
도 작성해야 한다.
🥸 내가 이해한 대로-
APIMixins
- 정의할 때 아래에서 사용할 기능을 수행하는
mixin
들을 선택적으로 상속받는다.generics.GenericAPIView
는 공통적으로 상속받는다.get()
post()
함수 내용은 작성할 필요없이 그냥return self.기능()
하면 된다.
APIGenerics
- 정의할 때 아래에서 사용할 기능을 수행하는
generic
하나를 상속받는다.generics.GenericAPIView
는 상속받지 않는다.get()
post()
함수 자체를 정의하지도 않고 따라서return
구문도 작성하지 않는다. 여러 개의mixins
를 사용하던 방식에서는 각mixins
를 메소드에 매칭시키는 과정이 필요했고 그것이 return 구문에 있었다면, 이제mixins
가 조합된generics
를 사용하다 보니 어차피 List+Create면 GET과 POST가 들어간다는 것을 이미 알고 있는 것이 되기 때문이다.
여태까지 작업했던 것은 하나의 클래스가 하나의 URL을 담당하는 방식이었다. URL마다 클래스를 만들었고 각 클래스에서 해당 URL로 들어오는 다양한 메소드를 처리할 수 있도록 하였다.
그러다 보니 queryset이나 serializer_class 부분이 겹치게 되었다. 하나의 클래스로 하나의 모델을 전부 처리해 줄 수 있으면 겹치는 부분이 없어질 것이다. Viewset
에서는 그러한 문제를 해결할 수 있다.
Viewset
은 내부적으로 5가지 기능의mixins
를 상속받아 정의된ModelViewSet
을 이용해queryset
과serializer_class
를 정의해 주는 것으로 모델에 대한 기본적인 REST API를 만들 수 있다.
from rest_framework import viewsets
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
from rest_framework import routers
from .views import BookViewSet
router = routers.SimpleRouter() # 라우터 객체 선언
router.register('books', BookViewSet) # 라우터에 ViewSet 등록
urlpatterns = router.urls
urlpatterns
에는 기존처럼 각 path를 일일이 등록하는 것이 아니라, router.urls를 include하여 마치 프로젝트의 urls.py에서 앱의 url을 불러오는 것처럼 작성하였다.