[DRF] 공식 문서 - views의 정리 6 - CBV(Viewsets)
1. ViewSets
- Django REST Framework를 사용하면 ViewSet이라는 단일 클래스 내에서 관련 뷰 그룹에 대한 논리를 통합할 수 있습니다. 다른 프레임워크에서 '리소스' 또는 '컨트롤러' 같이 개념적으로 유사한 구현을 찾을 수도 있다.
- ViewSet 클래스는 본질적으로 .get() 또는 .post()와 같은 특정 메소드 핸들러를 제공하지 않는 클래스 기반 뷰 유형입니다. 대신 .list() 및 .create()와 같은 작업을 제공한다.
- ViewSet에 대한 메서드 핸들러는 .as_view() 메서드를 사용하여 뷰를 마무리할 때 해당 작업과 연결된다.
- 일반적으로 urlconf 내의 ViewSet에 뷰를 명시적으로 등록하는 대신 ViewSet을 라우터 클래스에 등록한다.
- 이 라우터 클래스는 자동으로 urlconf를 결정한다.
2. Example
- 시스템의 모든 사용자를 나열하거나 검색하는데 사용 할 수 있는 간단한 viewset을 정의한다.
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from myapps.serializers import UserSerializer
from rest_framework import viewsets
from rest_framework.response import Response
class UserViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving users.
"""
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을 다음과 같이 두 개의 뷰 바인딩으로 정의할 수 있다.
user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})
- 일반적으로는 직접 개별 뷰를 바인딩 하지 않고, 대신 viewset을 라우터에 등록하고 urlconf가 자동으로 생성되는 방법을 사용한다.
from myapp.views import UserViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns = router.urls
- 자신만의 뷰 세트를 만드는 것보다 기본 동작 세트를 제공하는 기본 클래스를 사용하는 것을 선호하는 경우가 많다.
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing user instances.
"""
serializer_class = UserSerializer
queryset = User.objects.all()
- View 클래스를 사용하는 것보다 ViewSet 클래스를 사용하면 다음과 같은 두 가지 주요 이점이 있다.
- 결합 논리:
- 반복되는 로직을 하나의 클래스로 통합할 수 있다.
- 위의 예에서 쿼리 세트를 한 번만 지정하면 여러 뷰에서 사용할 수 있다.
- 라우터를 사용한 자동 URL 연결:
- 라우터를 활용하면 더 이상 URL 구성을 수동으로 구성할 필요가 없다.
- 라우팅 프로세스는 자동으로 처리됩니다.
- 그러나 이 두 가지 장점 모두에는 절충점이 있다.
- 일반 보기와 URL 구성을 사용하면 더욱 명확해지며 더 효과적으로 제어할 수 있다.
- ViewSet은 빠른 설정이 필요하거나 일관된 URL 구성을 유지하는 것이 중요한 대규모 API를 처리할 때 유용하다.
3. ViewSet actions
- REST 프레임워크에 포함된 기본 라우터는 creste / retirieve / update / destroy 스타일 액션들의 표준 세트를 위한 경로를 제공한다.
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
4. ViewSet 작업 살펴보기
- 디스패치 중에 ViewSet에서 다음 속성을 사용할 수 있다.
- basename: 생성된 URL 이름에 사용할 기본 이름이다
- action: 현재 작업의 이름(예: 목록, 생성).
- detail: 현재 작업이 목록 보기 또는 세부정보 보기에 대해 구성되었는지 나타내는 boolean 값이다.
- suffix: viewset 유형에 대한 표시 접미사 - 세부 속성을 반영한다.
- name: viewset 의 표시 이름입니다. 이 인수는 'suffix'와 상호 배타적이다.
- description : viewset의 개별 보기에 대한 표시 설명이다.
- 현재 동작에 따라 행동을 조정하기 위해 이 속성들을 검사할 수 있습니다. 예를 들어, 리스트 액션을 제외한 모든 것에 대한 권한을 제한할 수 있다.
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
if self.action == 'list':
permission_classes = [IsAuthenticated]
else:
permission_classes = [IsAdminUser]
return [permission() for permission in permission_classes]
5. 라우팅을 위한 추가 액션 표시
- 라우팅 가능해야 하는 ad-hoc 메소드(임시적인 또는 특수한 경우에만 사용되는 메소드)가 있다면, @action 데코레이터로 그런 것으로 표시할 수 있다.
- 일반 액션처럼, 추가 액션은 단일 객체 또는 전체 컬렉션을 대상으로 할 수 있다.
- 이를 표시하기 위해, detail 인수를 True 또는 False로 설정한다.
- 라우터는 그에 따라 URL 패턴을 설정한다.
- 예를 들어, DefaultRouter는 detail 액션을 그들의 URL 패턴에 pk를 포함하도록 설정한다.
from django.contrib.auth.models import User
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from myapp.serializers import UserSerializer, PasswordSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset that provides the standard actions
"""
queryset = User.objects.all()
serializer_class = UserSerializer
@action(detail=True, 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.validated_data['password'])
user.save()
return Response({'status': 'password set'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
@action(detail=False)
def recent_users(self, request):
recent_users = User.objects.all().order_by('-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)
- action 데코레이터는 기본적으로 GET 요청을 라우팅하지만, methods 인수를 설정함으로써 다른 HTTP 메소드도 받아들일 수 있다.
- 예를 들면 다음과 같다 :
@action(detail=True, methods=['post', 'delete'])
def unset_password(self, request, pk=None):
...
- Argument methods는 HTTPMethod로 정의된 HTTP 메소드도 지원합니다. 아래 예시는 위의 예시와 동일하다.
from http import HTTPMethod
@action(detail=True, methods=[HTTPMethod.POST, HTTPMethod.DELETE])
def unset_password(self, request, pk=None):
...
- 이 데코레이터를 사용하면 permission_classes, serializer_class, filter_backends 등의 뷰셋 레벨 설정을 재정의 한다.
from http import HTTPMethod
@action(detail=True, methods=[HTTPMethod.POST, HTTPMethod.DELETE])
def unset_password(self, request, pk=None):
...
- 그러면 두 개의 새로운 액션은 "^users/{pk}/set_password/$" 및 "^users/{pk}/unset_password/$"의 URL에서 사용 가능하게 된다.
- url_path와 url_name 매개변수를 사용하여 액션의 URL 세그먼트와 역방향 URL 이름을 변경할 수 있다.
- 모든 추가 액션을 보려면 .get_extra_actions() 메소드를 호출하면 된다.
6. Routing additional HTTP methods for extra actions
- 추가 액션은 추가적인 HTTP 메소드를 별도의 ViewSet 메소드에 매핑할 수 있다.
- 예를 들어, 위의 비밀번호 설정/해제 메소드는 단일 라우트로 통합될 수 있다.
- 추가 매핑은 인수를 받지 않는다는 점에 유의해야 한다.
@action(detail=True, methods=["put"], name="Change Password")
def password(self, request, pk=None):
"""Update the user's password."""
...
@password.mapping.delete
def delete_password(self, request, pk=None):
"""Delete the user's password."""
...
1) 비밀번호 변경: 사용자가 '/users/{pk}/password/' 경로에 PUT 요청을 보내면, Django는 이 요청을 'password' 액션에 연결된 메소드로 라우팅한다.
- 이 메소드는 요청을 처리하고 비밀번호를 변경한 후 응답을 반환한다.
2) 비밀번호 삭제: 사용자가 '/users/{pk}/password/' 경로에 DELETE 요청을 보내면, Django는 이 요청을 'delete_password' 액션에 연결된 메소드로 라우팅한다.
- 이 메소드는 요청을 처리하고 비밀번호를 삭제한 후 응답을 반환한다.
- 이런 방식으로, 하나의 URL 경로 ('/users/{pk}/password/')를 사용하여 두 가지 다른 동작 (비밀번호 변경, 비밀번호 삭제)을 처리할 수 있게 된다.
7. Reversing action URLs
- 액션의 URL을 얻어야 하는 경우, .reverse_action() 메소드를 사용하면 된다.
- 이 메소드는 reverse()에 대한 편리한 래퍼로, 뷰의 요청 객체를 자동으로 전달하고 url_name 앞에 .basename 속성을 추가한다.
- 라우터가 ViewSet 등록 중에 basename을 제공한다는 것에 유의해야 한다.
- 라우터를 사용하지 않는 경우, .as_view() 메소드에 basename 인자를 제공해야 한다.
- 이전 섹션의 예제를 사용하면 :
>>> view.reverse_action("set-password", args=["1"])
'http://localhost:8000/api/users/1/set_password'
- 대안으로, @action 데코레이터에 의해 설정된 url_name 속성을 사용할 수도 있다.
>>> view.reverse_action(view.set_password.url_name, args=['1'])
'http://localhost:8000/api/users/1/set_password'
- .reverse_action()의 url_name 인자는 @action 데코레이터에 대한 같은 인자와 일치해야 한다.
- 또한, 이 메소드는 list와 create와 같은 기본 액션을 반전시키는데 사용될 수 있다.
8. API Reference
- ViewSet
- ViewSet 클래스는 APIView로부터 상속받는다.
- permission_classes, authentication_classes와 같은 표준 속성을 이용해 viewset에서의 API 정책을 제어할 수 있다.
- ViewSet 클래스는 액션에 대한 구현체를 제공하지 않는다.
- ViewSet 클래스를 사용하려면 클래스를 오버라이드하고 액션 구현체를 명시적으로 정의해야 한다.
- GenericViewSet
- GenericViewSet 클래스는 GenericAPIView로부터 상속 받으며, 기본적으로 get_object, get_queryset 메소드 및 기타 일반 뷰 기본 동작을 제공하지만, 기본적으로는 어떠한 액션도 포함하지 않는다.
- GenericViewSet 클래스를 사용하려면 클래스를 오버라이드하고 필요한 믹스인 클래스를 믹스인하거나 액션 구현체를 명시적으로 정의해야 한다.
- ModelViewSet
- ModelViewSet 클래스는 GenericAPIView로부터 상속받고, 다양한 믹스인 클래스의 행동을 혼합하기에 다양한 액션에 대한 구현체를 포함하고 있다.
- example :
- ModelViewSet이 GenericAPIView를 확장하기 때문에, 일반적으로 적어도 queryset과 serializer_class 속성을 제공해야 한다. 예를 들면 다음과 같다.
class AccountViewSet(viewsets.ModelViewSet):
"""
A simple ViewSet for viewing and editing accounts.
"""
queryset = Account.objects.all()
serializer_class = AccountSerializer
permission_classes = [IsAccountAdminOrReadOnly]
- GenericAPIView에서 제공하는 표준 속성이나 메소드 오버라이드를 어떤 것이든 사용할 수 있음을 유의해야 한다.
- 예를 들어, 작동해야 할 쿼리셋을 동적으로 결정하는 ViewSet을 사용하려면 다음과 같이 할 수 있다.
class AccountViewSet(viewsets.ModelViewSet):
"""
A simple ViewSet for viewing and editing the accounts
associated with the user.
"""
serializer_class = AccountSerializer
permission_classes = [IsAccountAdminOrReadOnly]
def get_queryset(self):
return self.request.user.accounts.all()
- 그러나 ViewSet에서 queryset 속성을 제거하면, 관련된 라우터는 자동으로 모델의 basename을 파생시킬 수 없게 된다. 따라서 라우터 등록의 일부로 basename kwarg를 지정해야 한다.
- queryset이 있는 경우 :
- 이 경우, 라우터는 자동으로 모델의 이름을 basename으로 사용하여 URL 패턴을 생성합니다. 따라서 생성되는 URL 패턴은 'mymodel/' 형태가 된다.
from rest_framework import viewsets
from myapp.models import MyModel
from myapp.serializers import MyModelSerializer
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
from rest_framework import routers
from myapp.views import MyModelViewSet
router = routers.SimpleRouter()
router.register(r'mymodel', MyModelViewSet)
- queryset이 없는 경우 :
- 이렇게 하면 라우터는 URL 패턴을 생성할 때 'mymodel'을 기반으로 한다.
- 기본적으로, 라우터는 등록된 ViewSet의 queryset에서 모델 이름을 추출하여 basename을 생성한다. 그러나 queryset이 제거된 경우 이 과정이 불가능하므로, 개발자가 직접 basename을 지정해야 한다.
- 따라서 생성되는 URL 패턴은 'mymodel/' 형태가 된다.
from rest_framework import viewsets
from myapp.models import MyModel
from myapp.serializers import MyModelSerializer
class MyModelViewSet(viewsets.ModelViewSet):
serializer_class = MyModelSerializer
def get_queryset(self):
return MyModel.objects.all()
from rest_framework import routers
from myapp.views import MyModelViewSet
router = routers.SimpleRouter()
router.register(r'mymodel', MyModelViewSet, basename='mymodel')
- Django REST 프레임워크의 ViewSet 클래스는 기본적으로 CRUD(Create, Retrieve, Update, Delete) 작업을 모두 지원한다.
- 그러나 표준 권한 클래스를 사용하면 이러한 작업 중 일부에 대한 접근을 제한할 수 있다.
- 예를 들어, 아래 코드는 IsAuthenticated 권한 클래스를 사용하여 인증된 사용자만 ViewSet의 작업에 접근할 수 있도록 제한하는 방법을 보여준다.
from rest_framework import viewsets, permissions
from myapp.models import MyModel
from myapp.serializers import MyModelSerializer
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
permission_classes = [permissions.IsAuthenticated]
- 이 경우, 인증되지 않은 사용자는 MyModelViewSet의 어떠한 작업도 수행할 수 없다. 만약 특정 작업만 제한하고 싶다면, 다음과 같이 각 작업에 대해 권한을 지정할 수 있다.
- 이 경우, 'update'와 'destroy' 작업은 관리자 사용자만 수행할 수 있고, 그 외의 작업은 인증된 사용자라면 누구나 수행할 수 있다. 이런 식으로 표준 권한 클래스를 사용하면, ViewSet의 작업에 대한 접근을 세밀하게 제어할 수 있다.
from rest_framework import viewsets, permissions
from myapp.models import MyModel
from myapp.serializers import MyModelSerializer
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
def get_permissions(self):
if self.action in ['update', 'destroy']:
self.permission_classes = [permissions.IsAdminUser,]
else:
self.permission_classes = [permissions.IsAuthenticated,]
return super(MyModelViewSet, self).get_permissions()
- ReadOnlyModelViewSet
- ReadOnlyModelViewSet 클래스 역시 GenericAPIView로부터 상속는다.
- ModelViewSet과 마찬가지로 다양한 액션들의 구현을 포함하지만, ModelViewSet과는 달리 '읽기 전용' 액션인 .list()와 .retrieve()만 제공한다.
- Example :
- ModelViewSet과 마찬가지로, 일반적으로 최소한 queryset과 serializer_class 속성을 제공해야 한다.
- 또한, ModelViewSet에서처럼, GenericAPIView에서 사용 가능한 표준 속성과 메소드 오버라이드를 모두 사용할 수 있다.
class AccountViewSet(viewsets.ReadOnlyModelViewSet):
"""
A simple ViewSet for viewing accounts.
"""
queryset = Account.objects.all()
serializer_class = AccountSerializer
- Custom ViewSet base classes
- 전체 ModelViewSet 액션을 갖지 않거나, 다른 방식으로 동작을 커스텀화해야 하는 경우, 사용자 정의 ViewSet 클래스를 제공해야 할 수도 있다.
- Example :
- 생성(create), 목록 조회(list), 단일 조회(retrieve) 작업을 제공하는 기본 viewset 클래스를 만들려면, GenericViewSet을 상속받고 필요한 액션을 믹스인하면 된다.
- 자신만의 기본 ViewSet 클래스를 생성함으로써, API 전반에 걸쳐 여러 viewset에서 재사용할 수 있는 공통적인 동작을 제공할 수 있습니다.
from rest_framework import mixins
class CreateListRetrieveViewSet(mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet):
"""
A viewset that provides `retrieve`, `create`, and `list` actions.
To use it, override the class and set the `.queryset` and
`.serializer_class` attributes.
"""
pass
- 공식 사이트 문서 : https://www.django-rest-framework.org/api-guide/viewsets/#viewsets
- reference :
https://syujisu.tistory.com/entry/Django%EC%8B%AC%ED%99%943-viewset-router
https://testdriven.io/blog/drf-views-part-3/
'Django REST Framework > DRF 일반' 카테고리의 다른 글
[DRF] 공식 문서 - serializer relations 정리 (1) | 2024.01.15 |
---|---|
[DRF] 공식 문서 - serializers 정리 (1) | 2024.01.14 |
[DRF] 공식 문서 - views의 정리 5 - CBV(Concrete View Classes) (1) | 2024.01.11 |
[DRF] 공식 문서 - views의 정리 4 - CBV(Mixins) (0) | 2024.01.10 |
[DRF] 공식 문서 - views의 정리 3 - CBV(Generic views) (0) | 2024.01.10 |
댓글