Django REST Framework Views - Generic Views
Часто используемые представления, которые тесно связаны с моделями баз данных (например, создание экземпляра модели, его удаление, перечисление экземпляров и т.д.), уже предварительно встроены в представления Django REST Framework (DRF). Эти элементы повторно используемого поведения называются Универсальными представлениями.
Вы можете использовать общие представления в качестве строительных блоков - GenericAPIView
и миксины - для создания собственных представлений. Или вы можете использовать подключаемые и воспроизводимые конкретные представления, которые уже объединили GenericAPIView
и соответствующие миксины.
Серия просмотров фреймворка Django REST:
- Просмотры приложений
- Общие представления (эта статья!)
- Наборы для просмотра
Цели
К концу этой статьи вы должны быть в состоянии объяснить:
- Что такое
GenericAPIView
, что такое миксины и как они объединяются для создания конкретных представлений - Какие микшеры вы используете и на что они способны
- Как создать пользовательский микшер и как им пользоваться
- Какие универсальные представления вы можете использовать и что они делают
Общие представления
Общие представления - это набор часто используемых шаблонов.
Они построены поверх класса APIView
, который мы представили в предыдущей статье этой серии.
Их цель состоит в том, чтобы вы могли быстро создавать представления API, которые точно соответствуют вашим моделям баз данных, не повторяясь.
Они состоят из GenericAPIView
, смешивания и конкретных видов:
GenericAPIView
это более загруженная версияAPIView
. Сама по себе она не очень полезна, но может использоваться для создания действий многократного использования.- Миксины - это элементы обычного поведения. Они бесполезны без
GenericAPIView
. - Конкретные представления объединяются
GenericAPIView
с соответствующими миксинами для создания представлений, часто используемых в API.
DRF использует разные названия для конкретных представлений. В документации и комментариях к коду их можно найти как конкретные классы представлений, конкретные общие представления или конкретные представления.
Поскольку названия очень похожи, их легко перепутать. Общие представления - это слово, которое обозначает как смешанные, так и конкретные представления. При использовании общих представлений конкретные представления - это, вероятно, тот уровень, с которым вы будете работать.
Общий обзор
GenericAPIView является базовым классом для всех других универсальных представлений. Он предоставляет методы, такие как get_object
/get_queryset
и get_serializer
. Несмотря на то, что он предназначен для использования в сочетании с микшерами (поскольку он используется в общих представлениях), его можно использовать отдельно:
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
class RetrieveDeleteItem(GenericAPIView):
serializer_class = ItemSerializer
queryset = Item.objects.all()
def get(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def delete(self, request, *args, **kwargs):
instance = self.get_object()
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
При расширении GenericAPIView
, queryset
и serializer_class
должны быть установлены значения. В качестве альтернативы вы можете перезаписать get_queryset()
/get_serializer_class()
.
Поскольку существует несколько миксинов, предназначенных для использования с GenericAPIView
, я не рекомендую изобретать велосипед и использовать его сам по себе.
Миксины
Микшины обеспечивают общее поведение. Их нельзя использовать отдельно; они должны быть объединены с GenericAPIView
для создания функционального представления. Хотя классы mixin предоставляют действия по созданию/извлечению/обновлению/удалению, вам все равно необходимо привязать соответствующие действия к методам.
Доступные миксины:
Mixin | Usage |
---|---|
CreateModelMixin |
Create a model instance |
ListModelMixin |
List a queryset |
RetrieveModelMixin |
Retrieve a model instance |
UpdateModelMixin |
Update a model instance |
DestroyModelMixin |
Delete a model instance |
Вы можете использовать только один из них или комбинировать их для достижения желаемого результата.
Вот пример того, как выглядит миксинг:
class RetrieveModelMixin:
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
Как вы можете видеть, RetrieveModelMixin
предоставляет функцию (действие), называемую retrieve
, которая извлекает объект из базы данных и возвращает его в сериализованном виде.
ListModelMixin и CreateModelMixin
ListModelMixin реализует действие, которое возвращает сериализованное представление набора запросов (необязательно с разбивкой на страницы).
CreateModelMixin реализует действие, которое создает и сохраняет новый экземпляр модели.
Часто они используются вместе для создания конечной точки API для создания списка:
from rest_framework import mixins
from rest_framework.generics import GenericAPIView
class CreateListItems(mixins.ListModelMixin, mixins.CreateModelMixin, GenericAPIView):
serializer_class = ItemSerializer
queryset = Item.objects.all()
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
В CreateListItems
мы использовали serializer_class
и queryset
, предоставленные GenericAPIView
.
Мы сами определили get
и post
методы, которые использовали list
и create
действия, предоставляемые миксинами:
CreateModelMixin
обеспечиваетcreate
действиеListModelMixin
обеспечиваетlist
действие
Привязка действий к методам
Вы несете ответственность за привязку действий к методам.
Теоретически это означает, что вы могли бы связать методы POST с действиями list и методы GET с действиями create, и все бы "вроде как" сработало.
Например:
from rest_framework import mixins
from rest_framework.generics import GenericAPIView
class CreateList(mixins.ListModelMixin, mixins.CreateModelMixin, GenericAPIView):
serializer_class = ItemSerializer
queryset = Item.objects.all()
def get(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
Это приведет к следующим результатам...
Используя метод GET:
Используя метод POST:
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: То, что это возможно, не означает, что я советую вам это делать. Единственной целью этого было показать вам, как работают методы привязки и действия.
Извлекаем modelmixin, обновляем modelmixin и уничтожаем modelmixin
Извлекаем modelmixin, Обновляем modelmixin и Уничтожаем modelmixin все они работают с одним экземпляром модели .
RetrieveModelMixin
и UpdateModelMixin
оба возвращают сериализованные представления объекта, в то время как DestroyModelMixin
, в случае успеха, возвращает HTTP_204_NO_CONTENT
.
Вы можете использовать один из них или комбинировать их по своему усмотрению.
В этом примере мы объединили все три в единую конечную точку для каждого действия, которое возможно для подробного просмотра:
from rest_framework import mixins
from rest_framework.generics import GenericAPIView
class RetrieveUpdateDeleteItem(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView
):
serializer_class = ItemSerializer
queryset = Item.objects.all()
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
Предусмотренные действия:
RetrieveModelMixin
обеспечиваетretrieve
действиеUpdateModelMixin
обеспечиваетupdate
иpartial_update
действияDestroyModelMixin
обеспечиваетdestroy
действие
Таким образом, с помощью конечной точки RetrieveUpdateDeleteItem
пользователь может извлекать, обновлять или удалять отдельный элемент.
Вы также можете ограничить просмотр определенными действиями:
from rest_framework import mixins
from rest_framework.generics import GenericAPIView
class RetrieveUpdateItem(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, GenericAPIView):
serializer_class = ItemSerializer
queryset = Item.objects.all()
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
В этом примере мы опустили DestroyModelMixin
и использовали только update
действие из UpdateModelMixin
.
Группировка миксинов
Хорошей идеей является наличие единого представления для обработки всех экземпляров - перечисления всех экземпляров и добавления нового экземпляра - и другого представления для обработки одного экземпляра - извлечения, обновления и удаления отдельных экземпляров.
Тем не менее, вы можете комбинировать миксины так, как считаете нужным. Например, вы могли бы объединить RetrieveModelMixin
и CreateModelMixin
миксины:
from rest_framework import mixins
from rest_framework.generics import GenericAPIView
class RetrieveCreate(mixins.RetrieveModelMixin, mixins.CreateModelMixin, GenericAPIView):
serializer_class = ItemSerializer
queryset = Item.objects.all()
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Это создает конечную точку для извлечения одного экземпляра и добавления новых экземпляров:
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Опять же, то, что это возможно, не означает, что это хорошая идея. Приведенный выше пример предназначен исключительно для образовательных целей. Я не советую использовать его в производственном коде.
Пользовательские миксины
В реальных приложениях велика вероятность того, что вам потребуется какое-то пользовательское поведение, причем в нескольких местах. Вы можете создать пользовательский микшер, чтобы вам не нужно было повторять свой код и включать его в свой класс представления.
Допустим, вы хотите использовать другой сериализатор в зависимости от метода запроса. Вы могли бы добавить в представление несколько инструкций if, но это может быстро привести к путанице. Кроме того, через два месяца вы добавите еще одну модель, и вам нужно будет повторить то же самое.
В этом случае рекомендуется создать пользовательский миксинг, чтобы сопоставить сериализаторы с методами запроса:
class SerializerByMethodMixin:
def get_serializer_class(self, *args, **kwargs):
return self.serializer_map.get(self.request.method, self.serializer_class)
Здесь мы заменили метод get_serializer_class
из класса GenericAPIView
.
Может быть, вы хотите переопределить другие методы, такие как get_queryset
или get_object
? Взгляните на код внутри класса GenericAPIView, где создатели DRF указывают, какие методы вы, возможно, захотите переопределить.
Теперь вам просто нужно добавить SerializerByMethodMixin
в свой вид и установить атрибут serializer_map
:
class ListCreateItems(SerializerByMethodMixin, ListCreateAPIView):
queryset = Item.objects.all()
serializer_map = {
'GET': GetItemSerializer,
'POST': PostItemSerializer,
}
Убедитесь, что вы включили микширование в качестве первого параметра, чтобы его методы не были переопределены (сначала более высокий приоритет).
Пользовательский базовый класс
Если вы используете смешивание для одного и того же типа представления несколько раз, вы даже можете создать пользовательский базовый класс .
Например:
class BaseCreateListView((MixinSingleOrListSerializer, ListCreateAPIView)):
pass
Конкретные виды
Конкретные представления выполняют большую часть работы, которую нам необходимо выполнить самостоятельно при использовании APIView
. Они используют миксины в качестве основных строительных блоков, объединяют строительные блоки с помощью GenericAPIView
и привязывают действия к методам.
Взгляните на код для одного из конкретных классов представлений, ListCreateAPIView
:
# https://github.com/encode/django-rest-framework/blob/3.12.4/rest_framework/generics.py#L232
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Как вы можете видеть, это довольно просто и выглядит очень похоже на то, что мы создали сами, используя миксины. Они расширяют соответствующие миксины и GenericAPIView
. Они также определяют каждый из соответствующих методов и привязывают к ним соответствующие действия.
Если вам не нужно сильно настраиваемое поведение, то это наилучшее представление, если вы используете универсальные представления.
Существует девять классов, каждый из которых предоставляет комбинацию действий, которая может вам понадобиться:
Class | Usage | Method handler | Extends mixin |
---|---|---|---|
CreateAPIView |
create-only | post |
CreateModelMixin |
ListAPIView |
read-only for multiple instances | get |
ListModelMixin |
RetrieveAPIView |
read-only for single instance | get |
RetrieveModelMixin |
DestroyAPIView |
delete-only for single instance | delete |
DestroyModelMixin |
UpdateAPIView |
update-only for single instance | put , patch |
UpdateModelMixin |
ListCreateAPIView |
read-write for multiple instances | get , post |
CreateModelMixin , ListModelMixin |
RetrieveUpdateAPIView |
read-update for single instance | get , put , patch |
RetrieveModelMixin , UpdateModelMixin |
RetrieveDestroyAPIView |
read-delete for single instance | get , delete |
RetrieveModelMixin , DestroyModelMixin |
RetrieveUpdateDestroyAPIView |
read-update-delete for single instance | get , put , patch , delete |
RetrieveModelMixin , UpdateModelMixin , DestroyModelMixin |
Вот еще одна полезная таблица, которая показывает, к какому классу привязан обработчик конкретного метода:
Class | get |
post |
put /patch |
delete |
---|---|---|---|---|
CreateAPIView |
✓ | |||
ListAPIView |
✓ | |||
RetrieveAPIView |
✓ | |||
DestroyAPIView |
✓ | |||
UpdateAPIView |
✓ | |||
ListCreateAPIView |
✓ | ✓ | ||
RetrieveUpdateAPIView |
✓ | ✓ | ||
RetrieveDestroyAPIView |
✓ | ✓ | ||
RetrieveUpdateDestroyAPIView |
✓ | ✓ | ✓ |
Всем классам, которые расширяются из конкретного представления, требуется:
- набор запросов
- класс сериализатора
Кроме того, вы можете указать атрибуты политики, как описано в первой статье этой серии.
Далее мы рассмотрим примеры каждого из девяти конкретных представлений в действии.
Создать просмотр
Расширив здесь CreateAPIView
, мы создали конечную точку, в которой пользователь может создать новый элемент:
from rest_framework.generics import CreateAPIView
from rest_framework.permissions import IsAdminUser
class CreateItem(CreateAPIView):
permission_classes = [IsAdminUser]
queryset = Item.objects.all()
serializer_class = ItemSerializer
Мы добавили атрибут политики, чтобы конечная точка была доступна только администратору.
Просмотр списка
Здесь мы расширили ListAPIView
, чтобы создать конечную точку, в которой перечислены все "незавершенные" элементы:
from rest_framework.generics import ListAPIView
class ItemsNotDone(ListAPIView):
queryset = Item.objects.all().filter(done=False)
serializer_class = ItemSerializer
Наш набор запросов фильтруется на основе поля done
. Он не содержит никаких дополнительных политик, поэтому доступен любому пользователю.
Имейте в виду, что каждое из этих представлений должно быть включено в URL-адреса по отдельности:
# urls.py
from django.urls import path
from .views import ListItems
urlpatterns = [
path('all-items', ListItems.as_view())
]
RetrieveAPIView
В то время как ListAPIView
возвращает список всех элементов, RetrieveAPIView
предназначен для извлечения одного элемента:
from rest_framework.generics import RetrieveAPIView
class SingleItem(RetrieveAPIView):
queryset = Item.objects.all()
serializer_class = ItemSerializer
Пример urls.py для RetrieveAPIView
(и других представлений для одного экземпляра):
from django.urls import path
from .views import SingleItem
>
urlpatterns = [
path('items/<pk>', SingleItem.as_view())
]
Уничтожить просмотр
Расширение DestroyAPIView
создает конечную точку с единственной целью удаления одного элемента:
from rest_framework.generics import DestroyAPIView
from rest_framework.permissions import IsAuthenticated
class DeleteItem(DestroyAPIView):
permission_classes = [IsAuthenticated]
queryset = Item.objects.all()
serializer_class = ItemSerializer
Доступ ограничен только для прошедших проверку подлинности пользователей.
Обновить просмотр
Конечная точка для обновления отдельного элемента:
from rest_framework.generics import UpdateAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.throttling import UserRateThrottle
class UpdateItem(UpdateAPIView):
permission_classes = [IsAuthenticated]
throttle_classes = [UserRateThrottle]
queryset = Item.objects.all()
serializer_class = ItemSerializer
Здесь, используя атрибуты политики, мы уменьшили количество запросов и ограничили доступ к конечной точке только для прошедших проверку подлинности пользователей.
ListCreateAPIView
ListCreateAPIView
является первым конкретным классом представления с более чем одной ответственностью, перечисляющим все элементы и создающим новый элемент:
from rest_framework.generics import ListCreateAPIView
class ListCreateItems(ListCreateAPIView):
authentication_classes = [TokenAuthentication]
queryset = Item.objects.all()
serializer_class = ItemSerializer
В этом примере пользователь проходит проверку подлинности с помощью токена.
Получить обновлениеapiview
Расширение RetrieveUpdateAPIView
создает конечную точку для извлечения и обновления одного элемента:
from rest_framework.generics import RetrieveUpdateAPIView
class RetrieveUpdateItem(RetrieveUpdateAPIView):
renderer_classes = [JSONRenderer]
queryset = Item.objects.all()
serializer_class = ItemSerializer
Здесь мы использовали атрибут policy для возврата данных в формате JSON.
Извлеченный файл Destroyapiview
Расширив здесь RetrieveDestroyAPIView
, мы создали конечную точку для извлечения и удаления одного элемента:
from rest_framework.generics import RetrieveDestroyAPIView
class RetrieveDeleteItem(RetrieveDestroyAPIView):
queryset = Item.objects.all()
serializer_class = ItemSerializer
Извлеките обновленный файл Destroyapiview
Здесь, расширив RetrieveUpdateDestroyAPIView
, мы создали конечную точку, в которой доступны все возможные действия для одного элемента: извлечение, (частично) обновление и удаление.
from rest_framework.generics import RetrieveUpdateDestroyAPIView
class RetrieveUpdateDeleteItem(RetrieveUpdateDestroyAPIView):
queryset = Item.objects.all()
serializer_class = ItemSerializer
Заключение
Общие представления предлагают множество готовых решений.
Если у вас нет каких-либо особых требований, то лучше всего использовать конкретные виды (например, RetrieveDestroyAPIView
). Если вам нужно что-то менее строгое, вы можете использовать конкретные классы в качестве строительных блоков - GenericAPIView
и миксины (например, UpdateModelMixin
). Это все равно сэкономит некоторую работу по сравнению с простым использованием класса APIView
.
Серия просмотров фреймворка Django REST:
- Просмотры приложений
- Общие представления (эта статья!)
- Наборы для просмотра