ViewSets

После того, как маршрутизация определила, какой контроллер использовать для запроса, ваш контроллер отвечает за осмысление запроса и создание соответствующего вывода.

Ruby on Rails Documentation

REST-фреймворк Django позволяет объединить логику для набора связанных представлений в одном классе, называемом ViewSet. В других фреймворках вы также можете найти концептуально схожие реализации, названные, например, «Ресурсы» или «Контроллеры».

Класс ViewSet - это просто ** тип представления на основе класса, который не предоставляет никаких обработчиков методов**, таких как .get() или .post() , а вместо этого предоставляет действия, такие как .list() и .create().

Обработчики методов для ViewSet привязываются к соответствующим действиям только в момент финализации представления, используя метод .as_view().

Обычно вместо явной регистрации представлений в наборе представлений в urlconf, вы регистрируете набор представлений в классе маршрутизатора, который автоматически определяет urlconf для вас.

Пример

Давайте определим простой набор представлений, который можно использовать для перечисления или извлечения всех пользователей в системе.

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)

Если нужно, мы можем связать этот набор представлений в два отдельных представления, например, так:

user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})

Обычно мы не делаем этого, а вместо этого регистрируем набор представлений в маршрутизаторе и позволяем автоматически генерировать urlconf.

from myapp.views import UserViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')
urlpatterns = router.urls

Вместо того чтобы писать собственные наборы представлений, вы часто захотите использовать существующие базовые классы, которые предоставляют набор поведения по умолчанию. Например:

class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset for viewing and editing user instances.
    """
    serializer_class = UserSerializer
    queryset = User.objects.all()

Есть два основных преимущества использования класса ViewSet перед классом View.

  • Повторяющаяся логика может быть объединена в один класс. В приведенном выше примере нам нужно указать queryset только один раз, и он будет использоваться в нескольких представлениях.

  • Используя маршрутизаторы, нам больше не нужно самим заниматься подключением URL conf.

Оба варианта имеют свои недостатки. Использование обычных представлений и URL-конфигураций является более явным и дает вам больше контроля. Наборы представлений полезны, если вы хотите быстро приступить к работе, или если у вас большой API и вы хотите обеспечить последовательную конфигурацию URL.

Действия ViewSet

Маршрутизаторы по умолчанию, входящие в состав REST framework, обеспечивают маршруты для стандартного набора действий в стиле create/retrieve/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

Интроспекция действий набора представлений

Во время диспетчеризации на ViewSet доступны следующие атрибуты.

  • basename - основа, которую следует использовать для создаваемых имен URL.

  • action - имя текущего действия (например, list , create ).

  • detail - булево значение, указывающее, настроено ли текущее действие для просмотра списка или деталей.

  • suffix - суффикс отображения для типа набора представлений - зеркально отражает атрибут detail.

  • name - отображаемое имя для набора представлений. Этот аргумент является взаимоисключающим для suffix.

  • description - описание отображения для отдельного вида набора представлений.

Вы можете использовать эти атрибуты для настройки поведения в зависимости от текущего действия. Например, вы можете ограничить разрешения на все, кроме действия list, подобно этому:

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 = [IsAdmin]
    return [permission() for permission in permission_classes]

Отметка дополнительных действий для маршрутизации

Если у вас есть специальные методы, которые должны быть маршрутизируемыми, вы можете пометить их как таковые с помощью декоратора @action. Как и обычные действия, дополнительные действия могут быть предназначены как для одного объекта, так и для целой коллекции. Чтобы указать это, установите аргумент detail в значение True или False. Маршрутизатор настроит свои шаблоны URL соответствующим образом. Например, аргумент DefaultRouter настроит детальные действия так, чтобы они содержали pk в своих шаблонах URL.

Более полный пример дополнительных действий:

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, но может принимать и другие методы HTTP, задавая аргумент methods. Например:

@action(detail=True, methods=['post', 'delete'])
def unset_password(self, request, pk=None):
   ...

Декоратор позволяет переопределить любую конфигурацию уровня набора представлений, например, permission_classes , serializer_class , filter_backends…:

@action(detail=True, methods=['post'], permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
   ...

Два новых действия будут доступны по адресам ^users/{pk}/set_password/$ и ^users/{pk}/unset_password/$. Используйте параметры url_path и url_name, чтобы изменить сегмент URL и имя обратного URL действия.

Чтобы просмотреть все дополнительные действия, вызовите метод .get_extra_actions().

Маршрутизация дополнительных методов HTTP для дополнительных действий

Дополнительные действия могут сопоставлять дополнительные методы 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."""
    ...

