Сериализация объектов Django¶
Платформа сериализации Django предоставляет механизм «перевода» моделей Django в другие форматы. Обычно эти другие форматы являются текстовыми и используются для отправки данных Django по сети, но сериализатор может обрабатывать любой формат (текстовый или нет).
См.также
Если вы просто хотите получить некоторые данные из ваших таблиц в сериализованной форме, вы можете использовать команду управления dumpdata.
Сериализация данных¶
На самом высоком уровне вы можете сериализовать данные следующим образом:
from django.core import serializers
data = serializers.serialize("xml", SomeModel.objects.all())
Аргументами функции serialize являются формат для сериализации данных (см. ``Форматы сериализации`_) и QuerySet для сериализации. (На самом деле вторым аргументом может быть любой итератор, который возвращает экземпляры модели Django, но почти всегда это будет QuerySet).
- django.core.serializers.get_serializer(format)¶
Вы также можете использовать объект сериализатора напрямую:
XMLSerializer = serializers.get_serializer("xml")
xml_serializer = XMLSerializer()
xml_serializer.serialize(queryset)
data = xml_serializer.getvalue()
Это полезно, если вы хотите сериализовать данные непосредственно в файлоподобный объект (который включает HttpResponse):
with open("file.xml", "w") as out:
xml_serializer.serialize(SomeModel.objects.all(), stream=out)
Примечание
Вызов get_serializer() с неизвестным format вызовет исключение django.core.serializers.SerializerDoesNotExist.
Подмножество полей¶
Если вы хотите, чтобы сериализовалось только подмножество полей, вы можете указать аргумент fields для сериализатора:
from django.core import serializers
data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size'))
В этом примере будут сериализованы только атрибуты name и size каждой модели. Первичный ключ всегда сериализуется как элемент pk в результирующем выводе; он никогда не появляется в части «поля».
Примечание
В зависимости от вашей модели вы можете обнаружить, что невозможно десериализовать модель, которая сериализует только подмножество своих полей. Если в сериализованном объекте не указаны все поля, необходимые модели, десериализатор не сможет сохранить десериализованные экземпляры.
Унаследованные модели¶
Если у вас есть модель, определенная с использованием абстрактного базового класса, вам не нужно делать ничего особенного для сериализации этой модели. Вызовите сериализатор объекта (или объектов), который вы хотите сериализовать, и выходные данные будут полным представлением сериализованного объекта.
Однако если у вас есть модель, использующая многотабличное наследование, вам также необходимо сериализовать все базовые классы для модели. Это связано с тем, что сериализоваться будут только те поля, которые локально определены в модели. Например, рассмотрим следующие модели:
class Place(models.Model):
name = models.CharField(max_length=50)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
Если вы сериализуете только модель ресторана:
data = serializers.serialize('xml', Restaurant.objects.all())
поля сериализованного вывода будут содержать только атрибутserves_hot_dogs. Атрибут name базового класса будет игнорироваться.
Чтобы полностью сериализовать экземпляры «Ресторан», вам также необходимо сериализовать модели «Место»:
all_objects = [*Restaurant.objects.all(), *Place.objects.all()]
data = serializers.serialize('xml', all_objects)
Десериализация данных¶
Десериализация данных очень похожа на их сериализацию:
for obj in serializers.deserialize("xml", data):
do_something_with(obj)
Как видите, функция deserialize принимает тот же аргумент формата, что и serialize, строку или поток данных, и возвращает итератор.
Однако здесь все становится немного сложнее. Объекты, возвращаемые итератором deserialize, не являются* обычными объектами Django. Вместо этого они представляют собой специальные экземпляры DeserializedObject, которые обертывают созданный, но несохраненный объект и все связанные с ним данные взаимосвязей.
Вызов DeserializedObject.save() сохраняет объект в базе данных.
Примечание
Если атрибут pk в сериализованных данных не существует или имеет значение NULL, новый экземпляр будет сохранен в базе данных.
Это гарантирует, что десериализация является неразрушающей операцией, даже если данные в сериализованном представлении не соответствуют тем, что в данный момент находятся в базе данных. Обычно работа с экземплярами DeserializedObject выглядит примерно так:
for deserialized_object in serializers.deserialize("xml", data):
if object_should_be_saved(deserialized_object):
deserialized_object.save()
Другими словами, обычно перед этим необходимо проверить десериализованные объекты, чтобы убедиться, что они «подходят» для сохранения. Если вы доверяете своему источнику данных, вы можете сохранить объект напрямую и двигаться дальше.
Сам объект Django можно проверить как deserialized_object.object. Если поля в сериализованных данных не существуют в модели, будет выдана ошибка DeserializationError, если аргумент ignorenonexistent не будет передан как True:
serializers.deserialize("xml", data, ignorenonexistent=True)
Форматы сериализации¶
Django поддерживает ряд форматов сериализации, некоторые из которых требуют установки сторонних модулей Python:
Идентификатор |
Информация |
|---|---|
|
Сериализует простой диалект XML и обратно. |
|
Сериализуется в JSON и обратно. |
|
Сериализуется в YAML (YAML не является языком разметки). Этот сериализатор доступен только в том случае, если установлен PyYAML. |
XML¶
The basic XML serialization format looks like this:
<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0">
<object pk="123" model="sessions.session">
<field type="DateTimeField" name="expire_date">2013-01-16T08:16:59.844560+00:00</field>
<!-- ... -->
</object>
</django-objects>
Вся коллекция объектов, которые либо сериализованы, либо десериализованы, представлена тегом <django-objects>, который содержит несколько элементов <object>. Каждый такой объект имеет два атрибута: «pk» и «model», причем последний представлен именем приложения («сеансы») и строчным именем модели («сеанс»), разделенными точкой.
Каждое поле объекта сериализуется как элемент <field>, имеющий поля «тип» и «имя». Текстовое содержимое элемента представляет значение, которое должно быть сохранено.
Foreign keys and other relational fields are treated a little bit differently:
<object pk="27" model="auth.permission">
<!-- ... -->
<field to="contenttypes.contenttype" name="content_type" rel="ManyToOneRel">9</field>
<!-- ... -->
</object>
В этом примере мы указываем, что объект auth.Permission с PK 27 имеет внешний ключ для экземпляра contenttypes.ContentType с PK 9.
ManyToMany-relations are exported for the model that binds them. For instance,
the auth.User model has such a relation to the auth.Permission model:
<object pk="1" model="auth.user">
<!-- ... -->
<field to="auth.permission" name="user_permissions" rel="ManyToManyRel">
<object pk="46"></object>
<object pk="47"></object>
</field>
</object>
Этот пример связывает данного пользователя с моделями разрешений с ПК 46 и 47.
Управляющие персонажи
Если сериализуемый контент содержит управляющие символы, которые не принимаются стандартом XML 1.0, сериализация завершится ошибкой с исключением ValueError. Прочтите также объяснение W3C по HTML, XHTML, XML и управляющим кодам.
JSON¶
При использовании тех же данных примера, что и раньше, они будут сериализованы как JSON следующим образом:
[
{
"pk": "4b678b301dfd8a4e0dad910de3ae245b",
"model": "sessions.session",
"fields": {
"expire_date": "2013-01-16T08:16:59.844Z",
...
}
}
]
Форматирование здесь немного проще, чем в XML. Вся коллекция просто представлена в виде массива, а объекты представлены объектами JSON с тремя свойствами: «pk», «модель» и «поля». «Поля» снова являются объектом, содержащим имя и значение каждого поля в качестве свойства и значения свойства соответственно.
Внешние ключи имеют PK связанного объекта в качестве значения свойства. Отношения ManyToMany сериализуются для модели, которая их определяет, и представляются в виде списка ПК.
Имейте в виду, что не весь вывод Django может быть передан в неизмененном виде в json. Например, если у вас есть какой-то собственный тип в сериализуемом объекте, вам придется написать для него собственный кодировщик json. Что-то вроде этого будет работать:
from django.core.serializers.json import DjangoJSONEncoder
class LazyEncoder(DjangoJSONEncoder):
def default(self, obj):
if isinstance(obj, YourCustomType):
return str(obj)
return super().default(obj)
Затем вы можете передать cls=LazyEncoder в функциюserializers.serialize():
from django.core.serializers import serialize
serialize('json', SomeModel.objects.all(), cls=LazyEncoder)
Также обратите внимание, что GeoDjango предоставляет настраиваемый сериализатор GeoJSON.
All data is now dumped with Unicode. If you need the previous behavior,
pass ensure_ascii=True to the serializers.serialize() function.
DjangoJSONEncoder¶
- class django.core.serializers.json.DjangoJSONEncoder¶
Сериализатор JSON использует DjangoJSONEncoder для кодирования. Подкласс JSONEncoder, он обрабатывает следующие дополнительные типы:
datetimeСтрока вида
ГГГГ-ММ-ДДТЧЧ:мм:сс.ссссZилиГГГГ-ММ-ДДТЧЧ:мм:сс.сссс+ЧЧ:ММ, как определено в ``ECMA-262`_.dateСтрока формата
ГГГГ-ММ-ДД, как определено в ``ECMA-262`_.timeСтрока вида «ЧЧ:ММ:сс.сс», как определено в «ECMA-262».
timedeltaСтрока, представляющая продолжительность, определенную в ISO-8601. Например,
timedelta(дни=1, часы=2, секунды=3,4)представляется как'P1DT02H00M03.400000S'.Decimal,Promise(объектыdjango.utils.functional.lazy()),UUIDСтроковое представление объекта.
ЯМЛ¶
YAML serialization looks quite similar to JSON. The object list is serialized as a sequence mappings with the keys «pk», «model» and «fields». Each field is again a mapping with the key being name of the field and the value the value:
- fields: {expire_date: !!timestamp '2013-01-16 08:16:59.844560+00:00'}
model: sessions.session
pk: 4b678b301dfd8a4e0dad910de3ae245b
Ссылочные поля снова представлены ПК или последовательностью ПК.
All data is now dumped with Unicode. If you need the previous behavior,
pass allow_unicode=False to the serializers.serialize() function.
Естественные ключи¶
Стратегия сериализации по умолчанию для внешних ключей и отношений «многие-ко-многим» заключается в сериализации значения первичного ключа(ов) объектов в отношении. Эта стратегия хорошо работает для большинства объектов, но в некоторых случаях может вызвать трудности.
Рассмотрим случай списка объектов, внешний ключ которых ссылается на ContentType. Если вы собираетесь сериализовать объект, который ссылается на тип контента, вам для начала нужно иметь способ ссылаться на этот тип контента. Поскольку объекты ContentType автоматически создаются Django во время процесса синхронизации базы данных, первичный ключ данного типа контента предсказать непросто; это будет зависеть от того, как и когда была выполнена команда migrate. Это справедливо для всех моделей, которые автоматически генерируют объекты, в частности, включая Permission, Group и User.
Предупреждение
Никогда не следует включать автоматически сгенерированные объекты в фикстуру или другие сериализованные данные. Случайно первичные ключи в фикстуре могут совпадать с ключами в базе данных, и загрузка фикстуры не будет иметь никакого эффекта. В наиболее вероятном случае, когда они не совпадают, загрузка фикстуры завершится с ошибкой IntegrityError.
Есть еще вопрос удобства. Целочисленный идентификатор не всегда является самым удобным способом ссылки на объект; иногда может быть полезна более естественная ссылка.
Именно по этим причинам Django предоставляет естественные ключи. Естественный ключ — это кортеж значений, который можно использовать для уникальной идентификации экземпляра объекта без использования значения первичного ключа.
Десериализация естественных ключей¶
Рассмотрим следующие две модели:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
birthdate = models.DateField()
class Meta:
unique_together = [['first_name', 'last_name']]
class Book(models.Model):
name = models.CharField(max_length=100)
author = models.ForeignKey(Person, on_delete=models.CASCADE)
Обычно сериализованные данные для Book используют целое число для ссылки на автора. Например, в формате JSON книга может быть сериализована как:
...
{
"pk": 1,
"model": "store.book",
"fields": {
"name": "Mostly Harmless",
"author": 42
}
}
...
Это не совсем естественный способ обращения к автору. Для этого требуется, чтобы вы знали значение первичного ключа автора; также требуется, чтобы значение первичного ключа было стабильным и предсказуемым.
Однако если мы добавим в Person естественную обработку ключей, приспособление станет гораздо более гуманным. Чтобы добавить обработку естественных ключей, вы определяете менеджера по умолчанию для Person с помощью метода get_by_natural_key(). В случае с Person хорошим естественным ключом может быть пара имени и фамилии:
from django.db import models
class PersonManager(models.Manager):
def get_by_natural_key(self, first_name, last_name):
return self.get(first_name=first_name, last_name=last_name)
class Person(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
birthdate = models.DateField()
objects = PersonManager()
class Meta:
unique_together = [['first_name', 'last_name']]
Теперь книги могут использовать этот естественный ключ для ссылки на объекты Person:
...
{
"pk": 1,
"model": "store.book",
"fields": {
"name": "Mostly Harmless",
"author": ["Douglas", "Adams"]
}
}
...
Когда вы попытаетесь загрузить эти сериализованные данные, Django будет использовать метод get_by_natural_key() для разрешения ["Douglas", "Adams"] в первичный ключ фактического объекта Person.
Примечание
Whatever fields you use for a natural key must be able to uniquely
identify an object. This will usually mean that your model will
have a uniqueness clause (either unique=True on a single field, or
unique_together over multiple fields) for the field or fields
in your natural key. However, uniqueness doesn’t need to be
enforced at the database level. If you are certain that a set of
fields will be effectively unique, you can still use those fields
as a natural key.
Десериализация объектов без первичного ключа всегда будет проверять, есть ли у менеджера модели метод get_by_natural_key(), и если да, то использовать его для заполнения первичного ключа десериализованного объекта.
Сериализация естественных ключей¶
Так как же заставить Django выдавать естественный ключ при сериализации объекта? Во-первых, вам нужно добавить еще один метод — на этот раз к самой модели:
class Person(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
birthdate = models.DateField()
objects = PersonManager()
class Meta:
unique_together = [['first_name', 'last_name']]
def natural_key(self):
return (self.first_name, self.last_name)
That method should always return a natural key tuple – in this
example, (first name, last name). Then, when you call
serializers.serialize(), you provide use_natural_foreign_keys=True
or use_natural_primary_keys=True arguments:
>>> serializers.serialize('json', [book1, book2], indent=2,
... use_natural_foreign_keys=True, use_natural_primary_keys=True)
Когда указано use_natural_foreign_keys=True, Django будет использовать метод natural_key() для сериализации любой ссылки на внешний ключ к объектам типа, который определяет этот метод.
Если указано use_natural_primary_keys=True, Django не будет предоставлять первичный ключ в сериализованных данных этого объекта, поскольку он может быть вычислен во время десериализации:
...
{
"model": "store.person",
"fields": {
"first_name": "Douglas",
"last_name": "Adams",
"birth_date": "1952-03-11",
}
}
...
Это может быть полезно, когда вам нужно загрузить сериализованные данные в существующую базу данных, и вы не можете гарантировать, что значение сериализованного первичного ключа еще не используется, и вам не нужно гарантировать, что десериализованные объекты сохраняют одни и те же первичные ключи.
Если вы используете dumpdata для генерации сериализованных данных, используйте флаги командной строки dumpdata --natural-foreign и dumpdata --natural-primary для генерации естественных ключей.
Примечание
Вам не нужно определять natural_key() и get_by_natural_key(). Если вы не хотите, чтобы Django выдавал естественные ключи во время сериализации, но хотите сохранить возможность загружать естественные ключи, тогда вы можете отказаться от реализации метода natural_key().
И наоборот, если (по какой-то странной причине) вы хотите, чтобы Django выдавал естественные ключи во время сериализации, но не мог загружать эти значения ключей, просто не определяйте метод get_by_natural_key().
Естественные ключи и прямые ссылки¶
Иногда, когда вы используете естественные внешние ключи, вам необходимо десериализовать данные, где объект имеет внешний ключ, ссылающийся на другой объект, который еще не был десериализован. Это называется «прямой ссылкой».
Например, предположим, что в вашем приборе есть следующие объекты:
...
{
"model": "store.book",
"fields": {
"name": "Mostly Harmless",
"author": ["Douglas", "Adams"]
}
},
...
{
"model": "store.person",
"fields": {
"first_name": "Douglas",
"last_name": "Adams"
}
},
...
Чтобы справиться с этой ситуацией, вам необходимо передать handle_forward_references=True в serializers.deserialize(). Это установит атрибут deferred_fields для экземпляров DeserializedObject. Вам нужно будет отслеживать экземпляры DeserializedObject, где этот атрибут не имеет значения None, а затем вызывать для них save_deferred_fields().
Типичное использование выглядит так:
objs_with_deferred_fields = []
for obj in serializers.deserialize('xml', data, handle_forward_references=True):
obj.save()
if obj.deferred_fields is not None:
objs_with_deferred_fields.append(obj)
for obj in objs_with_deferred_fields:
obj.save_deferred_fields()
Чтобы это работало, параметр ForeignKey ссылочной модели должен иметь значение null=True.
Зависимости во время сериализации¶
Часто можно избежать явной обработки прямых ссылок, позаботившись об упорядочивании объектов внутри фикстуры.
Чтобы помочь в этом, вызовы dumpdata, использующие опцию dumpdata --natural-foreign, будут сериализовать любую модель с помощью метода natural_key() перед сериализацией стандартных объектов первичного ключа.
Однако этого не всегда может быть достаточно. Если ваш естественный ключ ссылается на другой объект (путем использования внешнего ключа или естественного ключа к другому объекту как части естественного ключа), то вам необходимо иметь возможность гарантировать, что объекты, от которых зависит естественный ключ, встречаются в сериализованных данных до того, как естественный ключ потребует их.
Чтобы контролировать этот порядок, вы можете определить зависимости от ваших методов natural_key(). Вы делаете это, устанавливая атрибут «зависимости» в самом методе «natural_key()».
Например, давайте добавим естественный ключ к модели «Книга» из примера выше:
class Book(models.Model):
name = models.CharField(max_length=100)
author = models.ForeignKey(Person, on_delete=models.CASCADE)
def natural_key(self):
return (self.name,) + self.author.natural_key()
Естественный ключ к «Книге» — это комбинация ее имени и автора. Это означает, что Person должен быть сериализован до Book. Чтобы определить эту зависимость, мы добавляем одну дополнительную строку:
def natural_key(self):
return (self.name,) + self.author.natural_key()
natural_key.dependencies = ['example_app.person']
Это определение гарантирует, что все объекты Person сериализуются раньше любых объектов Book. В свою очередь, любой объект, ссылающийся на «Книгу», будет сериализован после сериализации «Человека» и «Книги».