Сериализация объектов Django¶
Платформа сериализации Django предоставляет механизм «перевода» моделей Django в другие форматы. Обычно эти другие форматы являются текстовыми и используются для отправки данных Django по сети, но сериализатор может обрабатывать любой формат (текстовый или нет).
См.также
Если вы просто хотите получить некоторые данные из ваших таблиц в сериализованной форме, вы можете использовать команду управления dumpdata.
Сериализация данных¶
На самом высоком уровне вы можете сериализовать данные следующим образом:
from django.core import serializers
data = serializers.serialize("json", SomeModel.objects.all())
Аргументами функции serialize являются формат для сериализации данных (см. ``Форматы сериализации`_) и QuerySet для сериализации. (На самом деле вторым аргументом может быть любой итератор, который возвращает экземпляры модели Django, но почти всегда это будет QuerySet).
- django.core.serializers.get_serializer(format)¶
Вы также можете использовать объект сериализатора напрямую:
JSONSerializer = serializers.get_serializer("json")
json_serializer = JSONSerializer()
json_serializer.serialize(queryset)
data = json_serializer.getvalue()
Это полезно, если вы хотите сериализовать данные непосредственно в файлоподобный объект (который включает HttpResponse):
with open("file.json", "w") as out:
json_serializer.serialize(SomeModel.objects.all(), stream=out)
Примечание
Вызов get_serializer() с неизвестным format вызовет исключение django.core.serializers.SerializerDoesNotExist.
Подмножество полей¶
Если вы хотите, чтобы сериализовалось только подмножество полей, вы можете указать аргумент fields для сериализатора:
from django.core import serializers
data = serializers.serialize("json", 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("json", Restaurant.objects.all())
поля сериализованного вывода будут содержать только атрибутserves_hot_dogs. Атрибут name базового класса будет игнорироваться.
Чтобы полностью сериализовать экземпляры «Ресторан», вам также необходимо сериализовать модели «Место»:
all_objects = [*Restaurant.objects.all(), *Place.objects.all()]
data = serializers.serialize("json", all_objects)
Десериализация данных¶
Десериализация данных очень похожа на их сериализацию:
for obj in serializers.deserialize("json", data):
do_something_with(obj)
Как видите, функция deserialize принимает тот же аргумент формата, что и serialize, строку или поток данных, и возвращает итератор.
Однако здесь все становится немного сложнее. Объекты, возвращаемые итератором deserialize, не являются* обычными объектами Django. Вместо этого они представляют собой специальные экземпляры DeserializedObject, которые обертывают созданный, но несохраненный объект и все связанные с ним данные взаимосвязей.
Вызов DeserializedObject.save() сохраняет объект в базе данных.
Примечание
Если атрибут pk в сериализованных данных не существует или имеет значение NULL, новый экземпляр будет сохранен в базе данных.
Это гарантирует, что десериализация является неразрушающей операцией, даже если данные в сериализованном представлении не соответствуют тем, что в данный момент находятся в базе данных. Обычно работа с экземплярами DeserializedObject выглядит примерно так:
for deserialized_object in serializers.deserialize("json", data):
if object_should_be_saved(deserialized_object):
deserialized_object.save()
Другими словами, обычно перед этим необходимо проверить десериализованные объекты, чтобы убедиться, что они «подходят» для сохранения. Если вы доверяете своему источнику данных, вы можете сохранить объект напрямую и двигаться дальше.
Сам объект Django можно проверить как deserialized_object.object. Если поля в сериализованных данных не существуют в модели, будет выдана ошибка DeserializationError, если аргумент ignorenonexistent не будет передан как True:
serializers.deserialize("json", data, ignorenonexistent=True)
Форматы сериализации¶
Django поддерживает ряд форматов сериализации, некоторые из которых требуют установки сторонних модулей Python:
Идентификатор |
Информация |
|---|---|
|
Сериализует простой диалект XML и обратно. |
|
Сериализуется в JSON и обратно. |
|
Сериализуется в JSONL и обратно. |
|
Сериализуется в YAML (YAML не является языком разметки). Этот сериализатор доступен только в том случае, если установлен 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», причем последний представлен именем приложения («сеансы») и строчным именем модели («сеанс»), разделенными точкой.
Каждое поле объекта сериализуется как элемент <field>, имеющий поля «тип» и «имя». Текстовое содержимое элемента представляет значение, которое должно быть сохранено.
Внешние ключи и другие реляционные поля обрабатываются немного по-другому:
<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>
Этот пример связывает данного пользователя с моделями разрешений с ПК 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.
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Строковое представление объекта.
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 очень похожа на JSON. Список объектов сериализуется как сопоставление последовательностей с ключами «pk», «модель» и «поля». Каждое поле снова представляет собой сопоставление, где ключом является имя поля, а значением является значение:
- model: sessions.session
pk: 4b678b301dfd8a4e0dad910de3ae245b
fields:
expire_date: 2013-01-16 08:16:59.844560+00:00
Ссылочные поля снова представлены ПК или последовательностью ПК.
Пользовательские форматы сериализации¶
В дополнение к форматам по умолчанию вы можете создать собственный формат сериализации.
Например, давайте рассмотрим сериализатор и десериализатор CSV. Сначала определите классы «Сериализатор» и «Десериализатор». Они могут переопределять существующие классы формата сериализации:
path/to/custom_csv_serializer.py¶ import csv
from django.apps import apps
from django.core import serializers
from django.core.serializers.base import DeserializationError
class Serializer(serializers.python.Serializer):
def get_dump_object(self, obj):
dumped_object = super().get_dump_object(obj)
row = [dumped_object["model"], str(dumped_object["pk"])]
row += [str(value) for value in dumped_object["fields"].values()]
return ",".join(row), dumped_object["model"]
def end_object(self, obj):
dumped_object_str, model = self.get_dump_object(obj)
if self.first:
fields = [field.name for field in apps.get_model(model)._meta.fields]
header = ",".join(fields)
self.stream.write(f"model,{header}\n")
self.stream.write(f"{dumped_object_str}\n")
def getvalue(self):
return super(serializers.python.Serializer, self).getvalue()
class Deserializer(serializers.python.Deserializer):
def __init__(self, stream_or_string, **options):
if isinstance(stream_or_string, bytes):
stream_or_string = stream_or_string.decode()
if isinstance(stream_or_string, str):
stream_or_string = stream_or_string.splitlines()
try:
objects = csv.DictReader(stream_or_string)
except Exception as exc:
raise DeserializationError() from exc
super().__init__(objects, **options)
def _handle_object(self, obj):
try:
model_fields = apps.get_model(obj["model"])._meta.fields
obj["fields"] = {
field.name: obj[field.name]
for field in model_fields
if field.name in obj
}
yield from super()._handle_object(obj)
except (GeneratorExit, DeserializationError):
raise
except Exception as exc:
raise DeserializationError(f"Error deserializing object: {exc}") from exc
Затем добавьте модуль, содержащий определения сериализатора, в настройку SERIALIZATION_MODULES:
SERIALIZATION_MODULES = {
"csv": "path.to.custom_csv_serializer",
"json": "django.core.serializers.json",
}
Определение класса Deserializer было добавлено к каждому из предоставленных форматов сериализации.
Естественные ключи¶
Стратегия сериализации по умолчанию для внешних ключей и отношений «многие-ко-многим» заключается в сериализации значения первичного ключа(ов) объектов в отношении. Эта стратегия хорошо работает для большинства объектов, но в некоторых случаях может вызвать трудности.
Рассмотрим случай списка объектов, внешний ключ которых ссылается на 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:
constraints = [
models.UniqueConstraint(
fields=["first_name", "last_name"],
name="unique_first_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:
constraints = [
models.UniqueConstraint(
fields=["first_name", "last_name"],
name="unique_first_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» для одного поля, либо «UniqueConstraint» или «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:
constraints = [
models.UniqueConstraint(
fields=["first_name", "last_name"],
name="unique_first_last_name",
),
]
def natural_key(self):
return (self.first_name, self.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("json", 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. В свою очередь, любой объект, ссылающийся на «Книгу», будет сериализован после сериализации «Человека» и «Книги».