破棄されたブログ

このブログは破棄されました。

Django の Form のバリデーションについて整理する

この記事は、 2015 tech-yuruyuru アドベントカレンダー 5 日目の記事です。

Django の Form には、色々なバリデーションの種類が存在します。

色々な種類に分かれている以上、それぞれに役割が存在しますし、 適切な場所にバリデーションを書かないと、そもそも呼び出してもらえなかったりします。

呼び出し階層

Django 1.8 における Form のバリデーションは、 次のような呼び出し階層になっています

  1. forms.forms.BaseForm.is_valid:: self -> bool
    1. forms.forms.BaseForm.errors:: self -> ErrorDict
      1. forms.forms.BaseForm.full_clean:: self -> void
        1. forms.forms.BaseForm._clean_fields:: self -> void

          • すべてのフィールドについて、バリデーションを実行する
          • バリデータが ValidationError を投げた場合、 except して self.add_error(<fieldname>, ValidationError) する

          • raises ValidationError forms.fields.Field.clean:: str -> a

            1. raises ValidationError forms.fields.Field.to_python:: str -> a

              • Python のデータ型 a に変換できれば、変換した値を返す
              • 変換できなければ ValidationError を投げる
            2. raises ValidationError forms.fields.Field.validate:: a -> void

              • to_python:: str -> a で変換された a 型を受け取る
              • Field でのバリデーションを行う
              • バリデーションエラーがあれば、 ValidationError を投げる
            3. raises ValidationError forms.fields.Field.run_validators:: a -> void

              • self.validators ([a -> void]) を全て実行する
              • self.validators 内のいくつかのバリデータが ValidationError を投げた場合、 ひとつの ValidationError にまとめて投げ直す

              • raises ValidationError a -> void

          • raise ValidationError clean_<fieldname>:: self -> a

            • BaseForm のサブクラスが clean_<fieldname> というメソッドを 実装していた場合、実行される
        2. forms.forms.BaseForm._clean_form:: self -> void

          1. raises ValidationError forms.forms.BaseForm.clean:: self -> cleaned_data
            • 複数フィールドにまたがって行う必要があるバリデーションを実装する
            • 返り値が None なければ self.cleaned_data に設定される
            • フィールドのバリデーションで発生したエラーは、 self.errors から取得できる
        3. forms.forms.BaseForm._post_clean:: self -> void

変な表記の仕方のをしてしまいましたが

  • raises <ExceptionType> <Function-or-Method> は、 <Function-or-Method><ExceptionType> を raise するかも
  • <Function-or-Method>:: a -> b は、 型アノテーションみたいなやつ

という意味です

バリデーションの流れ

  1. フィールドのバリデーション
  2. フォームのバリデーション

という順序でバリデーションが実行されます。

class SomeForm(Form):
    # ...
    def clean_somefield(self):
        # フィールドごとのバリデーション

    def clean(self):
        # フィールドをまたいだバリデーション

フィールドのバリデーション

フィールのバリデーション大まかに、

  1. Field のバリデーション
  2. Form の clean_<fieldname>()

の 2 つがあります。

clean_<fieldname>() は、 Field を実装する必要がなく実装が楽ではありますが、 Field のバリデーションに失敗すると実行されないことに注意してください。

validators

Field には、 validators という便利な機能があります。

Field のコンストラクタに、バリデーション関数のコレクションを渡すだけで、 フィールドのバリデーションを設定できます。

def validate_today(value, today=None):
    if today is None:
        today = datetime.date.now()

    if value != today:
        raise ValidationError("Not today")


class SomeForm(Form):
    a_field = DateField(validators=[validate_today])

このバリデーション関数は、 フィールドの値を受け取り値の妥当性を検証し、 問題があれば ValidationError を raise するというシンプルな実装で済みます。

バリデータの再利用が可能というメリットはもちろんですが、 ただの関数であるため、 Form のインスタンスを生成せずにバリデータのテストが書ける というメリットもあります。

一方で、 Form の状態に依存するバリデーションを実装することはできませんし、 Field.clean() でバリデーションに失敗するとやはり validators は呼び出されない ので注意が必要です

フォームのバリデーション

フォームのバリデーションには、 フィールドをまたぐようなバリデーション処理を実装します

どこにバリデーションを書くか

例えば、以下のような指針が考えられます

  • Form の状態に依存しないフィールドのバリデーション: Field.validators
  • Form の状態に依存するフィールドのバリデーション: Form.clean_<fieldname>()
  • フィールドをまたいだバリデーション: Form.clean()

絶対ではありませんし、もちろんプロジェクトのアーキテクチャが優先にはなりますが、 それぞれのバリデータがどういった目的で存在するのかを整理しておくと、 Form や Field のバリデーションを実装するときに迷わずに済むのではないでしょうか。

広告を非表示にする