URL-адреса обратного действия

Если вам нужно получить URL действия, используйте метод .reverse_action(). Это удобная обертка для reverse() , автоматически передающая объект представления request и дополняющая url_name атрибутом .basename.

Обратите внимание, что аргумент basename предоставляется маршрутизатором во время регистрации ViewSet. Если вы не используете маршрутизатор, то вы должны предоставить аргумент basename методу .as_view().

Используя пример из предыдущего раздела:

>>> view.reverse_action('set-password', args=['1'])
'http://localhost:8000/api/users/1/set_password'

В качестве альтернативы можно использовать атрибут url_name, установленный декоратором @action.

>>> view.reverse_action(view.set_password.url_name, args=['1'])
'http://localhost:8000/api/users/1/set_password'

Аргумент url_name для .reverse_action() должен соответствовать такому же аргументу декоратора @action. Кроме того, этот метод можно использовать для отмены действий по умолчанию, таких как list и create.


Справочник по API

ViewSet

Класс ViewSet наследуется от APIView. Вы можете использовать любой из стандартных атрибутов, таких как permission_classes , authentication_classes для управления политикой API на наборе представлений.

Класс ViewSet не предоставляет никаких реализаций действий. Чтобы использовать класс ViewSet, вы должны переопределить его и явно определить реализацию действий.

GenericViewSet

Класс GenericViewSet наследуется от GenericAPIView , и предоставляет набор get_object , get_queryset методов и другое общее поведение базы представления, но по умолчанию не включает никаких действий.

Чтобы использовать класс GenericViewSet, вы переопределите его и либо смешаете необходимые классы-миксины, либо определите реализацию действий в явном виде.

ModelViewSet

Класс ModelViewSet наследуется от GenericAPIView и включает реализации для различных действий, смешивая поведение различных классов mixin.

Действия, предоставляемые классом ModelViewSet: .list() , .retrieve() , .create() , .update() , .partial_update() , и .destroy().

Поскольку 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()

Обратите внимание, что после удаления свойства queryset из вашей ViewSet , любая связанная с ней router не сможет автоматически вывести основное имя вашей Модели, и поэтому вам придется указать basename kwarg как часть вашей router registration.

Также обратите внимание, что хотя этот класс по умолчанию предоставляет полный набор действий create/list/retrieve/update/destroy, вы можете ограничить доступные операции с помощью стандартных классов разрешений.

ReadOnlyModelViewSet

Класс ReadOnlyModelViewSet также наследуется от GenericAPIView. Как и ModelViewSet, он также включает реализации различных действий, но в отличие от ModelViewSet предоставляет только действия «только для чтения», .list() и .retrieve().

Как и в случае с ModelViewSet , вам обычно нужно предоставить по крайней мере queryset и serializer_class атрибуты. Например:

class AccountViewSet(viewsets.ReadOnlyModelViewSet):
    """
    A simple ViewSet for viewing accounts.
    """
    queryset = Account.objects.all()
    serializer_class = AccountSerializer

Опять же, как и в случае с ModelViewSet , вы можете использовать любые стандартные атрибуты и переопределения методов, доступные для GenericAPIView.

Пользовательские базовые классы ViewSet

Вам может понадобиться предоставить пользовательские ViewSet классы, которые не имеют полного набора ModelViewSet действий, или которые настраивают поведение каким-либо другим способом.

Пример

Чтобы создать базовый класс viewset, который обеспечивает create , list и retrieve операции, наследуйте от GenericViewSet , и смешивайте необходимые действия:

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

Создавая свои собственные базовые классы ViewSet, вы можете обеспечить общее поведение, которое может быть повторно использовано в нескольких наборах представлений в вашем API.

Вернуться на верх