Spec-Zone.ru › Django 3.1

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

Изменено в Django 3.1:

Все данные теперь выгружаются с помощью Unicode. Если вам нужна предыдущая работа, передайте 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() objects), 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

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

Изменено в Django 3.1:

Все данные теперь сохраняются с кодировкой Unicode. Если вам нужна предыдущая работа, передайте allow_unicode=False в функцию serializers.serialize().

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

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

Рассмотрим случай списка объектов, имеющих внешний ключ, ссылающийся на ContentType. Если вы собираетесь сериализовать объект, который ссылается на тип контента, вам необходимо иметь способ ссылки на этот тип контента. Поскольку объекты ContentType автоматически создаются Django во время процесса синхронизации базы данных, первичный ключ заданного типа контента трудно предсказать; он будет зависеть от того, как и когда migrate был выполнен. Это справедливо для всех моделей, которые автоматически генерируют объекты, в том числе Permission, Group и User.

Предупреждение

Вы никогда не должны включать автоматически сгенерированные объекты в фикстуру или другие сериализованные данные. Случайно, первичные ключи в фикстуре могут совпасть с ключами в базе данных, и загрузка фикстуры не повлияет. В более вероятном случае, если они не совпадают, загрузка фикстуры завершится ошибкой IntegrityError.

Также существует вопрос удобства. Целое число id не всегда является наиболее удобным способом ссылки на объект; иногда более естественной ссылкой было бы удобно.

По этим причинам 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/3.1/topics/serialization/

Spec-Zone.ru

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