상속받는 클래스의 추상화(패턴화) 정도 한눈에 알아보기
APIView
<mixins
<generics APIView
<ViewSet
APIView
: 자주 쓰이는view
들의 패턴을 패턴화 시켜 최소한의 코드로 Model 에 대한view
를 구현하는 방법.mixins
:APIView
의 일반적인 로직들을 섞어 재사용성을 높임.generics APIView
:mixins
사용을 패턴화하여 정리.ViewSet
:generics APIViews
를 한번 더 합쳐서 정리.
DRF는 Django의 view
클래스를 서브클래싱하는 APIView
클래스를 제공합니다. APIView
클래스는 는 CBV에 기반하며, @api_view
장식자는 FBV에 기반합니다.
APIView
와 @api_view
에서 기본으로 제공하는 속성과 디폴트값은 다음과 같습니다.
rest_framework.renderers.JSONRenderer
rest_framework.renderers.TemplateHTMLRenderer
rest_framework.parsers.JSONParser
rest_framework.parsers.FormParser
rest_framework.MultiPartParser
rest_framework.authentication.SessionAuthentication
rest_framework.authentication.BasicAuthentication
rest_framework.permissions.AllowAny
rest_framework.negotiation.DefaultContentNegotiation
rest_framework.metadata.SimpleMetadata
🦝 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
내부에 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
데코레이터를 이용해 함수형 뷰로 구현하는 것도 좋습니다.
# 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)
APIView
구현에서 우리는 각 request method 마다 직접 serializer 처리를 반복하였습니다. 이러한 번거로움은 Mixins
를 사용하면 간소화시킬 수 있습니다.
Mixins
는 다른 클래스에 의해 상속되며 사용됩니다. queryset
과 serializer_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)
Mixins
를 상속함으로써 반복되는 내용을 많이 줄일 수 있었지만, 여러 상속으로 인한 가독성문제와 여전히 각 request method 에 대한 연결은 직접해줘야하는 번거로움이 있었습니다. 다행이 이러한 문제를 의식하고 DRF는 하나 이상의 Mixins
와 결합된 클래스 generics APIView
클래스를 제공하고 있습니다.
🦝 django-rest-framework/rest_framework/generics.py
mixins
를 썼을 때 보다 많이 간소화 되었지만 queryset
과 serializer_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
우리는 일반적으로 REST API를 구현할 때 ListAPI 와 DetailAPI 을 구현합니다. ListAPI 에서는 GET, POST 메서드를, DetailAPI 에서는 GET, PUT DELETE 메서드를 앞 선 예제들로 구현해 보았습니다.
DRF의 ViewSet
은 2개의 URL별로 구현된 5개의 메서드를 하나의 단일클래스에 제공합니다. 하나의 헬퍼클래스로 두 개 이상의 URL 처리가 가능한 것입니다.
VewSet
의 헬퍼 클래스는 두 가지가 있습니다.
viewsets.ReadOnlyModelViewSet
›› list 지원 → 1개의 url
›› detail 지원 → 1개의 url
viewsets.ModelViewSet
›› list / create 지원 → 1개의 url
›› detail / update / partial_update / delete 지원 → 1개의 url
queryset
과 serializer_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)),
]