概要
- Djangoは 1.7 から migration 機能を内蔵するようになった。
- makemigrations で自動で差分を抽出るために、Modelのカスタムフィールドやカスタムバリデーターに細工をする必要がある
- Fieldでは deconstruct メソッドの実装が必要
- Validator では deconstructible デコレータの付与と、 __eq__ メソッドの実装が必要
- 簡単に対応内容をまとめる。
遭遇したエラー
- カスタムバリデーターを作成し makemigrations を実行したら、以下のようなエラーに遭遇した
There are some values Django cannot serialize into migration files.
For more, see https://docs.djangoproject.com/en/1.9/topics/migrations/#migration-serializing
- これを解決しようとした時に、deconstruct の話に辿りついた
カスタムバリデーター
- クラスベースのカスタムバリデーター は 1.6 以前までは下記のように書いていた
# validators.py ----
from django.core.exceptions import ValidationError
class UploadSizeValidator:
def __init__(self, size=None):
if size:
self.size = size
def __call__(self, value):
if value.file.size > self.size:
raise ValidationError('Upload Size Error')
# models.py ----
class Question(models.Model):
question_image = models.ImageField(
upload_to='question_image',
validators=[UploadSizeValidator(size=200)],
default=None
)
- だが、1.7 からは Validatorを下記のように実装しないと上記のエラーが表示される。
from django.core.exceptions import ValidationError
from django.utils.deconstruct import deconstructible
@deconstructible # <- decoratorを付加
class UploadSizeValidator:
def __init__(self, size=None):
if size:
self.size = size
def __call__(self, value):
if value.file.size > self.size:
raise ValidationError('Upload Size Error')
def __eq__(self, other): # <- __eq__ メソッドを実装
return (
isinstance(other, self.__class__) and (self.size == other.size)
)
- これで初めて makemigrations が通る。
- __eq__ はなんのため必要なのかというと、migrationファイルを生成する時の差分比較に利用する。
- 具体的には以下のようなことが起きる。
1. UploadSizeValidator(size=100) をFieldに設定した状態で migration ファイルを作ったとする
2. sizeを 200 に変更して makemigrations を実行する
3. __eq__ メソッド で 変更前(other.size=100)と変更後(self.size=200) の size を比較
4. 一致しないので、AlterField の migration ファイルが生成される
- 実際に生成された migration の内容はこんな感じ
operations = [
migrations.AlterField(
model_name='question',
name='question_image',
field=models.ImageField(
default=None, upload_to='question_image',
validators=[polls.validators.UploadSizeValidator(size=200)]
),
),
- こうすることで、バリデーター の中身も migrate したり, backwards で戻したりできる。
- ちなみに関数ベースのバリデーターは差分比較できるものを持たないので、今までの書き方と一緒。
- validatorsに指定した時に、AlterField が生成されるのみ
def validate_even(value):
if value % 2 != 0:
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
カスタムフィールド
- カスタムフィールドを作る時は、deconstruct メソッドの実装が必要になる。
- deconstruct は 上記の __eq__ と似たような役割を持つ
- つまり Fieldに変更があったかどうかを比較するための情報を返す
- 情報は フィールド名(name), フィールドのパス(path), Field の constructorへの 引数(args)、キーワード引数(kwargs)
class HandField(models.Field):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104
super(HandField, self).__init__(*args, **kwargs)
def deconstruct(self): # <- deconstruct
name, path, args, kwargs = super(HandField, self).deconstruct()
del kwargs["max_length"]
return name, path, args, kwargs
# refs https://docs.djangoproject.com/ja/1.9/howto/custom-model-fields/#field-deconstruction
まとめ
- カスタムバリデータと、カスタムフィールドを作る特は細工が必要
- カスタムストレージクラスを作る時も deconstructible が必要