Spec-Zone.ru › Django 2.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 и из него.
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

Ссылки на поля снова представлены просто PK или последовательностью PK.

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

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

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

Естественные ключи и ссылки вперёд

Новое в Django 2.2.

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

Например, предположим, что у вас есть следующие объекты в фикстуре:

...
{
    "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/2.2/topics/serialization/

Spec-Zone.ru

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