Как работает ModelSerializer

После того, как в предыдущих статьях мы подробно разобрали работу сериалайзера на основе классов BaseSerializer и Serializer, мы можем перейти к классу-наследнику ModelSerializer. Класс модельных сериалайзеров отличается лишь тем, что у него есть несколько инструментов, позволяющих сократить код сериалайзера:

  • автоматическое создание полей сериалайзера на основе данных о корреспондирующих полях модели;

  • автоматическое включение в поля сериалайзера тех же валидаторов, что есть в полях модели, а также, при определенных условиях, метавалидаторов;

  • заранее определенные методы create и update.

Общие же принципы работы модельного сериалайзера, как на чтение, так и на запись, идентичны тому, как работает базовый класс Serializer.

1. ModelSerializer: необходимый минимум

Минимально для определения модельного сериалайзера нуженarrow-up-right лишь внутренний класс Meta с атрибутами:

  1. modelarrow-up-right — джанго-модель, которую будет обслуживать сериалайзер. Модель не должна быть абстрактной (подробнее об абстрактных джанго-моделях можно почитать здесьarrow-up-right);

  2. Один из следующих атрибутовarrow-up-right:

    • fields — поля джанго-модели, для которых будут созданы корреспондирующие поля сериалайзера;

    • exclude — поля джанго-модели, для которых не нужно создавать поля сериалайзера (для всех остальных они будут созданы).

2. Автоматически создаваемые и декларируемые поля

2.1. Автоматически создаваемые поля

Поля сериалайзера, имена которых указаны во внутреннем классе Meta явно (через fields) или неявно (через exclude), DRF создаст самостоятельно, сопоставив с одноименными полями модели. О том, по каким правилам он это делает, расскажем ниже. Если одноименного поля модели не найдётся, будет выброшено исключение через метод build_unknown_fieldarrow-up-right.

2.2. Декларируемые поля

Это "обычные" поля сериалайзера, которые мы описываем полностью самостоятельно вне класса Meta, как делали это в предыдущих статьях. Пример:

Поле name — декларируемое, класс поля и его атрибуты мы задали самостоятельно. Поля id и writers — автоматически создаваемые.

Обратите внимание, что name также включено в список fields. Дело в том, что если присутствует атрибут fields, в нем должны быть перечисленыarrow-up-right все поля сериалайзера, включая декларируемые.

Декларируемые поля могут понадобится, к примеру, когда нам не подходит класс поля, который модельный сериалайзер подбирает автоматически. Через декларируемые поля зачастую используют вспомогательные классы ReadOnlyField, HiddenField, SerializerMethodField.

Если же класс поля сериалайзера устраивает, но нужна тонкая настройка его параметров (включая создание нескольких полей сериалайзера для одного поля модели), то, скорее всего, будет достаточно воспользоваться атрибутом extra_kwargs внутреннего класса Meta (о нем я расскажу в этой статье чуть позже).

2.3. Что означает fields = '__all__'

Если в качестве значения fields выступает строка __all__, то в сериалайзере будут созданы поля для обслуживания всех полей модели (кроме тех полей модели, за работу с которыми будут отвечать декларируемые поля сериалайзера). Использование __all__ отменяет необходимостьarrow-up-right прописывать в fields декларируемые поля.

3. Как правильно использовать fields и exclude

На основе исходного кода DRF можно сформулировать несколько правил:

  1. exclude должен бытьarrow-up-right либо списком, либо кортежем из названий полей модели (даже, если поле одно)

  2. fields может быть задан в виде:

Примеры модельного сериалайзера для джанго-модели Writer

Сериалайзер, который будет обслуживать все поля модели:

Сериалайзер, который будет обслуживать только поля firstname и lastname:

Сериалайзер, который будет обслуживать все поля кроме firstname и lastname:

4. Как DRF создаёт поля для модельного сериалайзера

4.1. Сбор информации о модели и распределение полей по группам

Сначала DRF собирает детальную информацию о полях модели, для этого задействуется метод get_field_infoarrow-up-right из rest_framework.utils.model_meta. Результат — именованный кортеж FieldResult, в котором, в числе прочих есть, следующие элементы:

  • relationsarrow-up-right — словарь, объединяющий в себе поля отношений модели ("forward_relations"), а также объекты класса RelatedManagerarrow-up-right("reverse_relations");

  • fields_and_pk — словарь с остальными полями модели.

Для подбора корреспондирующих полей DRF использует три метода:

Рассмотрим на примерах:

