カスタムのモデルフィールド

revision-up-to:11321 (1.1)

はじめに

モデルリファレンス ドキュメントでは、 CharFieldDateField といった、 Django の標準のフィールドク ラスの使用法について説明しています。ほとんどの用途には、これらの標準フィー ルドクラスだけで十分でしょう。とはいえ、現行の Django が自分の要求をうまく 満たしていない場合や、 Django が提供しているものと全く異なるフィールドを使 いたい場合もあるでしょう。

Django の組み込みフィールド型は、データベースのカラムとして利用できる全ての データ型をサポートしているわけではなく、 VARCHARINTEGER のよう な一般的な型しかサポートしていません。幾何多角形 (geographic polygon) 型や、 PostgreSQL カスタム型 のようなユーザ定義型といった特異な型を扱うには、 Field 型のサブクラスを定義せねばなりません。

Field 型のサブクラス定義には、別の用途もあります。複雑な Python オブジェ クトをシリアライズして、標準的なデータベースのカラム型に変換して保存したい 場合です。こうした場合にも、 Field のサブクラスを定義しておき、モデルで 使うと便利です。

ブリッジの手を例題に

カスタムフィールドを作成するには、ちょっと注意が必要です。説明についていけ るように、このドキュメントでは一貫して、 ブリッジ の「手」を表す Python オブジェクトをラップするという例題を使うことにします。 (ブリッジの遊び方をしらなくても、心配することはありません。知っておくべきな のは、ブリッジは、 52 枚のカードを 4 人のプレイヤーで扱う遊びで、プレイヤー は伝統的に north, east, south, west と呼ばれることだけです。) さて、 Hand クラスは以下のように定義されています:

class Hand(object):
    def __init__(self, north, east, south, west):
        # パラメタはカードのリスト ('Ah', '9s', など)
        self.north = north
        self.east = east
        self.south = south
        self.west = west

    # ... (他のメソッドはここでは省略) ...

Hand はただの普通の Python クラスで、 Django 固有の定義は一切ありません。 このクラスを、モデルで扱えるようにしましょう (モデルの hand という属性 に Hand のインスタンスを入れたいとします):

example = MyModel.objects.get(pk=1)
print example.hand.north

new_hand = Hand(north, east, south, west)
example.hand = new_hand
example.save()

上の例では、他の Python クラスと同じように、 hand アトリビュートを代入 や値の参照に使っています。このトリックは、 Django に Hand オブジェクト の保存やロードの方法を教えることで実現されています。

モデルから Hand クラスを扱うために、 Hand に手を加える必要は 全くありません 。その方が、例えば既存のクラスでソースコードに手を加えら れないような場合にも、簡単にモデル上でサポートできるからです。

Note

単に、カスタムのデータベースカラム型を使って、文字列や浮動小数点型といっ た標準の Python データを扱いたいという場合もあるでしょう。このケースも Hand の例とほぼ同様で、違いは後で説明します。

基本的な考え方

データベースストレージ

モデルフィールドの役割を理解するには、モデルフィールドが何らかの Python 型 オブジェクト、すなわち文字列型やブール型、 datetime 型、そして Hand ようなより複雑なデータ型を扱ったり、データベースの保存に適した形式との間で 相互変換するためのものだと考えるのが手っ取り早いでしょう (シリアライズ用の 変換もありますが、後で説明するように、一度データベース側の変換ができるよう になれば、シリアライザ用の変換は結構簡単です)。

モデル内のフィールド値は、データベース上では何らかのカラムタイプに変換され ねばなりません。使えるカラムタイプはデータベースによって違いますが、カラム タイプが何のデータを表現するかだけに注意していれば問題ありません。すなわち、 データベースに保存したいものと、データベースのカラムタイプを合わせればよい のです。

Hand の例の場合には、プレイヤーのカード情報を、予め決まった順番、 north, east, south, west の順番にくっつけて、 104 文字の文字列に変 換します。従って、 Hand オブジェクトはデータベースにテキスト型はキャラ クタ型で保存できます。

フィールドクラスの役割

