序列化 Django 对象 — Django 文档

来自菜鸟教程
Django/docs/2.2.x/topics/serialization
跳转至:导航、​搜索

序列化 Django 对象

Django 的序列化框架提供了一种将 Django 模型“翻译”成其他格式的机制。 通常这些其他格式将基于文本并用于通过线路发送 Django 数据,但序列化程序可以处理任何格式(基于文本或非文本)。

也可以看看

如果你只是想从你的表中获取一些数据到序列化的形式,你可以使用 :djadmin:`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'))

在本例中,只会序列化每个模型的 namesize 属性。 主键始终序列化为结果输出中的 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 = [*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。 如果序列化数据中的字段在模型上不存在,除非将 ignorenonexistent 参数作为 True 传入,否则将引发 DeserializationError

serializers.deserialize("xml", data, ignorenonexistent=True)

序列化格式

Django 支持多种序列化格式,其中一些需要您安装第三方 Python 模块:

标识符 信息
xml 与简单的 XML 方言之间进行序列化。
json JSON 之间的序列化。
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>

序列化或反序列化的整个对象集合由包含多个 <object> 元素的 <django-objects> 标签表示。 每个这样的对象都有两个属性:“pk”和“model”,后者由应用程序的名称(“sessions”)和模型的小写名称(“session”)表示,中间用点分隔。

对象的每个字段都被序列化为一个 <field> 元素,其中包含字段“类型”和“名称”。 元素的文本内容表示应该存储的值。

外键和其他关系字段的处理方式略有不同:

<object pk="27" model="auth.permission">
    <!-- ... -->
    <field to="contenttypes.contenttype" name="content_type" rel="ManyToOneRel">9</field>
    <!-- ... -->
</object>

在此示例中,我们指定具有 PK 27 的 auth.Permission 对象具有指向具有 PK 9 的 contenttypes.ContentType 实例的外键。

为绑定它们的模型导出多对多关系。 例如,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 序列化程序

DjangoJSONEncoder

class django.core.serializers.json.DjangoJSONEncoder

JSON 序列化程序使用 DjangoJSONEncoder 进行编码。 JSONEncoder 的子类,它处理这些附加类型:

datetime
ECMA-262 中定义的 YYYY-MM-DDTHH:mm:ss.sssZYYYY-MM-DDTHH:mm:ss.sss+HH:MM 形式的字符串。
date
ECMA-262 中定义的 YYYY-MM-DD 形式的字符串。
time
ECMA-262 中定义的 HH:MM:ss.sss 形式的字符串。
timedelta
表示 ISO-8601 中定义的持续时间的字符串。 例如,timedelta(days=1, hours=2, seconds=3.4) 表示为 'P1DT02H00M03.400000S'
DecimalPromisedjango.utils.functional.lazy() 对象)、UUID
对象的字符串表示形式。


YAML

YAML 序列化看起来与 JSON 非常相似。 对象列表被序列化为具有键“pk”、“模型”和“字段”的序列映射。 每个字段又是一个映射,键是字段名,值是值:

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

引用字段再次仅由 PK 或 PK 序列表示。


自然键

外键和多对多关系的默认序列化策略是序列化关系中对象的主键的值。 此策略适用于大多数对象,但在某些情况下可能会造成困难。

考虑具有引用 ContentType 的外键的对象列表的情况。 如果您要序列化引用内容类型的对象,那么您需要有一种方法来引用该内容类型。 由于 ContentType 对象是 Django 在数据库同步过程中自动创建的,给定内容类型的主键不容易预测; 这将取决于 :djadmin:`migrate` 的执行方式和时间。 这适用于所有自动生成对象的模型,特别是包括 PermissionGroupUser

警告

您永远不应该在夹具或其他序列化数据中包含自动生成的对象。 偶然地,夹具中的主键可能与数据库中的主键匹配,并且加载夹具将不起作用。 在更可能的情况下,它们不匹配,夹具加载将失败并显示 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, on_delete=models.CASCADE)

通常,Book 的序列化数据将使用整数来指代作者。 例如,在 JSON 中,一本书可能被序列化为:

...
{
    "pk": 1,
    "model": "store.book",
    "fields": {
        "name": "Mostly Harmless",
        "author": 42
    }
}
...

这不是指代作者的一种特别自然的方式。 它要求您知道作者的主键值; 它还要求这个主键值是稳定的和可预测的。

但是,如果我们为 Person 添加自然键处理,则装置变得更加人性化。 要添加自然键处理,您可以使用 get_by_natural_key() 方法为 Person 定义一个默认管理器。 在 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=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",
    }
}
...

当您需要将序列化数据加载到现有数据库中并且您不能保证序列化主键值尚未被使用,并且不需要确保反序列化对象保留相同的主键时,这会很有用。

如果您使用 :djadmin:`dumpdata` 生成序列化数据,请使用 dumpdata --natural-foreigndumpdata --natural-primary 命令行标志生成自然键。

笔记

您不需要同时定义 natural_key()get_by_natural_key()。 如果你不希望 Django 在序列化过程中输出自然键,但又想保留加载自然键的能力,那么你可以选择不实现 natural_key() 方法。

相反,如果(出于某种奇怪的原因)您希望 Django 在序列化期间输出自然键,但 not 能够加载这些键值,只需不要定义 get_by_natural_key() 方法。


自然键和前向引用

2.2 版中的新功能。


有时,当您使用 自然外键 时,您需要反序列化数据,其中一个对象的外键引用了另一个尚未反序列化的对象。 这称为“前向参考”。

例如,假设您的夹具中有以下对象:

...
{
    "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()。 这将在 DeserializedObject 实例上设置 deferred_fields 属性。 您需要跟踪此属性不是 NoneDeserializedObject 实例,然后在它们上调用 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 --natural-foreign 选项调用 :djadmin:`dumpdata` 将在序列化标准主键对象之前使用 natural_key() 方法序列化任何模型。

然而,这可能并不总是足够的。 如果您的自然键引用另一个对象(通过使用另一个对象的外键或自然键作为自然键的一部分),那么您需要能够确保自然键所依赖的对象出现在序列化数据中在自然键需要它们之前。

要控制此顺序,您可以定义对 natural_key() 方法的依赖关系。 您可以通过在 natural_key() 方法本身上设置 dependencies 属性来完成此操作。

例如,让我们为上面示例中的 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 对象之前序列化。 反过来,在 PersonBook 都被序列化之后,任何引用 Book 的对象都将被序列化。