前提
Djangoの基本的な機能を一通り使ったことがある程度の初心者向けです。
環境
- Windows 10
- Python 3.7.x
- Django 2.2.x
はじめに
フォームを使う上でハマりがちだと感じる初期値設定の説明です。なお、説明では Form / ModelForm の違いを明確に区別しない場合があります。
フォームを使うイメージ
フォームの入力
次に、フォームの主要な入力項目(__init__()メソッドの引数の項目)まで表します。
各入力項目が何に対応するかを以下に示します。
- data
- request.GET, request.POST
- files
- request.FILES
- instance
- モデルインスタンス/dd>
- initial
- 初期値を設定した辞書
Bound / Unbound とは
公式ドキュメントに「Bound/Unboundの区別が重要」との記述がありますが、その通りここを押さえておかないとフォームの挙動が理解しにくいです。Bound/Unboundの定義は以下の通りです。
- Boundフォーム
- request.GET、request.POST、request.FILESのいずれかを設定したフォーム
- Unboundフォーム
- request.GET、request.POST、request.FILESのいずれも設定していないフォーム
これは、BaseFormクラスのソースコードからもわかります。
# BaseForm クラスのコードの一部
self.is_bound = data is not None or files is not None
実施に、どのような引数でフォームをインスタンス化するとBound(またはUnbound)になるのか、いくつか例を示します。
# Bound/Unbound フォームの例
form_bound_1 = SomeForm(request.GET)
form_bound_2 = SomeForm(request.POST)
form_bound_3 = SomeForm(request.POST, request.FILES)
form_unbound_1 = SomeForm()
form_unbound_2 = SomeForm(initial={"some_field": "some_valule"})
form_unbound_3 = SomeModelForm(initial={"some_field": "some_valule"}, instance=some_instance)
form_bound_4 = SomeModelForm(request.POST, initial={"some_field": "some_valule"}, instance=some_instance)
Bound / Unboundと初期値の関係
これまで見てきた「フォームの主要な入力項目」と「Bound/Unbound」が、初期値の設定に関わります。フォームには1つまたは複数の入力項目、つまりフィールドが定義されていますが、その各フィールドがHtmlマークアップをレンダーするために値を取得するコードを見てみましょう。
# BoundField クラスのメソッド
def value(self):
"""
Return the value for this BoundField, using the initial value if
the form is not bound or the data otherwise.
"""
data = self.initial
if self.form.is_bound:
data = self.field.bound_data(self.data, data)
return self.field.prepare_value(data)
上記から、Boundフォームのフィールドの値はinitialが使われない(上書きされる)ことがわかります。また、request.GET 等が空(≠None)の場合でもBoundフォームになることには注意が必要です。
対策
検索フォームを作ったり、Ajaxを使って1画面であれこれするような場合、以下のように分岐することで初期値設定を有効にできます。(コードはあくまで一例です。)
if "some_field" in request.GET:
form = SomeForm(request.GET)
else:
form = SomeForm(initial={"some_field": "some_value"})
モデル作成・モデル更新をそれぞれ個別のビューで実行しているうちは問題は起こらないのですが、応用し始めるとハマることがあります。
値の優先順
そのほか、フォームフィールドの値は以下の優先順で設定されることにも注意が必要です。
(正確には、Bound/Unbound の差異など無視した「優先度のイメージ」のようなものです)
- data (request.GET, request.POST)
- initial
- instance
- フォームフィールド初期値
⇒ フォームフィールドの定義において「some_field = forms.CharField(initial="init value")」 のように設定する値のこと
なお、ModelFormにおいて、Modelのdefault値がフォームフィールドのinitialにりそうに思えますがならないようです。
おわりに
「Bound / Unbound」と「初期値設定」の部分は想像できそうで出来ない部分なので、少し理解を深めておくと良いです。