序列化Django对象

通常情况下,这种形式是基于文本的,它被用来发送Django的数据,当然,序列化处理的形式也有例外(基于文本或者相反)。

请参见

如果您只是想从表中获取一些数据到序列化形式,可以使用dumpdata管理命令。

序列化数据

从最高层面来看,序列化数据是一项非常简单的操作

from django.core import serializers
data = serializers.serialize("xml", SomeModel.objects.all())

传递给 serialize 方法的参数有二:一个序列化目标格式(参见 Serialization formats) ,另外一个是序列号的对象QuerySet. (事实上,第二个参数可以是任何可迭代的Django Model实例,但它很多情况下就是一个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)

调用具有未知formatget_serializer()会产生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, 你不需要对它做任何特别的处理。 只需对需要序列化的对象调用serializers,就能输出该对象完整的字段。

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

class Place(models.Model):
    name = models.CharField(max_length=50)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)

如果你只序列化餐厅模型:

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 如果模型中不存在序列化数据中的字段,则会出现True,除非DeserializationError参数以ignorenonexistent

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

序列化格式

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

识别码 信息
XML 从简单的XML方言进行序列化。
JSON Serializes to and from JSON.
YAML 序列化为YAML(YAML不是标记语言)。 此串行器仅在安装了PyYAML时可用。

XML ¶ T0>

基本的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> -tag表示。 每个这样的对象有两个属性:“pk”和“model”,后者由应用程序的名称(“sessions”)和用点分隔的模型的小写名称(“会话”)表示。

对象的每个字段被序列化为<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实例具有外键。

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 ¶ T0>

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

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

这里的格式比使用XML简单一点。 整个集合仅表示为数组,对象由具有三个属性的JSON对象表示:“pk”,“model”和“fields”。 “fields”再次是一个对象,分别将每个字段的名称和值分别作为property和property-value。

外键只是将链接对象的PK作为属性值。 ManyToMany关系被序列化为定义它们的模型,并被表示为PK列表。

请注意,并非所有Django输出都可以未修改地传递给json 例如,如果对象中有一些自定义类型要序列化,则必须为其编写一个自定义的json编码器。 这样的东西会工作:

from django.utils.encoding import force_text
from django.core.serializers.json import DjangoJSONEncoder

class LazyEncoder(DjangoJSONEncoder):
    def default(self, obj):
        if isinstance(obj, YourCustomType):
            return force_text(obj)
        return super(LazyEncoder, self).default(obj)

然后,您可以将cls=LazyEncoder传递给serializers.serialize()函数:

from django.core.serializers import serialize

serialize('json', SomeModel.objects.all(), cls=LazyEncoder)
在Django更改1.11:

使用自定义编码器的能力 CLS = ... 加入。

另请注意,GeoDjango提供了一个customized GeoJSON serializer

DjangoJSONEncoder

django.core.serializers.json。 DjangoJSONEncoder T0> ¶ T1>

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

约会时间
A string of the form YYYY-MM-DDTHH:mm:ss.sssZ or YYYY-MM-DDTHH:mm:ss.sss+HH:MM as defined in ECMA-262.
日期
ECMA-262中定义的YYYY-MM-DD形式的字符串。
时间
ECMA-262中定义的HH:MM:ss.sss形式的字符串。
timedelta
表示ISO-8601中定义的持续时间的字符串。 例如,timedelta(days = 1, hours = 2, seconds = 3.4)表示为'P1DT02H00M03.400000S'
DecimalPromisedjango.utils.functional.lazy() objects),UUID
对象的字符串表示形式。
在Django更改1.10:

支持Promise

在Django更改1.11:

添加了timedelta的支持。

YAML ¶ T0>

YAML序列化看起来非常类似于JSON。 对象列表被序列化为具有键“pk”,“model”和“fields”的序列映射。 每个字段再次是一个映射,其中键是字段的名称和值的值:

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

参考字段再次仅由PK或PK序列表示。

自然键

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

考虑具有引用ContentType的外键的对象列表的情况。 如果你要序列化一个引用内容类型的对象,那么你需要有一种方法来引用该内容类型。 由于在数据库同步过程中,Django会自动创建ContentType对象,所以给定内容类型的主键不容易预测;这将取决于执行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
    }
}
...

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

然而,如果我们添加自然键处理到人,灯具变得更加人性化。 要添加自然键处理,请使用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将使用Person方法解析[“Douglas”, “Adams”] 转换为实际get_by_natural_key()对象的主键。

无论用于自然键的任何字段都必须能够唯一地标识对象。 这通常意味着您的模型将在自然键中的一个或多个字段中具有唯一性子句(单个字段上的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'),)

该方法应该总是返回一个自然键元组 - 在这个例子中,(第一个 名称, 最后 t4> 然后,当您调用use_natural_primary_keys=True时,您提供serializers.serialize()use_natural_foreign_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在序列化过程中输出自然键,但不能能够加载这些键值,只是不要定义get_by_natural_key()

序列化期间的依赖关系

由于自然键依赖于数据库查找来解析引用,因此重要的是数据在引用之前存在。 您不能使用自然键创建“前向引用” - 您引用的数据必须存在,然后才能包含该数据的自然键引用。

为了适应这个限制,使用dumpdata --natural-foreign选项的dumpdata的调用将序列化任何模型在序列化标准主键对象之前使用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对象之前序列化。 反过来,在BookBook序列化之后,引用Person的任何对象将被序列化。