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

revision-up-to:7127 (0.97pre SVN)

開発版の Django で新たに追加された機能です

はじめに

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

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

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

Our example object

カスタムフィールドを作成するには、ちょっと注意が必要です。説明についていけ るように、このドキュメントでは一貫して一つの例題を扱うことにします。 ここでは、まず ブリッジ の「手」を表す 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()

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

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

Note

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

基本的な考え方

データベースストレージ

モデルフィールドの役割とは、簡単に言えば、文字列、ブール型、 datetime 型、そして Hand のようなより複雑なデータ型の Python オブジェクトを 受け取って、データベースを操作するときに便利な形式に変換することです。 (シリアライザ用にも変換しますが、後で説明するように、一度データベース側の変 換ができるようになれば、シリアライザ用の変換はかなり簡単です)。

モデル内のフィールド値は、データベース上では何らかのカラムタイプに変換され ねばなりません。データベースはバックエンド毎に固有の異なったカラム型を提供 しています。

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 のサブクラスです。このクラスには、上のクラスと 永続ストレージとの間で相互変換を行うための処理が組み込まれています。

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

フィールドのサブクラスを定義するときには、まずは作りたいフィールドが既存の フィールドクラスのどれに一番近いかを考えましょう。

既存の 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 パラメタを無視します。 この場合、エラーは送出されません。

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

Field.__init__() メソッドは、以下のパラメタを (列挙順に) 取ります:

  • verbose_name
  • name
  • primary_key
  • max_length
  • unique
  • blank
  • null
  • db_index
  • core
  • rel: (ForeignKey のような) リレーションに関するフィールドで使 います。高度な用途でしか必要ありません。
  • default
  • editable
  • serialize: False の場合、モデルインスタンスを シリアライザ に渡しても、フィールドをシリアライズしません。デフォルトの値は True です。
  • prepopulate_from
  • unique_for_date
  • unique_for_month
  • unique_for_year
  • validator_list
  • choices
  • radio_admin
  • help_text
  • db_column
  • db_tablespace: 現状は Oracle バックエンドだけで使っています。 インデクス生成に使います。通常、このオプションは無視できます。

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

SubfieldBase メタクラス

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

Hand クラスのようにカスタムの Python 型を扱うフィールドを作成したい場合、 Django がモデルのインスタンスを生成して、フィールドの属性値をデータベースに 保存する時に、フィールドの値を適切な Python オブジェクトに変換する必要が あります。この処理のからくりはやや複雑ですが、フィールドクラスを定義すると きに書かねばならないコードはいたって単純で、 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()

実行時、すなわちフィールドクラスをインスタンス化する時にパラメタを指定でき た方がよいでしょう。パラメタを指定できるようにするには、以下のように __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 生成コードはこのフィー ルドの処理をスキップします。もちろん、その場合には、何らかの方法で正しいテー ブルに正しいカラムを生成する必要があります。

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 インスタンスを返していることに注意してください。 to_python() は、モデルの属性値として保存したい Python オブジェクト型を 返します。

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

get_db_prep_save(self, value)

to_python() の逆のメソッドで、データベースバックエンドを扱うときに呼び出 されます。 value パラメタは、モデルの現在の属性値です (このような設計に なっているのは、個々のフィールドが、自身を組み込んでいるモデルに対する参照 を持っていないため、フィールドが自分でモデルインスタンスから値を取り出せな いからです)。 このメソッドは、データベースバックエンドのクエリパラメタとし て使えるようにデータをフォーマットして返さねばなりません。

例を示します:

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

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

pre_save(self, model_instance, add)

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

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

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

get_db_prep_lookup(self, lookup_type, value)

データベース上で照合を行う時 (WHERE 制約付きで SQL を発行する時) に データベースに渡す値を前処理しまうs。 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 が正しい値でない場合 (単一の オブジェクトが指定されるべきところにリストが指定された場合など) には ValueError を、フィールドが指定した照合に対応していない場合には TypeError を送出せねばなりません。たいていのフィールドでは、特別な扱い の必要な照合タイプだけを処理して、残りは親クラスの get_db_prep_lookup() メソッドに委ねればよいでしょう。

get_db_prep_save() を定義するようなケースでは、おそらく get_db_prep_lookup() も定義する必要があります。また、このメソッドを 定義するたいていの理由は rangein による検索の実現です。 こうした場合には、引数としてオブジェクトのリストを受け取り、データベースに 渡せる何らかの適切な型のオブジェクトからなるリストに変換せねばなりません。 get_db_prep_save() を使い回したり、別のメソッドに共通の処理を括り出し ておくと便利です。

例を示します:

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

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

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

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

kwargs 辞書の内容は、全てフォームフィールドの __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() のドキュメントを読んでください。シリアライザに、フィールドの 型名として文字列を指定しておくと、 Django の外の何らかの場面でシリアライザ の出力を扱うときに便利だからです。

flatten_data(self, follow, obj=None)

変更される可能性あり

このメソッドは、フィールドのシリアライゼーション上実装する必要がありま すが、 API は将来変更されるかもしれません。

フィールドの属性名をデータに対応付ける辞書を返します。データはフラット化さ れた文字列で表現されています。このメソッドは、ここではあまり重要でない、内 部で使うためのものです (ほとんどはマニピュレータ関係です)。 さしあたって、属性名を文字列に対応付けている 1 要素の辞書を返すだけで十分で す。

このメソッドは、シリアライザがフィールドの値を出力する際に文字列に変換する ために使います。シライアライズ処理時は、 Field._get_val_from_obj(obj) を呼び出して値を取り出すのがよいのですが、パラメタは無視してもかまいません。

例えば、 HandField はデータストレージに文字列を使っているので、既存の変 換コードを再利用できます:

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

    def flatten_data(self, follow, obj=None):
        value = self._get_val_from_obj(obj)
        return {self.attname: self.get_db_prep_save(value)}

一般的なアドバイス

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

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