Djangoオブジェクトのシリアル化—Djangoドキュメント

提供:Dev Guides
< DjangoDjango/docs/3.2.x/topics/serialization
移動先:案内検索

Djangoオブジェクトのシリアル化

Djangoのシリアル化フレームワークは、Djangoモデルを他の形式に「変換」するためのメカニズムを提供します。 通常、これらの他の形式はテキストベースであり、Djangoデータをネットワーク経由で送信するために使用されますが、シリアライザーが任意の形式(テキストベースかどうか)を処理することは可能です。

も参照してください

テーブルからシリアル化された形式にデータを取得したいだけの場合は、:djadmin: `dumpdata` 管理コマンドを使用できます。


データのシリアル化

最高レベルでは、次のようにデータをシリアル化できます。

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

serialize関数の引数は、データをシリアル化する形式(シリアル化形式を参照)と、シリアル化する QuerySet です。 (実際には、2番目の引数は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'))

この例では、各モデルのname属性とsize属性のみがシリアル化されます。 主キーは、結果の出力で常にpk要素としてシリアル化されます。 fieldsの部分には表示されません。

ノート

モデルによっては、フィールドのサブセットのみをシリアル化するモデルを逆シリアル化できない場合があります。 シリアル化されたオブジェクトがモデルに必要なすべてのフィールドを指定していない場合、デシリアライザーは逆シリアル化されたインスタンスを保存できません。


継承されたモデル

抽象基本クラスを使用して定義されたモデルがある場合、そのモデルをシリアル化するために特別なことをする必要はありません。 シリアル化する1つまたは複数のオブジェクトでシリアライザーを呼び出すと、出力はシリアル化されたオブジェクトの完全な表現になります。

ただし、マルチテーブル継承を使用するモデルがある場合は、モデルのすべての基本クラスもシリアル化する必要があります。 これは、モデルでローカルに定義されているフィールドのみがシリアル化されるためです。 たとえば、次のモデルについて考えてみます。

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属性が存在しないか、nullの場合、新しいインスタンスがデータベースに保存されます。


これにより、シリアル化された表現のデータが現在データベースにあるデータと一致しない場合でも、逆シリアル化が非破壊的な操作であることが保証されます。 通常、これらの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 との間でシリアル化されます。
jsonl JSONL との間でシリアル化されます。
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>

シリアル化または逆シリアル化されたオブジェクトのコレクション全体は、複数の<object>要素を含む<django-objects>タグで表されます。 このような各オブジェクトには、「pk」と「model」の2つの属性があり、後者はアプリの名前(「セッション」)と、ドットで区切られたモデルの小文字の名前(「セッション」)で表されます。

オブジェクトの各フィールドは、フィールド「type」と「name」を備えた<field>要素としてシリアル化されます。 要素のテキストコンテンツは、保存する必要のある値を表します。

外部キーとその他のリレーショナルフィールドの扱いは少し異なります。

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

この例では、PK27のauth.Permissionオブジェクトが、PK9の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>

この例では、指定されたユーザーをPK46および47のアクセス許可モデルにリンクします。

制御文字

シリアル化されるコンテンツにXML1.0標準で受け入れられない制御文字が含まれている場合、シリアル化はValueError例外で失敗します。 HTML、XHTML、XML、および制御コードに関するW3Cの説明もお読みください。


JSON

以前と同じサンプルデータを使用する場合、次の方法でJSONとしてシリアル化されます。

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

ここでのフォーマットは、XMLよりも少し簡単です。 コレクション全体は配列として表され、オブジェクトは「pk」、「model」、「fields」の3つのプロパティを持つJSONオブジェクトで表されます。 「フィールド」も、各フィールドの名前と値をそれぞれプロパティとプロパティ値として含むオブジェクトです。

外部キーには、リンクされたオブジェクトの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)

次に、cls=LazyEncoderserializers.serialize()関数に渡すことができます。

from django.core.serializers import serialize

serialize('json', SomeModel.objects.all(), cls=LazyEncoder)

また、GeoDjangoはカスタマイズされたGeoJSONシリアライザーを提供していることにも注意してください。

バージョン3.1で変更:すべてのデータがUnicodeでダンプされるようになりました。 以前の動作が必要な場合は、ensure_ascii=Trueserializers.serialize()関数に渡します。


DjangoJSONEncoder

class django.core.serializers.json.DjangoJSONEncoder

JSONシリアライザーは、エンコードにDjangoJSONEncoderを使用します。 JSONEncoderのサブクラスであり、次の追加タイプを処理します。

