(DRF) APIView, Mixins, generics APIView, ViewSet 에 대해서

duo2208·2022년 1월 31일
3

Django

목록 보기
15/23
post-thumbnail

상속받는 클래스의 추상화(패턴화) 정도 한눈에 알아보기
APIView < mixins < generics APIView < ViewSet

  • APIView : 자주 쓰이는 view 들의 패턴을 패턴화 시켜 최소한의 코드로 Model 에 대한 view 를 구현하는 방법.
  • mixins : APIView 의 일반적인 로직들을 섞어 재사용성을 높임.
  • generics APIView : mixins 사용을 패턴화하여 정리.
  • ViewSet : generics APIViews 를 한번 더 합쳐서 정리.

APIView 와 @api_view


🚀 (DRF API Guide) Class-based Views & Function Based Views

DRF는 Django의 view 클래스를 서브클래싱하는 APIView 클래스를 제공합니다. APIView 클래스는 는 CBV에 기반하며, @api_view 장식자는 FBV에 기반합니다.

기본 속성

APIView@api_view 에서 기본으로 제공하는 속성과 디폴트값은 다음과 같습니다.

  • renderer_classes (직렬화 클래스)
    • JSON 직렬화 : rest_framework.renderers.JSONRenderer
    • HTML 직렬화 : rest_framework.renderers.TemplateHTMLRenderer
  • parser_classes (비직렬화 클래스)
    • JSON 포맷 처리 : rest_framework.parsers.JSONParser
    • FormParser : rest_framework.parsers.FormParser
    • MultiPartParser : rest_framework.MultiPartParser
  • authentication_classes (인증 클래스)
    • 세션에 기반한 인증 : rest_framework.authentication.SessionAuthentication
    • HTTP Basic 인증 : rest_framework.authentication.BasicAuthentication
  • throttle_classes (사용량 제한 클래스)
    • 빈튜플
  • permission_classes (권한 클래스)
    • 누구라도 접근 허용 : rest_framework.permissions.AllowAny
  • content_negotiation_class (요청에 따라 적절한 직렬화/비직렬화 선택)
    • rest_framework.negotiation.DefaultContentNegotiation
  • metadata_class
    • rest_framework.metadata.SimpleMetadata
  • versioning_class (요청 내역에서 API 버전 정보를 탐지할 클래스)
    • None : API 버전 정보를 탐지하지 않겠다.

APIView

  • 하나의 CBV 이므로 하나의 URL 만 처리 가능합니다.
  • 각 요청 method (get, post, put, delete) 에 맞게 멤버함수를 구현하면, 해당 method 요청이 들어올 때 호출됩니다.
  • 호출이 되면 호출 이전 단계(initial) 에서 다음을 처리합니다.
    (1) 직렬화 / 비직렬화 처리
    (2) 인증 체크
    (3) 사용량 제한 체크 : 호출 허용량 범위인지 체크
    (4) 권한 클래스 지정 : 비인증 / 인증 유저에 대해 해당 API 호출을 허용할 것인지 결정
    (5) 요청한 버전 체크 : 요청된 API 버전 문자열을 탐지하여, request.version 에 저장



APIView 내 dispatch

🦝 django-rest-framework/rest_framework/views.py

# Note: Views are made CSRF exempt from within `as_view` as to prevent
# accidental removal of this exemption in cases where `dispatch` needs to
# be overridden.

def dispatch(self, request, *args, **kwargs):
    """
    `.dispatch()` is pretty much the same as Django's regular dispatch,
    but with extra hooks for startup, finalize, and exception handling.
    """
    self.args = args
    self.kwargs = kwargs
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers

    try:
        **[self.initial(request, *args, **kwargs)](https://www.notion.so/EP04-APIView-a08bfeb63a2c49dea8431398488ad049)**

        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names: 
            handler = getattr(self, request.method.lower(),  
                              self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs) 

    except Exception as exc:
        response = self.handle_exception(exc)

    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response
 def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)

        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg

        # Determine the API version, if versioning is in use.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)



APIView 구현 예시

주로 APIView 를 상속 받은 형태로 구현됩니다.
APIView 내부에 csrf_exempt 가 구현되어 있기 때문에 post 요청에서 csrf token 체크를 하지 않아도 괜찮습니다.