Из модели Town DRF возьмет:

  • поле name и поместит его в fields_and_pk для дальнейшей обработки методом build_standard_field;

  • менеджер writers, который связывает модель Town с моделью Writer (writers — это значение атрибута related_name поля birth_place в связанной модели). Менеджер будет помещен в relations и в дальнейшем обработан методом build_relational_field или методом build_nested_field (об этом подробно расскажем чуть ниже).

Из модели Writer DRF возьмет:

  • поля firstname, lastname, patronymic, birth_date и поместит их в fields_and_pk для дальнейшей обработки методом build_standard_field;

  • поле birth_place, которое поместит в relations для дальнейшей обработки методом build_relational_field или методом build_nested_field;

  • метод get_full_name, который после проверки hasattr(model_class, field_name) переадресует методу build_property_field.

4.2. Как DRF подбирает классы полей сериалайзера для "стандартных" полей модели

"Стандартные" поля модели — это поля, которые не относятся к полям отношений. Под капотом в классе ModelSerializer есть атрибут, по которому DRF решает, какой класс поля сериалайзера подобрать для конкретного поля модели. Это атрибут serializer_fields_mappingarrow-up-right. При необходимости его можно дополнить или переопределить.

По дефолту классы полей модели и сериалайзера сопоставляются так:

Вне serializer_fields_mapping описана логикаarrow-up-right сопоставления полей JSONField, а также специфичных для PostgreSQL полейarrow-up-right.

4.3. Как DRF подбирает классы для полей отношений

Для ForeignKey полей модели в модельном сериалайзере могут создаватьсяarrow-up-right поля одного из двух классов:

Для ManyToMany полей модели в модельном сериалайзере создаётся поле класса PrimaryKeyRelatedFieldarrow-up-right.

Для обратных связей (объектов RelatedManager) создаётся поле класса PrimaryKeyRelatedFieldarrow-up-right.

Важный момент: названия автоматически создаваемых полей серилайзера для обратных связей нужно явно указывать в атрибуте fields класса Meta. Строка __all__ не включаетarrow-up-right в себя названия объектов RelatedManager модели.

Примеры.

4.4. Как DRF работает с методами модели и проперти

В этом случае ничего особенного не происходит: DRF создаётarrow-up-right ReadOnlyField, которое, как видно по названию, участвует только при работе сериалайзера на чтение, и без какой-либо дополнительной валидации возвращает значение из метода или проперти модели.

5. extra_kwargs: тонкая настройка автоматически создаваемых полей

Атрибут extra_kwargs определяют во внутреннем классе Meta. Это словарь, ключами которого выступают поля из fields (или поля модели, не перечисленные в exclude). Значением для каждого ключа служит словарь с атрибутами, которыми нужно дополнить то или иное поле сериалайзера.

Допустим, мы хотим, чтобы сериалайзер для модели Town работал так:

  • на чтение — возвращал названия городов из столбца name не в виде {'name': 'название_города'}, а в виде {'town': 'название_города'}

  • на запись — получал данные для столбца name в виде {'name': 'название_города'}.

По сути, нам нужно, чтобы одно и то же поле модели обслуживали поля сериалайзера с разными названиями (в зависимости от того, в какую сторону работает сериалайзер).

Вооружившись знаниями из предыдущих статей о том, как работает сериалайзер на чтение и на запись (об аргументе source, о writable- и readable-полях), можно прийти к такому решению:

TownModelSerializer(instance=Town.objects.first()).data вернет {'town': 'Вологда'} TownModelSerializer(data={'name': 'Анапа'}) после валидации вернет в validated_data словарь {'name': 'Анапа'}.

Разберем, какую донастройку модельного сериалайзера мы провели:

  • указали, что одноименное с полем модели поле name работает только на запись (в данных, получаемых из базы, ключа 'name' не будет);

  • добавили сериалайзеру ещё одно поле под названием 'town' и установили, что оно:

    • работает только на чтение, т.е. только при получении словаря с данными о записи в модели Town, там будет ключ 'town'

    • источником (source) значения для этого ключа будет атрибут (поле) name записи в модели Town

Отмечу, что read_only_fields можно задатьarrow-up-right и в качестве отдельного атрибута внутри Meta (обязательно в виде списка или кортежа).

Пример показывает, насколько гибким может быть сериалайзер, и что не следует ограничивать осмысление DRF схемой "количество полей сериалайзера == количество полей модели". С одним и тем же полем модели может работать несколько полей сериалайзера и даже несколько разных сериалайзеров.

