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
あとで読みやすいようにかければそれが一番いいのだけど。