Django REST Framework/DRF 일반

[DRF] 공식 문서 - views의 정리 6 - CBV(Viewsets)

bluebamus 2024. 1. 11.

 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

 

Viewsets - Django REST framework

viewsets.py After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output. — Ruby on Rails Documentation Django REST framework allows you to combine

www.django-rest-framework.org

 

 - reference : 

https://wikidocs.net/197563

 

03) DRF 뷰와 뷰셋

[TOC] ## DRF 뷰와 뷰셋 이 섹션에서는 Django REST Framework (DRF)의 뷰와 뷰셋에 대해 알아보겠습니다. DRF의 뷰는 API 엔드포인트의 동작…

wikidocs.net

https://syujisu.tistory.com/entry/Django%EC%8B%AC%ED%99%943-viewset-router

 

[Django_심화]3 - viewset & router

1. Views of DRF 모델 기반의 viewset을 작성했었다. from rest_framework import viewsets from .models import Post from .serializer import PostSerializer class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = View

syujisu.tistory.com

https://testdriven.io/blog/drf-views-part-3/

 

Django REST Framework Views - ViewSets

This article takes a deep dive into Django REST Framework's most powerful view, ViewSets.

testdriven.io

 

댓글