DjangoでFormのFieldの属性を動的に変更するとか

ModelChoiceFieldの選択肢を動的に変えたいとき、__init__でself.fieldsを参照して入れ替えることができる

class SelectForm(forms.Form):
    item = forms.ModelChoiceField(queryset=Item.objects.none())

    def __init__(self, category, *args, **kwargs):
        super(SelectForm, self).__init__(*args, **kwargs)
        if category:
            qs = Item.objects.filter(category=category)
            self.fields['item'].queryset = qs

ただ同じ方法でなんでも設定できるわけではなくて、例えばIntegerFieldのmin_valueやmax_valueは以下の方法では変更できない。

class PurchaseForm(forms.Form):
    num = forms.IntegerField(min_value=1)

    def __init__(self, item, *args, **kwargs):
        super(PurchaseForm, self).__init__(*args, **kwargs)
        if item:
            self.fields['num'].max_value = item.stock

理由はIntegerFieldの__init__を見ればわかるけど、Fieldのインスタンス変数にmax_valueを保持しているが、実際にチェックに使われるのはMaxValueValidatorに保持させるmax_valueである。

なので、Fieldのmax_valueを入れ替えても、MaxValueValidatorは追加されない。

class IntegerField(Field):
    default_error_messages = {
        'invalid': _('Enter a whole number.'),
    }

    def __init__(self, max_value=None, min_value=None, *args, **kwargs):
        self.max_value, self.min_value = max_value, min_value
        kwargs.setdefault('widget', NumberInput if not kwargs.get('localize') else self.widget)
        super(IntegerField, self).__init__(*args, **kwargs)

        if max_value is not None:
            self.validators.append(validators.MaxValueValidator(max_value))
        if min_value is not None:
            self.validators.append(validators.MinValueValidator(min_value))

max_valueのチェックを動的にかけたい場合は、以下の様にvalidatorsを追加すればできる。

class PurchaseForm(forms.Form):
    num = forms.IntegerField(min_value=1)

    def __init__(self, item, *args, **kwargs):
        super(PurchaseForm, self).__init__(*args, **kwargs)
        if item:
            self.fields['num'].validators.append(validators.MaxValueValidator(item.stock))


いろんなFieldのサブクラスがそれぞれの実装をもってるので、特殊なことをやりたい場合はソースを読む必要がある。

ただこういう書き方を多用するのが、よいことなのかどうか、よくわからない。


とくに上記の例なんかだと、単純にcleanでチェックをかけることができる。

Django1.7だとForm.add_errorが用意されてるので、エラーを特定のフィールドに紐付けるのも簡単にできる。

関連 : Django 1.7aのForm.add_errorを使ってみる - brainstorm


あとで読みやすいようにかければそれが一番いいのだけど。