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)
注意
使用未知 format 调用 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 = [*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
相同的 format 参数、数据字符串或流,并返回一个迭代器。
但是,这里稍微复杂一些。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 方言并从中反序列化。 |
|
序列化到 JSON 并从中反序列化。 |
|
序列化到 JSONL 并从中反序列化。 |
|
序列化到 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>
在此示例中,我们指定具有 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。多对多关系为定义它们的模型序列化,并表示为 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
格式为
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()
对象)、UUID
对象的字符串表示。
JSONL¶
JSONL 代表 JSON Lines。在此格式中,对象由换行符分隔,每行包含一个有效的 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”和“fields”。每个字段又是一个映射,键是字段名称,值是值。
- model: sessions.session
pk: 4b678b301dfd8a4e0dad910de3ae245b
fields:
expire_date: 2013-01-16 08:16:59.844560+00:00
引用字段再次由 PK 或 PK 序列表示。
自然键¶
外键和多对多关系的默认序列化策略是序列化关系中对象的主键值。此策略适用于大多数对象,但在某些情况下可能会导致困难。
考虑一下具有引用 ContentType
的外键的对象列表的情况。如果您要序列化引用内容类型的对象,那么您需要有一种方法来首先引用该内容类型。由于 ContentType
对象在数据库同步过程中由 Django 自动创建,因此给定内容类型的主键不容易预测;它取决于 migrate
的执行方式和时间。这对于自动生成对象的 所有模型都适用,尤其包括 Permission
、Group
和 User
。
警告
您绝不应在固定装置或其他序列化数据中包含自动生成的 对象。碰巧的是,固定装置中的主键可能与数据库中的主键匹配,加载固定装置将没有任何作用。在不太可能的情况下,它们不匹配,则固定装置加载将失败,并出现 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 中,一本 Book 可能会序列化为:
...
{"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):
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", "]
解析为实际 Person
对象的主键。
注意
您用于自然键的任何字段都必须能够唯一标识一个对象。这通常意味着您的模型将对自然键中的字段具有唯一性子句(单个字段上的 unique=True
,或者多个字段上的 UniqueConstraint
或 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:
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=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()
方法即可。
自然键和前向引用¶
有时,当您使用自然外键时,您需要反序列化数据,其中一个对象具有指向尚未反序列化的另一个对象的外部键。这称为“前向引用”。
例如,假设您的夹具中包含以下对象
...
{
"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
属性。您需要跟踪此属性不为None
的DeserializedObject
实例,然后在其上调用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()
方法上定义依赖项。您可以通过设置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
的自然键是其名称和作者的组合。这意味着必须在Book
之前序列化Person
。为了定义此依赖项,我们添加一行代码
def natural_key(self):
return (self.name,) + self.author.natural_key()
natural_key.dependencies = ["example_app.person"]
此定义确保在任何Book
对象之前序列化所有Person
对象。反过来,任何引用Book
的对象都将在Person
和Book
都序列化后进行序列化。