Django REST Framework Views - 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
и объединяет этот метод с соответствующим действием.
Method | List / Detail | Action |
---|---|---|
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
(для получения единственного экземпляра).
Действия
Следующие действия выполняются классом маршрутизатора по умолчанию:
list
create
retrieve
( требуется пк)update
( требуется пк)partial_update
( требуется пк)destroy
( требуется пк)
Вы также можете создавать пользовательские действия с помощью @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
является более мощным, чем базовыйViewSet
ModelViewSet
иReadOnlyModelViewSet
обеспечивают максимальную функциональность при минимальном объеме кода
Все вышеперечисленное обеспечивает простую настройку.
В большинстве случаев вы будете использовать либо APIView
, один из конкретных классов представлений, либо (ReadOnly)ModelViewSet
. Тем не менее, понимание того, как создаются представления, и преимуществ предков может оказаться полезным, когда вы пытаетесь разработать индивидуальное решение.
Серия просмотров фреймворка Django REST:
- Просмотры приложений
- Общие представления
- Наборы просмотров (эта статья!)