序列化Django对象

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

参见

如果只想将表中的一些数据转换为序列化形式,可以使用 dumpdata 管理命令。

序列化数据

在最高级别,可以像这样序列化数据:

from django.core import serializers

data = serializers.serialize("xml", SomeModel.objects.all())

关于 serialize 函数是将数据序列化到的格式(请参见 Serialization formats 和A QuerySet 序列化。(实际上,第二个参数可以是生成django模型实例的任何迭代器,但它几乎总是一个查询集)。

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() 带着未知 format 将提高 django.core.serializers.SerializerDoesNotExist 例外。

字段的子集

如果只希望对字段的子集进行序列化,则可以指定 fields 序列化程序的参数::

from django.core import serializers

data = serializers.serialize("xml", SomeModel.objects.all(), fields=["name", "size"])

在这个例子中,只有 namesize 将序列化每个模型的属性。主键始终序列化为 pk 结果输出中的元素;它从未出现在 fields 部分。

备注

根据您的模型,您可能会发现不可能反序列化只序列化其字段子集的模型。如果序列化对象未指定模型所需的所有字段,则反序列化程序将无法保存反序列化实例。

继承的模型

如果您有一个使用 abstract base class ,无需执行任何特殊操作来序列化该模型。对要序列化的一个或多个对象调用序列化程序,输出将是序列化对象的完整表示形式。

但是,如果您有一个使用 multi-table inheritance ,您还需要序列化模型的所有基类。这是因为只有在模型上本地定义的字段才会被序列化。例如,考虑以下模型:

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 . 如果序列化数据中的字段在模型上不存在,则 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> -包含多个的标记 <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>

此示例将给定用户与具有pks 46和pks 47的权限模型链接起来。

控制字符

如果要序列化的内容包含XML 1.0标准中不接受的控制字符,则序列化将失败,并返回 ValueError 例外。同时阅读W3C对 HTML, XHTML, XML and Control Codes .

JSON

当与以前的示例数据保持相同时,它将以以下方式序列化为JSON:

[
    {
        "pk": "4b678b301dfd8a4e0dad910de3ae245b",
        "model": "sessions.session",
        "fields": {
            "expire_date": "2013-01-16T08:16:59.844Z",
            # ...
        },
    }
]

这里的格式比XML要简单一些。整个集合仅表示为一个数组,对象由具有三个属性的JSON对象表示:“pk”、“model”和“fields”。字段”也是一个对象,分别包含每个字段的名称和值作为属性值和属性值。

外键将链接对象的主键作为属性值。许多关系被序列化为定义它们的模型,并表示为一个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=LazyEncoderserializers.serialize() 功能:

from django.core.serializers import serialize

serialize("json", SomeModel.objects.all(), cls=LazyEncoder)

还要注意,geodjango提供了 customized GeoJSON serializer .

DjangoJSONEncoder

class django.core.serializers.json.DjangoJSONEncoder

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

datetime

窗体的字符串 YYYY-MM-DDTHH:mm:ss.sssZYYYY-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

对象的字符串表示形式。

JSONL

JSONL 代表 JSON行 . 使用这种格式,对象由新行分隔,每行包含一个有效的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和FIELS的序列映射。每个字段都是一个映射,键是字段的名称,值是值:

- model: sessions.session
  pk: 4b678b301dfd8a4e0dad910de3ae245b
  fields:
    expire_date: 2013-01-16 08:16:59.844560+00:00

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

自然键

外键和多对多关系的默认序列化策略是序列化关系中对象的主键值。这个策略对大多数对象都很有效,但在某些情况下会造成困难。

考虑具有外键引用的对象列表的情况 ContentType . 如果要序列化引用内容类型的对象,则需要有一种方法来引用该内容类型。自从 ContentType 对象是由django在数据库同步过程中自动创建的,给定内容类型的主键不容易预测;它将取决于如何以及何时创建 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:
        constraints = [
            models.UniqueConstraint(
                fields=["first_name", "last_name"],
                name="unique_first_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}}
...

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

但是,如果我们为人添加自然的密钥处理,那么这个固定装置就会变得更加人性化。要添加自然的密钥处理,可以为具有 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:
        constraints = [
            models.UniqueConstraint(
                fields=["first_name", "last_name"],
                name="unique_first_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 在单个字段上,或在 UniqueConstraintunique_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:
        constraints = [
            models.UniqueConstraint(
                fields=["first_name", "last_name"],
                name="unique_first_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在序列化期间输出自然键,但是 not 能够加载这些键值,只是不要定义 get_by_natural_key() 方法。

自然键和转发引用

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

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

...
{
    "model": "store.book",
    "fields": {"name": "Mostly Harmless", "author": ["Douglas", "Adams"]},
},
...
{"model": "store.person", "fields": {"first_name": "Douglas", "last_name": "Adams"}},
...

为了处理这种情况,你需要通过 handle_forward_references=Trueserializers.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-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()

A的自然键 Book 是它的名称和作者的组合。这意味着 Person 必须在之前序列化 Book . 为了定义这种依赖关系,我们额外添加了一行:

def natural_key(self):
    return (self.name,) + self.author.natural_key()


natural_key.dependencies = ["example_app.person"]

这个定义确保 Person 对象在任何 Book 对象。反过来,任何对象引用 Book 将在两者之后序列化 PersonBook 已序列化。