django 3.1 序列化讲述

序列化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格式

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

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()使用未知 格式的调用将引发 django.core.serializers.SerializerDoesNotExist异常。

子集字段

如果只希望序列化字段的子集,则可以为序列化器指定一个fields参数:

from django.core import serializers
data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size'))

在此示例中,仅每个模型namesize属性将被序列化。主键始终被序列化为pk结果输出中元素。它永远不会出现在fields零件中。

注意

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

继承模型

如果您有使用抽象基类定义的模型,则无需执行任何特殊操作即可序列化该模型。在要序列化的一个或多个对象上调用序列化程序,输出将是序列化对象的完整表示。

但是,如果您有一个使用多表继承的模型,则还需要序列化该模型的所有基类。这是因为仅对模型上本地定义的字段进行序列化。例如,考虑以下模型:

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 = [*Restaurant.objects.all(), *Place.objects.all()]
data = serializers.serialize('xml', all_objects)

反序列化数据

反序列化数据与序列化非常相似:

for obj in serializers.deserialize("xml", data):
    do_something_with(obj)

如您所见,该deserialize函数采用与相同的format参数 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如果模型中不存在序列化数据中的字段,DeserializationError则将引发a, 除非将ignorenonexistent 参数传递为True

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

序列化格式

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

识别码信息
xml 在简单的XML方言之间进行序列化。
json JSON序列化
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>-elements -tag 表示每个此类对象都具有两个属性:“ pk”和“模型”,后者由应用程序名称(“会话”)表示,模型的小写名称(“会话”)用点分隔。

对象的每个字段都被序列化为一个带有<field>“类型”和“名称”字段元素。元素的文本内容表示应存储的值。

外键和其他关系字段的处理方式略有不同:

<object pk="27" model="auth.permission">
    <!-- ... -->
    <field to="contenttypes.contenttype" name="content_type" rel="ManyToOneRel">9</field>
    <!-- ... -->
</object>

在此示例中,我们指定auth.Permission具有PK 27 对象对具有contenttypes.ContentTypePK 9 实例具有外键

会为绑定它们的模型导出ManyToMany-relations。例如,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作为属性值。ManyToMany-relations对于定义它们的模型进行了序列化,并表示为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提供了自定义的GeoJSON序列化程序

在Django 3.1中进行了更改:

现在,所有数据都使用Unicode转储。如果您需要以前的行为,请传递ensure_ascii=True给该serializers.serialize()函数。

DjangoJSONEncoder

django.core.serializers.json.DjangoJSONEncoder

JSON序列化程序DjangoJSONEncoder用于编码。的子类 JSONEncoder,它处理以下其他类型:

datetime
形式YYYY-MM-DDTHH:mm:ss.sssZ或 YYYY-MM-DDTHH:mm:ss.sss+HH:MMECMA-262中所定义的字符串
date
格式YYYY-MM-DDECMA-262的字符串
time
格式HH:MM:ss.sssECMA-262的字符串
timedelta
代表ISO-8601中定义的持续时间的字符串。例如, 表示为 timedelta(days=1, hours=2, seconds=3.4)'P1DT02H00M03.400000S'
DecimalPromise(个django.utils.functional.lazy()对象),UUID
对象的字符串表示形式。

YAML 

YAML序列化看起来与JSON非常相似。对象列表通过键“ pk”,“模型”和“字段”序列化为序列映射。每个字段还是一个映射,键为字段名称,值为值:

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

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

在Django 3.1中进行了更改:

现在,所有数据都使用Unicode转储。如果您需要以前的行为,请传递allow_unicode=False给该serializers.serialize()函数。

自然键

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

考虑具有外键引用的对象列表的情况 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:
        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添加自然键处理,则固定装置将变得更加人性化。要添加自然键处理,请使用方法定义默认的Person Manager 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:
        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"]Person

注意

无论您将哪个字段用作自然键,都必须能够唯一标识一个对象。这通常意味着您的模型将为unique_together您的自然键中的一个或多个字段具有唯一性子句(在单个字段或多个字段上为unique = True )。但是,不需要在数据库级别强制执行唯一性。如果确定一组字段实际上是唯一的,则仍可以将这些字段用作自然键。

没有主键的对象的反序列化将始终检查模型管理器是否具有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:
        unique_together = [['first_name', '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在序列化期间输出自然键,但又无法加载这些键值,则只需不定义该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=Trueserializers.deserialize()这将deferred_fieldsDeserializedObject实例设置属性您需要跟踪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-foreignnatural_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都将经过序列化Person,并Book已系列化。

posted @ 2020-09-25 13:25  洋洋洋ax  阅读(273)  评论(0编辑  收藏  举报

载入天数...载入时分秒...