После того, как маршрутизация определила, какой контроллер использовать для запроса, ваш контроллер отвечает за осмысление запроса и создание соответствующего вывода.
DRF позволяет объединить логику для набора связанных представлений в одном классе, называемом 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 во всем.
Маршрутизаторы по умолчанию, входящие в состав DRF, обеспечивают маршруты для стандартного набора действий в стиле 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 = [IsAdminUser]
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):
...
Аргумент methods
также поддерживает методы HTTP, определенные как HTTPMethod. Пример ниже идентичен приведенному выше:
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
...:
@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-методы с отдельными методами 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-адрес действия, используйте метод .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
.
Класс ViewSet
наследуется от APIView
. Вы можете использовать любые стандартные атрибуты, такие как permission_classes
, authentication_classes
, чтобы управлять политикой API для набора представлений.
Класс ViewSet
не предоставляет никаких реализаций действий. Чтобы использовать класс ViewSet
, вам нужно переопределить его и явно определить реализацию действий.
Класс GenericViewSet
наследуется от GenericAPIView
и предоставляет стандартный набор методов get_object
, get_queryset
и другие базовые поведения общих представлений, но по умолчанию не включает никаких действий.
Чтобы использовать класс GenericViewSet
, вы должны переопределить его и либо смешать необходимые классы mixin, либо явно определить реализацию действий.
Класс ModelViewSet
наследуется от GenericAPIView
и включает в себя реализации различных действий, смешивая поведение различных классов-миксинов.
Класс 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
как часть вашей регистрации router.
Также обратите внимание, что хотя этот класс по умолчанию предоставляет полный набор действий create/list/retrieve/update/destroy, вы можете ограничить доступные операции с помощью стандартных классов разрешений.
Класс 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
, которые не имеют полного набора действий ModelViewSet
или настраивают поведение каким-либо другим способом.
Чтобы создать базовый класс набора представлений, обеспечивающий операции create
, list
и retrieve
, наследуйте от GenericViewSet
и добавьте необходимые действия:
from rest_framework import mixins, viewsets
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.