Djangoオブジェクトのシリアル化
Djangoのシリアル化フレームワークは、Djangoモデルを他の形式に「変換」するためのメカニズムを提供します。 通常、これらの他の形式はテキストベースであり、Djangoデータをネットワーク経由で送信するために使用されますが、シリアライザーが任意の形式(テキストベースかどうか)を処理することは可能です。
データのシリアル化
最高レベルでは、次のようにデータをシリアル化できます。
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)
フィールドのサブセット
フィールドのサブセットのみをシリアル化する場合は、シリアライザーに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=LazyEncoder
をserializers.serialize()
関数に渡すことができます。
from django.core.serializers import serialize
serialize('json', SomeModel.objects.all(), cls=LazyEncoder)
また、GeoDjangoはカスタマイズされたGeoJSONシリアライザーを提供していることにも注意してください。
バージョン3.1で変更:すべてのデータがUnicodeでダンプされるようになりました。 以前の動作が必要な場合は、ensure_ascii=True
をserializers.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'
として表されます。 Decimal
、Promise
(django.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=False
をserializers.serialize()
関数に渡します。
自然キー
外部キーおよび多対多リレーションのデフォルトのシリアル化戦略は、リレーション内のオブジェクトの主キーの値をシリアル化することです。 この戦略はほとんどのオブジェクトでうまく機能しますが、状況によっては問題が発生する可能性があります。
ContentType を参照する外部キーを持つオブジェクトのリストの場合を考えてみましょう。 コンテンツタイプを参照するオブジェクトをシリアル化する場合は、最初にそのコンテンツタイプを参照する方法が必要です。 ContentType
オブジェクトはデータベース同期プロセス中にDjangoによって自動的に作成されるため、特定のコンテンツタイプの主キーを予測するのは簡単ではありません。 :djadmin: `migrate` がいつどのように実行されたかによって異なります。 これは、オブジェクトを自動的に生成するすべてのモデルに当てはまります。特に、 Permission 、 Group 、 User などが含まれます。
警告
自動生成されたオブジェクトをフィクスチャやその他のシリアル化されたデータに含めないでください。 偶然にも、フィクスチャの主キーがデータベースの主キーと一致する可能性があり、フィクスチャをロードしても効果はありません。 それらが一致しない可能性が高い場合、フィクスチャのロードは 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=True
をserializers.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()
これが機能するためには、参照モデルのForeignKey
にnull=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
の自然キーは、その名前と作成者の組み合わせです。 つまり、Person
はBook
の前にシリアル化する必要があります。 この依存関係を定義するために、次の1行を追加します。
def natural_key(self):
return (self.name,) + self.author.natural_key()
natural_key.dependencies = ['example_app.person']
この定義により、すべてのPerson
オブジェクトがBook
オブジェクトの前にシリアル化されます。 次に、Book
を参照するオブジェクトは、Person
とBook
の両方がシリアル化された後にシリアル化されます。