수학과의 좌충우돌 프로그래밍

[Django] APIView, Mixins, generics APIView, ViewSet을 알아보자 본문

웹프로그래밍/DRF

[Django] APIView, Mixins, generics APIView, ViewSet을 알아보자

ssung.k 2019. 12. 27. 18:51

django 에서는 view 를 통해서 HTTP 요청을 처리합니다.

view에서 이를 처리하는 방법은 다양합니다. FBV(함수기반뷰), CBV(클래스기반뷰) 를 통해서도 API 를 만들 수 있지만 rest_framework 는 보다 쉽게, 효율적으로 만들 수 있습니다.

 

Serializer

[Django] django rest framework 를 위한 JSON 직렬화

 

[Django] django rest framework 를 위한 JSON 직렬화

ModelSerializer 를 통한 JSON 직렬화 DRF 에서는 ModelSerializer 를 통해 JSONRenderer 에서 변환가능한 형태로 먼저 데이터를 변환합니다. Serializer 는 장고의 Form 과 유사하며 ModelSerializer는 장고의 Mo..

ssungkang.tistory.com

저번 포스팅에서 Serializer 를 통해 Form 과 유사하게 데이터의 유효성검사 및 데이터베이스로의 저장을 구현하였습니다.

# serializers.py

from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'
        
# views.py

serializer = PostSerializer(data=request.POST)
if serializer.is_valid():
    serializer.save()
    return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
  

 

APIView 클래스와 api_view 장식자

이번 시간에는 본격적으로 APIView 에 대해서 알아보도록 합시다.

APIViewapi_view 는 각각 CBV와 FBV 에 대응되는 내용입니다.

두 가지 모두 뷰에 여러가지 기본 설정을 부여하게 됩니다. 이는 아래와 같고 상황에 맞춰서 이를 커스튬하여 사용하게 됩니다.

  • 직렬화 클래스 지정
    • renderer_classes
    • default
      • JSON 직렬화 : rest_framework.renderers.JSONRenderer
      • HTML 페이지 직렬화 : rest_framework.renderers.TemplateHTMLRenderer
  • 비직렬화 클래스 지정
    • parser_classes
    • default
      • JSON 포맷 처리 : rest_framework.parsers.JSONParser
      • FormParser : rest_framework.parsers.FormParser
      • MultiPartParser : rest_framework.parsers.MultiPartParser
  • 인증 클래스 지정
    • authentication_classes
    • default
      • 세션기반인증 : rest_framework.authentication.SessionAuthentication
      • HTTP basic 인증 : rest_framework.authentication.BasicAuthentication
  • 사용량 제한 클래스 지정
    • throttle_classes
    • default
      • 빈 튜플
  • 권한 클래스 지정
    • permission_classes
    • default
      • 누구라도 접근 허용 : rest_framework.permissions.AllowAny
  • 요청에 따라 적절한 직렬화/비직렬화 선택
    • content_negotiation_class
    • 같은 URL 요청에 대해서 JSON 응답을 할 지, HTML 응답을 할 지 판단
    • default
      • rest_framework.negotiation.DefaultContentNegotiation
  • 요청 내역에서 API 버전 정보를 탐지할 클래스 지정
    • versioning_class
    • 요청 URL의 HEADER에서 버전 정보를 탐지하여 맞는 버전을 호출
    • default
      • 버전 정보를 탐지하지 않습니다. : None

 

APIView

