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

Вы также можете напрямую использовать объект сериализатора:

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)

Если вы сериализуете только модель ресторана:

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 не является языком разметки). Этот сериализатор доступен, только если установлен 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>тегом -tag, который содержит несколько <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 и управляющих кодов .

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

класс 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

Новое в 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». Каждое поле снова является отображением с ключом, являющимся именем поля, а значением - значением:

-   fields: {expire_date: !!timestamp '2013-01-16 08:16:59.844560+00:00'}
    model: sessions.session
    pk: 4b678b301dfd8a4e0dad910de3ae245b

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

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

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

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

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

Рассмотрим случай списка объектов, имеющих ссылку на внешний ключ 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 естественную обработку ключей, приспособление станет намного более человечным. Чтобы добавить обработку естественного ключа, вы определяете Manager по умолчанию для Person с get_by_natural_key()методом. В случае с человеком хорошим естественным ключом может быть пара имени и фамилии:

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

Примечание

Какие бы поля вы ни использовали для естественного ключа, они должны иметь возможность однозначно идентифицировать объект. Обычно это означает, что ваша модель будет иметь условие уникальности (уникальное = 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=Trueuse_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-foreigndumpdata --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-foreignnatural_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()

Естественный ключ для a 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было сериализовать.

Copyright ©2021 All rights reserved