Вьюсеты и роутеры в Django



Предустановленный класс для работы с моделями: Viewset

На прошлом уроке вы реализовали все типовые операции CRUD, для этого потребовалось лишь два класса-дженерика и два эндпоинта. Шесть операций упакованы в два класса — неплохо, но можно лучше.

Всякий раз, когда снова потребуется реализовать CRUD, придётся снова и снова описывать тот же набор из аналогичных классов. А почему бы не описать всё в одном классе?

И когда у кого-то возник этот вопрос — появились вьюсеты.Вьюсет (англ. viewset, «набор представлений») — это высокоуровневый view-класс, реализующий все операции CRUD; он может вернуть объект или список объектов, создать, изменить или удалить объекты.

Во вьюсеты встроена обработка разных типов запросов, работа с сериализаторами и моделями, фильтрация и пагинация результатов, возврат ошибок. Не нужно ничего придумывать: всё работает «из коробки».

В библиотеке rest_framework есть несколько разных вьюсетов, они хранятся в пакете viewsets.

Начнём с самого популярного вьюсета. Встречайте: ModelViewSet.

Универсальный ModelViewSet

Класс ModelViewSet может выполнять любые операции CRUD с моделью. От разработчика не требуется описывать методы для чтения и записи данных для модели: эти операции уже реализованы.В классе, наследующемся от ModelViewSet, обязательно должны быть описаны два поля:

  • в поле queryset задаётся выборка объектов модели, с которой будет работать вьюсет;
  • в поле serializer_class указывается, какой сериализатор будет применён для валидации и сериализации.

И во второй раз перепишем Kittygram: вместо дженериков применим вьюсеты.Это практическая работа: вносите изменения, описанные в этом уроке, в проект Kittygram, развёрнутый на вашем компьютере.В начале работы нужно импортировать пакет viewsets и создать класс, наследующийся от ModelViewSet.

# views.py
from rest_framework import viewsets 

from .models import Cat
from .serializers import CatSerializer


class CatViewSet(viewsets.ModelViewSet):
    queryset = Cat.objects.all()
    serializer_class = CatSerializer 

Всё. Класс, который обработает все шесть типичных действий, готов.

Класс ModelViewSet предоставляет отличный набор инструментов для всех востребованных операций при работе с моделями. В большинстве стандартных ситуаций он будет работать «из коробки».

Выгода очевидна.

Класс ReadOnlyModelViewSet: только чтение

В пакете rest_framework.viewsets есть похожий на ModelViewSet, но ограниченный в правах класс ReadOnlyModelViewSet. Он может только получать данные модели, а записывать и изменять — не может.

Этот класс полезен в ситуациях, когда требуется только выдавать данные по запросу, без возможности их изменить. В остальном ReadOnlyModelViewSet работает точно так же, как и ModelViewSet.

Роутеры

При работе с view-классами и дженериками каждый эндпоинт отдельно описывается в urls.py. Но для вьюсетов есть более удобный и экономичный инструмент — роутеры (англ. routers).

С помощью роутера для заданных вьюсетов создаются эндпоинты по маске адреса:

  • URL-префикс/ и
  • URL-префикс/<int:pk>.

В DRF есть два стандартных роутера: SimpleRouter и DefaultRouter. Они очень похожи, начнём с первого.Добавьте роутеры в Kittygram. В файл urls.py импортируйте класс SimpleRouter и создайте экземпляр этого класса.

# urls.py
from rest_framework.routers import SimpleRouter

router = SimpleRouter() 

Чтобы роутер создал необходимый набор эндпоинтов, необходимо вызвать его метод register() (говорят «зарегистрировать эндпоинты»). В качестве аргументов этот метод принимает URL-префикс и название вьюсета, для которого создаётся набор эндпоинтов.

router.register('cats', CatViewSet) 

После регистрации надо включить новые эндпоинты в список urlpatterns: перечень эндпоинтов будет доступен в router.urls.

Создание эндпоинтов через router выглядит так:

# urls.py
from rest_framework.routers import SimpleRouter

from django.urls import include, path

from cats.views import CatViewSet

# Создаётся роутер
router = SimpleRouter()
# Вызываем метод .register с нужными параметрами
router.register('cats', CatViewSet)
# В роутере можно зарегистрировать любое количество пар "URL, viewset":
# например
# router.register('owners', OwnerViewSet)
# Но нам это пока не нужно

