LoginSignup
3
3

More than 5 years have passed since last update.

Django の deconstruct と deconstructible について

Last updated at Posted at 2016-07-04

概要

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

参考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3