Важный момент: некоторые атрибуты полей сочетать не имеет смысла, а иногда и вовсе может привести к появлению исключения. В классе модельных сериалайзеров за правильным сочетанием атрибутов полей следит метод include_extra_kwargsarrow-up-right.

Не нужно устанавливать:

  • атрибуты required, default, allow_blank, min_length, max_length, min_value, max_value, validators, queryset с атрибутом read_only=True. DRF обрежет эти атрибуты, оставив только read_only;

  • атрибут required с атрибутом default, в котором есть какое-либо truthy-значение. DRF удалит required, оставив default.

6. Работа с вложенными объектами

6.1. Атрибут depth

Рассмотрим следующий пример. Создадим сериалайзер для работы с записями из модели Town, с которой через внешний ключ связана модель Writer (related_namewriters).

По ключу writers мы получили список из айдишников родившихся в Вологде писателей. Если мы хотим раскрыть информацию о вложенных объектах, поможет атрибут depth класса Meta. Устанавливаем его в значении 1 и сериалайзер раскрывает содержимое объектов из списка writers.

Если установлен атрибут depth=1 включается метод build_nested_fieldarrow-up-right и поле сериалайзера, которое отвечает за поле отношения модели (или объект RelatedManager), становится объектом класса NestedSerializer. Его кодarrow-up-right очень прост:

Фактически объект из связанной модели обрабатывает сериалайзер внутри сериалайзера и выдаёт словарь со всеми полями этого объекта (fields = '__all__').

Вполне возможно, что не нужны все поля связанных объектов. Выход: явно указать, какой сериалайзер должен обслуживать прямые или обратные связи модели.

6.2. Сериалайзер в качестве поля

Для более гибкой работы с вложенными объектами мы можем передавать их сериалайзеру внутри сериалайзера самостоятельно, а не через объявление атрибута depth в классе Meta.

Для этого нужно сделать следующее:

  • поле, которое будет работать с вложенными объектами, нужно объявить явно в declared fields, иными словами, как самостоятельный атрибут сериалайзера.

Развивая пример из предыдушего раздела, нам нужно в декларируемые поля вытащить поле writers (не забываем, что его нужно всё равно упомянуть в fields в Meta).

  • далее нужно создать (или взять имеющийся) сериалайзер, который будет обрабатывать вложенные объекты.

В нашем примере поле writers будет иметь дело с объектами из модели Writer. Создадим для них сериалайзер, который будет отдавать только имя, фамилию, отчество автора и дату его рождения (иными словами, мы исключим поля id и birth_place).

Сериалайзер будет выглядеть так:

  • остается передать созданный сериалайзер в качестве поля в TownSerializer

Заметьте: мы используем many=True, потому что writers — это всегда список айдишников авторов (связь один-ко-многим).

Теперь, когда TownSerializer в числе прочих данных получит из записи в БД список writers, он передаст его вложенному сериалайзеру WriterSerializer, который, в свою очередь, обработает каждый объект в списке и вернет список словарей с интересующей нас информацией об авторах, родившихся в конкретном городе.

7. Особенности валидации в ModelSerializer

1. Метавалидаторы unique_together, unique_for_date, unique_for_month, unique_for_year

При наличии таких валидаторов в модели, DRF автоматически перенесет их в сериалайзер. За это отвечает метод get_validatorsarrow-up-right. Но здесь есть два подводных камня:

  1. Никакого автоматического переноса не будет, если во внутреннем классе Meta задан атрибут validators. Иными словами, нельзя полагаться на то, что можно указать в validators, к примеру, кастомный метавалидатор, а к нему автоматом подтянутся рассматриваемые метавалидаторы из модели. Нет. Если решили использовать атрибут validators, значит, нужно указывать в нем все метавалидаторы, включая те, что уже есть в модели.

  2. unique_together в настоящее время не рекомендованarrow-up-right для использования в джанго-моделях. Вместо него документация советует использовать опцию constraints и класс UniqueConstraintarrow-up-right. Если вы последовали рекомендации, то соответствующий валидатор нужно перенести в сериалайзер вручную, DRF автоматически этого не сделает.

2. Валидаторы на уровне поля

Валидаторы, как из параметра validators, так и из специальных параметров, за которыми стоят различные виды валидаторов (например, аргументы unique, max_value, min_value и т.д.), автоматически переносятся в поля сериалайзера. За это отвечает метод get_field_kwargs из restframework.utils.field_mapping.

Важный момент: сказанное относится только к автоматически создаваемым полям. Для декларируемых полей все валидаторы нужно указывать вручную.

Last updated