아래의 링크를 보고 거의 똑같이 요약한 글.
🌏[Django] APIView, Mixins, generics APIView, ViewSet을 알아보자
django는 view를 통해서 Http 요청을 처리한다.
view를 작성하는 방법은 다양하다.
크게는 FBV(Function-Based Views), CBV(Class-Based Views)로 나눌 수 있다.
이러한 방식으로 API를 만들 수도 있지만 django REST framework를 사용하는 방법도 있다.
APIView
는 CBV에 대응되며 api_view
는 FBV에 대응된다.
하지만 공통적으로 두 가지 모두 view에 기본 설정을 부여하는 역할을 한다.
부여하는 기본 설정은 아래와 같다. 아래의 내용이 default값이 된다.
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.parsers.MultiPartParser
authentication_classes=
: 인증 클래스 지정
session 기반 인증: rest_framework.authentication.SessionAuthentication
Http basic 인증: rest_framework.authentication.BasicAuthentication
throttle_classes=
:
기본값은 빈 튜플
permission_classes
:
누구에게나 접근 허용: [rest_framework.permissions.AllowAny]
content_negotiation_class=
: 요청에 따라 직렬화 또는 비직렬화 선택 / 같은 URL에 대한 요청에 대해 JSON응답 vs. HTML응답 판단: rest_framework.negotiation.DefaultContentNegotiation
versioning_class=
: 요청 내녁에서 API 버전 정보를 탐지할 클래스 지정 / 요청 URL의 HEADER에서 버전 정보를 탐지하고 맞는 버전을 호출: None
(버전 정보를 탐지하지 않음)
명시하지 않아도 기본값으로 들어가는 내용들이다. 필요 시 이를 명시하고 수정하여 상황에 맞게 쓸 수 있다.
APIView는 CBV의 일종이기 때문에 하나의 URL에 대한 처리만 가능하다.
get
: 목록post
: 생성get
: pk(int)번째 자원의 내용put
: pk(int)번째 자원에 대한 수정delete
: pk(int)번째 자원을 삭제요청의 method에 맞게 멤버함수를 정의해두면 해당 method의 request가 들어올 때마다, 일치하는 멤버함수가 호출된다.
#example
class RestAPI(APIView):
def get(self, request):
pass
# get 요청이 들어왔을 때 수행할 기능
def post(self, request):
pass
# post 요청이 들어왔을 때 수행할 기능
def put(self, request):
pass
# put 요청이 들어왔을 때 수행할 기능
def delete(self, request):
pass
# delete 요청이 들어왔을 때 수행할 기능
FBV에 url을 붙일 때는 view의 이름(함수 이름)만 써주면 그만이었다.
... path('user/', views.user_list), ...
CBV에 url을 붙일 때는 CBV이름.as_view()의 구조로 설정해야 한다.
... path('user/', views.user_list.as_view()), ...
rest_framework의 serializer를 사용한 스타일은 다음과 같다.
# views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Post
from .serializers import PostSerializer
# 포스팅 목록 및 새 포스팅 작성: APIView를 사용
class PostListAPIView(APIView):
# get 요청이 들어왔을 때
def get(self, request):
serializer = PostSerializer(Post.objects.all(), many=True)
return Response(serializer.data)
# post 요청이 들어왔을 때
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
# 포스팅 내용, 수정, 삭제: 마찬가지로 APIView를 사용
class PostDetailAPIView(APIView):
# get_object가 작동하는 방식은 여기서 설명하지 못 함(ㅠㅠ)
def get_object(self, pk):
return get_object_or_404(Post, pk=pk)
# get 요청이 들어왔을 떄
def get(self, request, pk, format=None):
post = self.get_object(pk)
serializer = PostSerializer(post)
return Response(serializer.data)
# put 요청이 들어왔을 때
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)
# delete 요청이 들어왔을 때
def delete(self, request, pk):
post = self.get_object(pk)
post.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
이 코드는 수학좌님의 코드를 그대로 복붙해놓은 것이다. 더 자세한 내용은 링크링크.
django ORM과 DRF의 serializer, validator 기능을 이용하여 코드를 체계적으로 정리하고 있다.
코드를 보면 알 수 있듯이 APIView
는 특정 method의 request가 들어올 때마다 따로따로 serializer=[...]
처리를 해준 것을 볼 수 있다. 결과적으로 코드의 중복이 기능의 중복을 불러온다.
따라서 이러한 약점(?)을 해결하는 기능을 rest_framework.mixins
에서 제공하고 있다.
일단 종류부터 알아본다.
CreateModelMixin
ListModeMixin
RetrieveModelMixin
UpdateModelMixin
DestroyModelMixin
위와 같은 클래스가 mixins.CreateModelMixin
, mxins.ListModeMixin
... 이런 식으로 쓰이게 된다.
수학좌님께서는 '이름이 굉장히 직관적이므로 각각에 대한 자세한 설명은 생략하겠습니다'라고 설명하셨다.
하지만 이 부족한 머리통은 이름에서 어떤 냄새만 맡을 수 있을 뿐이고 내 예상과 다른 기능을할까봐 너무 두렵다.
예시로 제시해주신 관련 코드와 설명을 보면서 뭐가뭔지 탐구해보도록 한다.
# 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
# mixins.
# ListModelMixin, CreateModelMixin을 상속
class PostListMixins(mixins.ListModelMixin, mixins.CreateModelMixin,generics.GenericAPIView):
# 기본적으로 queryset은 Post model안의 객체를 모두 불러오고
queryset = Post.objects.all()
# serializer는 PostSerializer를 사용한다
# 일반적으로 serializers.py에 명시해뒀을 것이다
serializer_class = PostSerializer
# get 요청이 들어오면 list를 반환한다
# mixins.ListModelMixin
def get(self, request, *args, **kwargs):
return self.list(request)
# post 요청이 들어오면 새로은 객체를 생성한다
# mixins.CreateModelMixin
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)
읽다보니 의문이 생긴다.
하나같이 마지막 상속에GenericAPIView
를 포함하고 있다.
pyyou님의 포스팅을 보고 알아본다.
pyyou님의 포스팅에서 심각하게 기가막히게 설명을 너무 잘해주셨기 때문에 아래의 내용도 수학좌님의 포스팅을 거의 복붙하다시피한 위의 내용처럼 복붙해놓은 수준이다.
다시 한번 절하고 배우겠습니다. 🙇🏻♂️
django REST framework를 사용하여 GenericAPIView
에 ListModeMixin
, CreateModMixin
등을 결합해 다양한 APIView를 구현할 수 있다.
GenericAPIView
는 CRUD에서 공통적으로 사용되는 속성을 제공한다Mixins
는 CRUD 중의 특정한 기능을 수행하는 메소드를 제공한다GenericAPIView
와 Mixins
로 정확한 기능 구현이 어려울 때, 오버라이드를 통한 커스터마이징이 가능하다< 기본 속성 >
queryset
: View에서 객체를 반환하는 데 사용해야 하는 쿼리셋
반드시 queryset 속성을 설정해주거나 get_queryset()
으로 오버라이드하여 사용해야 한다
serializer_class
: 받은 값을 검증하거나(validate) 역직렬화(deserialize)하거나 출력값을 직렬화(serialize)할 때 사용한다. 일반적으로 serializer_class
를 사용하거나 get_serializer_class()
메소드를 오버라이드하여 사용해야 한다
lookup_field
: 개별 모델의 인스턴스의 object를 조회할 때 사용해야하는 모델 필드이다. 기본값은 'pk'. 하이퍼링크 된 API에 커스텀값을 사용해야 하는 경우 APIView와 serializer클래스가 lookup필드를 설정해야 한다.
🔎 Mixin을 상속함으로써 반복되는 코드를 상당 줄일 수 있지만, 여러 개의 클래스를 상속해야 하다보니 코드가 깔끔해보이지 않을 수 있다.
이럴 때 이용하는 것이
generics APIView
라고 할 수 있다.
generics.CreateAPIView
: 생성
generics.ListAPIView
: 목록
generics.RetrieveAPIView
: 조회
generics.DestroyAPIView
: 삭제
generics.UpdateAPIView
: 수정
generics.RetrieveUpdateAPIView
: 조회/수정
generics.RetrieveDestroyAPIView
: 조회/삭제
generics.ListCreateAPIView
: 목록/생성
generics.RetrieveUpdateDestroyAPIView
: 조회/수정/삭제
위의 mixin 상속 부분의 코드에서 쓰임을 확인할 수 있다.
mixins.ListModelMixin
.list(request, *args, **kwargs)
메소드로 호출한다GenericAPIView
의 self.fiter_queryset
, self.get_queryset
, self.get_serializer
등의 메소드를 활용하여 데이터를 목록 형태로 return200 OK
mixins.CreateModelMixin
.create(request, *args, **kwargs)
메소드로 호출한다201 Created
/ 실패: 400 Bad Request
mixins.RetrieveModelMixin
.retrieve(request, *args, **kwargs)
메소드로 호출하여 사용200 Created
/ 실패: 400 Bad Request
mixins.UpdateModelMixin
.update(request, *args, **kwargs)
메소드로 호출하여 사용.partial_update(request, *args, **kwargs)
메소드를 호출하여야 하며 이 때, 요청은 HTTP PATCH requests
이어야 한다200 OK
/ 실패: 404 Not Found
mixins.DestoryModelMixin
.destroy(request, *args, **kwargs)
메소드를 호출하여 사용204 No Content
/ 실패: 404 Not Found
이렇게 GenericAPIView
와 Mixins
의 관계까지 알아볼 수가 있었다.
generic
으로 갈 수록 그 기능을 간편하게 정확히 쓸 수 있는 것 같고 API_View
로 갈 수록 직접 짜야 하는 코드는 많아지지만 자유도가 높아지는 것 같다.
django에서 제공하는 것이 굉장이 많게 보이다 보니 정리하고 사용해 보는 데에 시간이 꽤 드는 느낌이다.
요령을 찾으려면 다양하게 많은 API를 만들어 봐야겠단 생각이 든다.
나머지 내용도 추가해서 정리해야겠다.
도움주신 분들
수학좌: [Django] APIView, Mixins, generics APIView, ViewSet을 알아보자
phyyou님: DRF 공부하기 (7) :: GenericAPIView와 Mixins
현암 코딩님: 4-2 Generic Views (Mixin,Concrete)