Сериализация объектов 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() с неизвестным форматом вызовет исключение django.core.serializers.SerializerDoesNotExist.
Подмножество полей
Если нужно сериализовать только часть полей, укажите аргумент fields для сериализатора:
from django.core import serializers
data = serializers.serialize("json", 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("json", Restaurant.objects.all())
в сериализованном выводе будут только атрибуты serves_hot_dogs. Атрибут name базового класса будет проигнорирован.
Чтобы полностью сериализовать экземпляры Restaurant, необходимо также сериализовать модели Place:
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 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 связанного объекта. Связи ManyToMany сериализуются для модели, которая их определяет, и представлены списком 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.
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». Каждое поле также представлено отображением, где ключом является имя поля, а значением — значение поля:
- model: sessions.session
pk: 4b678b301dfd8a4e0dad910de3ae245b
fields:
expire_date: 2013-01-16 08:16:59.844560+00:00
Поля связей также представлены PK или последовательностью PK.
Пользовательские форматы сериализации
Помимо форматов по умолчанию, можно создать собственный формат сериализации.
Например, рассмотрим сериализатор и десериализатор CSV. Сначала определите классы Serializer и Deserializer. Они могут переопределять классы существующих форматов сериализации:
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)
Этот метод всегда должен возвращать кортеж естественного ключа — в данном примере (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("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(). Для этого задайте атрибут 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/6.0/topics/serialization/