Django REST framework는 ViewSet 이라는 단일 클래스에서 관련된 논리를 결합할 수 있다. ViewSet은 단순히 get()이나 post()와 같은 메소드 핸들러를 제공하지 않으며, 대신 list() 와 create() 같은 액션을 제공한다(CBV 유형). ViewSet 의 method 핸들러는 as_view() 함수가 호출되어 view가 끝나는 시점의 해당 액션에만 바인딩된다.
바인딩(binding)
각종 값들이 확정되어 더 이상 변경할 수 없는 상태가 되는 것이다. 식별자(identifier)가 그 대상인 메모리 주소, 데이터형 또는 실제값으로 배정되는 것을 뜻한다.
일반적으로 url설정시에 view를 명시적으로 등록하는 대신 router 클래스로 등록하여 자동으로 url을 설정한다.
START VIEWSETS
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from .serializers import UserSerializer
from rest_framework import viewsets
from rest_framework.response import Response
class UserViewSet(viewsets.ViewSet):
def list(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = User.objects.all()
user = get_object_or_404(queryset, pk=pk)
serializer = UserSerializer(user)
return Response(serializer.data)
일반적으로 개별 뷰 바인딩을 하지 않으며, ViewSet을 라우터에 등록하여 자동으로 URLconf(url 설정파일)이 생성되도록 한다.
from .views import UserViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns - router.urls
이때 viewset을 본인이 작성하는 것보다 기본 동작을 제공해주는 기본 클래스를 사용하는 것을 권장한다.
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
장점
view 클래스를 사용하는 것보다 viewsets 클래스를 사용하면 좋은 점이 2가지 있다.
반복되는 로직을 하나의 클래스로 결합할 수 있다. 즉, viewsets을 이용하면 queryset을 단 한번만 정의하면 된다.
router를 사용함으로써, URLconf(url 설정파일)을 다룰 필요가 없어진다.
하지만 아래와 같은 trade-off도 있다.
view와 url설정을 사용하면 보다 명확하고 상세하게 제어할 수 있다.
ViewSet 은 빠르게 실행하거나 대규모 API가 있는 경우, 또는 전체적으로 일관된 URLconf를 적용하려는 경우에 유리하다.
MARKING EXTRA ACTIONS FOR ROUTING
REST framework에 포함된 기본 router는 create / retrieve / update / destroy 스타일 작업의 기본 method를 제공한다.
class UserViewSet(viewsets.ViewSet):
"""
Example empty viewset demonstrating the standard
actions that will be handled by a router class.
If you're using format suffixes, make sure to also include
the 'format=None' keyword argument for each action.
"""
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass
라우팅이 필요한 임시 메소드가 있는 경우에는 @detail_router 또는 @list_router 데코레이터를 사용하여 라우팅을 필요로 한다는 것을 표시할 수 있다.
@detail_router 는 URL 패턴에 pk를 포함하며 단일 인스턴스를 요구하는 메소드에 대한 것이다.
@list_router 는 객체 목록에서 작동하는 메소드에 대한 것이다.
rom django.contrib.auth.models import User
from rest_framework import status
from rest_framework import viewsets
from rest_framework.decorators import detail_route, list_route
from rest_framework.response import Response
from .serializers import UserSerializer, PasswordSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
@detail_route(methods=['post'])
def set_password(self, request, pk=None):
user = self.get_object()
serializer = PasswordSerializer(data=request.data)
if serializer.is_valid():
user.set_password(serializer.data['password'])
user.save()
return Response({'status': 'password set'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
@list_route()
def recent_users(self, request):
recent_users = User.objects.all().order('-last_login')
page = self.paginate_queryset(recent_users)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(recent_users, many=True)
return Response(serializer.data)
queryset을 동적으로 결정하는 viewsets을 사용하려면 위와 같이 메소드 오버라이드를 할 수 있다.
여기서 주의해야할 점이 있다. ViewSet의 queryset 속성을 제거하면 연결된 라우터가 모델의 base_name을 자동으로 파생시킬 수 없으므로 base_name의 kwarg를 지정해야한다. 이 클래스는 기본적으로 create / list / retrieve / update /destroy 액션을 제공하지만 표준 권한 클래스(permission)를 사용해 제한할 수 있다.
ReadOnlyModelViewSet
ReadOnlyModelViewSet 클래스는 GenericAPIView 를 상속한다. ModelViewSet과 동일하게 다양한 action애 대한 구현도 포함되지만 오직 읽기 전용 action인 .list() 와 .retrieve()만 제공한다.
class AccountViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Account.objects.all()
serializer_class = AccountSerializer