Django のフィールド型 (このドキュメントで フィールド という場合には、基本 的に フォームフィールド ではなくモデルフィールド を指します) は、全て django.db.models.Field のサブクラスです。 Django がフィールドごとに記録する情報は、フィールドの名前、フィールド毎のヘ ルプテキスト、フィールドの値に対するバリデータのリスト、フィールドをユニー クキーにするかどうかのフラグ、といったもので、ほとんどのフィールドで共通し ています。こうした情報の保存は Field クラスが行います。 Field の動 作は後で詳しく説明しますが、さしあたっては、全ては Field から継承でき、 その中からクラスの挙動の鍵になる部分をカスタマイズするのだと考えておけば十 分です。

よくよく理解してほしいのは、「モデルの各アトリビュートには、 Django のフィー ルドクラスのインスタンスが保存されているのではない」ということです。モデル の各アトリビュートに入っているのは、アトリビュートの値を表すただ通常の Python オブジェクトです。モデルの中でフィールドを定義すると、そのフィールド のクラスは、モデルクラスの生成時に内部クラス Meta の中に保存されます (詳しい動作の仕組みは、ここではあまり重要ではあ りません)。モデルインスタン スのアトリビュートを作成したり変更する時には、フィールドクラスの機能は使わ れません。その代わり、フィールドクラスは、アトリビュートの値をデータベース に保存したり、 シリアライザ に送信するときに 必要な変換の手段を提供しているのです。

カスタムのフィールドクラスを定義する際には、上記のことを心に留めておいてく ださい。 Django の Field サブクラスは、 Python オブジェクトとデータベー スやシリアライザ向けの値との間で、色々な変換 (例えば、データを保存するた めの変換と、照合に使うための変換は別のものです) を実現するための機構を提供 します。この仕組みは一見トリッキーですが、心配しないでください。以下の例を 読み進めていくうちにはっきり分かるでしょう。ただ、たいていの場合、カスタム フィールドが必要な場面では、以下の二つのクラスを定義するということだけを覚 えておいてください:

  • 一つ目は、ユーザが操作する Python オブジェクトです。このオブジェクト はモデルのアトリビュートとして代入され、表示時に読み出されます。例で いうと Hand クラスです。
  • もう一つは Field のサブクラスです。このクラスには、上のクラスと データベースのような永続ストレージとの間で相互変換を行うための処理が 組み込まれています。

フィールドのサブクラスを定義する

Field のサブクラスを定義するときには、まずは作り たいフィールドに一番近い既存のフィールドクラスがないか考えましょう。既存の Django のフィールド型をサブクラス化すれば、作業が楽になるでしょうか? もしそうでなければ、 Field クラスをサブクラス化 します。

フィールドの初期化時には、 Field クラス (または 親クラス) 共通の引数と、新たなフィールドクラス固有の引数を分離して、前者を 親クラスの __init__() に渡します。

このドキュメントの例題では、定義するフィールドを HandField と呼ぶことに しましょう (フィールドを (何とか)Field と呼ぶようにしておけば、クラスが Field のサブクラスであるとすぐ分かるからです)。 HandField は既存のフィールドと似た部分はあまりないので、 Field クラスから直接サブクラス化します:

from django.db import models

class HandField(models.Field):
    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 104
        super(HandField, self).__init__(*args, **kwargs)

HandField は標準的なフィールドオプションのほとんどを指定できます。ただ し、このフィールドは高々 52 枚のカードの値と種類だけを保存できればよいので、 合計 104 文字分の固定長にしておきます。

Note

多くの Django のモデルフィールドは、そのフィールドに関係のないオプショ ンも引数に指定できます。例えば、 editableauto_now の両方を DateField に指定してもよく、 DateField は (auto_now を指定すると editable=False なので) editable パ ラメタを無視します。この場合、エラーは送出されません。

このような挙動にしておくと、不要なオプションをいちいちチェックしなくて よいので、フィールドクラスの定義が簡単になります。サブクラスでは、単に 全てのオプションを親クラスに渡しておけばよく、それ以上何もしなくてよい のです。選べるオプションを厳しく制限してもよいですし、簡単にしたければ、 何でも指定できるようにしておけばよいのです。

__init__() メソッドは、以下のパラメタを (並び 順通りに) 取ります:

上のリストで解説のないオプションは、通常の Django のフィールドと同じ意味を 持っています。詳しくは モデルのドキュメント を参 照してください。