우선 APIView 부터 자세히 알아보도록 합시다.

  • 위에서 말했듯이 이는 CBV 중 하나이기 때문에 하나의 URL 에 대해서만 처리를 할 수 있습니다.
    • /post/ 에 대한 CBV
      • get : 포스팅 목록
      • post : 새 포스팅 생성
    • /post/<int:pk>/ 에 대한 CBV
      • get : pk 번 포스팅 내용
      • put : pk번 포스팅 수정
      • delete : pk번 포스팅 삭제
  • 요청 method 에 맞게 맴버함수를 정의하면 해당 method 로 request가 들어올 때 호출되게 됩니다.def get(self, request): pass def post(self, request): pass def put(self, request): pass def delete(self, request): pass
  • 각 method 가 호출되면 위에서 봤던 설정에 맞춰 처리가 이루어집니다.
    • 직렬화/비직렬화
    • 인증 체크
    • 사용량 제한 체크
    • 권한 체크
    • 요청한 버전 체크
  • 실습
    # models.py
    
    from django.db import models
    
    class Post(models.Model):
        title = models.CharField(max_length=100)
        content = models.TextField()
        create_at = models.DateTimeField(auto_now_add=True)
        update_at = models.DateTimeField(auto_now=True)
    

    # serializers.py
    
    from rest_framework.serializers import ModelSerializer
    from .models import Post
    
    class PostSerializer(ModelSerializer):
        class Meta:
            model = Post
            fields = '__all__'
    

    # views.py
    
    from rest_framework.response import Response
    from rest_framework.views import APIView
    from .models import Post
    from .serializers import PostSerializer
    
    # 포스팅 목록 및 새 포스팅 작성
    class PostListAPIView(APIView):
        def get(self, request):
            serializer = PostSerializer(Post.objects.all(), many=True)
            return Response(serializer.data)
        def post(self, request):
            serializer = PostSerializer(data=request.data)
            if serializer.is_valid():
              	serializer.save()
                return Response(serializer.data, status=201)
            return Response(serializer.errors, status=400)  
          
    from django.shortcuts import get_object_or_404
    
    # 포스팅 내용, 수정, 삭제
    class PostDetailAPIView(APIView):
        def get_object(self, pk):
            return get_object_or_404(Post, pk=pk)
          
        def get(self, request, pk, format=None):
            post = self.get_object(pk)
            serializer = PostSerializer(post)
            return Response(serializer.data)
        
        def put(self, request, pk):
          	post = self.get_object(pk)
            serializer = PostSerializer(post, 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):
            post = self.get_object(pk)
            post.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
    

    from django.urls import path, include
    from . import views
    
    urlpatterns = [
        # FBV
        path('post/', views.PostListAPIView.as_view()),
        path('post/<int:pk>/',views.PostDetailAPIView.as_view()),
    ]
    
  • 각 클래스에 대해 as_view 로 라우팅을 해줍니다.
  • views 에서 이들을 불러와 처리를 해주게 됩니다.
  • 이에 대한 Seializer 입니다.
  • 실습을 위해 간단한 Post 모델을 정의하였습니다.

좌 : localhost:8000/post/  우 : localhost:8000/post/1

 

 

@api_view 장식자

api_view는 FBV 에 대해서 사용하는 장식자 입니다.

python decorator 란

 

[python] 장식자, 데코레이터(decorator) 를 알아보자

