序列化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 T0>(格式 T1>)¶ T2>

您也可以直接使用序列化程序对象:

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定义的模型,则不必做任何特殊的操作来序列化该模型。 只需调用要序列化的对象(或多个对象)上的序列化程序,输出将是序列化对象的完整表示。

但是,如果您有一个使用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属性不存在或为空,则将新实例保存到数据库。

这可以确保即使序列化表示中的数据与数据库中当前数据不匹配,反序列化也是非破坏性操作。 通常,使用这些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 ¶ 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>

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

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

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”又是一个包含每个字段的名称和值作为属性和属性值的对象。

外键只是链接对象的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)

You can then pass cls=LazyEncoder to the serializers.serialize() function:

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中定义的持续时间的字符串。 For example, timedelta(days=1, hours=2, seconds=3.4) is represented as 'P1DT02H00M03.400000S'.
DecimalPromisedjango.utils.functional.lazy()对象),UUID
对象的字符串表示形式。
在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的外键的对象列表的情况。 如果您要序列化引用内容类型的对象,则需要有一种方法来引用该内容类型。 由于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:
        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()方法定义一个默认的Manager Manager。 在一个人的情况下,一个好的自然钥匙可能是这对名字:

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将使用get_by_natural_key()方法来解析[“Douglas”, “Adams” t4>转换为实际的Person对象的主键。

注意

无论您用于自然键的字段必须能够唯一标识一个对象。 This will usually mean that your model will have a uniqueness clause (either unique=True on a single field, or unique_together over multiple fields) for the field or fields in your natural key. 但是,唯一性不需要在数据库级别执行。 如果你确定一组字段将是有效的唯一的,你仍然可以使用这些字段作为一个自然的关键。

与没有主键对象的反序列化将总是检查模型的管理器是否具有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'),)

That method should always return a natural key tuple – in this example, (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在序列化期间输出自然键,但是不能能够加载这些键值,只是不要定义get_by_natural_key()

序列化过程中的依赖关系

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

为了适应这个限制,使用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']

该定义确保所有Person对象在任何Book对象之前都被序列化。 反过来,在PersonBook序列化之后,任何引用Book的对象都将被序列化。