DjangoのFormSetで各フォームをまたがったバリデーションを行う
どうやるのかなと思って調べたときのメモ
例えば複数のブックマークを一括で入力できる画面を想定して、こんなFormとFormSetがある場合
from django.forms.formsets import formset_factory from django import forms class BookmarkForm(forms.Form): title = forms.CharField(max_length=100) url = forms.CharField(max_length=100) BookmarkFormSet = formset_factory(BookmarkForm, extra=1, max_num=100)
同じurlのブックマークが入力された場合にエラーにする、というのは各FormのバリデーションではできないのでFormSetの仕事になる。
FormSetにバリデーションの処理をさせるにはBaseFormSetを継承したクラスにcleanメソッドを実装し、formset_factoryの引数として渡す。
from django.forms.formsets import formset_factory from django import forms class BookmarkForm(forms.Form): title = forms.CharField(max_length=100) url = forms.CharField(max_length=100) class BaseBookmarkFormSet(BaseFormSet): def clean(self): url_list = [form['url'].value() for form in self.forms] if len(url_list) > len(set(url_list)): raise forms.ValidationError, u'duplicate url' BookmarkFormSet = formset_factory(BookmarkForm, formset=BaseBookmarkFormSet, extra=1, max_num=100)
Formsetのcleanメソッドは、各Formのバリデーションが全て実行されたあとに実行される。
エラーはnon_form_errorsメソッドで取得できる。
>>> data = {'form-TOTAL_FORMS': u'2',
'form-INITIAL_FORMS': u'2',
'form-0-title': '',
'form-0-url': 'http://www.yahoo.co.jp',
'form-1-title': u'Yahoo! Japan',
'form-1-url': 'http://www.yahoo.co.jp',
}
>>> fs = BookmarkFormSet(data)
>>> fs.is_valid()
False
>>> fs.errors
[{'title': [u'This field is required.']}, {}]
>>> fs.non_form_errors()
[u'duplicate url']