✏️ 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)
위 클래스형 뷰를 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)
그렇다면 수정(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.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.ListCreateAPIView
와 generics.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'
지금까지는 URL마다 클래스를 만들었기 때문에, queryset
이나 serializer_class
부분이 계속해서 겹치게 되었다.
이러한 중복을 최소화하기 위해 사용하는 것이 바로 Viewset이다.
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.
'books'
는 API의 엔드포인트를 의미하며, BookViewSet에 대한 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과 라우터가 가장 강력한 형태라고 해서 그것만이 정답은 아니다.
상황에 따라 적절히 잘 사용해보자!