Курс по Django: https://stepik.org/a/183363
Давайте
посмотрим на код, который у нас с вами получился в файле women/views.py. Здесь налицо
его дублирование в классах представлениях:
class WomenAPIList(generics.ListCreateAPIView):
queryset = Women.objects.all()
serializer_class = WomenSerializer
class WomenAPIUpdate(generics.UpdateAPIView):
queryset = Women.objects.all()
serializer_class = WomenSerializer
class WomenAPIDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Women.objects.all()
serializer_class = WomenSerializer
Конечно, если
проект небольшой, то это не особо критично и можно было бы оставить в таком
виде. Но при масштабировании этой программы, при добавлении новых моделей и API-представлений,
связанных с ними, у нас это дублирование будет очень быстро увеличиваться и
программу будет уже не так удобно поддерживать и менять. Благо разработчики Django REST Framework этим озаботились
и предоставили нам инструмент устранения такой избыточности. Это достигается за
счет использования вьюсетов (viewsets) – классов, которые объединяют
функциональность нескольких разных вьюх (views). И сейчас мы
разберемся, как их применять на практике.
Вначале я сделаю
очень простую вещь, классы WomenAPIList, WomenAPIUpdate и WomenAPIDetailView
поставлю в комментарии, а вместо них запишу один общий класс представления,
заменяющий эти три:
from rest_framework import viewsets
class WomenViewSet(viewsets.ModelViewSet):
queryset = Women.objects.all()
serializer_class = WomenSerializer
Смотрите,
вначале, конечно же, нужно импортировать ветку viewsets для более
удобного доступа к вьюсетам, а затем, определяем новый дочерний класс с именем WomenViewSet, наследуемый от
базового ModelViewSet. Почему именно ModelViewSet? Ну, я думаю,
вы догадались? Так как наши представления работают с моделью Women, то и вьюсет
должен поддерживать работу с моделями. Поэтому я им и воспользовался. Вообще,
полный список существующих классов-вьюсетов в DRF можно
посмотреть на следующей странице официальной документации:
https://www.django-rest-framework.org/api-guide/viewsets/
Как видите, их
не так много:
ViewSet, GenericViewSet,
ModelViewSet, ReadOnlyModelViewSet
и с моделями
связаны всего два: ModelViewSet и ReadOnlyModelViewSet. Отличие между ними
только в том, что первый реализует полный функционал, включая добавление,
изменение и удаление записей, а второй – только для чтения данных.
Хорошо, мы
определили наш собственный вьюсет WomenViewSet. Но, что он
может делать? Какой функционал в нем скрыт? Я покажу это, следующим образом.
Давайте перейдем в файл drfsite/urls.py и пропишем
маршруты для нашего вьюсета.
Вначале сделаем
это привычным нам способом – через метод as_view(). В этом
методе для классов вьюсетов можно указывать словарь, где ключ – это тип запроса
от клиента (GET, POST, PUT, DELETE и т.п.), а
значение – метод, который должен при этом запросе вызываться:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/womenlist/', WomenViewSet.as_view({'get': 'list'})),
path('api/v1/womenlist/<int:pk>/', WomenViewSet.as_view({'put': 'update'})),
]
В частности,
метод as_view({'get': 'list'}) связывает GET-запрос с
возвратом клиенту списка записей из модели таблицы БД. А метод as_view({'put': 'update'}) связывает PUT-запрос с
методом update() для изменения
текущей записи. Но откуда я знаю, какие методы можно писать в этом словаре? Все
написано в документации. Здесь в разделе «ViewSet actions» перечислены все
стандартные методы вьюсетов. Что они делают, легко понять по их названиям.
Соответственно, каждый из типов запроса можно связать с одним из этих методов.
Давайте
посмотрим, как это будет работать. Запускаем тестовый веб-сервер, открываем
страницу в браузере:
http://127.0.0.1:8000/api/v1/womenlist/
и видим список
записей из таблицы women. Если же дополнительно прописать
идентификатор статьи:
http://127.0.0.1:8000/api/v1/womenlist/9/
то получим
способ ее изменения методом PUT.
Роутеры (routers) для вьюсетов (viewsets)
Однако, это не
лучший способ описания маршрутов для класса вьюсета. Мы видим, что здесь один и
тот же класс связан с несколькими маршрутами, причем, формат этих URL-путей имеет
определенную закономерность: если указывать путь без идентификатора, то
получаем список записей, а с идентификатором – работаем с одной конкретной
записью. И это довольно частый случай. Поэтому и он был автоматизирован на
уровне Django REST Framework с
использованием, так называемых, роутеров. Подробнее о них можно почитать на
странице официальной документации:
https://www.django-rest-framework.org/api-guide/routers/
Понять работу
роутера проще всего на конкретном примере. Давайте в нашем проекте создадим
роутер для класса WomenViewSet. Вначале мы импортируем ветку routers:
from rest_framework import routers
а, затем,
создадим объект простого роутера (SimpleRouter):
router = routers.SimpleRouter()
Следующим шагом
нам нужно связать этот роутер с вьюсетом WomenViewSet. Делается это с помощью метода
register:
router.register(r'women', WomenViewSet)
Здесь первым
аргументом указана raw-строка «women» - это префикс
(prefix) для URL-адреса, по
которому будет доступен данный вьюсет (далее мы увидим, где этот префикс
фигурирует). А вторым аргументом – сам класс WomenViewSet.
Все, осталось
только связать маршруты роутера с самим проектом (сайтом). Для этого я
воспользуюсь уже знакомой нам функцией include (по урокам Django) и добавлю,
автоматически сгенерированные пути, в коллекцию urlpatterns:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include(router.urls)), # http://127.0.0.1:8000/api/v1/women/
]
Смотрите, роутер
SimpleRouter формирует два типа маршрутов:
-
http://127.0.0.1:8000/api/v1/women/ - для
извлечения списка записей;
-
http://127.0.0.1:8000/api/v1/women/pk/ - для работы с
конкретной записью.
Давайте
посмотрим, что у нас получится. Переходим в веб-браузер и по первому URL-пути получаем
список вместе с формой добавления новой записи. Такой функционал по умолчанию
предоставляет базовый класс ModelViewSet. Если же укажем
второй тип маршрута (с идентификатором записи), то отобразится запись (если она
была найдена) и будет возможность ее изменить и удалить. То есть, мы здесь
получили полный функционал по работе с записями таблицы БД с использованием
одного класса вьюсета.
Если же мы изменим
базовый класс на ReadOnlyModelViewSet:
class WomenViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Women.objects.all()
serializer_class = WomenSerializer
то автоматически
изменится и весь функционал. Теперь можно только читать данные, но не изменять
их.
Тонкая настройка функциональности класса-вьюсета
Давайте
посмотрим, как реализованы эти базовые классы. Фактически, они просто наследуют
определенный набор классов-миксинов плюс базовый класс GenericViewSet:
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet):
pass
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
pass
Это значит, что
мы можем легко определить свой вьюсет с нужным набором функционала. Например,
следующим образом:
class WomenViewSet(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
queryset = Women.objects.all()
serializer_class = WomenSerializer
В результате,
получим полный функционал кроме добавления новой записи. Или, если убрать mixins.DestroyModelMixin, то не сможем
удалять конкретную запись. И так далее. То есть, меняя набор классов-миксинов,
мы автоматически определяем нужный нам функционал класса-вьюсета. И это очень
удобно.
Курс по Django: https://stepik.org/a/183363