Как работает serializer в django rest framework
Фреймворк сериализации Django предоставляет механизм для «перевода» моделей Django в другие форматы. Обычно эти другие форматы основаны на тексте и используются для отправки данных Django по сети, но сериализатор может обрабатывать любой формат (текстовый или нет).
Если вы просто хотите получить данные из ваших таблиц в сериализованной форме, вы можете использовать команду dumpdata управления.
Сериализация данных ¶
На самом высоком уровне вы можете сериализовать данные следующим образом:
Аргументами serialize функции являются формат для сериализации данных (см. Форматы сериализации ) и формат QuerySet для сериализации. (Фактически, вторым аргументом может быть любой итератор, который возвращает экземпляры модели Django, но почти всегда это будет QuerySet).
django.core.serializers. get_serializer ( формат ) ¶
Вы также можете напрямую использовать объект сериализатора:
Вызов get_serializer() с неизвестным форматом вызовет django.core.serializers.SerializerDoesNotExist исключение.
Подмножество полей ¶
Если вы хотите сериализовать только подмножество полей, вы можете указать fields аргумент сериализатору:
В этом примере будут сериализованы только атрибуты name и size каждой модели. Первичный ключ всегда сериализуется как pk элемент результирующего вывода; он никогда не появляется в fields детали.
В зависимости от вашей модели вы можете обнаружить, что невозможно десериализовать модель, которая сериализует только подмножество своих полей. Если в сериализованном объекте не указаны все поля, которые требуются модели, десериализатор не сможет сохранить десериализованные экземпляры.
Унаследованные модели ¶
Если у вас есть модель, которая определена с использованием абстрактного базового класса , вам не нужно делать ничего особенного для сериализации этой модели. Вызовите сериализатор для объекта (или объектов), который вы хотите сериализовать, и на выходе будет полное представление сериализованного объекта.
Однако, если у вас есть модель, которая использует наследование из нескольких таблиц , вам также необходимо сериализовать все базовые классы для модели. Это связано с тем, что будут сериализованы только те поля, которые определены локально в модели. Например, рассмотрим следующие модели:
Если вы сериализуете только модель ресторана:
поля сериализованного вывода будут содержать только serves_hot_dogs атрибут. name Атрибут базового класса будет игнорироваться.
Чтобы полностью сериализовать ваши Restaurant экземпляры, вам также необходимо сериализовать Place модели:
Десериализация данных ¶
Десериализация данных очень похожа на их сериализацию:
Как видите, deserialize функция принимает тот же аргумент формата serialize , что и строка или поток данных, и возвращает итератор.
Однако здесь все немного усложняется. Объекты, возвращаемые deserialize итератором , не являются обычными объектами Django. Вместо этого они являются специальными DeserializedObject экземплярами, которые обертывают созданный, но несохраненный объект и любые связанные данные о взаимосвязи.
Вызов DeserializedObject.save() сохраняет объект в базе данных.
Если pk атрибут в сериализованных данных не существует или имеет значение null, новый экземпляр будет сохранен в базе данных.
Это гарантирует, что десериализация будет неразрушающей операцией, даже если данные в вашем сериализованном представлении не соответствуют тому, что в настоящее время находится в базе данных. Обычно работа с этими DeserializedObject экземплярами выглядит примерно так:
Другими словами, обычное использование - проверить десериализованные объекты, чтобы убедиться, что они «подходят» для сохранения, прежде чем делать это. Если вы доверяете своему источнику данных, вы можете вместо этого сохранить объект напрямую и двигаться дальше.
Сам объект Django можно проверить как deserialized_object.object . Если поля в сериализованных данных не существуют в модели, DeserializationError будет вызвано, если ignorenonexistent аргумент не передан как True :
Форматы сериализации ¶
Django поддерживает ряд форматов сериализации, некоторые из которых требуют установки сторонних модулей Python:
Идентификатор | Информация |
---|---|
xml | Сериализуется в простой диалект XML и обратно. |
json | Сериализуется в JSON и обратно . |
jsonl | Сериализуется в JSONL и обратно . |
yaml | Сериализуется в YAML (YAML не является языком разметки). Этот сериализатор доступен, только если установлен PyYAML . |
Базовый формат сериализации XML выглядит так:
Вся коллекция объектов, которая сериализована или десериализована, представлена <django-objects> тегом -tag, который содержит несколько <object> -элементов. Каждый такой объект имеет два атрибута: «pk» и «model», последний представлен именем приложения («sessions») и именем модели в нижнем регистре («session»), разделенных точкой.
Каждое поле объекта сериализуется как <field> -элемент, содержащий поля «тип» и «имя». Текстовое содержимое элемента представляет собой значение, которое следует сохранить.
Внешние ключи и другие реляционные поля обрабатываются немного иначе:
В этом примере мы указываем, что auth.Permission объект с PK 27 имеет внешний ключ для contenttypes.ContentType экземпляра с PK 9.
Этот пример связывает данного пользователя с моделями разрешений с PK 46 и 47.
Если сериализуемый контент содержит управляющие символы, которые не поддерживаются стандартом XML 1.0, сериализация завершится ошибкой с ValueError исключением. Прочтите также объяснение W3C HTML, XHTML, XML и управляющих кодов .
Если оставить тот же пример данных, что и раньше, он будет сериализован как JSON следующим образом:
Форматирование здесь немного проще, чем с XML. Вся коллекция просто представлена как массив, а объекты представлены объектами JSON с тремя свойствами: «pk», «model» и «fields». «Fields» - это снова объект, содержащий имя и значение каждого поля как свойство и значение свойства соответственно.
Внешние ключи имеют PK связанного объекта как значение свойства. Отношения ManyToMany сериализуются для модели, которая их определяет, и представлены в виде списка PK.
Имейте в виду, что не весь вывод Django можно передать без изменений json . Например, если у вас есть какой-то настраиваемый тип в сериализуемом объекте, вам придется написать для него собственный json кодировщик. Примерно так будет работать:
Затем вы можете перейти cls=LazyEncoder к serializers.serialize() функции:
Также обратите внимание, что GeoDjango предоставляет настраиваемый сериализатор GeoJSON .
Все данные теперь выгружаются с помощью Unicode. Если вам нужно предыдущее поведение, перейдите ensure_ascii=True к serializers.serialize() функции.
DjangoJSONEncoder ¶
Сериализатор JSON использует DjangoJSONEncoder для кодирования. Подкласс JSONEncoder , он обрабатывает следующие дополнительные типы:
datetime Строка формы YYYY-MM-DDTHH:mm:ss.sssZ или, YYYY-MM-DDTHH:mm:ss.sss+HH:MM как определено в ECMA-262 . date Строка формы, YYYY-MM-DD как определено в ECMA-262 . time Строка формы, HH:MM:ss.sss как определено в ECMA-262 . timedelta Строка, представляющая продолжительность, как определено в ISO-8601. Например, отображается как . timedelta(days=1, hours=2, seconds=3.4) 'P1DT02H00M03.400000S' Decimal , Promise ( django.utils.functional.lazy() объекты), UUID Строковое представление объекта.
JSONL ¶
JSONL расшифровывается как JSON Lines . В этом формате объекты разделяются новыми строками, и каждая строка содержит допустимый объект JSON. Сериализованные данные JSONL выглядят так:
JSONL может быть полезен для заполнения больших баз данных, поскольку данные могут обрабатываться построчно, а не загружаться в память сразу.
Сериализация YAML очень похожа на JSON. Список объектов сериализуется как сопоставления последовательности с ключами «pk», «model» и «fields». Каждое поле снова является отображением с ключом, являющимся именем поля, а значением - значением:
Ссылочные поля снова представлены PK или последовательностью PK.
Все данные теперь выгружаются с помощью Unicode. Если вам нужно предыдущее поведение, перейдите allow_unicode=False к serializers.serialize() функции.
Натуральные ключи ¶
Стратегия сериализации по умолчанию для внешних ключей и отношений «многие ко многим» заключается в сериализации значения первичного ключа (ов) объектов в отношении. Эта стратегия хорошо работает для большинства объектов, но при некоторых обстоятельствах может вызвать затруднения.
Рассмотрим случай списка объектов, имеющих ссылку на внешний ключ ContentType . Если вы собираетесь сериализовать объект, который ссылается на тип контента, вам нужно иметь способ для начала ссылаться на этот тип контента. Поскольку ContentType объекты автоматически создаются Django в процессе синхронизации базы данных, предсказать первичный ключ данного типа контента нелегко; это будет зависеть от того, как и когда migrate было выполнено. Это верно для всех моделей , которые автоматически генерируют объекты, в частности , в том числе Permission , Group и User .
Никогда не следует включать автоматически сгенерированные объекты в фикстуру или другие сериализованные данные. По случайности первичные ключи в фикстуре могут совпадать с ключами в базе данных, и загрузка фикстуры не будет иметь никакого эффекта. В более вероятном случае, если они не совпадают, загрузка прибора завершится ошибкой с расширением IntegrityError .
Есть еще вопрос удобства. Целочисленный идентификатор - не всегда самый удобный способ ссылки на объект; иногда может быть полезна более естественная ссылка.
По этим причинам Django предоставляет естественные ключи . Естественный ключ - это кортеж значений, который можно использовать для уникальной идентификации экземпляра объекта без использования значения первичного ключа.
Десериализация естественных ключей ¶
Рассмотрим следующие две модели:
Обычно в сериализованных данных для Book ссылки на автора используется целое число. Например, в JSON книга может быть сериализована как:
Это не очень естественный способ обращения к автору. Это требует, чтобы вы знали значение первичного ключа для автора; это также требует, чтобы это значение первичного ключа было стабильным и предсказуемым.
Однако, если мы добавим к Person естественную обработку ключей, приспособление станет намного более человечным. Чтобы добавить обработку естественного ключа, вы определяете Manager по умолчанию для Person с get_by_natural_key() методом. В случае с человеком хорошим естественным ключом может быть пара имени и фамилии:
Теперь книги могут использовать этот естественный ключ для ссылки на Person объекты:
Когда вы пытаетесь загрузить эти сериализованные данные, Django будет использовать этот get_by_natural_key() метод для преобразования в первичный ключ реального объекта. ["Douglas", "Adams"] Person
Какие бы поля вы ни использовали для естественного ключа, они должны иметь возможность однозначно идентифицировать объект. Обычно это означает, что ваша модель будет иметь условие уникальности (уникальное = True для одного поля или для unique_together нескольких полей) для поля или полей в вашем естественном ключе. Однако уникальность необязательно обеспечивать на уровне базы данных. Если вы уверены, что набор полей будет фактически уникальным, вы все равно можете использовать эти поля в качестве естественного ключа.
Десериализация объектов без первичного ключа всегда будет проверять, есть ли у менеджера модели get_by_natural_key() метод, и если да, то использовать его для заполнения первичного ключа десериализованного объекта.
Сериализация естественных ключей ¶
Так как же заставить Django выдавать естественный ключ при сериализации объекта? Во-первых, вам нужно добавить еще один метод - на этот раз в саму модель:
Этот метод всегда должен возвращать кортеж с естественным ключом - в этом примере . Затем, когда вы звоните , вы предоставляете аргументы или : (first name, last name) serializers.serialize() use_natural_foreign_keys=True use_natural_primary_keys=True
Если use_natural_foreign_keys=True указано, Django будет использовать этот natural_key() метод для сериализации любой ссылки внешнего ключа на объекты того типа, который определяет метод.
Если use_natural_primary_keys=True указано, Django не будет предоставлять первичный ключ в сериализованных данных этого объекта, поскольку он может быть вычислен во время десериализации:
Это может быть полезно, когда вам нужно загрузить сериализованные данные в существующую базу данных, и вы не можете гарантировать, что сериализованное значение первичного ключа еще не используется, и нет необходимости гарантировать, что десериализованные объекты сохраняют те же первичные ключи.
Если вы используете dumpdata для генерации сериализованных данных, использовать и командную строку флагов для создания природных ключей. dumpdata --natural-foreign dumpdata --natural-primary
Вам не нужно определять оба natural_key() и get_by_natural_key() . Если вы не хотите, чтобы Django выводил естественные ключи во время сериализации, но вы хотите сохранить возможность загрузки естественных ключей, вы можете отказаться от реализации этого natural_key() метода.
И наоборот, если (по какой-то странной причине) вы хотите, чтобы Django выводил естественные ключи во время сериализации, но не имел возможности загружать эти ключевые значения, просто не определяйте get_by_natural_key() метод.
Естественные ключи и прямые ссылки ¶
Иногда, когда вы используете естественные внешние ключи, вам нужно десериализовать данные, если у объекта есть внешний ключ, ссылающийся на другой объект, который еще не был десериализован. Это называется «прямой ссылкой».
Например, предположим, что в вашем приспособлении есть следующие объекты:
Типичное использование выглядит так:
Для того, чтобы это работало, объект ForeignKey на ссылающейся модели должен иметь null=True .
Зависимости при сериализации ¶
Часто можно избежать явной обработки прямых ссылок, позаботившись о порядке объектов в фикстуре.
Чтобы помочь с этим, вызовы, dumpdata которые используют этот параметр, будут сериализовать любую модель с помощью метода перед сериализацией стандартных объектов первичного ключа. dumpdata --natural-foreign natural_key()
Однако этого не всегда бывает достаточно. Если ваш естественный ключ относится к другому объекту (с помощью внешнего ключа или естественного ключа к другому объекту как части естественного ключа), тогда вам необходимо иметь возможность гарантировать, что объекты, от которых зависит естественный ключ, встречаются в сериализованных данных. до того, как они потребуются естественному ключу.
Чтобы управлять этим порядком, вы можете определить зависимости от ваших natural_key() методов. Вы делаете это, устанавливая dependencies атрибут в самом natural_key() методе.
Например, давайте добавим естественный ключ к Book модели из приведенного выше примера:
Естественный ключ для a Book - это комбинация его имени и автора. Это означает, что он Person должен быть сериализован раньше Book . Чтобы определить эту зависимость, мы добавляем одну дополнительную строку:
Это определение гарантирует, что все Person объекты будут сериализованы перед любыми Book объектами. В свою очередь, любой объект реферирование Book будет сериализовать после того, как Person и Book было сериализовать.
Сериализация - это процесс преобразования данных в формат, который может быть сохранен или передан, с последующим его восстановлением. Он постоянно используется при разработке приложений или хранении данных в базах данных, в памяти или преобразовании их в файлы.
Предположим, вы создаете программное обеспечение для сайта электронной коммерции и у вас есть Заказ, в котором регистрируется покупка одного продукта кем-то по указанной цене, на определенную дату:
Теперь представьте, что вы хотите сохранить и получить данные о заказе из базы данных «ключ-значение». К счастью, его интерфейс принимает и возвращает словари, поэтому вам нужно преобразовать свой объект в словарь:
И если вы хотите получить некоторые данные из этой базы данных, вы можете получить данные dict и превратить их в свой объект Order:
Это довольно просто сделать с простыми данными, но когда вам нужно иметь дело со сложными объектами, состоящими из сложных атрибутов, этот подход плохо масштабируется. Вам также необходимо выполнить проверку различных типов полей, а это придется делать вручную.
Вот где пригодятся сериализаторы фреймворков. Они позволяют создавать сериализаторы с небольшими шаблонами, которые будут работать в ваших сложных случаях.
Django поставляется с модулем сериализации, который позволяет «переводить» модели в другие форматы:
Он охватывает наиболее часто используемые случаи для веб-приложений, таких как JSON, YAML и XML. Но вы также можете использовать сторонние сериализаторы или создать свои собственные. Вам просто нужно зарегистрировать его в своем файле settings.py:
Чтобы создать свой собственный MyFormatSerializer , вам необходимо реализовать метод .serialize() и принять набор запросов и дополнительные параметры в качестве параметров:
Теперь вы можете сериализовать свой запрос в новый формат:
Вы можете использовать параметры options, чтобы определить поведение сериализатора. Например, если вы хотите определить, что вы собираетесь работать с вложенной сериализацией при работе с ForeignKeys , или вы просто хотите, чтобы эти данные возвращали свои первичные ключи, вы можете передать параметр flat=True в качестве опции и обработать его в своем методе:
Один из способов использования сериализации Django - это команды управления loaddata и dumpdata .
Сериализаторы DRF
В примере заказа вы можете создать сериализатор следующим образом:
И легко сериализуйте его данные:
Чтобы иметь возможность возвращать экземпляр из данных, вам необходимо реализовать два метода - create и update:
После этого вы можете создавать или обновлять экземпляры, вызывая is_valid() для проверки данных и save() для создания или обновления экземпляра:
Сериализаторы моделей
При сериализации данных вам часто нужно делать это из базы данных, следовательно, из ваших моделей. ModelSerializer, как и ModelForm, предоставляет API для создания сериализаторов из ваших моделей. Предположим, у вас есть модель заказа:
Вы можете создать для него сериализатор следующим образом:
Django автоматически включает в себя все модели поля в сериализатор и создает методы create и update .
Использование сериализаторов в представлениях на основе классов (CBV)
Как и формы с CBV от Django, сериализаторы хорошо интегрируются с DRF. Вы можете установить атрибут serializer_class так, чтобы сериализатор был доступен для представления:
Вы также можете определить метод get_serializer_class() :
В CBV есть и другие методы, которые взаимодействуют с сериализаторами. Например, get_serializer() возвращает уже созданный сериализатор, а get_serializer_context() возвращает аргументы, которые вы передадите сериализатору при создании его экземпляра.
В прошлой части мы в общих чертах рассмотрели, как устроен REST API на DRF при работе на чтение. Едва ли не самый сложный для понимания этап — сериализация. Вооружившись исходным кодом, полностью разберем этот этап — от приема набора записей из модели до их преобразования в список словарей.
Важный момент: мы говорим о работе сериалайзера только на чтение, то есть когда он отдаёт пользователю информацию из базы данных (БД) сайта. О работе на запись, когда данные поступают извне и их надо сохранить в БД, расскажем в следующей статье.
Код учебного проекта, который используется в этой статье, доступен в репозитории на Гитхабе.
Как создаётся сериалайзер, работающий на чтение
Создание экземпляра сериалайзера мы описывали следующим образом:
Благодаря many=True запускается метод many_init класса BaseSerializer .
Подробнее о методе many_init :
- При создании экземпляра сериалайзера он меняет родительский класс. Теперь родителем выступает не CapitalSerializer , а класс DRF для обработки наборов записей restframework.serializers.ListSerializer .
- Созданный экземпляр сериалайзера наделяется атрибутом child . В него включается дочерний сериалайзер — экземпляр класса CapitalSerializer.
Помимо many=True мы передали значение для атрибута instance (инстанс). В нём — набор записей из модели.
Важное замечание: чтобы не запутаться и понимать, когда речь идёт о сериалайзере в целом, а когда — о дочернем сериалайзере, далее по тексту мы будем говорить «основной сериалайзер» (в коде контроллера это serializer_for_queryset ) и «дочерний сериалайзер» (атрибут child основного сериалайзера).
После создания основного сериалайзера мы обращаемся к его атрибуту data :
Запускается целый набор операций, каждую из которых подробно рассмотрим далее.
Что под капотом атрибута data основного сериалайзера
Важное замечание: атрибут data есть и у основного, и у дочернего сериалайзеров. Поэтому, чтобы найти подходящий исходный код, нужно помнить: экземпляр основного ( serializer_for_queryset ) относится к классу ListSerializer .
Задействован атрибут data родительского BaseSerializer . Исходный код:
Поскольку никакие данные ещё не сгенерированы (нет атрибута _data ), ничего не валидируется (нет _errors ), но есть инстанс (набор записей для сериализации), запускается метод to_representation , который и обрабатывает набор записей из модели.
Как работает метод to_represantation основного сериалайзера
Возвращаемся в класс ListSerializer .
Сделаем небольшую остановку:
- Чтобы обрабатывать не одну запись из БД, а набор, при создании сериалайзера нужно указать many=True .
- В этом случае мы получим матрёшку — основной сериалайзер с дочерним внутри.
- Задача основного сериалайзера (он относится к классу ListSerializer ) — запустить цикл, в ходе которого дочерний обработает каждую запись и превратит ее в словарь.
Как работает метод to_representation дочернего сериалайзера
Дочерний сериалайзер — экземпляр класса CapitalSerializer — наследует от restframework.serializers.Serializer .
Пойдём по порядку: сначала создаётся пустой OrderedDict , далее идёт обращение к атрибуту _readable_fields .
Откуда берётся _readable_fields ? Смотрим исходный код:
То есть _readable_fields — это генератор, включающий поля дочернего сериалайзера, у которых нет атрибутa write_only со значением True . По умолчанию он False . Если объявить True , поле будет работать только на создание или обновление записи, но будет игнорироваться при её представлении.
В дочернем сериалайзере все поля могут работать на чтение (представление) — ограничений write only не установлено. Это значит, что генератор _readable_fields будет включать три поля — capital_city , capital_population , author .
Читаем код to_representation далее: генератор _readable_fields помещается в цикл, и у каждого поля вызывается метод get_attribute .
Если посмотреть код to_representation дальше, видно, что у поля вызывается и другой метод — to_representation . Это не опечатка: метод to_representation под одним и тем же названием, но с разной логикой:
- есть у основного сериалайзера в классе ListSerializer ;
- у дочернего сериалайзера в классе Serializer ;
- у каждого поля дочернего сериалайзера в классе соответствующего поля.
Итак, когда конкретная запись из модели попадает в сериалайзер, у каждого его поля включаются методы get_attribute и to_representation , чтобы наконец извлечь искомые данные.
Как запись из модели обрабатывается методами полей сериалайзера
Метод get_attribute работает с инстансом (instance). Важно не путать этот инстанс с инстансом основного сериалайзера. Инстанс основного сериалайзера — это набор записей из модели. Инстанс дочернего сериалайзера — каждая конкретная запись.
Вспомним строку из кода to_representation основного сериалайзера:
Этот item (отдельная запись из набора) и есть инстанс, с которым работает метод get_attribute конкретного поля.
Вызывается функция get_attribute , описанная на уровне всего модуля rest_framework.fields . Функция получает на вход запись из модели и значение атрибута поля source_attrs . Это список, который возникает в результате применения метода split (разделитель — точка) к строке, которая передавалась в аргументе source при создании поля. Если такой аргумент не передавали, то в качестве source будет взято имя поля.
Если вспомнить, как работает строковый метод split , станет понятно, что если при указании source не применялась точечная нотация, то список всегда будет из одного элемента.
У нас есть такие поля:
Получается следующая картина:
Как мы уже указывали, список source_attrs в качестве аргумента attrs передаётся в метод get_attribute rest_framework.fields :
Для полей capital_city и capital_population цикл for attr in attrs отработает однократно и выполнит инструкцию instance = getattr(instance, attr) . Встроенная Python-функция getattr извлекает из объекта записи (instance) значение, присвоенное конкретному атрибуту ( attr ) этого объекта.
При обработке записей из нашей таблицы рассматриваемую строку исходного кода можно представить примерно так:
- На первой итерации инстанс — это объект записи из модели Capital. Из source_attrs берётся первый элемент author , и значение одноимённого атрибута становится новым инстансом. author — объект из модели User, с которой Capital связана через внешний ключ.
- На следующей итерации из source_attrs берётся второй элемент username . Значение атрибута username будет взято уже от нового инстанса — объекта author . Так мы и получаем имя автора.
Извлечённые из объекта табличной записи данные помещаются в упорядоченный словарь ret , но перед этим с ними работает метод to_representation поля сериалайзера:
Задача метода to_representation — представить извлечённые из записи данные в определённом виде. Например, если поле сериалайзера относится к классу CharField , то извлечённые данные будут приведены к строке, а если IntegerField — к целому числу.
В нашем случае применение to_representation по сути ничего не даст. Например, из поля табличной записи capital_city будет извлечена строка. Метод to_representation поля CharField к извлечённой строке применит метод str . Очевидно, что строка останется строкой, то есть какого-то реального преобразования не произойдёт. Но если бы из поля табличной записи IntegerField извлекались целые числа и передавались полю класса CharField , то в итоге они превращались бы в строки.
При необходимости можно создать собственный класс поля сериалайзера, описать специфичную логику и для метода get_attribute , и для метода to_representation , чтобы как угодно преобразовывать поступившие на сериализацию данные. Примеры есть в документации — кастомные классы ColorField и ClassNameField .
Суммируем всё, что узнали
Преобразованный набор записей из Django-модели доступен в атрибуте data основного сериалайзера. При обращении к этому атрибуту задействуются следующие методы и атрибуты из-под капота DRF (разумеется, эти методы можно переопределить):
Метод, атрибут, функция | Класс, модуль | Действие |
---|---|---|
data | serializers.BaseSerializer | Запускает метод to_representation основного сериалайзера. |
to_representation | serializers.ListSerializer | Запускает цикл, в ходе которого к каждой записи из набора применяется метод to_representation дочернего сериалайзера. |
to_representation | serializers.Serializer | Сначала создаётся экземпляр упорядоченного словаря, пока он пустой. Далее запускается цикл по всем полям сериалайзера, у которых не выставлено write_only=True . |
get_attribute | fields (вызывается методом get_attribute класса fields.Field ) | Функция стыкует поле сериалайзера с полем записи из БД. По умолчанию идет поиск поля, чьё название совпадает с названием поля сериалайзера. Если передавался аргумент source , сопоставление будет идти со значением этого аргумента. Из найденного поля табличной записи извлекается значение — текст, числа и т.д. |
to_representation | fields.КлассПоляКонкретногоТипа | Извлечённое значение преобразуется согласно логике рассматриваемого метода. У каждого поля restframework она своя. Можно создать собственный класс поля и наделить его метод to_representation любой нужной логикой. |
В словарь заносится пара «ключ-значение»:
- ключ — название поля сериалайзера;
- значение — данные, возвращённые методом to_representation поля сериалайзера.
Итог: список из OrderedDict в количестве, равном числу переданных и сериализованных записей из модели.
Надеюсь, статья оказалась полезной и позволила дать картину того, как под капотом DRF происходит сериализация данных из БД. Если у вас остались вопросы, задавайте их в комментариях — разберёмся вместе.
Для чтения данной статьи потребуются базовые знания по Django REST фреймворку.
В этой статье мы расскажем, как можно эффективно использовать сериализаторы во время операций чтения, а также рассмотрим три мощные функции, которые помогут нам достичь желаемых результатов с меньшим количеством кода.
Мы обсудим следующие вопросы:
- несколько способов использовать аргумент source сериализатора;
- как и когда использовать метод SerializerMethodField ;
- как и когда использовать метод to_representation ;
Как использовать ключевой аргумент source
DRF-сериализатор имеет ключевой аргумент под названием source , который отличается гибкостью и может помочь избежать ряда типичных ошибок.
Давайте напишем сериализатор, способный создавать серийные представления класса User :
Теперь давайте произведем эту сериализацию:
Представим, что во фронтенде или в мобильном приложении, использующем такой API, требуется, чтобы ключ в серийном представлении был user_name вместо username .
Чтобы добиться этого, мы можем добавить в код метод CharField с ключевым аргументом source :
Давайте убедимся, что поле username заменилось на user_name в Meta.fields . Перезапустим оболочку и снова создадим экземпляр сериализатора:
На этом примере мы увидели, как аргумент source может взаимодействовать с полем User.source , а также работать с методами класса User .
В классе User есть метод под названием get_full_name . Давайте установим в нем поля first_name и last_name .
Давайте добавим в сериализатор поле под названием full_name . Свяжем аргумент source c User.get_full_name .
Теперь перезапустим оболочку и получим сериализованное представление класса User :
Обратите внимание, каким образом в поле full_name появляется желаемый результат. Под капотом DRF использует метод get_full_name для заполнения поля full_name .
Аргумент source также может без проблем взаимодействовать с отношениями, то есть с ForeignKey , OneToOneField и ManyToMany .
Представим, у нас есть модель Profile , которая имеет отношения один-к-одному ( OneToOneField ) с классом User .
Эта модель имеет следующий вид:
Допустим, мы хотим, чтобы названия улицы и города отправлялись в сериализованном представлении. Тогда мы могли бы добавить поле улицы и поле города в сериализатор и передать их в аргумент source .
Давайте перезапустим оболочку и посмотрим на результат:
Подобно тому как аргумент source работает с методами самого объекта, он также легко взаимодействует и с методами связанных объектов.
Мы хотим получить полный адрес пользователей, доступный с помощью метода user.profile.get_full_address () .
В данном случае мы можем просто передать в аргумент source метод profile.get_full_address .
Опять сериализуем класс User и получим:
Теперь давайте посмотрим, как легко работает аргумент source с отношением многие-ко-многим ( ManyToManyField ).
Мы также хотим получить связанные группы пользователей в сериализованном представлении. Давайте для начала добавим в класс User несколько групп.
Для каждой группы понадобятся поля id и name .
Нам нужно написать класс GroupSerializer , который будет сериализовать экземпляр каждой группы.
Выглядеть он будет следующим образом:
А способ, отвечающий духу DRF, состоит в том, чтобы добавить поле all_groups в сериализатор и установить его значение при помощи класса GroupSerializer .
Давайте произведем сериализацию и убедимся в том, что информация из связанной группы присутствует в сериализованных данных.
DRF достаточно умен, чтобы вызвать user.groups.all() , хотя мы просто установили аргумент source = groups . DRF заключает, что раз groups имеет тип ManyRelatedManager , то для получения всех связанных групп надо использовать .all() .
Если мы не хотим предоставлять групповую информацию по POST-запросу, мы можем добавить ключевой аргумент read_only=True в GroupSerializer .
Допустим, у нас есть модель под названием Article и она имеет отношение один-ко-многим ( ForeignKey ) к классу User . Тогда мы можем добавлять статьи (articles) пользователя (User) в сериализованное представление следующим образом:
Как и когда использовать метод SerializerMethodField
Бывают случаи, когда вам нужно запустить какой-то собственный код во время сериализации определенного поля.
Вот несколько примеров:
Рассмотрим первый сценарий. Мы хотим изменить значение поля first_name в регистр заголовка (написание с заглавной буквы) в процессе сериализации.
До сих пор мы не хотели запускать никакой собственный код для поля first_name , поэтому наличия first_name в полях Meta.fields было достаточно. Сейчас мы хотим запустить некоторый собственный код, поэтому нам нужно будет явно определить поле first_name как экземпляр класса SerializerMethodField .
Когда поле имеет тип SerializerMethodField , DRF вызывает метод get_ при вычислении значения для этого поля. Аргумент obj здесь является экземпляром класса user .
Перезапустим оболочку и произведем сериализацию:
Обратите внимание, что имя (значение поля first_name ) теперь пишется с заглавной буквы.
Если мы хотим преобразовать значение поля full_name в верхний регистр, нам нужно будет изменить сериализатор следующим образом:
Опять произведем сериализацию и увидим:
Если же мы захотим установить значение groups в None вместо пустого списка, наш сериализатор примет следующий вид:
Как и когда использовать метод to_representation
Сериализаторы в DRF имеют полезный метод под названием to_representation .
Предположим, мы хотим добавлять ключ под названием admin к сериализованным данным, только когда пользователь является суперпользователем. А для остальных пользователей этот ключ в сериализованных данных присутствовать не должен.
Тогда наш сериализатор будет иметь следующий вид:
Аргумент instance является экземпляром класса User .
Давайте проведем сериализацию для суперюзера.
А теперь отметим пользователя как не-суперюзера и сделаем тоже самое:
Обратите внимание, что ключ admin отсутствует в сериализованных данных для не-суперюзера.
Выводы
Мы рассмотрели поведение сериализатора Django при чтении. Если вы хотите разобраться, как эффективно использовать сериализаторы при операция записи, ждите продолжение.
Читайте также: