Сериализация объектов 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() с неизвестным форматом вызовет исключение django.core.serializers.SerializerDoesNotExist.
Подмножество полей
Если вам нужно сериализовать только подмножество полей, вы можете указать аргумент fields для сериализатора:
from django.core import serializers
data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size'))
В этом примере будут сериализованы только атрибуты name и size каждой модели. Первичный ключ всегда сериализуется как элемент pk в результирующем выводе; он никогда не отображается в части fields.
Примечание
В зависимости от вашей модели, вы можете обнаружить, что невозможно десериализовать модель, которая сериализует только подмножество полей. Если сериализованный объект не указывает все поля, необходимые модели, десериализатор не сможет сохранить экземпляры, полученные после десериализации.
Наследованные модели
Если у вас есть модель, определённая с помощью абстрактного базового класса, вам ничего не нужно делать, чтобы сериализовать эту модель. Вызовите сериализатор для объекта (или объектов), которые вы хотите сериализовать, и вывод будет полным представлением сериализованного объекта.
Однако, если у вас есть модель, использующая наследование с несколькими таблицами, вам также нужно сериализовать все базовые классы модели. Это связано с тем, что сериализуются только поля, локально определённые в модели. Например, рассмотрим следующие модели:
class Place(models.Model):
name = models.CharField(max_length=50)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
Если вы сериализуете только модель Restaurant:
data = serializers.serialize('xml', Restaurant.objects.all())
поля в сериализованном выводе будут содержать только атрибут serves_hot_dogs. Атрибут name базового класса будет проигнорирован.
Для полной сериализации ваших Restaurant экземпляров, вам необходимо сериализовать также модели Place:
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 | Сериализует в и из простого XML-диалекта. |
json | Сериализует в и из JSON. |
jsonl | Сериализует в и из JSONL. |
yaml | Сериализует в YAML (YAML Ain’t a Markup Language). Этот сериализатор доступен только если установлен PyYAML. |
XML
Основной формат сериализации XML выглядит следующим образом:
<?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», последний из которых представлен именем приложения («sessions») и нижним регистром имени модели («session»), разделёнными точкой.
Каждое поле объекта сериализуется как элемент <field>, имеющий атрибуты «type» и «name». Текстовое содержимое элемента представляет значение, которое должно храниться.
Внешние ключи и другие реляционные поля обрабатываются немного по-другому:
<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-связи экспортируются для модели, которая их связывает. Например, модель auth.User имеет такую связь с моделью auth.Permission:
<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>
Этот пример связывает данного пользователя с моделями разрешений с PK 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», «model» и «fields». «fields» снова является объектом, содержащим имя и значение каждого поля как свойство и значение свойства соответственно.
Внешние ключи имеют PK связанного объекта в качестве значения свойства. Многократные связи сериализуются для модели, которая их определяет, и представляются как список PK.
Обратите внимание, что не весь вывод 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-сериализатор.
Все данные теперь выгружаются с помощью Unicode. Если вам нужна предыдущая функциональность, передайте ensure_ascii=True в функцию serializers.serialize().
DjangoJSONEncoder
-
class django.core.serializers.json.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() objects),UUID - Строковое представление объекта.
JSONL
JSONL означает JSON Lines. В этом формате объекты разделены символами новой строки, а каждая строка содержит допустимый JSON-объект. Сериализованные данные JSONL выглядят так:
{"pk": "4b678b301dfd8a4e0dad910de3ae245b", "model": "sessions.session", "fields": {...}}
{"pk": "88bea72c02274f3c9bf1cb2bb8cee4fc", "model": "sessions.session", "fields": {...}}
{"pk": "9cf0e26691b64147a67e2a9f06ad7a53", "model": "sessions.session", "fields": {...}}
JSONL может быть полезен для заполнения больших баз данных, так как данные могут обрабатываться построчно, а не загружаться в память целиком.
YAML
Сериализация YAML довольно похожа на JSON. Список объектов сериализуется как последовательность отображений с ключами «pk», «model» и «fields». Каждый поле, в свою очередь, является отображением, где ключ — имя поля, а значение — значение:
- fields: {expire_date: !!timestamp '2013-01-16 08:16:59.844560+00:00'}
model: sessions.session
pk: 4b678b301dfd8a4e0dad910de3ae245b
Ссылочные поля снова представлены PK или последовательностью PK.
Все данные теперь выгружаются с кодировкой Unicode. Если вам нужна предыдущая работа, передайте allow_unicode=False в функцию serializers.serialize().
Естественные ключи
По умолчанию стратегия сериализации для внешних ключей и связей многие-ко-многим — сериализовать значение первичного ключа(ей) объектов в отношении. Эта стратегия хорошо работает для большинства объектов, но в некоторых случаях может вызвать трудности.
Рассмотрим случай списка объектов, которые имеют внешний ключ, ссылающийся на 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 объекта.
Примечание
Все поля, которые вы используете для естественного ключа, должны иметь возможность однозначно идентифицировать объект. Это обычно означает, что ваша модель будет иметь условие уникальности (либо unique=True для одного поля, либо unique_together для нескольких полей) для поля или полей в вашем естественном ключе. Однако уникальность не обязательно должна применяться на уровне базы данных. Если вы уверены, что набор полей будет эффективным уникальным, вы все равно можете использовать эти поля в качестве естественного ключа.
Десериализация объектов без первичного ключа всегда проверяет, имеет ли менеджер модели метод 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)
Этот метод всегда должен возвращать кортеж естественного ключа — в этом примере (first name, last name). Затем, когда вы вызываете serializers.serialize(), вы предоставляете аргументы use_natural_foreign_keys=True или use_natural_primary_keys=True:
>>> 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(). Вы делаете это, задавая атрибут dependencies на самом методе natural_key().
Например, добавим естественный ключ к модели Book из примера выше:
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()
Естественный ключ для Book — это комбинация его имени и автора. Это означает, что Person должен быть сериализован до Book. Чтобы определить эту зависимость, добавим одну дополнительную строку:
def natural_key(self):
return (self.name,) + self.author.natural_key()
natural_key.dependencies = ['example_app.person']
Это определение гарантирует, что все объекты Person будут сериализованы до любых объектов Book. В свою очередь, любой объект, ссылающийся на Book, будет сериализован после того, как будут сериализованы как Person, так и Book.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.2/topics/serialization/