Spec-Zone.ru › Django 5.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

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

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

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

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

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

Spec-Zone.ru

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