Для чего нужны стерилизаторы django
Django Rest Framework для начинающих: создаём API для чтения данных (часть 2)
В прошлой части мы в общих чертах рассмотрели, как устроен REST API на DRF при работе на чтение. Едва ли не самый сложный для понимания этап — сериализация. Вооружившись исходным кодом, полностью разберем этот этап — от приема набора записей из модели до их преобразования в список словарей.
Важный момент: мы говорим о работе сериалайзера только на чтение, то есть когда он отдаёт пользователю информацию из базы данных (БД) сайта. О работе на запись, когда данные поступают извне и их надо сохранить в БД, расскажем в следующей статье.
Код учебного проекта, который используется в этой статье, доступен в репозитории на Гитхабе.
Как создаётся сериалайзер, работающий на чтение
Создание экземпляра сериалайзера мы описывали следующим образом:
Подробнее о методе many_init :
Экземпляр сериалайзера | Описание | К какому классу относится |
---|---|---|
serializer_for_queryset | Обрабатывает набор табличных записей | ListSerializer — класс из модуля restframework.serializers |
serializer_for_queryset.child | Обрабатывает каждую отдельную запись в наборе | CapitalSerializer — наш собственный класс, наследует от класса Serializer модуля restframework.serializers |
Помимо many=True мы передали значение для атрибута instance (инстанс). В нём — набор записей из модели.
Важное замечание: чтобы не запутаться и понимать, когда речь идёт о сериалайзере в целом, а когда — о дочернем сериалайзере, далее по тексту мы будем говорить «основной сериалайзер» (в коде контроллера это serializer_for_queryset ) и «дочерний сериалайзер» (атрибут child основного сериалайзера).
После создания основного сериалайзера мы обращаемся к его атрибуту data :
Запускается целый набор операций, каждую из которых подробно рассмотрим далее.
Что под капотом атрибута data основного сериалайзера
Как работает метод to_represantation основного сериалайзера
Сделаем небольшую остановку:
Как работает метод to_representation дочернего сериалайзера
Как запись из модели обрабатывается методами полей сериалайзера
Метод get_attribute работает с инстансом (instance). Важно не путать этот инстанс с инстансом основного сериалайзера. Инстанс основного сериалайзера — это набор записей из модели. Инстанс дочернего сериалайзера — каждая конкретная запись.
Вспомним строку из кода to_representation основного сериалайзера:
Этот item (отдельная запись из набора) и есть инстанс, с которым работает метод get_attribute конкретного поля.
У нас есть такие поля:
Получается следующая картина:
Поле сериалайзера | Значение атрибута source поля | Значение source_attrs |
---|---|---|
capital_city | ‘capital_city’ | [‘capital_city’] |
capital_population | ‘capital_population’ | [‘capital_population’] |
author | ‘author.username’ | [‘author’, ‘username’] |
Как мы уже указывали, список source_attrs в качестве аргумента attrs передаётся в метод get_attribute rest_framework.fields :
С author.username ситуация интереснее. До значения атрибута username DRF будет добираться так:
Суммируем всё, что узнали
Преобразованный набор записей из Django-модели доступен в атрибуте data основного сериалайзера. При обращении к этому атрибуту задействуются следующие методы и атрибуты из-под капота DRF (разумеется, эти методы можно переопределить):
В словарь заносится пара «ключ-значение»:
Итог: список из OrderedDict в количестве, равном числу переданных и сериализованных записей из модели.
Надеюсь, статья оказалась полезной и позволила дать картину того, как под капотом DRF происходит сериализация данных из БД. Если у вас остались вопросы, задавайте их в комментариях — разберёмся вместе.
Как использовать сериализаторы в веб-платформе Django Python
Предположим, вы создаете программное обеспечение для сайта электронной коммерции и у вас есть Заказ, в котором регистрируется покупка одного продукта кем-то по указанной цене, на определенную дату:
Теперь представьте, что вы хотите сохранить и получить данные о заказе из базы данных «ключ-значение». К счастью, его интерфейс принимает и возвращает словари, поэтому вам нужно преобразовать свой объект в словарь:
И если вы хотите получить некоторые данные из этой базы данных, вы можете получить данные dict и превратить их в свой объект Order:
Это довольно просто сделать с простыми данными, но когда вам нужно иметь дело со сложными объектами, состоящими из сложных атрибутов, этот подход плохо масштабируется. Вам также необходимо выполнить проверку различных типов полей, а это придется делать вручную.
Вот где пригодятся сериализаторы фреймворков. Они позволяют создавать сериализаторы с небольшими шаблонами, которые будут работать в ваших сложных случаях.
Django поставляется с модулем сериализации, который позволяет «переводить» модели в другие форматы:
Он охватывает наиболее часто используемые случаи для веб-приложений, таких как JSON, YAML и XML. Но вы также можете использовать сторонние сериализаторы или создать свои собственные. Вам просто нужно зарегистрировать его в своем файле settings.py:
Теперь вы можете сериализовать свой запрос в новый формат:
Сериализаторы DRF
В сообществе Django фреймворк Django REST (DRF) предлагает самые известные сериализаторы. Хотя вы можете использовать сериализаторы Django для создания JSON, на который вы будете отвечать в своем API, один из фреймворка REST имеет приятные функции, которые помогут вам справиться и легко проверить сложные данные.
В примере заказа вы можете создать сериализатор следующим образом:
И легко сериализуйте его данные:
После этого вы можете создавать или обновлять экземпляры, вызывая is_valid() для проверки данных и save() для создания или обновления экземпляра:
Сериализаторы моделей
При сериализации данных вам часто нужно делать это из базы данных, следовательно, из ваших моделей. ModelSerializer, как и ModelForm, предоставляет API для создания сериализаторов из ваших моделей. Предположим, у вас есть модель заказа:
Вы можете создать для него сериализатор следующим образом:
Использование сериализаторов в представлениях на основе классов (CBV)
Как и формы с CBV от Django, сериализаторы хорошо интегрируются с DRF. Вы можете установить атрибут serializer_class так, чтобы сериализатор был доступен для представления:
Вы также можете определить метод get_serializer_class() :
В CBV есть и другие методы, которые взаимодействуют с сериализаторами. Например, get_serializer() возвращает уже созданный сериализатор, а get_serializer_context() возвращает аргументы, которые вы передадите сериализатору при создании его экземпляра.
Tutorial 1: Serialization
Introduction
This tutorial will cover creating a simple pastebin code highlighting Web API. Along the way it will introduce the various components that make up REST framework, and give you a comprehensive understanding of how everything fits together.
The tutorial is fairly in-depth, so you should probably get a cookie and a cup of your favorite brew before getting started. If you just want a quick overview, you should head over to the quickstart documentation instead.
Note: The code for this tutorial is available in the encode/rest-framework-tutorial repository on GitHub. The completed implementation is also online as a sandbox version for testing, available here.
Setting up a new environment
Before we do anything else we’ll create a new virtual environment, using venv. This will make sure our package configuration is kept nicely isolated from any other projects we’re working on.
Now that we’re inside a virtual environment, we can install our package requirements.
Getting started
Okay, we’re ready to get coding. To get started, let’s create a new project to work with.
Once that’s done we can create an app that we’ll use to create a simple Web API.
Okay, we’re ready to roll.
Creating a model to work with
For the purposes of this tutorial we’re going to start by creating a simple Snippet model that is used to store code snippets. Go ahead and edit the snippets/models.py file. Note: Good programming practices include comments. Although you will find them in our repository version of this tutorial code, we have omitted them here to focus on the code itself.
We’ll also need to create an initial migration for our snippet model, and sync the database for the first time.
Creating a Serializer class
The first part of the serializer class defines the fields that get serialized/deserialized. The create() and update() methods define how fully fledged instances are created or modified when calling serializer.save()
The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. The <'base_template': 'textarea.html'>flag above is equivalent to using widget=widgets.Textarea on a Django Form class. This is particularly useful for controlling how the browsable API should be displayed, as we’ll see later in the tutorial.
We can actually also save ourselves some time by using the ModelSerializer class, as we’ll see later, but for now we’ll keep our serializer definition explicit.
Working with Serializers
Before we go any further we’ll familiarize ourselves with using our new Serializer class. Let’s drop into the Django shell.
Okay, once we’ve got a few imports out of the way, let’s create a couple of code snippets to work with.
We’ve now got a few snippet instances to play with. Let’s take a look at serializing one of those instances.
Deserialization is similar. First we parse a stream into Python native datatypes.
. then we restore those native datatypes into a fully populated object instance.
Notice how similar the API is to working with forms. The similarity should become even more apparent when we start writing views that use our serializer.
We can also serialize querysets instead of model instances. To do so we simply add a many=True flag to the serializer arguments.
Using ModelSerializers
Our SnippetSerializer class is replicating a lot of information that’s also contained in the Snippet model. It would be nice if we could keep our code a bit more concise.
In the same way that Django provides both Form classes and ModelForm classes, REST framework includes both Serializer classes, and ModelSerializer classes.
Let’s look at refactoring our serializer using the ModelSerializer class. Open the file snippets/serializers.py again, and replace the SnippetSerializer class with the following.
It’s important to remember that ModelSerializer classes don’t do anything particularly magical, they are simply a shortcut for creating serializer classes:
Writing regular Django views using our Serializer
Let’s see how we can write some API views using our new Serializer class. For the moment we won’t use any of REST framework’s other features, we’ll just write the views as regular Django views.
Edit the snippets/views.py file, and add the following.
The root of our API is going to be a view that supports listing all the existing snippets, or creating a new snippet.
We’ll also need a view which corresponds to an individual snippet, and can be used to retrieve, update or delete the snippet.
Finally we need to wire these views up. Create the snippets/urls.py file:
We also need to wire up the root urlconf, in the tutorial/urls.py file, to include our snippet app’s URLs.
Testing our first attempt at a Web API
Now we can start up a sample server that serves our snippets.
Quit out of the shell.
. and start up Django’s development server.
In another terminal window, we can test the server.
We can test our API using curl or httpie. Httpie is a user friendly http client that’s written in Python. Let’s install that.
You can install httpie using pip:
Finally, we can get a list of all of the snippets:
Or we can get a particular snippet by referencing its id:
Similarly, you can have the same json displayed by visiting these URLs in a web browser.
Where are we now
We’re doing okay so far, we’ve got a serialization API that feels pretty similar to Django’s Forms API, and some regular Django views.
Our API views don’t do anything particularly special at the moment, beyond serving json responses, and there are some error handling edge cases we’d still like to clean up, but it’s a functioning Web API.
We’ll see how we can start to improve things in part 2 of the tutorial.
Django Rest Framework для начинающих: как работает ModelSerializer
Класс модельных сериалайзеров отличается лишь тем, что у него есть несколько инструментов, позволяющих сократить код сериалайзера:
ModelSerializer: необходимый минимум
Для определения модельного сериалайзера нужен внутренний класс Meta со следующими атрибутами:
Автоматически создаваемые и декларируемые поля
Поле name — декларируемое, класс поля и его атрибуты мы задали самостоятельно. Поля id и writers — автоматически создаваемые.
Что означает fields = ‘__all__’
Как правильно использовать fields и exclude
На основе исходного кода DRF можно сформулировать несколько правил.
Примеры модельного сериалайзера для джанго-модели Writer :
Сериалайзер, который будет обслуживать все поля модели:
Сериалайзер, который будет обслуживать только поля firstname и lastname :
Сериалайзер, который будет обслуживать все поля, кроме firstname и lastname :
Как DRF создаёт поля для модельного сериалайзера
Для подбора корреспондирующих полей DRF использует три метода:
Рассмотрим на примерах:
Из модели Town DRF возьмёт:
Из модели Writer DRF возьмёт:
По дефолту классы полей модели и сериалайзера сопоставляются так:
№ | Класс поля сериалайзера | Класс поля модели |
---|---|---|
1 | BooleanField | BooleanField, NullBooleanField |
2 | CharField | CharField, TextField |
3 | DateField | DateField |
4 | DateTimeField | DateTimeField |
5 | DecimalField | DecimalField |
6 | DurationField | DurationField |
7 | EmailField | EmailField |
8 | FileField | FileField |
9 | FilePathField | FilePathField |
10 | FloatField | FloatField |
11 | ImageField | ImageField |
12 | IPAddressField | GenericIPAddressField |
13 | IntegerField | AutoField, BigIntegerField, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, SmallIntegerField |
14 | SlugField | SlugField |
15 | TimeField | TimeField |
16 | URLField | URLField |
17 | UUIDField | UUIDField |
Подбор классов для полей отношений. Для ForeignKey-полей модели в модельном сериалайзере могут создаваться поля одного из двух классов:
Работа с вложенными объектами
Атрибут depth
Для примера возьмем сериалайзер из предыдущего раздела и используем его для чтения первой записи из модели:
Сериалайзер в качестве поля
Шаг второй. Нужно создать или взять имеющийся сериалайзер, который будет обрабатывать вложенные объекты.
extra_kwargs : тонкая настройка автоматически создаваемых полей
Допустим, мы хотим, чтобы сериалайзер для модели Town работал так:
По сути, нам нужно, чтобы одно и то же поле модели обслуживали поля сериалайзера с разными названиями — в зависимости от того, в какую сторону работает сериалайзер.
Вооружившись знаниями из предыдущих статей о том, как работает сериалайзер на чтение и на запись, можно прийти к такому решению:
TownModelSerializer(data=<'name': 'Анапа'>) после валидации вернёт в validated_data словарь <'name': 'Анапа'>.
Итак, какую донастройку модельного сериалайзера мы провели:
Пример показывает, насколько гибким может быть сериалайзер и что не следует ограничивать осмысление DRF схемой «количество полей сериалайзера = количество полей модели». С одним и тем же полем модели может работать несколько полей сериалайзера и даже несколько разных сериалайзеров.
Особенности валидации в ModelSerializer
Важно: сказанное относится только к автоматически создаваемым полям. Для декларируемых полей все валидаторы нужно указывать вручную.
На этом я завершаю цикл статей о том, как устроены сериалайзеры в Django REST Framework. Надеюсь, что этот материал позволит заложить крепкий фундамент в понимании одной из важнейших частей фреймворка и поможет создавать отлично работающие API в ваших проектах.
Все статьи серии:
Django Rest Framework для начинающих: создаём API для чтения данных: часть 1 и часть 2
Django Rest Framework для начинающих: создаём API для записи и обновления данных: часть 1 и часть 2
Сериализация объектов Django ¶
Фреймворк сериализации Django предоставляет механизм для «перевода» моделей Django в другие форматы. Обычно эти другие форматы основаны на тексте и используются для отправки данных Django по сети, но сериализатор может обрабатывать любой формат (текстовый или нет).
Если вы просто хотите получить данные из ваших таблиц в сериализованной форме, вы можете использовать команду dumpdata управления.
Сериализация данных ¶
На самом высоком уровне вы можете сериализовать данные следующим образом:
Аргументами serialize функции являются формат для сериализации данных (см. Форматы сериализации ) и формат QuerySet для сериализации. (Фактически, вторым аргументом может быть любой итератор, который возвращает экземпляры модели Django, но почти всегда это будет QuerySet).
django.core.serializers. get_serializer ( формат ) ¶
Вы также можете напрямую использовать объект сериализатора:
Это полезно, если вы хотите сериализовать данные непосредственно в файловый объект (который включает HttpResponse ):
Вызов get_serializer() с неизвестным форматом вызовет django.core.serializers.SerializerDoesNotExist исключение.
Подмножество полей ¶
Если вы хотите сериализовать только подмножество полей, вы можете указать fields аргумент сериализатору:
В этом примере будут сериализованы только атрибуты name и size каждой модели. Первичный ключ всегда сериализуется как pk элемент результирующего вывода; он никогда не появляется в fields детали.
В зависимости от вашей модели вы можете обнаружить, что невозможно десериализовать модель, которая сериализует только подмножество своих полей. Если в сериализованном объекте не указаны все поля, которые требуются модели, десериализатор не сможет сохранить десериализованные экземпляры.
Унаследованные модели ¶
Если вы сериализуете только модель ресторана:
поля сериализованного вывода будут содержать только serves_hot_dogs атрибут. name Атрибут базового класса будет игнорироваться.
Чтобы полностью сериализовать ваши Restaurant экземпляры, вам также необходимо сериализовать Place модели:
Десериализация данных ¶
Десериализация данных очень похожа на их сериализацию:
Однако здесь все немного усложняется. Объекты, возвращаемые deserialize итератором , не являются обычными объектами Django. Вместо этого они являются специальными DeserializedObject экземплярами, которые обертывают созданный, но несохраненный объект и любые связанные данные о взаимосвязи.
Вызов DeserializedObject.save() сохраняет объект в базе данных.
Если pk атрибут в сериализованных данных не существует или имеет значение null, новый экземпляр будет сохранен в базе данных.
Это гарантирует, что десериализация будет неразрушающей операцией, даже если данные в вашем сериализованном представлении не соответствуют тому, что в настоящее время находится в базе данных. Обычно работа с этими DeserializedObject экземплярами выглядит примерно так:
Форматы сериализации ¶
Django поддерживает ряд форматов сериализации, некоторые из которых требуют установки сторонних модулей Python:
Базовый формат сериализации XML выглядит так:
В этом примере мы указываем, что auth.Permission объект с PK 27 имеет внешний ключ для contenttypes.ContentType экземпляра с PK 9.
Отношения ManyToMany экспортируются для модели, которая их связывает. Например, auth.User модель имеет такое отношение к auth.Permission модели:
Этот пример связывает данного пользователя с моделями разрешений с PK 46 и 47.
Если оставить тот же пример данных, что и раньше, он будет сериализован как JSON следующим образом:
Внешние ключи имеют PK связанного объекта как значение свойства. Отношения ManyToMany сериализуются для модели, которая их определяет, и представлены в виде списка PK.
Затем вы можете перейти cls=LazyEncoder к serializers.serialize() функции:
Все данные теперь выгружаются с помощью Unicode. Если вам нужно предыдущее поведение, перейдите ensure_ascii=True к serializers.serialize() функции.
DjangoJSONEncoder ¶
JSONL ¶
JSONL может быть полезен для заполнения больших баз данных, поскольку данные могут обрабатываться построчно, а не загружаться в память сразу.
Ссылочные поля снова представлены PK или последовательностью PK.
Все данные теперь выгружаются с помощью Unicode. Если вам нужно предыдущее поведение, перейдите allow_unicode=False к serializers.serialize() функции.
Натуральные ключи ¶
Стратегия сериализации по умолчанию для внешних ключей и отношений «многие ко многим» заключается в сериализации значения первичного ключа (ов) объектов в отношении. Эта стратегия хорошо работает для большинства объектов, но при некоторых обстоятельствах может вызвать затруднения.
Десериализация естественных ключей ¶
Рассмотрим следующие две модели:
Обычно в сериализованных данных для 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() метод, и если да, то использовать его для заполнения первичного ключа десериализованного объекта.
Сериализация естественных ключей ¶
Если use_natural_foreign_keys=True указано, Django будет использовать этот natural_key() метод для сериализации любой ссылки внешнего ключа на объекты того типа, который определяет метод.
Если use_natural_primary_keys=True указано, Django не будет предоставлять первичный ключ в сериализованных данных этого объекта, поскольку он может быть вычислен во время десериализации:
Это может быть полезно, когда вам нужно загрузить сериализованные данные в существующую базу данных, и вы не можете гарантировать, что сериализованное значение первичного ключа еще не используется, и нет необходимости гарантировать, что десериализованные объекты сохраняют те же первичные ключи.
И наоборот, если (по какой-то странной причине) вы хотите, чтобы Django выводил естественные ключи во время сериализации, но не имел возможности загружать эти ключевые значения, просто не определяйте get_by_natural_key() метод.
Естественные ключи и прямые ссылки ¶
Иногда, когда вы используете естественные внешние ключи, вам нужно десериализовать данные, если у объекта есть внешний ключ, ссылающийся на другой объект, который еще не был десериализован. Это называется «прямой ссылкой».
Например, предположим, что в вашем приспособлении есть следующие объекты:
Типичное использование выглядит так:
Зависимости при сериализации ¶
Часто можно избежать явной обработки прямых ссылок, позаботившись о порядке объектов в фикстуре.
Однако этого не всегда бывает достаточно. Если ваш естественный ключ относится к другому объекту (с помощью внешнего ключа или естественного ключа к другому объекту как части естественного ключа), тогда вам необходимо иметь возможность гарантировать, что объекты, от которых зависит естественный ключ, встречаются в сериализованных данных. до того, как они потребуются естественному ключу.
Чтобы управлять этим порядком, вы можете определить зависимости от ваших natural_key() методов. Вы делаете это, устанавливая dependencies атрибут в самом natural_key() методе.
Например, давайте добавим естественный ключ к Book модели из приведенного выше примера:
Это определение гарантирует, что все Person объекты будут сериализованы перед любыми Book объектами. В свою очередь, любой объект реферирование Book будет сериализовать после того, как Person и Book было сериализовать.