# serializers.py

from rest_framework.serializers import ModelSerializer
from .models import Post

class PostSerializer(ModelSerialzier):
	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

""" list / create """
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.erros, status=400)

from django.shortcuts import get_object_or_404

""" detail / update / delete """
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()
   		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)    
# urls.py

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

urlpatterns = [
	path('post', views.PostListAPIView.as_view()),
 	path('post/<int:pk>/', views.PostDetailAPIView.as_view()),
]

@api_view 구현 예시

간단한 로직은 @api_view 데코레이터를 이용해 함수형 뷰로 구현하는 것도 좋습니다.

# 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'])	# 어떤 HTTP method 를 지원할 것인지 리스트로 명시
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)
        



Mixins


🚀 (DRF API Guide) Mixins

APIView 구현에서 우리는 각 request method 마다 직접 serializer 처리를 반복하였습니다. 이러한 번거로움은 Mixins 를 사용하면 간소화시킬 수 있습니다.

DRF에서 지원하는 mixins

  • CreateModelMixin
  • ListModelMixin
  • RetrieveModelMixin
  • UpdataModelMixin
  • DestroyModelMixin

mixins 구현 예시

Mixins 는 다른 클래스에 의해 상속되며 사용됩니다. querysetserializer_class 를 지정해 주고 상속받은 Mixins 와 연결만 해주면 됩니다. 허나 로직만 구현된 것이므로 request method 와 연결되는 부분은 우리가 정의해줘야 합니다. 이 부분이 여전히 번거롭긴 합니다.

# 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)
        
	def put(self, request, *args, **kwargs):
		return self.update(request)
        
	def delete(self, request, *args, **kwargs):
		return self.delete(request)        



generics APIView


🚀 (DRF API Guide) GenericAPIView

Mixins 를 상속함으로써 반복되는 내용을 많이 줄일 수 있었지만, 여러 상속으로 인한 가독성문제와 여전히 각 request method 에 대한 연결은 직접해줘야하는 번거로움이 있었습니다. 다행이 이러한 문제를 의식하고 DRF는 하나 이상의 Mixins 와 결합된 클래스 generics APIView 클래스를 제공하고 있습니다.

DRF에서 지원하는 generic APIView

🦝 django-rest-framework/rest_framework/generics.py

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

generics APIView 구현 예시

mixins 를 썼을 때 보다 많이 간소화 되었지만 querysetserializer_class 가 공통적임에도 불구하고 따로 기재하고 있는 모습이 보입니다

# 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



ViewSet


🚀 (DRF API Guide) ViewSet

우리는 일반적으로 REST API를 구현할 때 ListAPI 와 DetailAPI 을 구현합니다. ListAPI 에서는 GET, POST 메서드를, DetailAPI 에서는 GET, PUT DELETE 메서드를 앞 선 예제들로 구현해 보았습니다.

DRF의 ViewSet 은 2개의 URL별로 구현된 5개의 메서드를 하나의 단일클래스에 제공합니다. 하나의 헬퍼클래스로 두 개 이상의 URL 처리가 가능한 것입니다.

ModelViewSet 의 종류

VewSet 의 헬퍼 클래스는 두 가지가 있습니다.

  • viewsets.ReadOnlyModelViewSet
    ›› list 지원 → 1개의 url
    ›› detail 지원 → 1개의 url

  • viewsets.ModelViewSet
    ›› list / create 지원 → 1개의 url
    ›› detail / update / partial_update / delete 지원 → 1개의 url

ViewSet 구현 예시

querysetserializer_class 를 지정해주고, url을 매핑하면 됩니다. url 을 매핑하는 방법은 두 가지가 있습니다.

# 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

(1) 개별 View 로 등록

# views.py

""" urls.py 에 직접 path를 전부 써야합니다 """
post_list = PostViewSet.as_view({'get': 'list',})
post_detail = PostViewSet.as_view({'get': 'retrireve'})

(2) Router 를 통해 일괄적으로 등록 (💡권장)

# urls.py

from rest_framework.routers import DefaultRouter

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

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

📌 참고 출처

0개의 댓글