SubfieldBase メタクラス

はじめに でも説明したように、フィールド型のサブクラスが必要な理由は主に 二つあります。一つはカスタムのデータベースカラム型を利用したい場合、もう一 つは複雑な Python のデータ型を扱いたい場合です。もちろん、二つを合わせて実 現するのも可能です。カスタムのデータベースカラム型を扱っているだけで、かつ Python からモデルフィールドを扱うときのデータ型が、データベースバックエンド から取り出したデータの型と一致している場合には、この節を気にする必要はあり ません。

Hand クラスのようにカスタムの Python 型を扱うフィールドを作成したい場合、 Django がモデルのインスタンスを生成して、フィールドのアトリビュート値をデー タベースに保存する時に、フィールドの値を適切な Python オブジェクトに変換す る必要があります。この処理のからくりはやや複雑ですが、 Field クラスを定 義するときに書かねばならないコードはいたって単純で、特殊なメタクラスを使っ てサブクラスを作成するだけです:

class django.db.models.SubfieldBase

例えば:

class HandField(models.Field):
    __metaclass__ = models.SubfieldBase

    def __init__(self, *args, **kwargs):
        # ...

こうしておけば、属性値を初期化する際に、自動的に to_python() メソッド が呼び出されるようになります。 to_python() メソッドについては後で解説 します。

便利なメソッド

Field のサブクラスを作成して、 __metaclass__ を組み込んだら、次はフィールドの挙動に応じて標準メソッド をいくつかオーバライドします。以下のメソッドは上からよく使う順に挙げていま す。

データベース型のカスタマイズ

db_type(self)

DATABASE_ENGINE 設定に指定されているデータベースで、フィールドの データを保存するために使うカラム型を返します。

例えば、 PostgreSQL 固有のカスタム型 mytype を作成したとしましょう。 Django からこの型を使うには、以下のように Field をサブクラス化して、 db_type() メソッドを実装します:

from django.db import models

class MytypeField(models.Field):
    def db_type(self):
        return 'mytype'

MytypeField を定義したら、他のフィールド型と同じようにモデルで利用でき ます:

class Person(models.Model):
    name = models.CharField(max_length=80)
    gender = models.CharField(max_length=1)
    something_else = MytypeField()

データベースに依存しないアプリケーションを構築したいなら、データベース毎 のカラム型の違いに注意しておかねばなりません。例えば、 PostgreSQL では日付 や時間に関するカラム型は timestamp 型ですが、 MySQL では datetime 型です。この違いを db_type() メソッドの中で吸収するには、Djangoの settings モジュールを import しておいて、以下のように DATABASE_ENGINE 設定をチェックします:

class MyDateField(models.Field):
    def db_type(self):
        from django.conf import settings
        if settings.DATABASE_ENGINE == 'mysql':
            return 'datetime'
        else:
            return 'timestamp'

