Сериализация объектов 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
( формат ) ¶
Вы также можете напрямую использовать объект сериализатора:
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)
Если вы сериализуете только модель ресторана:
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 и обратно . |
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>
тегом -tag, который содержит несколько
<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 и управляющих кодов .
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 .
Все данные теперь выгружаются с помощью Unicode. Если вам нужно предыдущее поведение, перейдите ensure_ascii=True
к serializers.serialize()
функции.
DjangoJSONEncoder
¶
-
класс
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.
Все данные теперь выгружаются с помощью 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 естественную обработку ключей, приспособление станет намного более человечным. Чтобы добавить обработку естественного ключа, вы определяете Manager по умолчанию для Person с get_by_natural_key()
методом. В случае с человеком хорошим естественным ключом может быть пара имени и фамилии:
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
Заметка
Какие бы поля вы ни использовали для естественного ключа, они должны иметь возможность однозначно идентифицировать объект. Обычно это будет означать, что ваша модель будет иметь предложение уникальности (уникальное = 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()
Естественный ключ для a 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
было сериализовать.