技術

Django モデルフィールドに任意のPythonオブジェクトを保存する(BASE64使わない版)

調べてみてもASCII形式でpickle化するか、バイナリ形式でpickle化しつつBASE64を使う方法しか見つけれなかった。
(不思議なのが、一番多く目にするパターンが、ASCII形式でpickle化しつつBASE64を使ってCharFieldに保存する方法なんだけど、CharFieldに保存するならどっちか片方でよくない?それこそ無駄に容量食うし。)
そこでバイナリ形式でpickle化したものをそのまま保存する方法を考えてみた。
ASCII形式でpickle化したり、BASE64使ったりするとデータ量が増えるから嫌だ。

セオリーどおりdjango.db.models.Fieldを継承して新しいモデルフィールドを定義した。
環境はDjango1.2とPython2.7

from django.db import models
from django.conf import settings
import cPickle

__pickle_protocol__ = 2

class ObjectField(models.Field):
    u'''
    PythonオブジェクトをシリアライズしてDBに格納するためのフィールド
    '''
    
    __metaclass__ = models.SubfieldBase
    
    def db_type(self):
        if settings.DATABASES['default']['ENGINE'].endswith('mysql'):
            return 'LONGBLOB'
        else:
            raise NotImplementedError
    
    def to_python(self, value):
        u'''
        loadsしてみて成功したら結果を返す。
        失敗したらそのまま返す。
        つまりload可能な文字列オブジェクトはloadされてしまうためそのまま保存できない。
        '''
        if isinstance(value, str):
            try:
                return cPickle.loads(value)
            except (cPickle.UnpicklingError, EOFError), e:
                return value
        return value
    
    def get_db_prep_save(self, value):
        if value is None:
            return None
        return cPickle.dumps(value, __pickle_protocol__)

このフィールドクラスを使って例えば以下のようにモデルを定義すると・・・

class Hoge(models.Model):
    name = models.CharField(u'名前', max_length = 99)
    status = ObjectField(u'状態')

ビューとかで以下のようにデータを読み書き出来る。(自前で作ったクラスのインスタンスなどpickleで扱えるものならほぼなんでも保存できる。)

hoge = Hoge()
hoge.name = 'test1'
hoge.status = {'a': 1, 'b': 'aiueo', 'c': [1, 2, 3, 4, 5], 'd': {'a': 0.1, 'b': 'aeiou'}}
hoge.save()
for hoge in Hoge.objects.all():
    print hoge.status['d']['b']

ただし、ソースにも書いてるけど問題点がいくつかあって、
今回作ったObjectFieldクラスはDBバックエンドがmysqlの場合しか考慮してないけど、defaultのDBしかチェックしてない点が一つ。(Django1.2から複数種類のDBを使えるようになった点に対してとりあえずの対応しかしていない。)
もう一つは、pickle.load可能な文字列オブジェクトを保存しようとした場合、一度unpickle化されてから保存されてしまうため、次に読みだしたときに元の文字列ではなくunpickleされたオブジェクトが返ってきてしまう点。

ASCII形式のpickle化やBASE64で容量を常に無駄にしてしまうのと違って、自分の用途では上記の問題点は関係ないのでとりあえずコレで。

コメントを残す

メールアドレスが公開されることはありません。



※画像をクリックして別の画像を表示

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください