Spec-Zone.ru › Django 1.8

Сериализация объектов 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 каждой модели.

Примечание

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

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

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

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

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.

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.

Типы, связанные с датой и временем, обрабатываются сериализатором JSON особым образом, чтобы формат был совместим с ECMA-262.

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

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

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

Также обратите внимание, что GeoDjango предоставляет настраиваемый сериализатор GeoJSON.

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.

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

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

Рассмотрим случай списка объектов, имеющих внешний ключ, ссылающийся на ContentType. Если вы собираетесь сериализовать объект, который ссылается на тип контента, вам нужно иметь возможность ссылаться на этот тип контента. Поскольку объекты ContentType автоматически создаются Django во время процесса синхронизации базы данных, первичный ключ заданного типа контента нелегко предсказать; он будет зависеть от того, как и когда была выполнена команда migrate. Это верно для всех моделей, которые автоматически генерируют объекты, в том числе Permission, Group и User.

END_OF_DOCUMENT_MARKER

Предупреждение

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

Обычно сериализованные данные для 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 для генерации сериализованных данных, используйте командные флаги --natural-foreign и --natural-primary для генерации естественных ключей.

Примечание

Вам не нужно определять как natural_key(), так и get_by_natural_key(). Если вы не хотите, чтобы Django выводил естественные ключи во время сериализации, но хотите сохранить возможность загружать естественные ключи, то вы можете не реализовывать метод natural_key().

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

Ранее был только аргумент use_natural_keys для serializers.serialize() и командные флаги -n или –natural. Они были устаревшими и заменены аргументами use_natural_foreign_keys и use_natural_primary_keys и соответствующими опциями командной строки --natural-foreign и --natural-primary для dumpdata.

Исходный аргумент и флаги командной строки остаются для обратной совместимости и отображаются на новый аргумент use_natural_foreign_keys и флаг командной строки –natural-foreign . Они будут удалены в Django 1.9.

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

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

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

    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.8/topics/serialization/

Spec-Zone.ru

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