В предыдущих статьях мы подробно разобрали работу сериалайзера на основе классов BaseSerializer и Serializer, и теперь мы можем перейти к классу-наследнику ModelSerializer.   Класс модельных сериалайзеров отличается лишь тем, что у него есть несколько инструментов, позволяющих сократить код сериалайзера: Общие принципы работы модельного сериалайзера как на чтение, так и на запись идентичны тому, как работает базовый класс Serializer. Для определения модельного сериалайзера нужен внутренний класс Meta со следующими атрибутами: Автоматически создаваемые поля. Поля сериалайзера, имена которых указаны во внутреннем классе Meta явно через fields или неявно через exclude, DRF создаст самостоятельно, сопоставив с одноимёнными полями модели. Если одноимённого поля модели не найдётся, будет выброшено исключение через метод build_unknown_field. Декларируемые поля. Это обычные поля сериалайзера, которые мы описываем самостоятельно вне класса Meta, как делали это в предыдущих статьях, например, в статье о создании сериалайзера, работающего на запись. Поле name — декларируемое, класс поля и его атрибуты мы задали самостоятельно. Поля id и writers — автоматически создаваемые. Обратите внимание, что name также включено в список fields. Дело в том, что, если присутствует атрибут fields, в нём должны быть перечислены все поля сериалайзера, в том числе и декларируемые. Декларируемые поля могут понадобится, когда нам не подходит класс поля, который модельный сериалайзер подберёт автоматически. А ещё через декларируемые поля зачастую используют вспомогательные классы ReadOnlyField, HiddenField, SerializerMethodField. Если вас устраивает класс поля сериалайзера, но нужна тонкая настройка его параметров, включая создание нескольких полей сериалайзера для одного поля модели, скорее всего, будет достаточно воспользоваться атрибутом extra_kwargs внутреннего класса Meta. Если в качестве значения fields выступает строка __all__, то в сериалайзере будут созданы поля для обслуживания всех полей модели, кроме тех, за работу с которыми будут отвечать декларируемые поля сериалайзера. Использование __all__ отменяет необходимость прописывать в fields декларируемые поля. На основе исходного кода DRF можно сформулировать несколько правил. Примеры модельного сериалайзера для джанго-модели Writer: Сериалайзер, который будет обслуживать все поля модели: Сериалайзер, который будет обслуживать только поля firstname и lastname: Сериалайзер, который будет обслуживать все поля, кроме firstname и lastname: Сбор информации о модели и распределение полей по группам. Сначала DRF собирает детальную информацию о полях модели. Для этого он задействует метод get_field_info из rest_framework.utils.model_meta. Результат — именованный кортеж FieldResult, в котором есть следующие элементы: Для подбора корреспондирующих полей DRF использует три метода: Рассмотрим на примерах: Из модели Town DRF возьмёт: Из модели Writer DRF возьмёт: Подбор классов полей сериалайзера для стандартных полей модели. Стандартные поля модели — это поля, которые не относятся к полям отношений. В классе ModelSerializer есть атрибут, по которому DRF решает, какой класс поля сериалайзера подобрать для конкретного поля модели. Это атрибут serializer_fields_mapping. При необходимости его можно дополнить или переопределить. По дефолту классы полей модели и сериалайзера сопоставляются так: Вне serializer_fields_mapping описана логика сопоставления полей JSONField, а также специфичных полей для PostgreSQL. Подбор классов для полей отношений. Для ForeignKey-полей модели в модельном сериалайзере могут создаваться поля одного из двух классов: Для полей ManyToMany и обратных связей (объектов RelatedManager) создаётся поле класса PrimaryKeyRelatedField. Важно: названия автоматически создаваемых полей сериалайзера для обратных связей нужно явно указывать в атрибуте fields класса Meta. Строка __all__ не включает в себя названия объектов RelatedManager-модели. Работа с методами модели и проперти. В этом случае ничего особенного не происходит: DRF создаёт ReadOnlyField, которое участвует только при работе сериалайзера на чтение. Без какой-либо дополнительной валидации оно возвращает значение из метода или проперти модели. Для примера возьмем сериалайзер из предыдущего раздела и используем его для чтения первой записи из модели: По ключу writers мы получили список из айдишников писателей, которые родились в Вологде.
Если мы хотим раскрыть информацию о вложенных объектах, поможет атрибут depth класса Meta. Устанавливаем ему значение 1, и тогда сериалайзер раскрывает содержимое объектов из списка writers. Если установлен атрибут depth=1, включается метод build_nested_field. Тогда поле сериалайзера, которое отвечает за поле отношения модели или объект RelatedManager, становится объектом класса NestedSerializer. Его код очень прост: Фактически объект из связанной модели обрабатывает сериалайзер внутри сериалайзера и выдаёт словарь со всеми полями этого объекта — fields = '__all__'.
Вполне возможно, что все поля связанных объектов не нужны. Выход — явно указать, какой сериалайзер должен обслуживать прямые или обратные связи модели. Для более гибкой работы с вложенными объектами можно передавать их сериалайзеру внутри сериалайзера самостоятельно, а не через объявление атрибута depth в классе Meta.
Для этого нужно сделать три шага. Шаг первый. Поле, которое будет работать с вложенными объектами, нужно объявить явно в declared fields — как самостоятельный атрибут сериалайзера.
Возвращаясь к примеру из предыдущего раздела, нам нужно вытащить поле writers в декларируемые поля. Не забываем, что его нужно упомянуть в fields в Meta. Шаг второй. Нужно создать или взять имеющийся сериалайзер, который будет обрабатывать вложенные объекты. В нашем примере поле writers будет иметь дело с объектами из модели Writer. Создадим для них сериалайзер, который будет отдавать только имя, фамилию, отчество автора и дату его рождения. Иными словами, мы исключим поля id и birth_place. Шаг третий. Остаётся передать созданный сериалайзер в качестве поля в TownSerializer. Важно: мы используем many=True, потому что writers — это всегда список айдишников авторов со связью «один-ко-многим». Теперь, когда TownSerializer вместе с остальными данными получит из записи в БД список writers, он передаст его вложенному сериалайзеру WriterSerializer. Он обработает каждый объект в списке и вернёт список словарей с интересующей нас информацией об авторах, которые родились в конкретном городе. Атрибут extra_kwargs определяют во внутреннем классе Meta. Это словарь, ключами которого выступают поля из fields или любого поля модели из тех, которые не перечислены в exclude. Значением для каждого ключа служит словарь с атрибутами, которыми нужно дополнить то или иное поле сериалайзера. Допустим, мы хотим, чтобы сериалайзер для модели Town работал так: По сути, нам нужно, чтобы одно и то же поле модели обслуживали поля сериалайзера с разными названиями — в зависимости от того, в какую сторону работает сериалайзер. Вооружившись знаниями из предыдущих статей о том, как работает сериалайзер на чтение и на запись, можно прийти к такому решению: TownModelSerializer(instance=Town.objects.first()).data вернёт {'town': 'Вологда'}. TownModelSerializer(data={'name': 'Анапа'}) после валидации вернёт в validated_data словарь {'name': 'Анапа'}. Итак, какую донастройку модельного сериалайзера мы провели: Отмечу, что read_only_fields можно задать и в качестве отдельного атрибута внутри Meta, но обязательно в виде списка или кортежа. Пример показывает, насколько гибким может быть сериалайзер и что не следует ограничивать осмысление DRF схемой «количество полей сериалайзера = количество полей модели». С одним и тем же полем модели может работать несколько полей сериалайзера и даже несколько разных сериалайзеров. Важно: некоторые атрибуты полей не имеет смысла сочетать, а иногда это даже может привести к появлению исключения. В классе модельных сериалайзеров за правильным сочетанием атрибутов полей следит метод include_extra_kwargs.
Не нужно устанавливать: Метавалидаторы unique_together, unique_for_date, unique_for_month, unique_for_year. При наличии таких валидаторов в модели DRF автоматически перенесёт их в сериалайзер. За это отвечает метод get_validators. Но здесь есть два подводных камня. Валидаторы на уровне поля. Валидаторы, как из параметра validators, так и из специальных параметров, за которыми стоят различные виды валидаторов, например аргументы unique, max_value, min_value, автоматически переносятся в поля сериалайзера. За это отвечает метод get_field_kwargs из restframework.utils.field_mapping. Важно: сказанное относится только к автоматически создаваемым полям. Для декларируемых полей все валидаторы нужно указывать вручную. На этом я завершаю цикл статей о том, как устроены сериалайзеры в Django REST Framework. Надеюсь, что этот материал позволит заложить крепкий фундамент в понимании одной из важнейших частей фреймворка и поможет создавать отлично работающие API в ваших проектах. Все статьи серии:
Django Rest Framework для начинающих: создаём API для чтения данных: часть 1 и часть 2
Django Rest Framework для начинающих: создаём API для записи и обновления данных: часть 1 и часть 2