Spec-Zone.ru › Django 1.11

Сериализация объектов 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 связанного объекта в качестве значения свойства. Многие-ко-многим связи сериализуются для модели, которая их определяет, и представляются как список PK.

Обратите внимание, что не все выходы Django могут быть переданы без изменений в json. Например, если у вас есть какой-то пользовательский тип в объекте, который нужно сериализовать, вам нужно будет написать пользовательский кодировщик json для него. Что-то вроде этого будет работать:

from django.utils.encoding import force_text
from django.core.serializers.json import DjangoJSONEncoder

class LazyEncoder(DjangoJSONEncoder):
    def default(self, obj):
        if isinstance(obj, YourCustomType):
            return force_text(obj)
        return super(LazyEncoder, self).default(obj)

Затем вы можете передать cls=LazyEncoder функции serializers.serialize():

from django.core.serializers import serialize

serialize('json', SomeModel.objects.all(), cls=LazyEncoder)
Изменено в Django 1.11:

Была добавлена возможность использования пользовательского кодировщика с помощью cls=....

Также обратите внимание, что 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
Строковое представление объекта.
Изменено в Django 1.10:

Добавлена поддержка Promise.

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

Добавлена поддержка timedelta.

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):
    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/1.11/topics/serialization/

Spec-Zone.ru

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