動的にフォームの choices の値を作りたい場合など、フォームの内容のためにクエリーすることはよくあると思います。
でも、フィールドの __init__
でクエリーしてしまうコードを書くとインポート時に実行されてしまうので注意が必要です。
ダメな例
class FoobarChoiceField(forms.ChoiceField): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.choices = [(foobar.name, foobar.title) for foobar in Foobar.objects.all()] class HogeForm(forms.Form): foobar = FoobarChoiceForm()
理由
フィールドの __init__
はインポート時に実行されてしまうので、インポート時に(上記の場合Foobarへの)クエリーが実行されてしまいます。
インポート時にHogeFormが作成されて、FoobarChoiceField.__init__
も実行されます。
choicesがリストの場合、そのままクエリーも実行されてしまいます。
マイグレーション時(DBが作られる前)などに実行されてしまうとFoobarに対応するテーブルがまだ作られていないのでエラーになります。 また、サーバー起動時にクエリーされる場合、DBに変更があってもchoicesの値が変わらなくなってしまいます。
直す例
Django1.8からchoices指定にcallableを渡せるので、callableにしておきましょう。
class FoobarChoiceField(forms.ChoiceField): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.choices = lambda: [(foobar.name, foobar.title) for foobar in Foobar.objects.all()]
もちろん、フィールドを作らない場合はFormでやってもOK。
まぁFormの __init__
は(普通に書く分には)インポート時に呼び出されないので、1.8以前のように __init__
で choices を作るように書いてもOKです。
class HogeForm(forms.Form): foobar = ChoiceForm(choices=lambda: [(foobar.name, foobar.title) for foobar in Foobar.objects.all()])