Сериализация объектов Django

Механизм сериализации Django предоставляет механизм для «перевода» моделей Django в другие форматы. Обычно эти другие форматы являются текстовыми и используются для передачи данных Django по проводам, но сериализатор может работать с любым форматом (текстовым или нет).

См.также

Если вы просто хотите получить некоторые данные из ваших таблиц в сериализованном виде, вы можете использовать команду управления dumpdata.

Сериализация данных

На самом высоком уровне вы можете сериализовать данные следующим образом:

from django.core import serializers
data = serializers.serialize("xml", SomeModel.objects.all())

Аргументами функции serialize являются формат для сериализации данных (см. Serialization formats) и итератор 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() с неизвестным format вызовет исключение django.core.serializers.SerializerDoesNotExist.

Подмножество полей

Если вы хотите, чтобы только подмножество полей было сериализовано, вы можете указать аргумент fields для сериализатора:

from django.core import serializers
data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size'))

В этом примере будут сериализованы только атрибуты name и size каждой модели. Первичный ключ всегда сериализуется как элемент pk в результирующем выводе; он никогда не появляется в части fields.

Примечание

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

Унаследованные модели

Если у вас есть модель, которая определена с помощью abstract base class, вам не нужно делать ничего особенного для сериализации этой модели. Вызовите сериализатор на объекте (или объектах), который вы хотите сериализовать, и на выходе вы получите полное представление сериализованного объекта.

Однако, если у вас есть модель, использующая multi-table inheritance, вам также необходимо сериализовать все базовые классы модели. Это связано с тем, что будут сериализованы только те поля, которые локально определены в модели. Например, рассмотрим следующие модели:

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 в сериализованных данных не существует или является нулевым, в базу данных будет сохранен новый экземпляр.

Это гарантирует, что десериализация является неразрушающей операцией, даже если данные в вашем сериализованном представлении не совпадают с данными в базе данных. Обычно работа с этими экземплярами 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 не является языком разметки). Этот сериализатор доступен, только если установлен 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>-элемент, содержащий поля «тип» и «имя». Текстовое содержимое элемента представляет собой значение, которое должно быть сохранено.

Внешние ключи и другие реляционные поля обрабатываются немного по-другому:

<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 and Control Codes.

JSON

Если взять тот же пример данных, что и раньше, то они будут сериализованы как JSON следующим образом:

[
    {
        "pk": "4b678b301dfd8a4e0dad910de3ae245b",
        "model": "sessions.session",
        "fields": {
            "expire_date": "2013-01-16T08:16:59.844Z",
            ...
        }
    }
]

Форматирование здесь немного проще, чем в XML. Вся коллекция просто представлена в виде массива, а объекты представлены объектами JSON с тремя свойствами: «pk», «model» и «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 предоставляет customized GeoJSON serializer.

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() объектов), UUID
Строковое представление объекта.

JSONL

New in Django 3.2.

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.

Натуральные ключи

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

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

Естественные ключи и прямые ссылки

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

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

...
{
    "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.

Вернуться на верх