Spec-Zone.ru › Django 1.9

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

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

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

Добавлена поддержка 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

Ссылочные поля представлены первичным ключом или последовательностью первичных ключей.

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

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

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

Spec-Zone.ru

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