背景
DjangoのFormクラス、あるいはFlaskのWTFormsは柔軟な故使いこなすのに苦労する割に情報も少ない。
そこでこちらの記事では実際に使ってみながら機能を確認していく。
今回はDjangoのFormクラスを利用する。
フォームを文字列で出力してみる
実装
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField()
def index(request):
return HttpResponse(str(ContactForm()))
表示
出力されるのはinput fieldのみでform要素やsubmit要素などは出力されない
<input type="text" name="subject" required="" id="id_subject">
内部実装
BaseForm::__str__
を経て BaseForm::as_table
(HTML table要素としてフォームを出力するメソッド)を呼び出している
フォームインスタンスに格納されたデータを見てみる
実装
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField()
age = forms.IntegerField()
def index(request):
form = ContactForm()
params={
'form': form
}
return render(request, 'index.html', params)
<div>
{{form.data}}
</div>
<div>
{{form.fields}}
</div>
表示
{}
OrderedDict([('subject', <django.forms.fields.CharField object at 0x104ab1f98>), ('age', <django.forms.fields.IntegerField object at 0x104aca080>)])
内部実装
- dataは初期化時に引数を見て、無ければ空のdictが入る
- fieldsは初期化時にOrderedDictとして作られる。引数で並び替えをすることもできる
データを初期化して色々出してみる
実装
data = {'subject': 'hello',
'age': 21}
def index(request):
form = ContactForm(data)
params={
'form': form
}
return render(request, 'index.html', params)
<div>
{{form.data}}
</div>
<div>
{{form.fields}}
</div>
<div>
{{form}}
</div>
<div>
{{form.subject}}
</div>
<div>
{{form.age}}
</div>
出力
メモ
インタラクティブシェルでの動作
>>> form.fields['age']
<django.forms.fields.IntegerField object at 0x10d906e10>
>>> str(form.fields['age'])
'<django.forms.fields.IntegerField object at 0x10d906e10>'
>>> form['age']
<django.forms.boundfield.BoundField object at 0x10d906fd0>
>>> str(form['age'])
'<input type="number" name="age" value="21" required id="id_age">'
boundfieldは
https://docs.djangoproject.com/en/2.2/ref/forms/api/#more-granular-output
POSTできるようにする
ドキュメントより
以下のようにformタグとsubmit要素は自前で実装する
<form action="/your-name/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
viewsの実装
Djangoの場合Viewクラスを使っていい感じに書くこともできるがミニマルに実装するならこんな風に出し分ける
def index(request):
if request.method == 'POST':
form = ContactForm(request.POST)
# 中略
else:
form = ContactForm(request.GET or None)
# 中略
request.GET request.POSTの内容
def index(request):
print(request.GET)
print(request.POST)
GET時
<QueryDict: {}>
<QueryDict: {}>
POST時
<QueryDict: {}>
<QueryDict: {'csrfmiddlewaretoken': ['<ハッシュ>'], 'subject': ['hello'], 'age': ['21']}>
request.GET
型でリクエストした場合は form.data
がQueryDict型で初期化される。
request.POST
は要するにリクエストペイロードが含まれる
QueryDict
dict型を継承した MultiValueDict
をさらに継承したもの
immutableなので変更できない
def index(request):
if request.method == 'POST':
form = ContactForm(request.POST)
form.data['age'] = 103 # こういうことをするとエラーになる
Boundfieldのデータを書き換えることもできない
form['age'].data = 103 # これもエラー
form.fields['age'].label = '年齢' # これはOK
form.fields['age'].initial = 100 # エラーにはならないが値は反映されない
ラベルのアップデート: バウンドされたfieldに定義してもfieldsオブジェクトに紐づいたものでもOK。両方書いた場合はBoundFieldsが優先される。
def index(request):
...
form['age'].label = '年齢1'
form.fields['age'].label = '年齢2'
...
# 年齢1が表示される
form.fields
と BoundField
の関係
Boundfield
が form.field
への参照を持っているような形。
>>> form['age'].field
<django.forms.fields.IntegerField object at 0x10d906e10>
>>> form.fields['age']
<django.forms.fields.IntegerField object at 0x10d906e10>
同じオブジェクトを参照していることを確認。
まとめ的なもの
- form: form周りのいろんな役割をするクラス
- form.data: フォームのデータを格納。初期化時に定義し書き換え不可。
- request.POST: リクエストデータが格納されて、これをformの引数として渡す。
- form.fields: フィールドオブジェクトが格納された配列。
- form.<キー名>: BoundField。formにバインドされたfield情報。加えてフィールドオブジェクトへの参照も持つ