FBV가 아닌 CBV를 이용해 API views를 작성할 수도 있다. 공통적인 함수를 재사용할 수 있고, 코드를 DRY 정책을 따르도록 하는 강력한 패턴이다.
DRY: Don't Repeat Yourself.
기존의 FBV로 구현한 API views를 CBV로 리팩토링해보자. 많이 바꾸지는 않는다.
# views.py
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from django.http import Http404
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
class SnippetList(APIView):
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SnippetSerializer(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_404_BAD_REQUEST)
class SnippetDetail(APIView):
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def post(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
전체적인 모양은 FBV 때와 크게 다르지 않다.
다만 각 메소드명을 HTTP 메소드명과 동일하게 사용하고 있다는 점이 가장 큰 차이점이고,
이전 포스트에서 언급했듯이 FBV에서 사용하던 @api_view 데코레이터 대신에 APIView 클래스를 상속받아 각 용도에 맞는 클래스를 만들었다.
@api_view 데코레이터에 HTTP 메소드를 전달하는 대신 동일한 이름의 메소드를 정의하는 것이다.
CBV 패턴에 맞게 urls.py의 내용도 수정하자.
# urls.py
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
path('snippets/', views.SnippetList.as_view()),
path('snippets/<int:pk>/', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
FBV 패턴에서는 각 URL마다 호출할 메소드를 직접 명시했다. (views.snippet_list())
CBV 패턴에서는 상속했던 APIView 클래스의 클래스 메소드인 as_view()를 호출한다.
as_view() 메소드는 해당 클래스의 인스턴스를 생성하고, dispatch() 메소드를 호출한다. 호출된 이 dispatch() 메소드가 요청을 분석해서 어떤 HTTP 메소드 요청인지 얻어낸다. POST 요청이면 앞에서 생성된 인스턴스 안에서 post라는 이름을 가진 메소드로 요청을 중계한다.
매칭되는 메소드가 정의되지 않은 경우, HTTPResponseNotAllowed 예외를 발생시킨다.
CBV 패턴의 큰 이점 중 하나는 재사용 가능한 함수를 쉽게 조합할 수 있다는 점이다.
지금까지 사용해왔던 CRUD는 우리가 생성하는 모델 기반 API views와 굉장히 유사하다. DRF의 믹스인 클래스들을 통해 CRUD와 같은 공통적인 함수가 구현되어 있다.
# views.py
from rest_framework import mixins
from rest_framework import generics
from django.http import Http404
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
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 SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
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)
변경된 views 코드를 보면 GenericAPIView, ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin를 상속받도록 정의했다.
GenericAPIView 클래스는 핵심 함수를 제공하고, 각 믹스인은 .retrieve(), .update(), .destory() 등의 액션을 제공한다.
믹스인 클래스들을 사용하여 코드의 양을 전보다 조금 줄였고, 더 놀라운 것은 아직 한 단계가 더 있다는 것이다.
views.py 모듈을 더 다듬을 수 있도록 DRF에서 믹스인을 포함한 Generic view들을 제공하고 있다.
# views.py
from rest_framework import generics
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
이게 뭐지? 싶을 정도로 짧아졌다. 마치 추상화 클래스를 정의한 것 같은 모양새다. 모델로부터 불러온 queryset과 Serializer의 인스턴스를 클래스 속성에 저장하여 사용한다.
DRF의 generics 모듈로부터 Generic view를 상속받아 사용하는데, 용도에 따라 선택할 수 있다.
Create 기능 하나만 제공하는 CreateAPIView 클래스가 있고, Create와 Read 기능을 함께 제공하는 ListCreateAPIView 클래스도 있어서 입맛에 맞게 상속받아 사용하면 된다.