* 목차
- Intro
- Class 기반 View로 API 재작성
- Mixin 사용
- Class기반 Generic view 사용
*이전글
Intro
function 기반 view가 아닌 class 기반 view를 사용해서 API view를 작성할 수 도 있습니다. 이는 공통 기능을 재사용할 수 있고 코드를 깔끔하게 유지하는데 도움이 되는 강력한 패턴입니다.
Class 기반 view로 API 재작성
toy/views.py에 tutorial 2에서 작성했던 function 기반 view들을 class로 재작성합니다.
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import *
from .serializers import *
class Snippets(APIView):
def get(self, request, format=None):
serializer = SnippetSerializer(Snippet.objects.all(), 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)
class SnippetDetail(APIView):
def _get_object(self, pk):
try:
return Snippet.objects.all()
except Snippet.DoesNotExist:
raise Response(status=status.HTTP_404_NOT_FOUND)
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 not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_404_NOT_FOUND)
serializer.save()
return Response(serializer.data)
def delete(self, request, pk, format=None):
snippet = self._get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
그리고 views가 class로 작성되어 있으므로 toy/urls.py도 형식을 맞춰줍니다.
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from . import views
urlpatterns = [
path('snippets/', views.Snippets.as_view()),
path('snippets/<int:pk>/', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
위와 같이 작성이 완료되면 runserver를 동작해 봅니다.
Mixin 사용
* Mixin이란 : 상속(inherited)받지 않고 다른 클래스의 method를 포함하는 class. 보통은 "포함됨(include)"이란 개념으로 설명. 다중상속이 미지원되는 언어(JAVA. C#, javascript)에서는 상속받지 않고 다른 class의 method를 사용하는 다양한 기법들이 있습니다. 그러나 python은 다중상속이 가능하여 특별한 기법 없이 다중 상속으로 class의 method를 사용할 수 있습니다. 그럼에도 불구하고 mixin이란 용어를 사용하는 이유는 rest framework에서 상속할 mixin class는 단독적으로 사용하지 않고 기능의 재조합과 재사용을 목적으로 한 class이기 때문입니다. 그 의도가 담겨져 mixins라는 이름이 붙었습니다.
class 기반 view를 사용할 때의 장점 중 하나는 재사용 가능한 동작을 쉽게 구성할 수 있다는 것입니다. 지금까지 사용해온 create/retrieve/update/delete 작업은 우리가 만드는 모든 model에 적용이 될 수 있습니다. 이러한 공통 작업은 rest framework의 mixin class에서 구현할 수 있습니다. mixin class를 사용하여 view를 구성하는 방법을 살펴보겠습니다.
toy/views.py
from rest_framework import mixins, generics
# from rest_framework.views import APIView
# from rest_framework.response import Response
# from rest_framework import status
from .models import *
from .serializers import *
class Snippets(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
## generic view 형식으로 설정
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
## ListModelMixin method 사용
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
## CreateModelMixin method 사용
return self.create(request, *args, **kwargs)
보시면 기존의 import가 전부 빠지고 mixins, generics이 자리잡은 모습입니다. 먼저 Generic APIView부터 설명 드리겠습니다.
Generic API View
이 class는 standard list 및 detail view에 일반적으로 필요한 동작을 추가하여 rest framework의 api view class를 확장합니다. generic view는 GenericAPIView와 한개 이상의 mixin class와 결합하여 구축됩니다.
아래는 Generic API View의 Attribute 설명입니다.
Attribute | 설명 |
queryset | view에서 객체를 반환하거나 불러내는데 사용. 일반적으로 queryset을 다루거나 get_queryset()을 overriding해야 한다. |
serializer_class | 입력의 유효성을 확인하고 deserialization하는데 사용할 class. 일반적으로 serializer_class을 다루거나 get_serializer_class()를 overriding해야 한다. |
lookup_field | 개별 모델 인스턴스의 객체 조회를 수행하는데 사용해야 하는 model field. 기본값은 'pk'. |
lookup_url_kwarg | 객체 조회에 사용해야 하는 URL kwarg. 이 값을 설정하지 않으면 lookup_field와 동일한 값을 사용. |
pagination_class | 목록 결과의 페이지를 셀 때 사용. 기본값은 DEFAULT_PAGINATION_CLASS 설정과 동일. |
filter_backends | 쿼리 집합을 필터링 하는데 사용. 기본값은 DEFAULT_FILTER_BACKENDS 설정과 동일 |
이러한 Attribute 제공으로 인해 queryset, serializer_class를 설정했습니다. self.list와 self.create는 mixins에서 가져온 method입니다.
SnippetDetail class도 마저 작성하겠습니다.
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)
API의 동작을 기술하는 code가 꽤 단순해졌습니다. 놀랍게도 이전과 동일하게 동작합니다. 이번에는 삭제 동작을 진행해보겠습니다.
저는 pk가 4번인 객체를 조회했습니다. 여기서 Delete버튼을 누르겠습니다.
그러면 정말 삭제할 것인지 한 번 더 물어봅니다. 이러한 기능은 제가 만들지 않아도 기본으로 제공해주는 rest framework입니다. 정말 편리하네요.
보시면 HTTP 204 No Content를 응답 받았습니다. 다시 전체 목록으로 가봅니다.
4번 항목이 삭제되었음을 확인할 수 있습니다.
Class기반 Generic view 사용
여기서 한 단계 더 나아가겠습니다. rest framework는 이미 mixins된 Generic view를 제공하고 있습니다. 이것을 사용하면 views.py를 더욱 줄일 수 있습니다.
from rest_framework import generics
from .models import *
from .serializers import *
class Snippets(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
코드가 정말 깔끔해졌습니다. function 기반의 view를 다시 한번 볼까요?
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import *
from .serializers import *
@api_view(["GET", "POST"])
def snippets(request, format=None):
if request.method == "GET":
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
return Response(serializer.data)
elif request.method == "POST":
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)
@api_view(["GET", "PUT", "POST"])
def snippet_detail(request, pk, format=None):
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == "GET":
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
elif request.method == "PUT":
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_404_NOT_FOUND)
elif request.method == "DELETE":
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
놀랍게도 이 코드는 generic view와 동일하게 동작합니다! 만약에 여러분이 좀 더 customize하게 작성하고 싶다면 function 기반의 view 작성이 좋을 수도 있지만 단순한 동작만을 필요로 한다면 class 기반의 generic view를 사용해보시기 바랍니다.
다음에는 인증과 권한에 대해 알아보겠습니다.
*reference
https://www.django-rest-framework.org/api-guide/generic-views/#genericapiview
https://www.django-rest-framework.org/api-guide/generic-views/#mixins
https://en.wikipedia.org/wiki/Mixin
https://www.django-rest-framework.org/tutorial/3-class-based-views/
'Python > STL' 카테고리의 다른 글
Python - np.array vs list (1) | 2023.03.16 |
---|