APIViews --> GenericAPIView + Mixins --> 그리고 Generic classes 중에서도
한층 더 발전된 형태의 Concrete View Classes!
Concrete view classes는 기존의 GenericAPIViews class와 mixins의 기능이 더 연장된 것으로 완전히 다른 것은 아니다. 이를테면 기존 GenericAPIViews의 RetrieveModelMixin과 UpdateModelMixin이 합쳐진 형태로 Concrete view classes내에는 RetrieveUpdateAPIView가 존재한다. 이렇듯 끝판왕 느낌이 있는지라 가장 빠르고, 가장 쉽게 읽힌다. 하지만 그런만큼 GenericAPIView와 mixins에 대한 선행 이해가 없으면 사용하기 어렵기도 하다.
1) 기존에 작성한 ebooksapi/ebooks/api/views.py를 ConcreteView를 이용하여 수정하기
먼저 기존에 작성한 class를 주석처리 한 후,
from rest_framework import generics
from rest_framework import mixins
from ebooks.models import Ebook
from ebooks.api.serializers import EbookSerializer
#class EbookListCreateAPIView(mixins.ListModelMixin,
# mixins.CreateModelMixin,
# generics.ListCreateAPIView):
# queryset = Ebook.objects.all()
# serializer_class = EbookSerializer
#
# def get(self, request, *args, **kwargs):
# return self.list(request, *args, **kwargs)
#
# def post(self, request, *args, **kwargs):
# return self.create(request, *args, **kwargs)
다음을 작성해본다.
from rest_framework import generics
from rest_framework import mixins
from ebooks.models import Ebook
from ebooks.api.serializers import EbookSerializer
class EbookListCreateAPIView(generics.ListCreateAPIView):
queryset = Ebook.objects.all()
serializer_class = EbookSerializer
# mixins 클래스만 상속하지 않을 뿐 여기까지는 똑같다. 근데 여기까지가 끝.
# ConcreteView는 더 고수준으로 추상화된 클래스를 제공하기 때문에 더 코드가 짧아질 수밖에 없다.
class EbookDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
# RetrieveUpdateDestroyAPIView의 definition을 살펴보면,
# 이 클래스 자체에 이미 mixins(RetrieveModel, UpdateModel, DestroyModel)들과
# GenericAPIView를 상속하고 있음을 확인할 수 있다. 상속하고 있는 부모 클래스는
# 모두 자식 클래스에서 사용이 가능하므로, 이 클래스(자식) 내에서도 당연히 사용이 가능한 것이다.
queryset = Ebook.objects.all()
serializer_class = EbookSerializer
2) ebooksapi/ebooks/api/urls.py 수정
기존에 작성했던 코드에 한 줄을 더 작성한다.
from django.urls import path
from ebooks.api.views import EbookListCreateAPIView
urlpatterns = [
path('ebooks/', EbookListCreateAPIView.as_view(), name='ebook-list'),
path('ebooks/<int:pk>/', EbookDetailAPIView.as_view(), name='ebook-detail'),
# 이 라인을 추가하면서 <int:pk>에 대한 이야기를 안할 수 없는데,
# 이 부분이 바로 GenericAPIViews에서 속성으로 작성해야 하는 값 중 하나인
# lookup_field에 해당하는 것이다. (따로 작성하지 않을 경우 default 값으로 pk를 가짐. 지금이 그 경우라 urls.py에 그냥 <int:pk>해도 API 연결에 무리가 없는 것.)
]
3) python manage.py runserver
4) 127.0.0.1:8000/api/ebooks/1 (pk=1)로 들어가보면 pk=1에 해당하는 ebook의 Json으로 표현된 정보가 보이고, 화면 상단과 하단에 delete, put, patch 기능도 함께 제공하는 것을 볼 수 있다.
5) 다음으로 ReviewCreateAPIViews를 작성하기 위해 ebooksapi/ebooks/api/views.py를 재수정
from rest_framework import generics
from rest_framework.generics import get_object_or_404
from ebooks.models import Ebook, Review #Review 모델과
from ebooks.api.serializers import EbookSerializer, ReviewSerializer #ReviewSerializer를 추가 임포트 한다.
class EbookListCreateAPIView(generics.ListCreateAPIView):
queryset = Ebook.objects.all()
serializer_class = EbookSerializer
class EbookDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Ebook.objects.all()
serializer_class = EbookSerializer
class ReviewCreateAPIView(generics.CreateAPIView):
queryset = Review.objects.all()
serialzer_class = ReviewSerializer
# 이 경우, 새로운 리뷰를 작성하여 추가하는 것인데
# 우리는 새로운 리뷰를 작성할 때 내가 리뷰를 작성하는 그 ebook에 대해서 자동연결되기를 원한다.
# 게다가 Review 모델을 살펴보면, Review 모델은 ebook과 이미 FK로 묶여 있다.
# 여기에 새로운 메소드를 추가하며 해당 작업을 해준다.
def perform_create(self, serializer):
# 이 때, 원래 rest_framework의 mixins를 임포트하던 것을
# rest_framework.generics의 get_objects_or_404를 임포트하도록 수정한다.
# ConcreteAPIView를 사용하며 mixins 임포트의 필요성은 사라졌지만, 지금 하려는 작업이
# get_object_or_404를 필요로 하기 때문이다.
# 현재 클래스는 CreateAPIView를 상속받고 있는데,
# CreateAPIView는 mixins.CreateModelMixin을 상속한다.
# 그리고 CreateModelMixin 클래스는 클래스 안에 create 메소드를 정의하고 있고,
# create 메소드는 같은 클래스 내 다른 메소드인 perform_create를 작동시키도록 되어있다.
# 그리고 perform_create 메소드가 ⭐️serializer.save()를 정의하고 있다.
# 그렇기 때문에 요 세 줄 아래에 마지막에 serializer.save(ebook=ebook)이 작동하는 것. (정말 먼 길 돌아온다....ㅋㅋㅋ)
ebook_pk = self.kwargs.get("ebook_pk")
# get메소드에 인자로 넣은 ebook_pk는 urls.py에서 kwargs로 정의될 인자 값이다
ebook = get_object_or_404(Ebook, pk=ebook_pk) # 위에서 정의된 인자를 이용하여 Ebook 모델에서 객체를 찾아 반환하여 ebook에 넣는다 (없으면 404 반환)
serializer.save(ebook=ebook) # 위에서 찾은 값을 ebook 인자에 대입하여 저장한다.
# ⭐️ 여기서 이렇게 ebook 값을 직접 지정하기 때문에,
# ⭐️ ReviewSerializer에서 ebook 값을 제외한 채로 직렬화하여야만 한다!!
6) ebooksapi/ebooks/api/serializers.py 수정
from rest_framework import serializers
from ebooks.models import Ebook, Review
class ReviewSerializer(serializers.ModelSerializer):
class Meta:
model = Review
exclude = ("ebook", )
# 원래는 fields="__all__"로 모든 필드를 받았지만, 이젠 ebook을 제외한다.
class EbookSerializer(serializers.ModelSerializer):
reviews = ReviewSerializer(many=True, read_only=True)
class Meta:
model = Ebook
fields = "__all__"
7) 이번에는 ReviewDetailAPIViews를 작성하기 위해 ebooksapi/ebooks/api/views.py를 재재수정
from rest_framework import generics
from rest_framework.generics import get_object_or_404
from ebooks.models import Ebook, Review
from ebooks.api.serializers import EbookSerializer, ReviewSerializer
class EbookListCreateAPIView(generics.ListCreateAPIView):
queryset = Ebook.objects.all()
serializer_class = EbookSerializer
class EbookDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Ebook.objects.all()
serializer_class = EbookSerializer
class ReviewCreateAPIView(generics.CreateAPIView):
queryset = Review.objects.all()
serialzer_class = ReviewSerializer
def perform_create(self, serializer):
ebook_pk = self.kwargs.get("ebook_pk")
ebook = get_object_or_404(Ebook, pk=ebook_pk)
serializer.save(ebook=ebook)
class ReviewDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Review.objects.all()
serialzer_class = ReviewSerializer
8) ebooksapi/ebooks/api/urls.py에 Review 관련하여 만든 두 API 연결해주기
from django.urls import path
from ebooks.api.views import (EbookListCreateAPIView, EbookDetailAPIView,
ReviewCreateAPIView, ReviewDetailAPIView)
urlpatterns = [
path('ebooks/', EbookListCreateAPIView.as_view(), name='ebook-list'),
path('ebooks/<int:pk>', EbookDetailAPIView.as_view(), name='ebook-detail'),
path('ebooks/<int:ebook_pk>/review',
ReviewCreateAPIView.as_view(), name='ebook-review'),
path('reviews/<int:pk>',
ReviewDetailAPIView.as_view(), name='review-detail')
9) 잘 작동하는 것을 확인할 수 있다.