Spec-Zone.ru › Django 2.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 = list(Restaurant.objects.all()) + list(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.

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

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

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

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

Рассмотрим случай списка объектов, имеющих внешний ключ, ссылающийся на 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):
    objects = PersonManager()

    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'),)

Теперь книги могут использовать этот естественный ключ для ссылки на 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):
    objects = PersonManager()

    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)

    birthdate = models.DateField()

    def natural_key(self):
        return (self.first_name, self.last_name)

    class Meta:
        unique_together = (('first_name', '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().

Зависимости при сериализации

Поскольку естественные ключи полагаются на запросы к базе данных для разрешения ссылок, важно, чтобы данные существовали до того, как на них будет сделана ссылка. Вы не можете создать «ссылку вперёд» с естественными ключами — данные, на которые вы ссылаетесь, должны существовать до включения ссылки на них с помощью естественного ключа.

Чтобы учесть это ограничение, вызовы 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/2.1/topics/serialization/

Spec-Zone.ru

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