Form.non_field_errors()を復習する

Djangoのnon_field_errors()を学習した時「フィールドが無いエラー・・・?」と誤解したことがありました(それはno_field_error(s)ですかね)。

実際には「特定のフィールドに関連付けられていない エラーのリスト」ということですが、復習を兼ねて内容をまとめました。

今回は以下の環境で確認しています。

  • Django: 4.2
  • Python: 3.12

non_field_errors()

リファレンスには以下の説明があります。

このメソッドは Form.errors から、特定のフィールドに関連付けられていないエラーのリストを返します。これには、 Form.clean() で発生された ValidationError と、 Form.add_error(None, "...") で追加されたエラーが含まれます。

https://docs.djangoproject.com/ja/4.2/ref/forms/api/#django.forms.Form.non_field_errors より

他のページには以下の説明がありました。

Form.clean() をオーバーライドして発生させた例外は、特定のフィールドに結びつかない点に注意してください。これらは特別な "フィールド" (all と呼ばれます) に格納され、必要に応じて non_field_errors() メソッドを通じてアクセスできます。特定のフィールドにエラーを紐付けて格納したい場合は、add_error() を呼び出す必要があります。

https://docs.djangoproject.com/ja/4.2/ref/forms/validation/#form-and-field-validation より

注: allとなっている箇所は正確には__all__ です。はてなブログのMarkdown記法で変換されているようです。

個人的にはこちらのページの説明の方がわかりやすかったです。

実際にコードを交えてみていきます。

サンプルコード

ユーザーからの問い合わせ用のフォームとして作成しました。

clean()メソッドでメールアドレスと確認用メールアドレスが一致しているかを確認しています。

class UserInquiryForm(forms.Form):
    email = forms.EmailField(max_length=30)
    confirm_email = forms.EmailField(max_length=30)
    message = forms.CharField(max_length=140)


    def clean(self):
        cleaned_data = super().clean()
        email = cleaned_data.get("email")
        confirm_email = cleaned_data.get("confirm_email")

        if email and confirm_email:
            if email != confirm_email:
                raise forms.ValidationError(message="メールアドレスが一致しません。")

        return cleaned_data

このフォームの挙動をDjangoのshell(or shell_plus)で確認します。

まずは成功例です。

バリデーションに成功(is_valid()がTrue)していることがわかり、エラーも格納されていません。

>>> from tasks.forms import UserInquiryForm
>>> example = {
...     "email": "nibu@example.com",
...     "confirm_email": "nibu@example.com",
...     "message": "タスクが登録できません。画面を開くとエラーになります。",
... }
>>> form = UserInquiryForm(example)
>>> form.is_valid()
True
>>> form.errors
{}
>>> form.non_field_errors()
[]

では続いて、失敗例(non_field_errors())に格納されるケースです。

>>> from tasks.forms import UserInquiryForm
>>> clean_validation_example = {
...     "email": "nibu@example.com",
...     "confirm_email": "nibutan@example.com",
...     "message": "タスクが登録できません。画面を開くとエラーになります。",
... }
>>> form = UserInquiryForm(clean_validation_example)
>>> form.is_valid()
False
>>> form.errors
{'__all__': ['メールアドレスが一致しません。']}
>>> form.non_field_errors()
['メールアドレスが一致しません。']
  • errorsを確認すると、__all__に格納されていること
  • non_field_errors()でアクセスできること

が確認できました。

add_error() でも追加できる

最初のサンプルコードだと、forms.ValidationErrorを利用して例外を発生させていましたが、add_error()を利用することでエラーを追加することができます。

 def clean(self):
     cleaned_data = super().clean()
     email = cleaned_data.get("email")
     confirm_email = cleaned_data.get("confirm_email")
     if email and confirm_email:
         if email != confirm_email:
             self.add_error(field=None, error="メールアドレスが一致しません。")
     return cleaned_data

この時、field=Noneとすることで特定のフィールドに紐づかないようにすることができます。

今回のケースでは一致しているかどうかのチェックだけなので、forms.ValidationErrorで良いと思いますが、他にもチェックするものがあればadd_error()の方が適していそうです。

バリデーション周りは他にも色々な機能がありますが、まずはnon_field_errors()について書いてみました。

また他のところも別の機会に書いていきます。