cleanメソッドでエラーをパラメータにひもづける

djangoのForm、cleanメソッドでValidationErrorをraiseすると、エラーメッセージは__all__キーに保存されるので、テンプレートで表示するときは

{{ form.non_field_errors }}

で表示します。

cleanは複数フィールドの組み合わせ条件をテストするんで、フィールドに結びつかない(non_field)なエラーってことですね。



しかしエラーをどれかのフィールドに結び付けたいときはどうすればいいんでしょうか


例えばパスワードと確認用のパスワードを入力させるフォームで、パスワードが一致しているかは複数フィールドにまたがるチェックになるんでcleanでチェックしますが、
エラーとしては確認用のパスワードが間違ってることにしたい、みたいなとき


djangosnippetにそれっぽいのが見つかったので試してみました。



import文を追加したりとか、微妙に修正してます。

formutils.py

from django.forms.util import ErrorList


def errors_append(form, field_name, message):
    u"""
    Add an ValidationError to a field (instead of __all__) during Form.clean():

    class MyForm(forms.Form):
        def clean(self):
            value_a=self.cleaned_data['value_a']
            value_b=self.cleaned_data['value_b']
            if value_a==... and value_b==...:
                formutils.errors_append(self, 'value_a', u'Value A must be ... if value B is ...')
            return self.cleaned_data
    """
    error_list = form.errors.get(field_name)
    if error_list is None:
        error_list = ErrorList()
        form.errors[field_name] = error_list
    error_list.append(message)

forms.py

# coding=utf-8
from django import forms
from apps import formutils


class MyForm(forms.Form):
    password = forms.CharField(widget=forms.PasswordInput())
    password_confirm = forms.CharField(widget=forms.PasswordInput())

    def clean(self):
        password = self.cleaned_data['password']
        password_confirm = self.cleaned_data['password_confirm']
        if password and password_confirm and password != password_confirm:
            formutils.errors_append(self, 'password_confirm', u'パスワードが一致しないでござる')
        return self.cleaned_data        
>>> from apps.forms import *
>>> f = MyForm({'password': 'a', 'password_confirm': 'b'})
>>> MyForm({'password': 'a', 'password_confirm': 'b'})
<apps.forms.MyForm object at 0x101edc890>
>>> f.errors
{'password_confirm': [u'\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u4e00\u81f4\u3057\u306a\u3044\u3067\u3054\u3056\u308b']}


意図したとおりに動いてるっぽい


参考 : Django snippets: Add ValidationError to a field instead of __all__ during Form.clean()