db_type() メソッドは、Django がアプリケーションのテーブルを生成する ための CREATE TABLE` 文を構築する瞬間、すなわち最初にテーブルを生成する 瞬間しか呼び出されません。それ以外の場合に呼び出されることはないので、上記 の DATABASE_ENGINE をチェックする例のような多少ややこしいコード を書いてもさして問題ではありません。

カラムのデータ型にはパラメタを取るものがあります。例えば CHAR(25) は、 25 がカラムの最大長を表しています。こうした場合には、 db_type() メソッド内でハードコードしておくよりも、モデル内でパラメタを指定して使える 方が柔軟性を高められます。例えば、以下に示すような CharMaxlength25Field はあまり便利ではありません:

# パラメタをハードコードしているおバカな例
class CharMaxlength25Field(models.Field):
    def db_type(self):
        return 'char(25)'

# モデル内での使い方
class MyModel(models.Model):
    # ...
    my_field = CharMaxlength25Field()

実行時、すなわちフィールドクラスをインスタンス化する時にパラメタを指定でき た方がよいでしょう。パラメタを指定できるようにするには、以下のように django.db.models.Field.__init__() を実装します:

# より柔軟性のある例
class BetterCharField(models.Field):
    def __init__(self, maxlength, *args, **kwargs):
        self.max_length = max_length
        super(BetterCharField, self).__init__(*args, **kwargs)

    def db_type(self):
        return 'char(%s)' % self.max_length

# モデル内での使い方
class MyModel(models.Model):
    # ...
    my_field = BetterCharField(25)

最後に、カラムの操作に非常に複雑な SQL が必要な場合には、 db_type()None を返させましょう。そうすれば、 Django の SQL 生成コードはこの フィールドの処理をスキップします。もちろん、その場合には、何らかの方法で正 しいテーブルに正しいカラムを生成する必要があります。

データベース上の値を Python オブジェクトに変換する

to_python(self, value)

データベース (またはシリアライザ) の返す値を Python オブジェクトに変換しま す。

デフォルトの実装では、単に value を返します。通常は、データベースバック エンドは正しい形式 (Python 文字列型など) でデータを返すからです。

カスタムのフィールドクラスで、文字列や日時、整数や浮動小数点型といったデー タ型よりも複雑なデータ構造を扱いたい場合、このメソッドをオーバライドする必 要があります。一般的な規則として、このメソッドは以下の引数をきちんと扱えね ばなりません:

  • 正しい型のインスタンス (このドキュメントの例では Hand オブジェク ト)
  • 文字列 (デシリアライザなどが返す値)
  • このフィールドのカラムタイプに対して、データベースバックエンドが返す 値

HandField クラスでは、データを VARCHAR フィールドで保存しているので、 to_python() は文字列と Hand インスタンスの両方を扱えねばなりません:

import re

class HandField(models.Field):
    # ...

    def to_python(self, value):
        if isinstance(value, Hand):
            return value

        # The string case.
        p1 = re.compile('.{26}')
        p2 = re.compile('..')
        args = [p2.findall(x) for x in p1.findall(value)]
        return Hand(*args)

このメソッドは常に Hand インスタンス、つまりモデルのアトリビュートとし て保存したい Python オブジェクトを返します。

忘れないで!: カスタムフィールドに対して to_python() を呼び出した ければ、上で述べた SubfieldBase メタクラス を使わねばなりません。メタク ラスを使わないと、 to_python() は自動的に呼び出されません。

Python オブジェクトをデータベース保存用の値に変換する

get_db_prep_value(self, value)

to_python() の逆のメソッドで、データベースバックエンドを扱うときに呼 び出されます。 value パラメタは、現在のアトリビュートの値です (フィール ドは自分を含むモデルインスタンスへの参照を持たないので、モデルインスタンス 経由でフィールドの値を取り出せません。そのため、 value パラメタで値を受 け取ります)。 このメソッドは、 value で受け取ったデータを、各データベー スバックエンドのクエリパラメタとして使える形式にフォーマットして返さねばな りません。

例を示します:

class HandField(models.Field):
    # ...

    def get_db_prep_value(self, value):
        return ''.join([''.join(l) for l in (value.north,
                value.east, value.south, value.west)])
get_db_prep_save(self, value)

前述のメソッドと同じですが、フィールドの値をデータベースに 保存 するとき に呼び出されます。デフォルトの実装では単に get_db_prep_value を呼び出し ています。従って、データベースにデータを保存するときに、通常のクエリパラメ タと違うものを使わねばならないような特殊な状況を除き、このメソッドを実装す る必要はありません (通常のクエリパラメタの変換は、 get_db_prep_value で で実装しています)。

保存前に値に前処理をほどこす

pre_save(self, model_instance, add)

このメソッドは、 get_db_prep_save() の直前に呼び出され、このフィール ドの model_instance での適切な値を生成して返さねばなりません。アトリビュ ートの名前は self.attrname で参照できます (この値はフィールドのインス タンス化時に設定されます)。モデルインスタンスをデータベースに初めて保存しよ うとしている場合、 add パラメタの値は True です。それ以外の値は False です。

このメソッドをオーバライドする必要があるのは、フィールドの値を、保存の直前 に前処理したい場合だけです。例えば、 Django の DateTimeField は、 auto_nowauto_now_add で生成された場合、このメソッド を使って正しい値をセットします。

このメソッドをオーバライドする場合、最後にアトリビュートの値を返さねばなり ません。また、フィールドの値を変更した場合、モデル上の対応する値も更新して、 モデルインスタンスを参照しているコードが正しい値を読み出せるようにせねばな りません。

データベース照合時のパラメタを SQL 用に変換する

get_db_prep_lookup(self, lookup_type, value)

データベース上で照合を行う時 (WHERE 制約付きで SQL を発行する時) にデー タベースに渡す値を前処理します。 lookup_type は、 Django のフィルタ照合 条件である exact, iexact, contains, icontains, gt, gte, lt, lte, in, startswith, istartswith, endswith, iendswith, range, year, month, day, isnull, search, regex, iregex のいずれかの値を取ります。

このメソッドをオーバライドする場合、上に挙げた lookup_type を全て扱えね ばなりません。また、 value が正しい値でない場合 (value に単一のオブ ジェクトが入っているはずなのに、リストが入っていた場合など) には ValueError を、フィールドが指定した照合に対応していない場合には TypeError を送出せねばなりません。たいていのフィールドでは、特別な扱い の必要な照合タイプだけを処理して、残りは親クラスの get_db_prep_lookup() メソッドに委ねればよいでしょう。

get_db_prep_save を定義するようなケースではたいてい、 get_db_prep_lookup も定義する必要があります。定義しなければ、 exact, gt, gte, lt, lte, in, range の照合時には、 デフォルトの実装に従って get_db_prep_value が呼び出されます。

また、カスタムのフィールド型でサポートする照合タイプを制限したい場合にも、 このメソッドを定義する必要があるでしょう。

rangein を使った照合を行う場合、 get_db_prep_lookup は 引数として (適切な型の) オブジェクトのリストを受け取り、データベースに渡せ るなにがしか適切な型のオブジェクトからなるリストに変換せねばなりません。 大抵の場合は get_db_prep_save を使い回したり、別のメソッドに共通の 処理を括り出しておけます。

例えば、以下のコードでは get_db_prep_lookup を実装して、照合タイプを exactin に制限しています:

class HandField(models.Field):
    # ...

    def get_db_prep_lookup(self, lookup_type, value):
        # 'exact' および 'in' だけを扱います。それ以外はエラーにします。
        if lookup_type == 'exact':
            return [self.get_db_prep_value(value)]
        elif lookup_type == 'in':
            return [self.get_db_prep_value(v) for v in value]
        else:
            raise TypeError('Lookup type %r not supported.' % lookup_type)

モデルフィールドに対応したフォームフィールドを指定する

formfield(self, form_class=forms.CharField, **kwargs)

このフィールドをモデルに組み込んで、フォームで表示したときに使うデフォルト のフォームフィールドを返します。このメソッドは、 ModelForm ヘルパから呼び出されます。

kwargs 辞書の内容は、全てフォームフィールドの Field__init__() メソッドに渡されます。通常、このメソッ ドの定義では、適切な form_class 引数のデフォルト値を設定して、それ以降 の処理を親クラスに移譲しているだけです。フォームフィールドを定義する場合、 カスタムのフォームフィールド (や、フォームウィジェット) を書く必要があるか もしれません。カスタムのフォームフィールドについては forms のドキュメント を参照してください。また、 カスタムウィジェットは django.contrib.localflavor のコードを参照する とよいでしょう。

これまでの例に合わせて書くと、 formfield() メソッドは以下のように書け ます:

class HandField(models.Field):
    # ...

    def formfield(self, **kwargs):
        # メソッドの呼び出し側がデフォルトをオーバライド
        # できるようにするための標準的な書き方
        defaults = {'form_class': MyFormField}
        defaults.update(kwargs)
        return super(HandField, self).formfield(**defaults)

上の例では、 MyFormField フィールドクラスを import しているのが前提です (そして、 MyFormField でデフォルトのウィジェットを定義しています)。カス タムのフォームフィールドの定義方法は、このドキュメントでは扱いません。

組み込みのフィールド型をエミュレートする

get_internal_type(self)

データベースレベルでエミュレーションしているフィールド型の名前を返すメソッ ドです。このメソッドは、簡単な方法でフィールド型を定義する際に、データベー スのカラム型を決定するために使われます。

db_type() メソッドを定義していれば、 get_internal_type() を気に する必要はありません。このメソッドは、ほとんど使われません。ただし、フィー ルド型のデータベースストレージが、他のフィールドと同じような型である場合、 このメソッドを定義しておくと、他のフィールドのロジックを使ってカラムを生成 させられます。

例を示します:

class HandField(models.Field):
    # ...

    def get_internal_type(self):
        return 'CharField'

どのデータベースバックエンドを使っているかに関わらず、上のように定義してお くと、 syncdb などの SQL 関係のコマンドを実行した時に、文字列を保存す るための正しいカラム型を生成します。

get_internal_type() が、 Django にもデータベースバックエンドにもない 型を示す文字列、すなわち django.db.backends.<db_name>.creation.DATA_TYPES に存在しない文字列を返 を返す場合、シリアライザはこの文字列をそのまま受け取りますが、デフォルトの db_type() 実装は None を返します。なぜこうした動作が適切なのかを 知りたければ、db_type() のドキュメントを読んでください。 たとえフィールドが DATA_TYPES にないデータ型を返す場合でも、 シリアライザの出力を Django の外で扱う予定があるのなら、シリアライザが扱え るように、 get_internal_type() にフィールドのデータ型を表す文字列を返 させるのがよいでしょう。

シリアライゼーション用にフィールドデータを変換する

value_to_string(self, obj)

このメソッドは、シリアライザがフィールドの値を出力する際に文字列に変換する ために使います。シリアライズデータを生成するには、 Field._get_val_from_obj(obj)() を呼び出すとよいでしょう。例えば、例え ば、 HandField はデータストレージに文字列を使っているので、既存の変換コー ドを再利用できます:

class HandField(models.Field):
    # ...

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

一般的なアドバイス

カスタムフィールドの定義は、とりわけ Python 型とデータベース、そしてシリア ライゼーション形式との間で複雑な変換を行う場合には、トリッキーな処理になり がちです。よりスムーズにフィールドを定義するためのアドバイスを、以下に示し ます:

  1. 既存のフィールド型定義 (django/db/models/fields/__init__.py にあります) を見て、インスピレーションを得ましょう。全く何もない状態 からスクラッチでフィールドを定義するのではなく、自分の実現したいこと に近い処理を行っているフィールド型を探して、それをちょっとだけ拡張す るようにしましょう。
  2. フィールドとしてラップしたいクラスに、 __str__()__unicode__() メソッドを定義しましょう。フィールドの処理におけ るデフォルトの動作として、ラップしているクラスのインスタンスに対して force_unicode() を呼び出すような処理が たくさんあります。 (このドキュメントの例では、 valueHand のインスタンスであり、 HandField のインスタンスではないので) __unicode__() メソッドを定義しておき、 Python オブジェクトが自 動的に文字列形式に変換されるようにしておけば、作業を大幅に減らせます。

FileField のサブクラスを定義する

上に挙げたメソッドに加えて、ファイルを扱うフィールドを定義するときには、い くつか必要な条件があります。データベースストレージの操作やデータの取得など、 フィールド定義に必要なメカニズムのほとんどは FileField で定義しています が、特定のタイプのファイルをサポートするための処理はサブクラスに委ねていま す。

Django はファイルのコンテンツや操作をプロキシする File クラスを提供して います。このクラスはサブクラス化でき、ファイルのアクセス方法やメソッドをカ スタマイズできます。 File クラスは django.db.models.fields.files で 定義されており、デフォルトの動作は ファイルクラスのドキュメント で解説しています。

File のサブクラスを作ったら、 FileField サブクラスの attr_class に指定して、 FileField サブクラスに指定のファイルクラスを使うよう指示し てください。

アドバイス

上に挙げた説明に加えて、カスタムフィールドのコードの効率や可読性を挙げるた めのガイドラインをいくつか示しておきます。

  1. Django の ImageField (django/db/models/fields/files.py) は、 FileField をサブクラス化して特定のファイルタイプをサポートしてい る素晴らしい例題です。このクラスでは、上に述べたテクニックを余すとこ ろ無く使っています。
  2. 可能な限り、ファイルの属性をキャッシュしましょう。ファイルは遠隔のス トレージシステム上にあるので、ファイルデータの取得には時間もお金もか かります。しかも、常に必要なわけではありません。一度ファイルを取得し て、コンテンツに関するデータを収集したら、データ可能な限りキャッシュ して、以後のアクセスでファイルを再取得する回数を減らしましょう。