본문 바로가기

Python/STL

Django REST framework tutorial3 - Class-based Views

* 목차

- Intro

- Class 기반 View로 API 재작성

- Mixin 사용

- Class기반 Generic view 사용

 

*이전글

 

Django REST framework tutorial2 - Requests and Responses

* 목차 - Intro - Code 작성 - URL에 format suffix 추가하기 - Test *이전글 Django REST framework tutorial1 - Serialization * 목차 - intro - Set Environment - Serialization을 위한 Model 작성 - Serializer class 만들기 - Serializer 사용

tyoon9781.tistory.com

 


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