Сериализация объектов Django¶
Механизм сериализации Django предоставляет механизм для «перевода» моделей Django в другие форматы. Обычно эти другие форматы являются текстовыми и используются для передачи данных Django по проводам, но сериализатор может работать с любым форматом (текстовым или нет).
См.также
Если вы просто хотите получить некоторые данные из ваших таблиц в сериализованном виде, вы можете использовать команду управления dumpdata
.
Сериализация данных¶
На самом высоком уровне вы можете сериализовать данные следующим образом:
from django.core import serializers
data = serializers.serialize("xml", SomeModel.objects.all())
Аргументами функции serialize
являются формат для сериализации данных (см. Serialization formats) и итератор 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
в результирующем выводе; он никогда не появляется в части fields
.
Примечание
В зависимости от вашей модели, вы можете обнаружить, что невозможно десериализовать модель, которая сериализует только подмножество своих полей. Если в сериализованном объекте указаны не все поля, требуемые моделью, десериализатор не сможет сохранить десериализованные экземпляры.
Унаследованные модели¶
Если у вас есть модель, которая определена с помощью abstract base class, вам не нужно делать ничего особенного для сериализации этой модели. Вызовите сериализатор на объекте (или объектах), который вы хотите сериализовать, и на выходе вы получите полное представление сериализованного объекта.
Однако, если у вас есть модель, использующая multi-table inheritance, вам также необходимо сериализовать все базовые классы модели. Это связано с тем, что будут сериализованы только те поля, которые локально определены в модели. Например, рассмотрим следующие модели:
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
в сериализованных данных не существует или является нулевым, в базу данных будет сохранен новый экземпляр.
Это гарантирует, что десериализация является неразрушающей операцией, даже если данные в вашем сериализованном представлении не совпадают с данными в базе данных. Обычно работа с этими экземплярами 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 и из него. |
yaml |
Сериализует в 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», причем последний представлен именем приложения («sessions») и строчным именем модели («session»), разделенными точкой.
Каждое поле объекта сериализуется как <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>
Этот пример связывает данного пользователя с моделями разрешений с PK 46 и 47.
Управляющие символы
Если сериализуемый контент содержит управляющие символы, которые не приняты в стандарте XML 1.0, сериализация завершится неудачей с исключением ValueError
. Читайте также разъяснение W3C по поводу HTML, XHTML, XML and Control Codes.
JSON¶
Если взять тот же пример данных, что и раньше, то они будут сериализованы как JSON следующим образом:
[
{
"pk": "4b678b301dfd8a4e0dad910de3ae245b",
"model": "sessions.session",
"fields": {
"expire_date": "2013-01-16T08:16:59.844Z",
...
}
}
]
Форматирование здесь немного проще, чем в XML. Вся коллекция просто представлена в виде массива, а объекты представлены объектами JSON с тремя свойствами: «pk», «model» и «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 предоставляет customized GeoJSON serializer.
Теперь все данные сбрасываются с Юникодом. Если вам нужно прежнее поведение, передайте 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()
объектов),UUID
- Строковое представление объекта.
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.
Теперь все данные сбрасываются с Юникодом. Если вам нужно прежнее поведение, передайте 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()
.
Естественные ключи и прямые ссылки¶
Иногда при использовании natural foreign keys необходимо десериализовать данные, в которых объект имеет внешний ключ, ссылающийся на другой объект, который еще не был десериализован. Это называется «прямой ссылкой».
Например, предположим, что в вашем приспособлении есть следующие объекты:
...
{
"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
.