urlpatterns = [
    # Все зарегистрированные в router пути доступны в router.urls
    # Включим их в головной urls.py
    path('', include(router.urls)),
] 

Только что созданный роутер сгенерирует два эндпоинта:

  • cats/,
  • cats/<int:pk>/.

Теперь через эти эндпоинты будут доступны любые операции с моделью:

  • POST-запрос на cats/ создаст новую запись.
  • Запросы PUT, PATCH или DELETE к адресу cats/<pk>/ изменят или удалят существующую запись.
  • GET-запрос на те же адреса вернёт список объектов или один объект.

Параметр name в эндпоинтах

В urls.py для каждого маршрута можно указать необязательный параметр name. Если этот параметр определён, то во view-функциях или view-классах через функцию reverse() можно получить соответствующие URL — это очень удобно и соответствует принципу DRY.

На практике вы уже работали с этими функциями при написании тестов для Django-проекта; при тестировании API name в эндпоинтах вам тоже понадобятся.

Роутеры сами автоматически создают name для каждого эндпоинта, его значение создаётся

  • из имени модели, с которой работает вьюсет,
  • и суффикса:
    • -list (для эндпоинта, работающего с коллекцией объектов)
    • или -detail (для эндпоинта, работающего отдельным объектом).

Например, для роутера, созданного для вьюсета CatViewSet().

class CatViewSet(viewsets.ModelViewSet):
    queryset = Cat.objects.all()
    serializer_class = CatSerializer 

имена эндпоинтов будут такими:

urlpatterns = [
    # Здесь имя "cat" взято из queryset, 
    # с которым работает вьюсет CatViewSet
    path('cat/', ..., name='cat-list'),
    path('cat/<int:pk>/', ..., name='cat-detail'),
] 

При работе с вьюсетами и роутерами вы можете столкнуться с ошибкой «не определён аргумент basename»:

'basename' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute. 

Речь идёт о необязательном аргументе роутера basename: в нём можно вручную указать префикс для параметра name в эндпоинтах, созданных роутером.Например, можно переопределить префикс cat в name='cat-list' и name='cat-detail'

router.register('cats', CatViewSet, basename='tiger') 

В результате name для эндпоинтов будут начинаться с tiger:

urlpatterns = [
    path('cat/', ..., name='tiger-list'),
    path('cat/<int:pk>/', ..., name='tiger-detail'),
] 

Однако есть случаи, когда параметр basename обязательно должен быть указан. Это необходимо в тех случаях, когда queryset однозначно не задан во вьюсете, а определён через метод get_queryset().

# Если бы пользователи могли оставлять комментарии к котикам, 
# то эндпоинт для работы с комментариями выглядел бы примерно так:
# cats/{cat_id}/comments/

class CommentViewSet(viewsets.ModelViewSet):
    serializer_class = CommentSerializer
    # queryset во вьюсете не указываем
    # Нам тут нужны не все комментарии, а только связанные с котом с id=cat_id
    # Поэтому нужно переопределить метод get_queryset и применить фильтр
    def get_queryset(self):
        # Получаем id котика из эндпоинта
        cat_id = self.kwargs.get("cat_id")
        # И отбираем только нужные комментарии
        new_queryset = Comment.objects.filter(cat=cat_id)
        return new_queryset 

В подобных ситуациях создать name автоматически не получится, и параметр basename придётся указать явным образом.

SimpleRouter vs DefaultRouter

DefaultRouter — это расширенная версия SimpleRouter: он умеет всё то же, что и SimpleRouter, а в дополнение ко всему генерирует корневой эндпоинт /, GET-запрос к которому вернёт список ссылок на все ресурсы, доcтупные в API.У этого эндпоинта тоже есть nameapi-root. Маршрут для него мог бы выглядеть примерно так:

urlpatterns = [
    ...
    path('', ..., name='api-root'),
] 

Если применён DefaultRouter, то в ответ на GET-запрос к адресу http://127.0.0.1:8000/ вернётся список ссылок на доступные ресурсы.

При работе с документированием вам потребуется получить коллекцию ссылок на все ресурсы API, так что далее в курсе будем работать именно с DefaultRouter.



Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: