Представления Django REST Framework - ViewSets
До этого момента мы рассматривали создание отдельных представлений с помощью APIViews и Общих представлений. Часто имеет смысл объединить логику представления для набора связанных представлений в один класс. Это может быть достигнуто в Django REST Framework (DRF) путем расширения одного из классов ViewSet.
Классы ViewSet устраняют необходимость в дополнительных строках кода и в сочетании с маршрутизаторами помогают поддерживать согласованность ваших URL-адресов.
Серия просмотров фреймворка Django REST:
Наборы просмотров
ViewSet - это тип представления на основе классов.
Вместо обработчиков методов, таких как .get() и .post(), он предоставляет действия, такие как .list() и .create().
Наиболее значительным преимуществом ViewSets является то, что построение URL-адресов выполняется автоматически (с помощью класса router). Это помогает обеспечить согласованность соглашений об URL-адресах в вашем API и минимизирует объем кода, который вам нужно написать.
Существует четыре типа наборов представлений, от самых простых до самых мощных:
- Набор представлений
- Общий набор представлений
- Только для чтения ModelViewSet
- ModelViewSet
В основном они построены на основе классов, с которыми вы познакомились в предыдущей статье из этой серии:

ViewSetMixin это класс, в котором происходит все "волшебство". Это единственный класс, который используют все четыре набора представлений. Он переопределяет метод as_view и объединяет этот метод с соответствующим действием.
| Метод | Список/экземпляр | Дейтсиве |
|---|---|---|
post |
List | create |
get |
List | list |
get |
Detail | retrieve |
put |
Detail | update |
patch |
Detail | partial_update |
delete |
Detail | destroy |
Класс ViewSet
Класс ViewSet использует преимущества класса APIView. По умолчанию он не предоставляет никаких действий, но вы можете использовать его для создания собственного набора представлений:
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
class ItemViewSet(ViewSet):
queryset = Item.objects.all()
def list(self, request):
serializer = ItemSerializer(self.queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
item = get_object_or_404(self.queryset, pk=pk)
serializer = ItemSerializer(item)
return Response(serializer.data)
Этот набор представлений предоставляет HTTP-метод GET, сопоставленный с действием list (для перечисления всех экземпляров) и действием retrieve (для получения единственного экземпляра).
Действия
Следующие действия выполняются классом маршрутизатора по умолчанию:
listcreateretrieve( требуется pk)update( требуется pk)partial_update( требуется pk)destroy( требуется pk)
Вы также можете создавать пользовательские действия с помощью @action декоратора.
Например:
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
class ItemsViewSet(ViewSet):
queryset = Item.objects.all()
def list(self, request):
serializer = ItemSerializer(self.queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
item = get_object_or_404(self.queryset, pk=pk)
serializer = ItemSerializer(item)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def items_not_done(self, request):
user_count = Item.objects.filter(done=False).count()
return Response(user_count)
Здесь мы определили пользовательское действие, называемое items_not_done.
Разрешенным HTTP-методом является GET.
Здесь мы задали это явно, но функция GET разрешена по умолчанию.
Параметр methods является необязательным, в то время как параметр detail таковым не является. Параметр detail должен быть установлен как True, если действие предназначено для одного объекта, или False, если оно предназначено для всех объектов.
Это действие доступно по умолчанию по следующему URL-адресу: /items_not_done. Чтобы изменить этот URL-адрес, вы можете задать параметр url_path в декораторе.
Если вы уже давно пользуетесь ViewSets, то, возможно, помните декораторы
@list_routeи@detail_routeвместо@action. Они устарели начиная с версии 3.9.
Обработка URL-адресов
Хотя вы могли бы сопоставить URL-адреса ваших наборов представлений так же, как и с другими представлениями, это не относится к наборам представлений.
Вместо использования urlpatterns от Django Наборы представлений поставляются с классом router, который автоматически генерирует конфигурации URL.
DRF поставляется с двумя готовыми маршрутизаторами:
Основное различие между ними заключается в том, что маршрутизатор по умолчанию включает в себя корневое представление API по умолчанию:

В корневом представлении API по умолчанию отображаются списки с гиперссылками, что упрощает навигацию по приложению.
Также возможно создать пользовательский маршрутизатор.
Маршрутизаторы также можно комбинировать с url-шаблонами:
# urls.py
from django.urls import path, include
from rest_framework import routers
from .views import ChangeUserInfo, ItemsViewSet
router = routers.DefaultRouter()
router.register(r'custom-viewset', ItemsViewSet)
urlpatterns = [
path('change-user-info', ChangeUserInfo.as_view()),
path('', include(router.urls)),
]
Здесь мы создали маршрутизатор (используя маршрутизатор по умолчанию, поэтому мы получаем представление API по умолчанию) и зарегистрировали в нем ItemsViewSet. При создании маршрутизатора вы должны указать два аргумента:
- Префикс URL-адреса для представлений
- Представление само установилось
Затем мы включили маршрутизатор внутрь urlpatterns.
Это не единственный способ включить маршрутизаторы. Дополнительные опции приведены в документации по Маршрутизаторам.
Во время разработки список элементов будет доступен по адресу http://127.0.0.1:8000/custom-viewset/, а отдельный элемент будет доступен по адресу http://127.0.0.1:8000/custom-viewset/{id}/.
Поскольку мы определили только действия list и retrieve в нашем ItemsViewSet, единственным разрешенным методом является GET.
Наше пользовательское действие будет доступно по адресу http://127.0.0.1:8000/custom-viewset/items_not_done/.
Вот как маршрутизатор сопоставляет методы с действиями:
# https://github.com/encode/django-rest-framework/blob/3.12.4/rest_framework/routers.py#L83
routes = [
# List route.
Route(
url=r'^{prefix}{trailing_slash}$',
mapping={
'get': 'list',
'post': 'create'
},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}
),
# Dynamically generated list routes. Generated using
# @action(detail=False) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=False,
initkwargs={}
),
# Detail route.
Route(
url=r'^{prefix}/{lookup}{trailing_slash}$',
mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Instance'}
),
# Dynamically generated detail routes. Generated using
# @action(detail=True) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=True,
initkwargs={}
),
]
GenericViewSet
В то время как ViewSet расширяется APIView, GenericViewSet расширяется GenericAPIView.
Класс GenericViewSet предоставляет базовый набор универсальных методов просмотра вместе с методами get_object и get_queryset.
Вот как создаются классы ViewSet и GenericViewSet:
# https://github.com/encode/django-rest-framework/blob/3.12.4/rest_framework/viewsets.py#L210
class ViewSet(ViewSetMixin, views.APIView):
pass
# https://github.com/encode/django-rest-framework/blob/3.12.4/rest_framework/viewsets.py#L217
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
pass
Как вы можете видеть, они оба расширяют ViewSetMixin и либо APIView, либо GenericAPIView. Кроме этого, дополнительного кода нет.
Чтобы использовать класс GenericViewSet, вам нужно переопределить класс и либо использовать смешанные классы, либо явно определить реализации действий для достижения желаемого результата.
Использование GenericViewSet с миксинами
from rest_framework import mixins, viewsets
class ItemViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
serializer_class = ItemSerializer
queryset = Item.objects.all()
Этот GenericViewSet комбинируется со смешиванием ListModelMixin и RetrieveModelMixin. Поскольку это набор представлений, маршрутизатор выполняет сопоставление URL-адресов, а микшины предоставляют действия для просмотра списка и подробных сведений.
Использование GenericViewSet с явными реализациями действий
При использовании миксинов вам нужно только указать атрибуты serializer_class и queryset; в противном случае вам нужно будет выполнить действия самостоятельно.
Чтобы подчеркнуть преимущество GenericViewSet перед ViewSet, мы приведем несколько более сложный пример:
from rest_framework import status
from rest_framework.permissions import DjangoObjectPermissions
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
class ItemViewSet(GenericViewSet):
serializer_class = ItemSerializer
queryset = Item.objects.all()
permission_classes = [DjangoObjectPermissions]
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(serializer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def list(self, request):
serializer = self.get_serializer(self.get_queryset(), many=True)
return self.get_paginated_response(self.paginate_queryset(serializer.data))
def retrieve(self, request, pk):
item = self.get_object()
serializer = self.get_serializer(item)
return Response(serializer.data)
def destroy(self, request):
item = self.get_object()
item.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Здесь мы создали набор представлений, который позволяет выполнять действия create, list, retrieve, и destroy.
Поскольку мы расширили GenericViewSet, мы:
- Использовал
DjangoObjectPermissionsи нам не нужно было самостоятельно проверять права доступа к объекту - Вернул ответ с разбивкой по страницам
ModelViewSet
ModelViewSet предоставляет действия по умолчанию create, retrieve, update, partial_update, destroy и list, поскольку он использует GenericViewSet и все доступные миксины.
ModelViewSet это самый простой в использовании вид. Вам нужно всего три строки:
class ItemModelViewSet(ModelViewSet):
serializer_class = ItemSerializer
queryset = Item.objects.all()
Затем, после того как вы зарегистрируете просмотр на маршрутизаторе, все готово!
# urls.py
from django.urls import path, include
from rest_framework import routers
from .views import ChangeUserInfo, ItemsViewSet, ItemModelViewSet
router = routers.DefaultRouter()
router.register(r'custom-viewset', ItemsViewSet)
router.register(r'model-viewset', ItemModelViewSet) # newly registered ViewSet
urlpatterns = [
path('change-user-info', ChangeUserInfo.as_view()),
path('', include(router.urls)),
]
Теперь вы можете:
- создайте элемент и перечислите все элементы
- извлеките, обновите и удалите отдельный элемент
Только для чтения ModelViewSet
ReadOnlyModelViewSet - это набор представлений, который предоставляет только действия list и retrieve, комбинируя GenericViewSet с RetrieveModelMixin и ListModelMixin смешивание.
Например ModelViewSet, ReadOnlyModelViewSet для работы нужны только атрибуты queryset и serializer_class:
from rest_framework.viewsets import ReadOnlyModelViewSet
class ItemReadOnlyViewSet(ReadOnlyModelViewSet):
serializer_class = ItemSerializer
queryset = Item.objects.all()
Сводная информация о представлениях API, общих представлениях и наборах представлений
Краткое содержание серии:
- В первой и второй второй статьях рассказывалось о том, как создавать представления API путем расширения
APIViewи универсальных представлений, соответственно. - В этой статье мы рассмотрели, как создавать представления API с помощью ViewSets.
Для более глубокого понимания представлений мы рассмотрели все основные элементы, но в реальной жизни вы, скорее всего, будете использовать один из следующих:
APIView- конкретные виды
ModelViewSet/ReadOnlyModelViewSet
Чтобы быстро увидеть различия между ними, давайте рассмотрим пример того, как все три в действии достигают одного и того же результата.
Три конечные точки:
- Перечисление всех элементов
- Создание нового элемента
- Извлечение, обновление и удаление отдельного элемента
Вот как вы можете добиться этого, расширив APIView.:
class ItemsList(APIView):
def get(self, request, format=None):
items = Item.objects.all()
serializer = ItemSerializer(items, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = ItemSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class ItemDetail(APIView):
def get(self, request, pk, format=None):
item = get_object_or_404(Item.objects.all(), pk=pk)
serializer = ItemSerializer(item)
return Response(serializer.data)
def put(self, request, pk, format=None):
item = get_object_or_404(Item.objects.all(), pk=pk)
serializer = ItemSerializer(item, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
item = get_object_or_404(Item.objects.all(), pk=pk)
item.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Вот как вы делаете то же самое с конкретными общими представлениями:
class ItemsListGeneric(ListCreateAPIView):
queryset = Item.objects.all()
serializer_class = ItemSerializer
class ItemDetailGeneric(RetrieveUpdateDestroyAPIView):
queryset = Item.objects.all()
serializer_class = ItemSerializer
И вот строки, которые вам нужны с ModelViewSet:
class ItemsViewSet(ModelViewSet):
serializer_class = ItemSerializer
queryset = Item.objects.all()
Наконец, вот как будут выглядеть соответствующие конфигурации URL-адресов:
# APIView
from django.urls import path
from views import ItemsList, ItemDetail
urlpatterns = [
path('api-view', ItemsList.as_view()),
path('api-view/<pk>', ItemDetail.as_view()),
]
# generic views
from django.urls import path,
from views import ItemsListGeneric, ItemDetailGeneric
urlpatterns = [
path('generic-view', ItemsListGeneric.as_view()),
path('generic-view/<pk>', ItemDetailGeneric.as_view()),
]
# ViewSet
from django.urls import path, include
from rest_framework import routers
from views import ItemsViewSet
router = routers.DefaultRouter()
router.register(r'viewset', ItemsViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Заключение
Просмотры DRF представляют собой сложную, запутанную сеть:

Существует три основных способа их использования с несколькими дополнительными возможностями:
- Расширение класса
APIView- можно также использовать функциональные представления с помощью декоратора
- Использование универсальных представлений
GenericAPIViewявляется основойGenericAPIViewможно сочетать с одним или несколькими миксерами- Классы Concrete view уже охватывают комбинирование
GenericAPIViewс миксинами всеми широко используемыми способами
ViewSetобъединяет все возможные действия в один классGenericViewSetявляется более мощным, чем базовыйViewSetModelViewSetиReadOnlyModelViewSetобеспечивают максимальную функциональность при минимальном объеме кода
Все вышеперечисленное обеспечивает простую настройку.
В большинстве случаев вы будете использовать либо APIView, один из конкретных классов представлений, либо (ReadOnly)ModelViewSet. Тем не менее, понимание того, как создаются представления, и преимуществ предков может оказаться полезным, когда вы пытаетесь разработать индивидуальное решение.
Серия просмотров фреймворка Django REST:
Вернуться на верх