Django オブジェクトのシリアライズ

revision-up-to:17812 (1.4)

Django のシリアライズフレームワークを使うと、 Django オブジェクトを他の形式 に「翻訳」できます。通常、こうした形式はテキストベースで、 Django オブジェ クトをネットワーク越しに伝送するために使われますが、 Django のシリアライザ は任意の形式 (テキストベースもそうでないものも) 扱えます。

See also

もしテーブルに入っているデータをシリアライズされた形式で取り出したいだ けなら、 dumpdata コマンドを使うことができます。

データのシリアライズ

高水準では、データのシリアライズは極めて簡単な操作です:

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

serialize 関数の引数には、データのシリアライズに使うフォーマット (シリアライズの形式 参照) と、シリアライズ対象の QuerySet (実際には、第二引数は Django オブジェク トを返す任意のイテレータにできますが、大抵の場合は QuerySet を使うことにな るでしょう)。

シリアライザオブジェクトを直接使ってもかまいません:

XMLSerializer = serializers.get_serializer("xml")
xml_serializer = XMLSerializer()
xml_serializer.serialize(queryset)
data = xml_serializer.getvalue()

シリアライザオブジェクトを直接使うと、以下のようにファイルライクオブジェク ト (もちろん HttpResponse も使えます) に対して直接シ リアライズできるので便利です:

out = open("file.xml", "w")
xml_serializer.serialize(SomeModel.objects.all(), stream=out)

Note

get_serializer() に不明な format を渡すと、 SerializerDoesNotExist が送出されます。

一部のフィールドだけをシリアライズする

一部のフィールドだけをシリアライズしたい場合には、シリアライザに fields 引数を指定します:

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

上の例では、 namesize だけがシリアライズされます。

Note

モデルによっては、フィールドの一部だけをシリアライズすると、そこからデ シリアライズできない場合があります。シリアライズ後のオブジェクトに、モ デル上で必須のフィールドがひとつでも抜け落ちていると、デシリアライザは デシリアライズ後のインスタンスを保存できないでしょう。

継承を行っているモデルインスタンスのシリアライズ

抽象ベースクラス を使って定義したモデルを扱っ ていても、モデルのシリアライズのために特に行うことはありません。単にシリア ライズしたオブジェクトに対してシリアライザを呼び出せば、完全なシリアライズ 済みオブジェクトが出力されます。

ただし、 マルチテーブル継承 を使って定義さ れているモデルを扱う場合、モデルの全てのベースクラスをシリアライズする必要 があります。これは、モデルごとに固有に定義されたフィールドがシリアライズさ れるためです。例えば、以下のようなモデルを考えましょう:

class Place(models.Model):
    name = models.CharField(max_length=50)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField()

ここで、 Restaurant モデルだけをシリアライズしたとします:

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() を呼び出すと、データベースにオブジェクトを保 存します。

上のような仕様から、デシリアライズは、たとえシリアライズされていたデータの 表現形式が現在のデータベースの構成と一致していなかったとしても非破壊的な操 作になるよう保証されています。通常、 DeserializedObject インスタンスの 操作は以下のように行います:

for deserialized_object in serializers.deserialize("xml", data):
    if object_should_be_saved(deserialized_object):
        deserialized_object.save()

すなわち、デシリアライズしたオブジェクトを保存する場合、前もって保存に適し ているかどうかを調べるのが普通のやり方なのです。もちろん、データソースを信 頼できるのなら、単にデータを保存してもかまいません。

Django オブジェクト自体に対するインスペクションは、 deserialized_object.object で行えます。

シリアライズの形式

Django は複数のシリアライズ形式に対応しています。そのうちいくつかではサード パーティモジュールのインストールが必要です。

名前 情報
xml 単純な XML シリアライザです。
json JSON シリアライザ (Django に付属の simplejson を使ったも の) です。
python 「単純な」Python オブジェクト (リスト、辞書、文字列など) の シリアライザです。単体では取り立てて便利ではありませんが、 他のシリアライザのベースになっています。
yaml YAML (YAML Ain’t a Markup Language) へのシリアライザです。 このシリアライザは PyYAML がインストールされている場合のみ 利用できます。

各シリアライズ形式についての注意

json

UTF-8 (や、非 ASCII エンコーディング) でエンコードされたデータを JSON シリ アライザで扱うには、 serialize() のパラメタに ensure_ascii=False を 指定してください。さもないと、出力のエンコードがおかしくなってしまいます。

例:

json_serializer = serializers.get_serializer("json")()
json_serializer.serialize(queryset, ensure_ascii=False, stream=response)

Django のソースコードには simplejson モジュールが付属しています。 ですが、もし (組み込みバージョンのモジュールが付属する) Python 2.6 かそれ以 降のバージョンを使っているなら、 Django は自動的に組み込みの json モジュ ールを使います。Cベースの高速化拡張を含むシステムインストール版や、もっと最 近のバージョンを使っているなら Django に付属しているバージョンの代わりにシス テムバージョンが使われます。

注意してほしいのは、このモジュールを直接使ってシリアライズを実行すると、一 部の Django オブジェクトは何らかの変更が加えられた上で simplejson に渡されて しまうということです。特に、 遅延翻訳オブジェクト をシリアライズする場合は、 特殊なエンコーダ が必要 です。以下のように書くと、うまくいくでしょう:

