カスタムのモデルフィールドを作成する¶
はじめに¶
モデルリファレンス ドキュメントでは、Djangoの標準フィールドクラスである、 CharField
や、 DateField
などの使い方を説明しています。多くの場合、これらのクラスがあれば十分です。ただし、Djangoのバージョンが正確な要件を満たしていない場合や、Djangoに同梱されているものとはまったく異なるフィールドを使用したい場合があります。
Django の組み込みフィールド型は、データベースで利用可能な全てのカラム型をカバーしているわけではなく、 VARCHAR
や INTEGER
のような一般的な型のみに対応しています。地理的な多角形や PostgreSQL カスタム型 のようなユーザが作成した型など、より曖昧なカラム型については、独自の Django Field
サブクラスを定義することができます。
あるいは、複雑な Python オブジェクトを、標準的なデータベースのカラム型に合うようにシリアライズすることもできます。これもまた、Field
サブクラスがオブジェクトをモデルで使用するのに役立つケースです。
私たちのサンプルオブジェクト¶
カスタムフィールドを作成するには、細部に少し注意する必要があります。わかりやすくなるように、このドキュメントでは「ブリッジ Bridge の手札のやり取りを表すPythonオブジェクトの実装」を一貫した例として説明することにします。なお、この実装例を理解するために、ブリッジのプレイルールを理解する必要はありません。52枚のカードが、伝統的に 北 、 東 、 南 および 西 と呼ばれる4人のプレイヤーに均等に配られることだけを覚えておいてください。クラスは以下のように実装します:
class Hand:
"""A hand of cards (bridge style)"""
def __init__(self, north, east, south, west):
# Input parameters are lists of cards ('Ah', '9s', etc.)
self.north = north
self.east = east
self.south = south
self.west = west
# ... (other possibly useful methods omitted) ...
これはDjangoの仕様を使っていない、普通のPythonクラスです。モデルでこれと同じようなことを実行できるようにしたいと思います(モデルの中の 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
クラスを使用するために、このクラスを変更する必要はありません。 これは、ソースコードを変更できない既存のクラスのモデルサポートを簡単に記述できるため、理想的です。
注釈
例えば文字列や浮動小数点数のような、Pythonの標準的な型としてカスタムデータベースのカラム型を扱い、データを利用したいこともあります。このケースは Hand
の例と似ているので、違いを後で説明することにします。
背景理論¶
データベースストレージ¶
モデルフィールドから始めましょう。分解すると、モデルフィールドは通常のPythonオブジェクト(文字列、ブール値、 datetime
、あるいは Hand
のような複雑なもの)を、データベースを扱うときに便利な形式に変換する方法を提供します。(このような形式はシリアライズの際にも便利ですが、後で説明するように、データベース側を制御できるようになれば、その方が簡単です)。
モデルのフィールドは、既存のデータベースのカラムの型に適合するように何らかの方法で変換されなければなりません。データベースによって有効なカラム型のセットは異なりますが、利用できる型が決まっているという決まりは同じです。データベースに保存したいものはすべて、これらの型のいずれかに適合しなければなりません。
通常、特定のデータベースの列タイプに一致するようにDjangoフィールドを作成するか、データを文字列などに変換する方法が必要になります。
Hand
の例では、すべてのカードをあらかじめ決められた順序で連結することで、カードデータを104文字の文字列に変換できます。たとえば、すべての 北 カード、次に 東 、 南 および 西 カードです。 したがって、Hand
オブジェクトをデータベースのテキストまたは文字列に保存できます。
フィールドクラスが行うこと¶
Djangoのすべてのフィールド(およびこのドキュメントで フィールド (Field) と言うときは、常に フォームフィールド ではなくモデルフィールドを意味します)は django.db.models.Field
のサブクラスです。 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
サブクラスであることが簡単にわかるためです)
既存のフィールドのようには動作しないため、この例では Field
から直接サブクラス化しています。
from django.db import models
class HandField(models.Field):
description = "A hand of cards (bridge style)"
def __init__(self, *args, **kwargs):
kwargs["max_length"] = 104
super().__init__(*args, **kwargs)
私たちの HandField
は、ほとんどの標準フィールドオプションを受け入れます(以下のリストを参照してください)。しかし、52枚のカードの値とそれらのスート(マーク)を保持するだけで十分なため、固定長を持つようにします。合計で104文字です。
注釈
多くのDjangoのモデルフィールドは、受け取ってもなにも起こらないオプションを受け取ります。例えば、 editable
と auto_now
は django.db.models.DateField
に渡すことができますが、 editable
のパラメーターを無視します。( auto_now
には editable=False
がセットされています)この場合、エラーは発生しません。
この振る舞いはフィールドクラスをシンプルにします。なぜなら、必要のないオプションをチェックする必要がないからです。すべてのオプションを親クラスに渡し、オプションを使用しないのです。フィールドにより厳密にオプションを扱ってほしいのか、より柔軟なカレントフィールドの動作を使うのかは、使う人次第です。
Field.__init__()
メソッドは以下のパラメーターを取ります:
name
rel
: 関連フィールドに使用される (ForeignKey
など)。上級者向けです。serialize
:False
の場合、Django の シリアライザ にモデルが渡されたときに、フィールドをシリアライズしません。デフォルトの値はTrue
です。もしバックエンドが テーブル空間 (tablespace) をサポートする場合、
db_tablespace
: はインデックスの作成のみ行います。通常、このオプションは無視できます。モデル継承で使用される
OneToOneField
のように、フィールドが自動的に作成された場合は、auto_created
はTrue
です。 高度な使用向けです。
上記のリストに説明のないオプションはすべて、通常のDjangoフィールドと同じ意味を持ちます。例と詳細については、 フィールドのドキュメント を参照してください。
フィールドの解体¶
__init__()
メソッドとは対照的なものとして、 deconstruct()
メソッドがあります。これは モデルのマイグレーション 中に使用されるもので、新しいフィールドのインスタンスを取得する方法、インスタンスをどのようにシリアライズして減らすかの方法をDjangoに指示します。特に、インスタンスを再生成するためにどの引数を __init__()
に渡すかを指示します。
継承元のフィールドの上に追加のオプションを追加していない場合、新しい deconstruct()
メソッドを記述する必要はありません。 ただし、__init__()
で渡される引数を変更する場合(HandField
のように)、渡される値を補足する必要があります。
deconstruct()
関数は4要素のタプルを返します。それは、フィールドの属性、インポートするフィールドクラスのフルパス、位置引数(リスト型)、キーワード引数(辞書型)です。この関数は deconstruct()
メソッドとは異なることに注意してください。 deconstruct()
メソッドは カスタムクラスのためのもの で、3要素のタプルを返します。
カスタムフィールドの作成者は、最初の2つの値を気にする必要はありません。 ベース Field
クラスには、フィールドの属性名とインポートパスを計算するためのすべてのコードがあります。 ただし、位置引数とキーワード引数は、変更する可能性が高いため、注意する必要があります。
たとえば、 HandField
クラスでは、常に __init__()
でmax_lengthを強制的に設定しています。 Field
ベースクラスの deconstruct()
メソッドはこれを見て、キーワード引数でそれを返そうとします。 したがって、読みやすくするためにキーワード引数から削除できます:
from django.db import models
class HandField(models.Field):
def __init__(self, *args, **kwargs):
kwargs["max_length"] = 104
super().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
del kwargs["max_length"]
return name, path, args, kwargs
新しいキーワード引数を追加する場合、自分で kwargs
に値を設定する deconstruct()
でコードを記述する必要があります。 また、デフォルト値が使用されている場合など、フィールドの状態を再構築する必要がない場合は、kwargs
から値を省略してください:
from django.db import models
class CommaSepField(models.Field):
"Implements comma-separated storage of lists"
def __init__(self, separator=",", *args, **kwargs):
self.separator = separator
super().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
# Only include kwarg if it's not the default
if self.separator != ",":
kwargs["separator"] = self.separator
return name, path, args, kwargs
より複雑な例はこのドキュメントの範囲外ですが、これだけは覚えておいてください - deconstruct()
は、フィールドインスタンスのどのような設定に対しても、その状態を再構築するために __init__
に渡す引数を返さなければなりません。
Field
スーパークラス内の引数に新しいデフォルト値を設定する場合には、特に注意してください。古いデフォルト値を使うと消えるようにするのではなく、デフォルト値が常に含まれるようにしてください。
また、値を位置引数として返さないようにしてください。 可能な限り、将来の互換性を最大限にするために、キーワード引数として値を返してください。コンストラクタの引数リストにおける位置よりも、頻繁に引数の名前を変更する場合、位置引数で指定しがちになるかもしれません。しかし、利用者はかなり長期間(おそらく何年も)にわたって、シリアライズされたバージョンからフィールドを再構築することに注意してください。これはバージョンのライフサイクルの長さにも依存します。
フィールドを含む移行を調べることで分解の結果を確認できます。フィールドを分解して再構築することで、単体テストで分解をテストできます:
name, path, args, kwargs = my_field_instance.deconstruct()
new_instance = MyField(*args, **kwargs)
self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute)
データベースのカラム定義に影響しないフィールド属性¶
Field.non_db_attrs
をオーバーライドすることで、カラム定義に影響しないフィールドの属性をカスタマイズできます。これはモデルのマイグレーションの間に使われ、no-opの AlterField
操作を検出します。
例えば:
class CommaSepField(models.Field):
@property
def non_db_attrs(self):
return super().non_db_attrs + ("separator",)
カスタムフィールドのベースクラスを変更する¶
カスタムフィールドの基底クラスを変更することはできません。なぜなら、 Django はその変更を検知できず、マイグレーションも行わないからです。例えば次のように書き始めたとして、:
class CustomCharField(models.CharField): ...
そして、代わりに TextField
を使用することに決めた場合、サブクラスを次のように変更することはできません:
class CustomCharField(models.TextField): ...
代わりに、新しいカスタムフィールドクラスを作成し、モデルを更新してそれを参照する必要があります:
class CustomCharField(models.CharField): ...
class CustomTextField(models.TextField): ...
フィールドを削除する で説明したように、それを参照するマイグレーションがある限り、元の CustomCharField
クラスを保持する必要があります。
カスタムフィールドのドキュメントを書く¶
いつものように、フィールドタイプのドキュメントを作成し、それがどんなものかをユーザがわかるようにしましょう。開発者にとって便利な docstring を提供するだけでなく、 django.contrib.admindocs アプリケーションを通して、管理者アプリのユーザにフィールドタイプの短い説明を表示することもできます。これを行うには、カスタムフィールドの description
クラス属性に説明文を記述します。上記の例では、 admindocs
アプリケーションが表示する HandField
の説明は 'A hand of cards (bridge style)' (日本語で「手札(ブリッジスタイル)」の意)となります。
django.contrib.admindocs
の表示では、フィールドの説明は field.__dict__
によって補完され、説明文にフィールドの引数を組み込むことができます。例えば、 CharField
の説明は次のようになります:
description = _("String (up to %(max_length)s)")
便利なメソッド¶
Field
サブクラスを作成したら、フィールドの動作に応じて、いくつかの標準メソッドをオーバーライドすることを検討できます。以下のメソッドのリストは、おおよそ重要度の高いものから順に並んでいるので、上から始めてください。
カスタムデータベースタイプ¶
mytype
というPostgreSQLのカスタム型を作成したとします。このとき、 Field
をサブクラス化し、 db_type()
関数を以下のように実装できます:
from django.db import models
class MytypeField(models.Field):
def db_type(self, connection):
return "mytype"
MytypeField
を取得したら、他の Field
タイプと同様に、どのモデルでも使用できます:
class Person(models.Model):
name = models.CharField(max_length=80)
something_else = MytypeField()
データベースに依存しないアプリケーションを構築することを目指している場合、データベースカラムの型の違いを考慮する必要があります。たとえば、PostgreSQL の日付や時刻のカラムの型は timestamp
と呼ばれますが、同じカラムが MySQL では datetime
と呼ばれます。この違いは db_type()
メソッド内で connection.vendor
属性をチェックすることでハンドリングできます。現在のビルトインされているベンダー名は、sqlite
、postgresql
、mysql
、oracle
です。
例えば:
class MyDateField(models.Field):
def db_type(self, connection):
if connection.vendor == "mysql":
return "datetime"
else:
return "timestamp"
Django は、アプリケーションのために CREATE TABLE
ステートメントを構築するとき、つまり最初にテーブルを作成するときに db_type()
と rel_db_type()
メソッドを呼びます。このメソッドは、モデルのフィールドを含む WHERE
句を構築するとき、つまり QuerySet の get()
、filter()
、exclude()
などのメソッドを使用してデータを取得するときにも呼ばれます。
一部のデータベースカラムの型は CHAR(25)
などのパラメータを受け付けます。ここで、パラメータ 25
はカラムの最大長を表します。このような場合、パラメータを db_type()
メソッド内でハードコードするよりも、モデル内で指定したほうがより柔軟になります。たとえば、ここで示すように、CharMaxlength25Field
のようなフィールドを持つ意味はあまりありません。
# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
def db_type(self, connection):
return "char(25)"
# In the model:
class MyModel(models.Model):
# ...
my_field = CharMaxlength25Field()
これを行うためのよりよい方法は、実行時に、つまりクラスがインスタンス化されるときにパラメータを指定できるようにする方法です。そのためには、次のように Field.__init__()
を実装します。
# This is a much more flexible example.
class BetterCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super().__init__(*args, **kwargs)
def db_type(self, connection):
return "char(%s)" % self.max_length
# In the model:
class MyModel(models.Model):
# ...
my_field = BetterCharField(25)
最後に、カラムが本当に複雑な SQL のセットアップを必要とする場合、db_type()
から None
を返してください。こうすると、Django の SQL 生成コードがこのフィールドをスキップするようになります。その後、別の方法で正しいテーブルにカラムを作成すする必要がありますが、Django に邪魔をしないように伝えることができます。
rel_db_type()
メソッドは、他のフィールドを指す ForeignKey
や OneToOneField
などのフィールドによって、データベースのカラムのデータ型を特定するために呼ばれます。たとえば、UnsignedAutoField
がある場合、同じデータ型を使うためにそのフィールドを指す外部キーも必要になります。
# MySQL unsigned integer (range 0 to 4294967295).
class UnsignedAutoField(models.AutoField):
def db_type(self, connection):
return "integer UNSIGNED AUTO_INCREMENT"
def rel_db_type(self, connection):
return "integer UNSIGNED"
変数を 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
のインスタンスも処理する必要があります。
import re
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
def parse_hand(hand_string):
"""Takes a string of cards and splits into a full hand."""
p1 = re.compile(".{26}")
p2 = re.compile("..")
args = [p2.findall(x) for x in p1.findall(hand_string)]
if len(args) != 4:
raise ValidationError(_("Invalid input for a Hand instance"))
return Hand(*args)
class HandField(models.Field):
# ...
def from_db_value(self, value, expression, connection):
if value is None:
return value
return parse_hand(value)
def to_python(self, value):
if isinstance(value, Hand):
return value
if value is None:
return value
return parse_hand(value)
これらのメソッドからは常に Hand
インスタンスが返されることに注意してください。これは、モデルの属性に保存したいPythonオブジェクトの型です。
to_python()
において、値の変換中に何か問題が発生した場合は、 ValidationError
例外を発生させる必要があります。
Python オブジェクトをクエリ変数に変換する¶
データベースを使用するには両方の変換が必要なので、 from_db_value()
をオーバーライドする場合は、 get_prep_value()
もオーバーライドして Python オブジェクトをクエリの値に戻す必要があります。
例えば:
class HandField(models.Field):
# ...
def get_prep_value(self, value):
return "".join(
["".join(l) for l in (value.north, value.east, value.south, value.west)]
)
警告
カスタムフィールドでMySQLの CHAR
、VARCHAR
、TEXT
型を使用する場合は、 get_prep_value()
が常に文字列型を返すようにする必要があります。MySQL はこれらの型に対して整数を指定してクエリを実行したとき、柔軟で想定困難なマッチングを行うため、クエリの結果に予想外のオブジェクトが含まれてしまうことがあります。この問題は get_prep_value()
から常に文字列型を返せば発生しません。
クエリの変数をデータベースの変数に変換する¶
いくつかのデータ型(例えば、日付)はデータベースのバックエンドで使用する前に特定の形式にする必要があります。 get_db_prep_value()
はこれらの変換を行うメソッドです。クエリに使用される特定の接続は connection
パラメータとして渡されます。これにより、必要に応じてバックエンド固有の変換ロジックを使用できます。
例えば、Django は BinaryField
で以下のメソッドを使います:
def get_db_prep_value(self, value, connection, prepared=False):
value = super().get_db_prep_value(value, connection, prepared)
if value is not None:
return connection.Database.Binary(value)
return value
カスタムフィールドを保存する際に、通常のクエリパラメータで使われる変換とは別の特別な変換が必要な場合、 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()
メソッドは次のように書けます:
class HandField(models.Field):
# ...
def formfield(self, **kwargs):
# This is a fairly standard way to set up some defaults
# while letting the caller override them.
defaults = {"form_class": MyFormField}
defaults.update(kwargs)
return super().formfield(**defaults)
これは MyFormField
フィールドクラス (デフォルトのウィジェットを持ちます) のインポートを想定しています。このドキュメントでは、カスタムフォームフィールドの書き方の詳細は説明しません。
組み込みフィールド・タイプのエミュレート¶
もし db_type()
メソッドを作成したのなら、 get_internal_type()
はあまり使わないので気にしなくても大丈夫です。しかし、データベースストレージの型は他のフィールドと似ていることがあるので、正しいカラムを作成するために他のフィールドのロジックを使うこともできます。
例えば:
class HandField(models.Field):
# ...
def get_internal_type(self):
return "CharField"
どのデータベースのバックエンドを使用していても、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
はデータの保存に文字列を使うので、既存の変換コードを再利用できます:
class HandField(models.Field):
# ...
def value_to_string(self, obj):
value = self.value_from_object(obj)
return self.get_prep_value(value)
一般的なアドバイス¶
カスタムフィールドの作成は、特に 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
をサブクラス化する方法の良い例です。できるだけファイル属性をキャッシュします。ファイルはリモートのストレージシステムに保存されている可能性があるため、それらを取得するには、必ずしも必要ではない余分な時間やコストがかかる可能性があります。一度ファイルを検索してその内容に関するデータを取得したら、そのデータをできるだけ多くキャッシュして、その情報を取得するために以後ファイルを検索する回数を減らします。