Edited at

Django の deconstruct と deconstructible について

More than 3 years have passed since last update.


概要


  • 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 が必要


参考