カスタムモデルフィールドの作成
序章
モデルリファレンスのドキュメントでは、Djangoの標準フィールドクラス( CharField 、 DateField など)の使用方法について説明しています。 多くの目的のために、それらのクラスはあなたが必要とするすべてです。 ただし、Djangoバージョンが正確な要件を満たしていない場合や、Djangoに付属しているものとはまったく異なるフィールドを使用したい場合があります。
Djangoの組み込みフィールドタイプは、考えられるすべてのデータベース列タイプをカバーしているわけではなく、VARCHAR
やINTEGER
などの一般的なタイプのみをカバーしています。 地理ポリゴンなどのよりあいまいな列タイプ、または PostgreSQLカスタムタイプなどのユーザー作成タイプについては、独自のDjango Field
サブクラスを定義できます。
または、標準のデータベース列タイプに収まるようにシリアル化できる複雑なPythonオブジェクトがある場合もあります。 これは、Field
サブクラスがモデルでオブジェクトを使用するのに役立つもう1つのケースです。
サンプルオブジェクト
カスタムフィールドを作成するには、細部に少し注意を払う必要があります。 わかりやすくするために、このドキュメント全体で一貫した例を使用します。 Bridge の手札でカードの取引を表すPythonオブジェクトをラップします。 心配しないでください。この例に従うためにBridgeのプレイ方法を知る必要はありません。 52枚のカードが、伝統的に北、東、南、西と呼ばれる4人のプレーヤーに均等に配られることを知っておく必要があります。 ]。 私たちのクラスは次のようになります。
これは通常のPythonクラスであり、Django固有のものはありません。 モデルでこのようなことを実行できるようにしたいと思います(モデルのhand
属性はHand
のインスタンスであると想定しています)。
他のPythonクラスと同じように、モデルのhand
属性に割り当てて取得します。 秘訣は、そのようなオブジェクトの保存と読み込みを処理する方法をDjangoに指示することです。
モデルでHand
クラスを使用するために、このクラスを変更する必要はありません。 これは、ソースコードを変更できない既存のクラスのモデルサポートを簡単に記述できることを意味するため、理想的です。
ノート
カスタムデータベースの列タイプを利用して、モデルの標準Pythonタイプとしてデータを処理したいだけかもしれません。 たとえば、文字列、またはフロート。 このケースは、Hand
の例に似ており、違いに注意していきます。
背景理論
データベースストレージ
モデルフィールドから始めましょう。 分解すると、モデルフィールドは、通常のPythonオブジェクト(文字列、ブール値、datetime
、またはHand
などのより複雑なもの)を取得して、フォーマットとの間で変換する方法を提供します。これは、データベースを扱うときに役立ちます。 (このような形式はシリアル化にも役立ちますが、後で説明するように、データベース側を制御できるようにすると簡単になります)。
モデル内のフィールドは、既存のデータベース列タイプに適合するように何らかの方法で変換する必要があります。 データベースが異なれば、有効な列タイプのセットも異なりますが、ルールは同じです。操作する必要があるタイプはこれらだけです。 データベースに保存するものはすべて、これらのタイプのいずれかに当てはまる必要があります。
通常、特定のデータベース列タイプに一致するようにDjangoフィールドを作成するか、データを文字列などに変換する方法が必要になります。
Hand
の例では、すべてのカードを事前に決められた順序で連結することで、カードデータを104文字の文字列に変換できます。たとえば、最初にすべての北カード、次にすべてのカードです。 東、南、西カード。 したがって、Hand
オブジェクトをデータベースのテキスト列または文字列に保存できます。
フィールドクラスは何をしますか?
Djangoのすべてのフィールド(このドキュメントで fields と言うときは、 form fields ではなく常にモデルフィールドを意味します)は django.db.models.Field [のサブクラスです。 X176X]。 Djangoがフィールドに関して記録する情報のほとんどは、名前、ヘルプテキスト、一意性など、すべてのフィールドに共通です。 そのすべての情報の保存はField
によって処理されます。 Field
でできることの正確な詳細については後で説明します。 今のところ、すべてがField
の子孫であり、クラスの動作の重要な部分をカスタマイズしていると言えば十分です。
Djangoフィールドクラスはモデル属性に格納されているものではないことを理解することが重要です。 モデル属性には、通常のPythonオブジェクトが含まれています。 モデルで定義するフィールドクラスは、モデルクラスの作成時に、実際にはMeta
クラスに格納されます(これがどのように行われるかについての正確な詳細は、ここでは重要ではありません)。 これは、属性を作成および変更するだけの場合は、フィールドクラスが必要ないためです。 代わりに、属性値とデータベースに格納されているもの、またはシリアライザーに送信されるものとの間で変換するための機構を提供します。
独自のカスタムフィールドを作成するときは、このことに注意してください。 作成したDjango Field
サブクラスは、Pythonインスタンスとデータベース/シリアライザーの値をさまざまな方法で変換するための機構を提供します(たとえば、値の格納とルックアップに値を使用することには違いがあります)。 これが少しトリッキーに聞こえても、心配しないでください。以下の例でより明確になります。 カスタムフィールドが必要な場合、2つのクラスを作成することになることがよくあることを覚えておいてください。
- 最初のクラスは、ユーザーが操作するPythonオブジェクトです。 彼らはそれをモデル属性に割り当て、表示目的でそれから読み取ります。 これは、この例の
Hand
クラスです。 - 2番目のクラスは
Field
サブクラスです。 これは、最初のクラスを永続ストレージ形式とPython形式の間で相互に変換する方法を知っているクラスです。
フィールドサブクラスの記述
Field サブクラスを計画するときは、最初に、新しいフィールドが最も類似している既存の Field クラスについて考えてください。 既存のDjangoフィールドをサブクラス化して、作業を節約できますか? そうでない場合は、すべての子孫である Field クラスをサブクラス化する必要があります。
新しいフィールドの初期化は、ケースに固有の引数を共通の引数から分離し、後者を Field (または親クラス)の__init__()
メソッドに渡すことです。
この例では、フィールドをHandField
と呼びます。 ( Field サブクラスを<Something>Field
と呼ぶことをお勧めします。これにより、 Field サブクラスとして簡単に識別できます。)既存のフィールドのようには動作しません。 フィールドから直接サブクラス化します。
HandField
は、ほとんどの標準フィールドオプションを受け入れますが(以下のリストを参照)、52枚のカード値とそのスーツを保持するだけでよいため、長さが固定されていることを確認します。 合計104文字。
ノート
Djangoのモデルフィールドの多くは、何もしないオプションを受け入れます。 たとえば、 editable と auto_now の両方を django.db.models.DateField に渡すことができ、 editable パラメーターは無視されます。 ( auto_now が設定されている場合は、editable=False
を意味します)。 この場合、エラーは発生しません。
この動作により、フィールドクラスが簡素化されます。これは、フィールドクラスが不要なオプションをチェックする必要がないためです。 それらはすべてのオプションを親クラスに渡し、後でそれらを使用しません。 フィールドで選択するオプションをより厳密にするか、現在のフィールドのより寛容な動作を使用するかは、あなた次第です。
Field.__init__()
メソッドは、次のパラメーターを取ります。
verbose_name
name
primary_key
max_length
unique
blank
null
db_index
rel
:関連フィールド( ForeignKey など)に使用されます。 高度な使用のみ。default
editable
serialize
:False
の場合、モデルがDjangoのシリアライザーに渡されるときに、フィールドはシリアル化されません。 デフォルトはTrue
です。unique_for_date
unique_for_month
unique_for_year
choices
help_text
db_column
- db_tablespace :バックエンドがテーブルスペースをサポートしている場合、インデックス作成のみ。 通常、このオプションは無視してかまいません。
- auto_created :
True
フィールドが自動的に作成された場合、モデルの継承で使用される OneToOneField と同様。 高度な使用のみ。
上記のリストに説明のないすべてのオプションは、通常のDjangoフィールドの場合と同じ意味を持ちます。 例と詳細については、フィールドドキュメントを参照してください。
フィールドの脱構築
__init__()
メソッドを作成することの対位法は、 deconstruct()メソッドを作成することです。 これは、モデルの移行中に、新しいフィールドのインスタンスを取得してシリアル化された形式に縮小する方法、特に__init__()
に渡して再作成する方法をDjangoに指示するために使用されます。 。
継承元のフィールドの上にオプションを追加していない場合は、新しいdeconstruct()
メソッドを作成する必要はありません。 ただし、__init__()
で渡される引数を変更する場合(HandField
の場合のように)、渡される値を補足する必要があります。
deconstruct()
は、フィールドの属性名、フィールドクラスの完全なインポートパス、位置引数(リストとして)、およびキーワード引数(dictとして)の4つの項目のタプルを返します。 これは、3つのタプルを返すカスタムクラスのdeconstruct()
メソッドとは異なることに注意してください。
カスタムフィールドの作成者は、最初の2つの値を気にする必要はありません。 基本Field
クラスには、フィールドの属性名とインポートパスを計算するためのすべてのコードが含まれています。 ただし、位置引数とキーワード引数は変更する可能性が高いため、注意する必要があります。
たとえば、HandField
クラスでは、常に__init__()
にmax_lengthを強制的に設定しています。 基本Field
クラスのdeconstruct()
メソッドはこれを確認し、キーワード引数で返そうとします。 したがって、読みやすさのためにキーワード引数から削除できます。
新しいキーワード引数を追加する場合は、その値をkwargs
に自分で配置するコードをdeconstruct()
に記述する必要があります。 また、デフォルト値が使用されている場合など、フィールドの状態を再構築する必要がない場合は、kwargs
から値を省略する必要があります。
より複雑な例はこのドキュメントの範囲を超えていますが、覚えておいてください-Fieldインスタンスの構成では、deconstruct()
は__init__
に渡してその状態を再構築できる引数を返す必要があります。
Field
スーパークラスの引数に新しいデフォルト値を設定する場合は、特に注意してください。 古いデフォルト値を使用した場合に表示されなくなるのではなく、常に含まれていることを確認する必要があります。
さらに、位置引数として値を返さないようにしてください。 可能な場合は、将来の互換性を最大化するために、キーワード引数として値を返します。 コンストラクターの引数リストでの位置よりも頻繁に名前を変更する場合は、位置を好むかもしれませんが、シリアル化されたバージョンからかなりの期間(場合によっては数年)フィールドを再構築することに注意してください。あなたの移行は生き続けます。
フィールドを含む移行を調べることで分解の結果を確認でき、フィールドを分解して再構築することで単体テストで分解をテストできます。
カスタムフィールドの基本クラスの変更
Djangoは変更を検出して移行しないため、カスタムフィールドの基本クラスを変更することはできません。 たとえば、次のように開始した場合:
次に、代わりにTextField
を使用することを決定します。次のように、サブクラスを変更することはできません。
代わりに、新しいカスタムフィールドクラスを作成し、それを参照するようにモデルを更新する必要があります。
フィールドの削除で説明したように、元のCustomCharField
クラスを参照する移行がある限り、それを保持する必要があります。
カスタムフィールドの文書化
いつものように、フィールドタイプを文書化して、ユーザーがそれが何であるかを理解できるようにする必要があります。 開発者に役立つdocstringを提供することに加えて、管理アプリのユーザーが django.contrib.admindocs アプリケーションを介してフィールドタイプの簡単な説明を表示できるようにすることもできます。 これを行うには、カスタムフィールドの description クラス属性に説明テキストを入力します。 上記の例では、admindocs
アプリケーションによってHandField
に対して表示される説明は、「カードの手(ブリッジスタイル)」になります。
django.contrib.admindocs 表示では、フィールドの説明がfield.__dict__
で補間され、説明にフィールドの引数を組み込むことができます。 たとえば、 CharField の説明は次のとおりです。
便利な方法
Field サブクラスを作成したら、フィールドの動作に応じて、いくつかの標準メソッドをオーバーライドすることを検討してください。 以下のメソッドのリストは、重要度の高い順になっているため、上から始めてください。
カスタムデータベースタイプ
mytype
というPostgreSQLカスタムタイプを作成したとします。 次のように、Field
をサブクラス化し、 db_type()メソッドを実装できます。
MytypeField
を入手したら、他のField
タイプと同じように、どのモデルでも使用できます。
データベースに依存しないアプリケーションを構築することを目的とする場合は、データベースの列タイプの違いを考慮する必要があります。 たとえば、PostgreSQLの日付/時刻列タイプはtimestamp
と呼ばれ、MySQLの同じ列はdatetime
と呼ばれます。 connection.vendor
属性をチェックすることにより、 db_type()メソッドでこれを処理できます。 現在の組み込みベンダー名は、sqlite
、postgresql
、mysql
、およびoracle
です。
例えば:
db_type()メソッドと rel_db_type()メソッドは、フレームワークがアプリケーションのCREATE TABLE
ステートメントを作成するとき、つまり最初にテーブルを作成するときにDjangoによって呼び出されます。 。 このメソッドは、モデルフィールドを含むWHERE
句を作成するとき、つまりget()
、filter()
、exclude()
そして引数としてモデルフィールドを持っています。 これらは他の時間には呼び出されないため、上記の例のconnection.settings_dict
チェックなどの少し複雑なコードを実行する余裕があります。
一部のデータベース列タイプは、CHAR(25)
などのパラメーターを受け入れます。ここで、パラメーター25
は最大列長を表します。 このような場合、db_type()
メソッドでハードコーディングするよりも、モデルでパラメーターを指定する方が柔軟性があります。 たとえば、次のようにCharMaxlength25Field
を使用してもあまり意味がありません。
これを行うためのより良い方法は、実行時、つまりクラスがインスタンス化されるときにパラメーターを指定可能にすることです。 これを行うには、次のようにField.__init__()
を実装します。
最後に、列で本当に複雑なSQLセットアップが必要な場合は、 db_type()からNone
を返します。 これにより、DjangoのSQL作成コードがこのフィールドをスキップします。 次に、他の方法で適切なテーブルに列を作成する責任がありますが、これにより、Djangoに邪魔にならないように指示する方法が提供されます。
rel_db_type()メソッドは、データベース列のデータ型を決定するために別のフィールドを指すForeignKey
やOneToOneField
などのフィールドによって呼び出されます。 たとえば、UnsignedAutoField
がある場合、同じデータ型を使用するには、そのフィールドを指す外部キーも必要です。
値をPythonオブジェクトに変換する
カスタム Field クラスが文字列、日付、整数、または浮動小数点数よりも複雑なデータ構造を処理する場合は、 from_db_value()および to_python( )。
フィールドサブクラスに存在する場合、from_db_value()
は、データがデータベースからロードされるときに、集計や values()呼び出しを含め、すべての状況で呼び出されます。
to_python()
は、逆シリアル化によって、およびフォームから使用される clean()メソッド中に呼び出されます。
原則として、to_python()
は、次の引数のいずれかを適切に処理する必要があります。
- 正しいタイプのインスタンス(たとえば、進行中の例では
Hand
)。 - 文字列
None
(フィールドでnull=True
が許可されている場合)
HandField
クラスでは、データをVARCHARフィールドとしてデータベースに格納しているため、from_db_value()
で文字列とNone
を処理できる必要があります。 to_python()
では、Hand
インスタンスも処理する必要があります。
これらのメソッドから常にHand
インスタンスを返すことに注意してください。 これが、モデルの属性に格納するPythonオブジェクトタイプです。
to_python()
の場合、値の変換中に問題が発生した場合は、 ValidationError 例外を発生させる必要があります。
Pythonオブジェクトをクエリ値に変換する
データベースを使用するには両方の方法で変換する必要があるため、 from_db_value()をオーバーライドする場合は、 get_prep_value()もオーバーライドしてPythonオブジェクトをクエリ値に戻す必要があります。
例えば:
警告
カスタムフィールドでMySQLのCHAR
、VARCHAR
、またはTEXT
タイプを使用する場合は、 get_prep_value()が常に文字列タイプを返すようにする必要があります。 MySQLは、これらのタイプでクエリが実行され、提供された値が整数である場合、柔軟で予期しないマッチングを実行します。これにより、クエリの結果に予期しないオブジェクトが含まれる可能性があります。 get_prep_value()から常に文字列型を返す場合、この問題は発生しません。
クエリ値をデータベース値に変換する
一部のデータ型(日付など)は、データベースバックエンドで使用する前に特定の形式にする必要があります。 get_db_prep_value()は、これらの変換を行う必要があるメソッドです。 クエリに使用される特定の接続は、connection
パラメーターとして渡されます。 これにより、必要に応じてバックエンド固有の変換ロジックを使用できます。
たとえば、Djangoは BinaryField に次のメソッドを使用します。
カスタムフィールドを保存するときに、通常のクエリパラメータに使用される変換とは異なる特別な変換が必要な場合は、 get_db_prep_save()をオーバーライドできます。
保存する前に値を前処理する
保存する直前に値を前処理したい場合は、 pre_save()を使用できます。 たとえば、Djangoの DateTimeField は、 auto_now または auto_now_add の場合、このメソッドを使用して属性を正しく設定します。
このメソッドをオーバーライドする場合は、最後に属性の値を返す必要があります。 モデルへの参照を保持するコードが常に正しい値を参照できるように、値に変更を加えた場合は、モデルの属性も更新する必要があります。
モデルフィールドのフォームフィールドの指定
ModelForm で使用されるフォームフィールドをカスタマイズするには、 formfield()をオーバーライドできます。
フォームフィールドクラスは、form_class
およびchoices_form_class
引数を介して指定できます。 後者は、フィールドに選択肢が指定されている場合に使用され、前者はそれ以外の場合に使用されます。 これらの引数が指定されていない場合、 CharField または TypedChoiceField が使用されます。
すべてのkwargs
ディクショナリは、フォームフィールドの__init__()
メソッドに直接渡されます。 通常、あなたがする必要があるのは、form_class
(そしておそらくchoices_form_class
)引数の適切なデフォルトを設定し、それからさらに処理を親クラスに委任することです。 これには、カスタムフォームフィールド(およびフォームウィジェット)を作成する必要がある場合があります。 これについては、フォームのドキュメントを参照してください。
進行中の例を続けると、 formfield()メソッドを次のように記述できます。
これは、MyFormField
フィールドクラス(独自のデフォルトウィジェットがある)をインポートしたことを前提としています。 このドキュメントでは、カスタムフォームフィールドの作成の詳細については説明していません。
組み込みフィールドタイプのエミュレート
db_type()メソッドを作成した場合は、 get_internal_type()について心配する必要はありません。あまり使用されません。 ただし、データベースストレージのタイプが他のフィールドと似ている場合があるため、他のフィールドのロジックを使用して適切な列を作成できます。
例えば:
使用しているデータベースバックエンドに関係なく、これは:djadmin: `migrate` およびその他のSQLコマンドが文字列を格納するための適切な列タイプを作成することを意味します。
get_internal_type()が、使用しているデータベースバックエンドのDjangoに認識されていない文字列を返した場合、つまりdjango.db.backends.<db_name>.base.DatabaseWrapper.data_types
に表示されない場合、その文字列は引き続きによって使用されます。シリアライザーですが、デフォルトの db_type()メソッドはNone
を返します。 これが役立つ理由については、 db_type()のドキュメントを参照してください。 Django以外の場所でシリアライザーの出力を使用する場合は、シリアライザーのフィールドのタイプとして説明文字列を入力すると便利です。
シリアル化のためのフィールドデータの変換
シリアライザーによる値のシリアル化方法をカスタマイズするには、 value_to_string()をオーバーライドできます。 value_from_object()を使用することは、シリアル化の前にフィールドの値を取得するための最良の方法です。 たとえば、HandField
はとにかくデータストレージに文字列を使用するため、既存の変換コードを再利用できます。
いくつかの一般的なアドバイス
カスタムフィールドの作成は、特にPythonタイプとデータベースおよびシリアル化形式の間で複雑な変換を行う場合は、注意が必要なプロセスになる可能性があります。 物事をよりスムーズに進めるためのヒントをいくつか紹介します。
- インスピレーションを得るために、既存のDjangoフィールド(
django/db/models/fields/__init__.py
内)を見てください。 まったく新しいフィールドを最初から作成するのではなく、必要なものに類似したフィールドを見つけて少し拡張してみてください。 - フィールドとしてラップするクラスに
__str__()
メソッドを配置します。 フィールドコードのデフォルトの動作が値に対してstr()
を呼び出すことである場所はたくさんあります。 (このドキュメントの例では、value
はHandField
ではなくHand
インスタンスになります)。 したがって、__str__()
メソッドがPythonオブジェクトの文字列形式に自動的に変換される場合は、多くの作業を節約できます。
FileFieldサブクラスの作成
上記の方法に加えて、ファイルを処理するフィールドには、考慮しなければならない他のいくつかの特別な要件があります。 データベースのストレージと取得の制御など、FileField
によって提供されるメカニズムの大部分は変更せずに、特定のタイプのファイルをサポートするという課題に対処するためのサブクラスを残すことができます。
DjangoはFile
クラスを提供します。これは、ファイルの内容と操作のプロキシとして使用されます。 これをサブクラス化して、ファイルへのアクセス方法と使用可能なメソッドをカスタマイズできます。 django.db.models.fields.files
にあり、デフォルトの動作はファイルのドキュメントで説明されています。
File
のサブクラスが作成されたら、新しいFileField
サブクラスにそれを使用するように指示する必要があります。 これを行うには、新しいFile
サブクラスをFileField
サブクラスの特別なattr_class
属性に割り当てます。
いくつかの提案
上記の詳細に加えて、フィールドのコードの効率と読みやすさを大幅に向上させることができるいくつかのガイドラインがあります。
- Django独自の
ImageField
(django/db/models/fields/files.py
内)のソースは、FileField
をサブクラス化して特定のタイプのファイルをサポートする方法の優れた例です。これには、説明されているすべての手法が組み込まれています。その上。 - 可能な限りファイル属性をキャッシュします。 ファイルはリモートストレージシステムに保存される可能性があるため、ファイルの取得には余分な時間や費用がかかる場合がありますが、必ずしも必要なわけではありません。 ファイルを取得してそのコンテンツに関するデータを取得したら、そのデータをできるだけ多くキャッシュして、その情報の後続の呼び出しでファイルを取得する必要がある回数を減らします。