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
클래스도 있어서 입맛에 맞게 상속받아 사용하면 된다.