序列化 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)

注意

使用未知的 格式呼叫 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)

如果您只序列化 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 相同的格式引數、資料字串或資料串流,並傳回迭代器。

但是,這裡會變得稍微複雜。由 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。如果序列化資料中的欄位在模型上不存在,則會引發 DeserializationError,除非將 ignorenonexistent 引數以 True 傳入

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

序列化格式

Django 支援多種序列化格式,其中某些格式需要您安裝協力廠商 Python 模組

識別碼

資訊

xml

序列化為簡單的 XML 方言以及從其還原序列化。

json

序列化為 JSON 以及從其還原序列化。

jsonl

序列化為 JSONL 以及從其還原序列化。

yaml

序列化為 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」)以點分隔表示。

物件的每個欄位都序列化為帶有欄位「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

當保持與先前範例相同的資料時,它會以如下方式序列化為 JSON

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

此處的格式設定比 XML 簡單一些。整個集合僅表示為陣列,而物件則由具有三個屬性的 JSON 物件表示:「pk」、「model」和「fields」。「fields」又是包含每個欄位名稱和值作為屬性和屬性值的物件。

外鍵將連結物件的主鍵(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.sssZYYYY-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

參考欄位再次以主鍵或主鍵序列表示。

自然鍵

外鍵和多對多關係的預設序列化策略是序列化關係中物件的主鍵值。這種策略對於大多數物件都適用,但在某些情況下可能會導致困難。

考慮一個具有外鍵參考 ContentType 的物件列表。 如果您要序列化一個引用內容類型的物件,那麼您需要有一種方法可以從一開始就引用該內容類型。 由於 ContentType 物件是在資料庫同步過程中由 Django 自動建立的,因此給定內容類型的主鍵不容易預測;這將取決於 migrate 的執行方式和時間。 這適用於所有自動生成物件的模型,特別包括 PermissionGroupUser

警告

您永遠不應在 fixture 或其他序列化資料中包含自動生成的物件。 如果碰巧 fixture 中的主鍵與資料庫中的主鍵相符,則載入 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 中,一本書可能會序列化為

...
{"pk": 1, "model": "store.book", "fields": {"name": "Mostly Harmless", "author": 42}}
...

這不是引用作者的特別自然的方式。它要求您知道作者的主鍵值;它還要求此主鍵值穩定且可預測。

但是,如果我們將自然鍵處理新增到 Person,則 fixture 會變得更加人性化。要新增自然鍵處理,您可以為 Person 定義一個預設 Manager,其中包含一個 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", "Adams"] 解析為實際 Person 物件的主鍵。

注意

您用於自然鍵的任何欄位都必須能夠唯一識別物件。這通常表示您的模型將對自然鍵中的欄位具有唯一性子句(單個欄位上的 unique=True,或多個欄位上的 UniqueConstraintunique_together)。但是,不需要在資料庫層級強制執行唯一性。如果您確定一組欄位將實際上是唯一的,您仍然可以使用這些欄位作為自然鍵。

反序列化沒有主鍵的物件時,將始終檢查模型的 manager 是否具有 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)

該方法應始終傳回自然鍵元組 – 在此範例中,為 (名字, 姓氏)。然後,當您呼叫 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() 方法即可。

自然鍵和前向參照

有時,當您使用 自然外鍵 時,您需要反序列化資料,其中物件具有外鍵引用尚未反序列化的另一個物件。 這稱為「前向參照」。

例如,假設您的 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 屬性設定好。您需要追蹤此屬性不是 NoneDeserializedObject 實例,並稍後對它們調用 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

序列化期間的依賴性

通常可以透過仔細安排 fixture 中物件的順序來避免明確處理前向參照。

為了協助處理此問題,使用 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 的自然鍵是其名稱和作者的組合。這表示 Person 必須在 Book 之前序列化。為了定義此依賴性,我們添加一行額外的程式碼

def natural_key(self):
    return (self.name,) + self.author.natural_key()


natural_key.dependencies = ["example_app.person"]

這個定義確保所有 Person 物件都會在任何 Book 物件之前序列化。反過來說,任何參照 Book 的物件都會在 PersonBook 都序列化之後才進行序列化。

返回頂部