datetime
ECMA-262 で定義されているYYYY-MM-DDTHH:mm:ss.sssZまたはYYYY-MM-DDTHH:mm:ss.sss+HH:MMの形式の文字列。
date
ECMA-262 で定義されているYYYY-MM-DDの形式の文字列。
time
ECMA-262 で定義されているHH:MM:ss.sssの形式の文字列。
timedelta
ISO-8601で定義されている期間を表す文字列。 たとえば、timedelta(days=1, hours=2, seconds=3.4)'P1DT02H00M03.400000S'として表されます。
DecimalPromisedjango.utils.functional.lazy()オブジェクト)、UUID
オブジェクトの文字列表現。


JSONL

バージョン3.2の新機能。


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」を使用したシーケンスマッピングとしてシリアル化されます。 各フィールドもマッピングであり、キーはフィールドの名前であり、値は値です。

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

参照フィールドは、PKまたはPKのシーケンスによって再び表されます。

バージョン3.1で変更:すべてのデータがUnicodeでダンプされるようになりました。 以前の動作が必要な場合は、allow_unicode=Falseserializers.serialize()関数に渡します。


自然キー

外部キーおよび多対多リレーションのデフォルトのシリアル化戦略は、リレーション内のオブジェクトの主キーの値をシリアル化することです。 この戦略はほとんどのオブジェクトでうまく機能しますが、状況によっては問題が発生する可能性があります。

ContentType を参照する外部キーを持つオブジェクトのリストの場合を考えてみましょう。 コンテンツタイプを参照するオブジェクトをシリアル化する場合は、最初にそのコンテンツタイプを参照する方法が必要です。 ContentTypeオブジェクトはデータベース同期プロセス中にDjangoによって自動的に作成されるため、特定のコンテンツタイプの主キーを予測するのは簡単ではありません。 :djadmin: `migrate` がいつどのように実行されたかによって異なります。 これは、オブジェクトを自動的に生成するすべてのモデルに当てはまります。特に、 PermissionGroupUser などが含まれます。

警告

自動生成されたオブジェクトをフィクスチャやその他のシリアル化されたデータに含めないでください。 偶然にも、フィクスチャの主キーがデータベースの主キーと一致する可能性があり、フィクスチャをロードしても効果はありません。 それらが一致しない可能性が高い場合、フィクスチャのロードは IntegrityError で失敗します。


利便性の問題もあります。 整数IDは、オブジェクトを参照するための最も便利な方法であるとは限りません。 より自然な参照が役立つ場合があります。

Djangoが自然キーを提供するのはこれらの理由によるものです。 自然キーは、主キー値を使用せずにオブジェクトインスタンスを一意に識別するために使用できる値のタプルです。

自然キーの逆シリアル化

次の2つのモデルを検討してください。

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()メソッドを使用してPersonのデフォルトのマネージャーを定義します。 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:
        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オブジェクトの主キーに解決します。

ノート

自然キーに使用するフィールドはすべて、オブジェクトを一意に識別できる必要があります。 これは通常、モデルに1つまたは複数の自然キーのフィールドに対する一意性句(単一のフィールドでunique = True、または複数のフィールドで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:
        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=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",
    }
}
...

これは、シリアル化されたデータを既存のデータベースにロードする必要があり、シリアル化された主キー値がまだ使用されていないことを保証できず、逆シリアル化されたオブジェクトが同じ主キーを保持することを保証する必要がない場合に役立ちます。

:djadmin: `dumpdata` を使用してシリアル化されたデータを生成する場合は、dumpdata --natural-foreignおよびdumpdata --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()に渡す必要があります。 これにより、DeserializedObjectインスタンスにdeferred_fields属性が設定されます。 この属性がNoneではないDeserializedObjectインスタンスを追跡し、後でそれらに対して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()

これが機能するためには、参照モデルのForeignKeynull=Trueが必要です。


シリアル化中の依存関係

フィクスチャ内のオブジェクトの順序に注意することで、前方参照を明示的に処理する必要を回避できることがよくあります。

これを支援するために、dumpdata --natural-foreignオプションを使用する:djadmin: `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の自然キーは、その名前と作成者の組み合わせです。 つまり、PersonBookの前にシリアル化する必要があります。 この依存関係を定義するために、次の1行を追加します。

def natural_key(self):
    return (self.name,) + self.author.natural_key()
natural_key.dependencies = ['example_app.person']

この定義により、すべてのPersonオブジェクトがBookオブジェクトの前にシリアル化されます。 次に、Bookを参照するオブジェクトは、PersonBookの両方がシリアル化された後にシリアル化されます。