2
1

More than 3 years have passed since last update.

Django2 Form(フォーム)のBound/Unboundと初期値の関係

Last updated at Posted at 2020-05-04

前提

Djangoの基本的な機能を一通り使ったことがある程度の初心者向けです。

環境

  • Windows 10
  • Python 3.7.x
  • Django 2.2.x

はじめに

フォームを使う上でハマりがちだと感じる初期値設定の説明です。なお、説明では Form / ModelForm の違いを明確に区別しない場合があります。

フォームを使うイメージ

まずは、フォームの大雑把なイメージを図示します。
フォームイメージ1.png

フォームの入力

次に、フォームの主要な入力項目(__init__()メソッドの引数の項目)まで表します。
フォームイメージ2.png

各入力項目が何に対応するかを以下に示します。

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 の差異など無視した「優先度のイメージ」のようなものです)

  1. data (request.GET, request.POST)
  2. initial
  3. instance
  4. フォームフィールド初期値
    ⇒ フォームフィールドの定義において「some_field = forms.CharField(initial="init value")」 のように設定する値のこと

なお、ModelFormにおいて、Modelのdefault値がフォームフィールドのinitialにりそうに思えますがならないようです。

おわりに

「Bound / Unbound」と「初期値設定」の部分は想像できそうで出来ない部分なので、少し理解を深めておくと良いです。

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1