앞의 편에서 우리는 GenericAPIView
와 Mixins
을 (결합) 상속해서 여러 가지 기능을 수행하는 뷰 함수를 짤 수 있었다.
이번에는 GenericAPIView
와 Mixins
을 이미 결합해 놓은 Concrete View 클래스를 하나만 상속해서 동일한 기능을 수행하는 뷰 함수를 짜는 것을 알아보겠다.
먼저 Concrete View가 무엇인지를 알기 위해 앞에서 작성한 코드와 Concrete View로 작성된 코드를 비교해 보자.
아래 코드는 Ebook 전체항목을 리스트로 보여주는 기능과 새로운 Ebook 객체를 생성해 주는 코드이다. 이를 위해 ListModelMixin
과 CreateModelMixin
을 상속 받아 사용하였고, get함수에서는 ListModelMixin
의 list메소드를, post 함수에서는 CreateModelMixin
의 create 메소드를 호출하여 사용하였다.
[views.py 코드]
class EbookListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
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)
이 코드가 어떻게 작동하는지 알기 위해서는 사용한 각 Mixin의 소스코드를 살펴볼 필요가 있다.
먼저 ListModelMixin
는 list
함수를 가지고 있다. list
함수는 쿼리셋과 serializer 클래스를 인자로 받고, 해당 쿼리셋을 인자로 전달된 serializer 클래스에 맞게 serialize한 후 그 데이터(JSON으로 변환된 데이터)를 리턴해 준다.
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
CreateModelMixin
은 create 함수를 가지고 있다.
이 함수는 1) request.data를 받아서, 2) deserialize하고, 3) is_valid
로 데이터 유효성 검사를 하고, 4) create
함수에 내장된 perform_create
함수를 통해 전송된 데이터를 해당 객체에 저장을 해 주고, 5) 마지막으로 해당 데이터와 status, header를 리턴해 주는 역할을 한다.
class CreateModelMixin:
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
위와 정확히 동일한 기능을 수행하는 뷰 함수를 아래와 같이 ListCreateAPIView
라는 concrete view class를 상속받아서 사용할 수 있다. 가장 큰 차이점은 get, post 함수를 작성할 필요가 없다는 것이다.
왜냐하면 concrete view 클래스는 1) genericAPIView
+ Mixins
클래스를 단순히 합친 것이 아니라, 2) 해당 기능에 맞는 get, post, put 등 메소드 핸들러를 이미 내장하고 있기 때문이다. 따라서 7줄의 코드가 단 3줄로 줄어들게 되었다.
[views.py]
class EbookListCreateAPIView(generics.ListCreateAPIView):
queryset = Ebook.objects.all().order_by("id")
serializer_class = EbookSerializer
위의 코드만 보면 ListCreateAPIView가 정확히 어떤 역할을 수행하는지 감이 안 올 수 있다. 따라서
[ListCreateAPIView 소스코드]
ListCreateAPIView
를 보면 알 수 있듯이, 이 함수는 이미 get과 post 함수를 이미 내장하고 있다. 따라서 ConcreteView class에서는 따로 customize를 해야 할 이유가 없다면, get, post와 같은 함수를 정의해 줄 필요가 없다.
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset or creating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Review object는 request.data로 전달되는 데이터 외에 ebook pk값과 author이 같이 저장되어야 한다. 이렇게 serialzier.save()에서 추가로 저장해야 되는 데이터(forkeign key로 연결된 데이터)가 있을 때는 perform_create()
를 override해 줘야 한다.
class ReviewCreateAPIView(generics.CreateAPIView):
queryset = Review.objects.all()
serializer_class = ReviewSerializer
def perform_create(self, serializer):
ebook_pk = self.kwargs.get("ebook_pk")
ebook = get_object_or_404(Ebook, pk=ebook_pk)
review_author = self.request.user
serializer.save(ebook=ebook, review_author=review_author)
Concrete View Class에서 언제, 어떤 메소드를 override하게 되는가? 자주 사용하게 되는 메소드와 케이스를 정리해 보자.