序列化 Django 对象¶
Django 的序列化框架提供了一种将 Django 模型“转换”为其他格式的机制。通常这些其他格式是基于文本的,用于在网络上传输 Django 数据,但序列化器也可以处理任何格式(无论是否基于文本)。
另请参阅
如果你只是想将表中的一些数据转换成序列化形式,可以使用 dumpdata 管理命令。
序列化数据¶
在最高层面上,你可以像这样序列化数据:
from django.core import serializers
data = serializers.serialize("json", SomeModel.objects.all())
serialize 函数的参数是序列化数据的格式(参见 序列化格式)以及要序列化的 QuerySet。(实际上,第二个参数可以是任何产生 Django 模型实例的迭代器,但通常几乎总是 QuerySet)。
- django.core.serializers.get_serializer(format)¶
你也可以直接使用序列化器对象:
JSONSerializer = serializers.get_serializer("json")
json_serializer = JSONSerializer()
json_serializer.serialize(queryset)
data = json_serializer.getvalue()
如果你想将数据直接序列化到类文件对象(包括 HttpResponse)中,这很有用。
with open("file.json", "w") as out:
json_serializer.serialize(SomeModel.objects.all(), stream=out)
注意
使用未知 格式 调用 get_serializer() 将引发 django.core.serializers.SerializerDoesNotExist 异常。
字段子集¶
如果你只想序列化字段的一个子集,可以向序列化器指定 fields 参数:
from django.core import serializers
data = serializers.serialize("json", 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("json", Restaurant.objects.all())
序列化输出中的字段将仅包含 serves_hot_dogs 属性。基类的 name 属性将被忽略。
为了完全序列化你的 Restaurant 实例,你需要同时序列化 Place 模型:
all_objects = [*Restaurant.objects.all(), *Place.objects.all()]
data = serializers.serialize("json", all_objects)
反序列化数据¶
反序列化数据与序列化数据非常相似:
for obj in serializers.deserialize("json", data):
do_something_with(obj)
正如你所见,deserialize 函数接受与 serialize 相同的格式参数(字符串或数据流),并返回一个迭代器。
然而,这里变得稍微复杂了一些。deserialize 迭代器返回的对象不是常规的 Django 对象。相反,它们是特殊的 DeserializedObject 实例,包装了已创建(但未保存)的对象以及任何相关的关系数据。
调用 DeserializedObject.save() 将对象保存到数据库中。
注意
如果序列化数据中的 pk 属性不存在或为 null,则会将一个新实例保存到数据库中。
这确保了反序列化是一个非破坏性的操作,即使你的序列化表示中的数据与当前数据库中的数据不匹配。通常,处理这些 DeserializedObject 实例的方法如下:
for deserialized_object in serializers.deserialize("json", data):
if object_should_be_saved(deserialized_object):
deserialized_object.save()
换句话说,通常的做法是在保存之前检查反序列化的对象,确保它们是“合适”的。如果你信任你的数据源,可以直接保存对象并继续操作。
Django 对象本身可以通过 deserialized_object.object 进行检查。如果序列化数据中的字段在模型上不存在,除非将 ignorenonexistent 参数设置为 True,否则将引发 DeserializationError。
serializers.deserialize("json", data, ignorenonexistent=True)
序列化格式¶
Django 支持多种序列化格式,其中一些需要你安装第三方 Python 模块:
标识符 |
信息 |
|---|---|
|
序列化为简单的 XML 方言,或从中反序列化。 |
|
序列化为 JSON,或从中反序列化。 |
|
序列化为 JSONL,或从中反序列化。 |
|
序列化为 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”)通过点号分隔组成。
对象的每个字段都序列化为带有 “type” 和 “name” 属性的 <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 作为属性值。多对多关系是在定义它们的模型上序列化的,并表示为 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 的子类,它处理以下额外类型:
datetimeYYYY-MM-DDTHH:mm:ss.sssZ或YYYY-MM-DDTHH:mm:ss.sss+HH:MM形式的字符串,定义于 ECMA-262。dateYYYY-MM-DD形式的字符串,定义于 ECMA-262。timeHH: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 序列表示。
自定义序列化格式¶
除了默认格式外,你还可以创建自定义序列化格式。
例如,让我们考虑一个 CSV 序列化器和反序列化器。首先,定义一个 Serializer 和一个 Deserializer 类。这些可以覆盖现有的序列化格式类:
path/to/custom_csv_serializer.py¶ import csv
from django.apps import apps
from django.core import serializers
from django.core.serializers.base import DeserializationError
class Serializer(serializers.python.Serializer):
def get_dump_object(self, obj):
dumped_object = super().get_dump_object(obj)
row = [dumped_object["model"], str(dumped_object["pk"])]
row += [str(value) for value in dumped_object["fields"].values()]
return ",".join(row), dumped_object["model"]
def end_object(self, obj):
dumped_object_str, model = self.get_dump_object(obj)
if self.first:
fields = [field.name for field in apps.get_model(model)._meta.fields]
header = ",".join(fields)
self.stream.write(f"model,{header}\n")
self.stream.write(f"{dumped_object_str}\n")
def getvalue(self):
return super(serializers.python.Serializer, self).getvalue()
class Deserializer(serializers.python.Deserializer):
def __init__(self, stream_or_string, **options):
if isinstance(stream_or_string, bytes):
stream_or_string = stream_or_string.decode()
if isinstance(stream_or_string, str):
stream_or_string = stream_or_string.splitlines()
try:
objects = csv.DictReader(stream_or_string)
except Exception as exc:
raise DeserializationError() from exc
super().__init__(objects, **options)
def _handle_object(self, obj):
try:
model_fields = apps.get_model(obj["model"])._meta.fields
obj["fields"] = {
field.name: obj[field.name]
for field in model_fields
if field.name in obj
}
yield from super()._handle_object(obj)
except (GeneratorExit, DeserializationError):
raise
except Exception as exc:
raise DeserializationError(f"Error deserializing object: {exc}") from exc
然后将包含序列化器定义的模块添加到你的 SERIALIZATION_MODULES 设置中。
SERIALIZATION_MODULES = {
"csv": "path.to.custom_csv_serializer",
"json": "django.core.serializers.json",
}
每个提供的序列化格式都添加了 Deserializer 类定义。
自然键 (Natural Keys)¶
外键和多对多关系的默认序列化策略是序列化关系中对象的主键值。此策略适用于大多数对象,但在某些情况下可能会导致困难。
考虑引用 ContentType 的外键对象列表。如果你要序列化一个引用内容类型的对象,那么你需要有一种方法来引用该内容类型。由于 ContentType 对象是在数据库同步过程中由 Django 自动创建的,因此给定内容类型的主键不容易预测;它取决于 migrate 执行的时间和方式。对于所有自动生成对象的模型来说,情况都是如此,特别是 Permission、Group 和 User。
警告
你不应该在 fixture 或其他序列化数据中包含自动生成的对象。由于主键可能与数据库中的冲突,加载 fixture 可能无法产生效果或失败并引发 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 添加自然键处理,fixture 就会变得更加人性化。要添加自然键处理,你可以为 Person 定义一个带有 get_by_natural_key() 方法的默认 Manager。对于 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", "Adams"] 解析为实际 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() 方法即可。
自然键与前向引用¶
有时当你使用 自然外键 时,你需要反序列化数据,其中一个对象的外键引用了尚未被反序列化的另一个对象。这称为“前向引用”。
例如,假设你的 fixture 中有以下对象:
...
{
"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("json", 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。
序列化期间的依赖关系¶
通常可以通过注意 fixture 中对象的顺序来避免显式处理前向引用。
为了帮助实现这一点,使用 dumpdata --natural-foreign 选项调用 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"]
此定义确保在任何 Book 对象之前序列化所有 Person 对象。相应地,任何引用 Book 的对象将在 Person 和 Book 都被序列化后才进行序列化。