Spec-Zone.ru › Django 5.2

Сериализация объектов 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

Ссылочные поля снова представлены 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",
}
Изменено в Django 5.2:

Для каждого из предоставленных форматов сериализации был добавлен класс 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("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.2/topics/serialization/

Spec-Zone.ru

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