Spec-Zone.ru › Django 4.0

Сериализация объектов 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 связанного объекта в качестве значения свойства. Связи 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

Введено в Django 3.2.

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

Ссылка на поля снова представлена первичным ключом или последовательностью первичных ключей.

Естественные ключи

По умолчанию для сериализации внешних ключей и связей «многие ко многим» используется сериализация значения первичных ключей объектов в связи. Этот подход хорошо работает для большинства объектов, но в некоторых случаях может вызвать затруднения.

Рассмотрим случай списка объектов, у которых внешний ключ ссылается на 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/4.0/topics/serialization/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API