from django.utils.functional import Promise
from django.utils.encoding import force_unicode

class LazyEncoder(simplejson.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Promise):
            return force_unicode(obj)
        return obj

自然キー (natural keys)

リリースノートを参照してください

外部キーや多対多のリレーションをシリアライズする時の基本方針は、リレーションオ ブジェクトの主キー値をシリアライズすることです。 この方針はたいていのオブジェクトに対してうまくいきますが、特定の状況では難しい 問題を引き起こします。

ContentType を参照する外部キーを 持つオブジェクトのリストを考えてください。オブジェクトをシリアライズしようとす ると、 ContentType を参照する方法がなければいけません。 ContentType オブジェクトはデータベースへの同期処理で Django が自動的に生成 するので、 ContentType の主キーを予測するのは困難です。それはいつ syncdb が実行されたかによって決まるでしょう。このことはオブジェクト を生成する全てのモデル、特に PermissionGroupUser において成り立ちます。

Warning

自動生成されたオブジェクトを、フィクスチャや他のシリアライズされたデータに 含めるべきではありません。フィクスチャの中の主キーが偶然データベースの中の 主キーに一致すると、フィクスチャのロードが効果を持たなくなってしまいます。 もっとありそうなのは、主キーが一致せずにロードが 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)

通常、 Book のシリアライズ済みデータは著者を参照するのに整数を使います。 例えば JSON では、 Book はこのようにシリアライズされるかもしれません:

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

これは著者を参照するための特に自然な方法とは言えません。著者を参照する主キーの値 を知っていなければなりませんし、主キーの値は変更がなく、予測可能なものでなければ なりません。

しかし、 Person を扱う自然キーを追加すれば、このフィクスチャはより理解しや すいものになります。自然キーの扱いを追加するには、 Person のデフォルトマネ ジャに 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):
    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'),)

これで Book は Person オブジェクトを参照する自然キーを使えます:

...
{
    "pk": 1,
    "model": "store.book",
    "fields": {
        "name": "Mostly Harmless",
        "author": ["Douglas", "Adams"]
    }
}
...

シリアライズ済みデータをロードしようとすると、 ["Douglas", "Adams"] を実際 の Person オブジェクトの主キーへと解決するために、 Django は get_by_natural_key() を使います。

Note

自然キーに使うフィールドは、オブジェクトを一意に特定することができなけ ればなりません。これは普通、そのモデルがユニーク句を持っていることを意味しま す。(単独のフィールドで unique=True であるか、複数のフィールドに対して unique_together であるか)しかし、データベースレベルでユニークであること は強制されません。実際上ユニークだという確証があれば、それらのフィールドをナ チュラルキーとして使うことができます。

自然キーのシリアライズ

さて、オブジェクトをシリアライズする時に 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'),)

このメソッドは常に自然キーのタプルを返すべきです。この例では (first name, last name) です。それから、 serializers.serialize() を呼ぶ時 に use_natural_keys=True 引数を渡します:

>>> serializers.serialize('json', [book1, book2], indent=2, use_natural_keys=True)

use_natural_keys=True が指定されると、 Django はこのメソッドを定義している型の オブジェクト参照をシリアライズするために、 natural_key() メソッドを使います。

シリアライズデータを生成するために dumpdata を使う時は、 –natural コマンドラインフラグを使います。

Note

natural_key()get_by_natural_key() の両方を定義する必要はありませ ん。シリアライズの時に自然キーを出力させたくなく、しかも自然キー をロードすることもできるようにするには、 natural_key() メソッドを実装しな いということも可能です。

逆に言えば、(何かの変わった理由で)シリアライズ時に自然キーを出力させ たく、かつこれらのキーを出力できないようにしたければ、 get_by_natural_key() メソッドを定義するだけにすれば良いのです。

シリアライズ中の依存関係

自然キーは参照を解決するためにデータベースのルックアップに依存するので、参 照される前にデータが存在することが大切です。自然キーで forward refernce を作ることはできません。自然キーをデータに含める前に、参照したいデータが存 在していなければならないのです。

この制限に適応させるため、 dumpdata--natural オプシ ョンを使うと、どんなモデルでも標準的な主キーオブジェクトでのシリアライズより前に natural_key() メソッドでシリアライズされます。

しかし、いつもこれで充分とは限りません。外部キーか自然キーによってナチュラ ルキーの一部である別のオブジェクトを参照している時には、シリアライズされたデータ のなかでは依存される側のオブジェクトが先に出現することを保証できなければなりませ ん。

この順序を保証するため、 natural_key() メソッドに依存関係を定義できます。 natural_key() メソッド自体に dependencies 属性を設定するのです。

例えば、 上の例で見た Book モデルに自然キーを与えましょう:

class Book(models.Model):
    name = models.CharField(max_length=100)
    author = models.ForeignKey(Person)

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

Book の自然キーは名前と著者の組み合わせです。つまり PersonBook より先にシリアライズされる必要があります。この依存関係を定義するため、 もう一行加えます:

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

この定義により、どんな Person オブジェクトも Book オブジェクトより先に シリアライズされることが保証されます。逆に、 Book を参照するどんなオブジェ クトも、 PersonBook の両方がシリアライズされた後にシリアライズされ るでしょう。