python 으로 작성된 다른 코드들을 보다보면 아래와 같이 어느 함수 위에 @ 가 있는 걸 볼 수 있습니다. 이 @ 는 무엇을 나타내고 어떤 역할을 하지는 알아보도록 합시다. @decorator def function1(): print("ssu..

ssungkang.tistory.com

장식자에 대한 설명은 위의 링크를 참고하시면 됩니다.

  • 실습
    # views.py
    
    from rest_framework.response import Response
    from rest_framework.views import APIView
    from .models import Post
    from .serializers import PostSerializer
    from rest_framework.decorators import api_view
    
    @api_view(['GET','POST'])
    def post_list(request):
        if request.method == 'GET':
            qs = Post.objects.all()
            serializer = PostSerializer(qs, many=True)
            return Response(serializer.data)
        else:
            serializer = PostSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=201)
            return Response(serializer.errors, status=400)
    
    @api_view(['GET','PUT','DELETE'])
    def post_detail(request, pk):
        post = get_object_or_404(Post, pk=pk)
        if request.method == 'GET':
            serializer = PostSerializer(post)
            return Response(serializer.data)
        elif request.method == 'PUT':
            serializer = PostSerializer(post, data=reqeust.data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        else:
            post.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
    
  • # urls.py from django.urls import path, include from . import views urlpatterns = [ # FBV path('cbv/post/', views.post_list), path('cbv/post/<int:pk>/',views.post_detail), ]
  • api_view의 첫번째 인자로 해당 함수에서 가능한 request method 를 리스트로 지정해줍니다.

cbv

 

 

Mixins 상속

APIView 는 위에서 봤듯이 각 request method 마다 직접 serializer 처리를 해주었습니다.

하지만 이러한 부분들은 많이 사용되므로 여러 serializer 에 대해서 중복이 발생합니다.

따라서 rest_framework.mixins 에서는 이러한 기능들이 미리 구현이 되어 있습니다.

  • CreateModelMixin
  • ListModelMixin
  • RetrieveModelMixin
  • UpdateModelMixin
  • DestroyModelMixin

이름이 굉장히 직관적이므로 각각에 대한 자세한 설명은 생략하겠습니다.

querysetserializer_class 를 지정해주기만 하면 나머지는 상속받은 Mixin 과 연결해주기만 하면 됩니다.

# views.py

from rest_framework.response import Response
from rest_framework import generics
from rest_framework import mixins
from .models import Post
from .serializers import PostSerializer

class PostListMixins(mixins.ListModelMixin, mixins.CreateModelMixin,generics.GenericAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request)
    
    def post(self, request, *args, **kwargs):
        return self.create(request)

class PostDetailMixins(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    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.delete(request, *args, **kwargs)

 

이에 대한 urls 입니다.

# urls.py

from django.urls import path, include
from . import views

urlpatterns = [
    # Mixin
    path('mixin/post/', views.PostListMixins.as_view()),
    path('mixin/post/<int:pk>/', views.PostDetailMixins.as_view()),
]

 

결과 화면은 위와 동일하므로 생략하도록 하겠습니다.

 

generics APIView

Mixin 을 상속함으로서 반복되는 내용을 많이 줄일 수 있었습니다. 하지만 여러 개를 상속해야 하다보니 가독성이 떨어집니다. 다행히도 rest_framework 에서는 저들을 상속한 새로운 클래스를 정의해놨습니다.

총 9개의 클래스로 다음과 같습니다.

  • generics.CreateAPIView : 생성
  • generics.ListAPIView : 목록
  • generics.RetrieveAPIView : 조회
  • generics.DestroyAPIView : 삭제
  • generics.UpdateAPIView : 수정
  • generics.RetrieveUpdateAPIView : 조회/수정
  • generics.RetrieveDestroyAPIView : 조회/삭제
  • generics.ListCreateAPIView : 목록/생성
  • generics.RetrieveUpdateDestroyAPIView : 조회/수정/삭제

하나만 예시로 살펴보면 위에서 해주었던 Mixin 과 GenericAPIView 상속, 각 request method 에 대한 연결을 해주고 있습니다.

# rest_framework/generics.py

class ListCreateAPIView(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        GenericAPIView):

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

 

이를 통해 코드를 많이 줄일 수 있습니다.

# views.py

from rest_framework import generics
from .models import Post
from .serializers import PostSerializer

class PostListGenericAPIView(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

class PostDetailGenericAPIView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

 

urls 와 결과 화면은 중복되므로 생략하도록 하겠습니다.

 

ViewSet

마지막으로 알아볼 ViewSet 입니다. generics APIView 를 통해서 코드를 많이 간소화 하였지만 아직 queryset 과 serializer_class 가 공통적인데도 불구하고 따로 기재해주어야합니다. 이를 한 번에 처리해주는게 바로 ViewSet 입니다.

ViewSet 은 CBV 가 아닌 헬퍼클래스로 두 가지 종류가 있습니다.

  • viewsets.ReadOnlyModelViewSet : 목록 조회, 특정 레코드 조회
  • viewsets.ModelViewSet : 목록 조회, 특정 레코드 생성/조회/수정/삭제

 

Post 모델에 대해서 ViewSet으로 구현해보도록 하겠습니다.

# views.py

from .models import Post
from .serializers import PostSerializer
from rest_framework import viewsets

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

 

다음에 라우팅 하는 방법은 위와 조금 다릅니다.

Router 를 통해서 하나의 url 로 처리가 가능합니다.

#. urls.py

from django.urls import path, include
from . import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('viewset',views.PostViewSet)

urlpatterns = [
    path('',include(router.urls)),
]

 

http://localhost:8000/

http://localhost:8000/viewset/

http://localhost:8000/viewset/<int:pk>/

ViewSet 에 대해서는 다음 포스팅에서 좀 더 자세히 알아보도록 하겠습니다.

Comments