함수형이 익수해졌기 때문에, 굳이 class형으로 view를 짜야할까라는 의구심이 가득했었다. 그러나 해당 튜토리얼을 보면서 그런 생각은 다 사라졌고, class형 view를 열심히 공부해둬야겠다는 생각밖에 없었다. 일단 공식문서에서 설명하기로는 재사용성이 증가한다고 한다. 그리고 한번 쓱 훓어본 결과, 장고에서 지원하는 제너릭뷰, apiView 상속, 믹스인이라는 강력한 기능일 지원했기 때문이다. 공부하면서 드는 생각인데 나름 뛰어난 개발자들은 어디서 뭘 보고 공부하는 것일까.. 공식문서 보고 공부하는 걸까..? 그런 정형화 된 틀을 알면 진짜 공부하기 편할텐데. 독학의 슬픔인가 ㅠ 그래도 걱정마라! 장고공식문서에 따르면 클래스형 뷰 작성이 항상 우수하다고 말할 수 있는것은 아니라고 나온다 !
여튼 이번에는 클래스를 기반으로 View를 작성하는 방법에 대해서 알아보도록 하겠다!
# 클래스 기반 View 작성하기
## SnippetList
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
"""
List all snippets, or create a new snippet.
"""
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_400_BAD_REQUEST)
코드는 당연히 공식문서의 코드를 참고했다. 가장 크게 달라진점은, View자체를 def(함수)가 아닌 class(클래스)로 만들었다는 점과 (APIView)를 상속받았다는 것 이다. 그 외 코드는 똑같다. 클래스 안에서 get과 post로 HTTP-method를 나눴는데 함수형 view에서는 직접 request.method == "GET" 으로 나눈거에 비해 간결해졌다는 것을 알 수 있다.
## SnippetDetail
class SnippetDetail(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
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 put(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)
여기도 똑같다. 아직까지 코드의 구성은 크게 달라진게 없고, 어디선가 들어오는 pk를 매개변수로 받아 각 게시글의 고유 id값 즉 primary-key로 사용한다.
## class형 view의 urls작성법
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)
urls.py를 약간 수정해줘야 한다. 원래는 views.SnippetList 이렇게 사용했었지만 뒤에 .as_view()를 추가해줬다. 그거 말고는 다른게 없다.
# APIView ?
view를 class로 작성하면서 APIView를 상속 받게 했다. APIView가 그래서 뭘까 궁금해질 수 밖에 없다. 그래서 찾아 본걸 정리해보려고 한다.
class SnippetList(APIView):
def get(self, request, format=None):
return
def post(self, request, format=None):
return
def update(self, request, format=None) :
return
def delete(self, requset, format=None) :
return
- Request는 Django의 HttpRequest 인스턴스가 아닌 rest-framework의 request의 인스턴스를 사용한다
- 장고의 HttpResponse가 아닌 rest-framework의 Response를 반환한다.
- APIException 예외케이스가 발견되면 적절하게 response를 변환해 사용할 수 있게 한다.
- 들어오는 request를 authenicate하고, 적절한 권한 혹은 throttle(제한사항)을 체크하여 실행한다.
라고 하는데 몇몇은 정확히 와 닿지가 않는다 사용해가면서 알아갈 거 같다. 장고 공식문서에서도 DRF의 믹스인이나 제너릭뷰 뷰셋등이 처음에는 복잡할 수 밖에 없다고 한다. 그래서 공식문서외에 추가적인 문서를 추천해주는데 잊지 않게 하기 위해 기록해 두겠다.
아 그리고 추가적으로 CBV(클래스 기반 뷰)의 장점은 Detail에서 get_object()함수를 만들어 클래스내에서 재사용 한거와 같이, 재사용성이 용이하다는 것이다!
# 믹스인 사용, Mixins
API를 작성하면 생성/ 검색 / 업데이트 / 삭제는 항상 구성하는 요소일 것이고 그 형태또한 비슷할 것으로 예상된다. 그래서 DRF에서는 이를 미리 정의 시켜 놓고 편리하게 사용 할 수 있도록 해주는 기능인거 같다.
특정한 클래스에 상속을 통해 새로운 속성이나 새로운 기능을 추가 하는 것을 의미한다. <- Mix + in
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics
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)
간단한 설명을 붙이겠다. mixins.ListModelMixin, mixins.CreateModelMixin을 사용했다. 그리고 queryset = Snippet.objects.all()과 serializer_class = SnippetSerializer를 지정해줬는데, 사실 이건 왜 이렇게 했는지는 정확히 모르겠지만, class기반을 사용할때 저렇게 꼭 정해진 변수(이름)으로 뭔가를 지정해주고, 그것을 기준점으로 하여 get, post가 쓰이는걸로 유추 중이다. 확실히 알게 된다면 다시 돌아와서 글을 수정하도록 하겠다. 여튼 그렇게 하고나면 def get(....)에서 self.list를 쓸 수 있게 된다. 순전히 mixins.List..를 사용했기 때문에 저렇게 쉽게 list를 뽑아와서 response해준다. post또한 똑같다. 이렇게 비슷한 원리로 아래와 같이, put과 delete도 구현할 수 있게 된다. 일단, 정말 코드가 간결해진걸 알 수 있다.
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)
난 사실 이게 작동할까라는 굉장한 의구심을 가지고 있었는데.. 당연한거지만 잘 작동한다. 일단 여기서 궁금한건 현업에서 개발할때는 이렇게 절대 안쓰겠지..? 커스텀하겠지...?
이렇게 믹스인의 막강한 기능을 확인해 볼 수 있었다.
# 제너릭 클래스 뷰 generic class-based views
사실 이거 보고 충격을 금치 않았다.. 난 장고 공부하면서 나름 게시판 CRUD구현 했던거에 엄청 뿌듯했었다.
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
근데 이거면 끝난다고 한다.. 제너릭에서 리스트 크리에이트 뷰 또는 리트라이브 업데이트 디스토리를 상속받는다. 아까 믹스인에서 상속받은걸 제너릭에서 이렇게 상송받아 버렸다.. 이게 과연 작동할까..?
당연히 잘 작동한다. 세상을 부정하고 싶다. 이 막강한 제너릭 뷰에 대해서 조금 흥미가 생겨서 알아봐야겠다.
제너릭뷰에 대해서 잘 설명하신 분이 계신다. 일단 이번 포스팅은 여기서 끝내고, 다음에 추가적으로 정리를 하던가 다음 튜툐리얼을 진행해 보도록 하겠다!