背景
前回「Django2で動的にformを作る」でも回答がシングルとなるようなCharField
やChoiceField
などは対応できていますが
回答が複数となるような値。つまりMultipleChoiceField
には対応できていませんでした。
eclipseのDEBUGで見ると、request.POST
自体には値が複数入っているんですが
request.POST['変数名']
とすると最後の値のみ取得されてしまうんですよね…。
また入力フォームって入力画面→確認画面→登録って流れが多いですよね。
それらに対応しようという訳で、前回のものをアップデートしていきます。
views.py
from django.shortcuts import render
from .forms import EnqForm
from django import forms
# Create your views here.
def dynamic(request):
context = {}
content = {}
form_item = {}
qs = []
# define enquete fields
qs.append( {'title':'title1', 'description': 'note1', 'type': 'text' , 'required': False} )
qs.append( {'title':'title2', 'description': 'note2', 'type': 'text' , 'required': True} )
qs.append( {'title':'title3', 'description': 'note3', 'type': 'radio' , 'required': False} )
qs.append( {'title':'title4', 'description': 'note4', 'type': 'multi' , 'required': True} )
# create enquete form objects
no = 0
for q in qs:
if q['type'] == 'text':
form_item.update( { ('ans%d' % no): forms.CharField(label=q['title'], label_suffix=q['description'], required=q['required'], max_length=256) } )
elif q['type'] == 'radio':
form_item.update( { ('ans%d' % no): forms.ChoiceField(label=q['title'], label_suffix=q['description'], required=q['required'], choices=(('a','a'),('b','b'),('c','c'),('d','d'))) } )
elif q['type'] == 'multi':
form_item.update( { ('ans%d' % no): forms.MultipleChoiceField(label=q['title'], label_suffix=q['description'], required=q['required'], choices=(('a','a'),('b','b'),('c','c'),('d','d')), widget=forms.CheckboxSelectMultiple) } )
no += 1
# create dynamic form
DynamicEnqForm = type('DynamicEnqForm', (EnqForm,), form_item )
# get enquete answers
if request.method == 'POST':
formset = DynamicEnqForm(request.POST or None)
if formset.is_valid():
for key in formset.cleaned_data:
if key != 'csrfmiddlewaretoken' and key != 'method':
content[key] = formset.cleaned_data[key]
if 'method' in request.POST and request.POST['method'] == 'regist':
# regist
pass
else:
# confirm
context['method'] = 'confirm'
context['qs'] = []
f_items = form_item['declared_fields']
for item in f_items:
if type(content[item]) == type([]):
context['qs'].append({'title': f_items[item].label, 'description': f_items[item].label_suffix, 'answer': ','.join(content[item])})
else:
context['qs'].append({'title': f_items[item].label, 'description': f_items[item].label_suffix, 'answer': content[item]})
# draw enquete form
DynamicForm = DynamicEnqForm(content)
context['enq_form'] = DynamicForm
return render(request, "dynamic.html", context)
MultipleChoiceFieldに対応させるため初期データとしてmultiを定義しました。
# define enquete fields
qs.append( {'title':'title1', 'description': 'note1', 'type': 'text' , 'required': False} )
qs.append( {'title':'title2', 'description': 'note2', 'type': 'text' , 'required': True} )
qs.append( {'title':'title3', 'description': 'note3', 'type': 'radio' , 'required': False} )
qs.append( {'title':'title4', 'description': 'note4', 'type': 'multi' , 'required': True} )
これらを読み込みFieldオブジェクトを生成していきます。
# create enquete form objects
no = 0
for q in qs:
if q['type'] == 'text':
form_item.update( { ('ans%d' % no): forms.CharField(label=q['title'], label_suffix=q['description'], required=q['required'], max_length=256) } )
elif q['type'] == 'radio':
form_item.update( { ('ans%d' % no): forms.ChoiceField(label=q['title'], label_suffix=q['description'], required=q['required'], choices=(('a','a'),('b','b'),('c','c'),('d','d'))) } )
elif q['type'] == 'multi':
form_item.update( { ('ans%d' % no): forms.MultipleChoiceField(label=q['title'], label_suffix=q['description'], required=q['required'], choices=(('a','a'),('b','b'),('c','c'),('d','d')), widget=forms.CheckboxSelectMultiple) } )
no += 1
MultipleChoiceField
の場合、デフォルトのSelectBoxでCtrlキーを押しながら複数選択もITリテラシーのある方には良いのでしょうが
一般受けするであろうチェックボックスタイプCheckboxSelectMultiple
をwidget
に指定しています。
Multipleな値を取り出す
ここで前回対応できていなかったMultipleChoiceFieldなどの複数回答を取得する部分を記述します。
# get enquete answers
if request.method == 'POST':
formset = DynamicEnqForm(request.POST or None)
if formset.is_valid():
for key in formset.cleaned_data:
if key != 'csrfmiddlewaretoken' and key != 'method':
content[key] = formset.cleaned_data[key]
form.cleaned_data
から値を取得することで複数選択の場合はlist型で値が取得できます。
method
にはconfirm
を代入し、それによって確認画面の識別をする設計です。
今回は説明のため登録画面ははぶいて初期フォーム画面に戻りますけどね(笑)
登録確認画面の実装
登録確認画面には設問とその回答を画面表示させたいですよね。
その部分の処理は以下の様に実装しました。
context['method'] = 'confirm'
context['qs'] = []
f_items = form_item['declared_fields']
for item in f_items:
if type(content[item]) == type([]):
context['qs'].append({'title': f_items[item].label, 'description': f_items[item].label_suffix, 'answer': ','.join(content[item])})
else:
context['qs'].append({'title': f_items[item].label, 'description': f_items[item].label_suffix, 'answer': content[item]})
context['qs']
は、質問内容、その説明、回答を登録しています。
ここで回答が複数存在する場合は,
区切りで表示できるようにしました。
templateの修正
今回は登録画面、確認画面を用意したのでテンプレートもそれに合わせて修正します。
<form action="" method="POST">
{% csrf_token %}
{% if method == 'confirm' %}
<div>
<div>
{% for item in qs %}
<p>{{ item.title }}</p>
<p>{{ item.description}}</p>
<p>{{ item.answer}}</p>
{% endfor %}
{% for field in enq_form %}
{{ field.as_hidden }}
{% endfor %}
<input type="hidden" name="method" value="regist" />
</div>
</div>
{% else %}
{% for field in enq_form.visible_fields %}
<div>
<div>
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
<p>{{ field.field.label_suffix }}</p>
{{ field }}
</div>
</div>
{% endfor %}
{% endif %}
<input type="submit">
</form>
また横着して必要な部分しか記載していませんが…
上半分がmethod == 'confirm'
つまり確認画面のテンプレートとなります。
{% if method == 'confirm' %}
<div>
<div>
{% for item in qs %}
<p>{{ item.title }}</p>
<p>{{ item.description}}</p>
<p>{{ item.answer}}</p>
{% endfor %}
{% for field in enq_form %}
{{ field.as_hidden }}
{% endfor %}
<input type="hidden" name="method" value="regist" />
</div>
</div>
{% else %}
登録確認画面では画面に表示するとともにhiddenタグで回答を次画面へ引き継ぎます。
用途に応じてはSessionを使う場合もあるとは思いますが、ブラウザの開き方で同一SessionIDで違う画面を出されるととっても困りますのでhiddenタグとしてます。
通常のformのfieldをhiddenとして表示するにはwidget
を明示的に指定する方法もありますが
field.as_hidden
の方が簡単ですね。
MultipulCoiceFieldの場合は選択された数だけhiddenが増えます。
実行画面
色気はありませんが、こんな感じで登録画面→確認画面が表示されます。
確認画面のソースはこんな感じになります。
<form action="" method="POST">
<input type="hidden" name="csrfmiddlewaretoken" value="Muw2LlRNEdNS9k2ARWF9zKrFAliT2tn6meqTkDbFYHjyrSdPhzJMJIky3LzJ7zSy">
<div>
<div>
<p>title1</p>
<p>note1</p>
<p>aaa</p>
<p>title2</p>
<p>note2</p>
<p><script>alert('test')</script></p>
<p>title3</p>
<p>note3</p>
<p>c</p>
<p>title4</p>
<p>note4</p>
<p>b,c,d</p>
<input type="hidden" name="ans0" value="aaa" id="id_ans0">
<input type="hidden" name="ans1" value="<script>alert('test')</script>" id="id_ans1">
<input type="hidden" name="ans2" value="c" id="id_ans2">
<input type="hidden" name="ans3" value="b" id="id_ans3_0"><input type="hidden" name="ans3" value="c" id="id_ans3_1"><input type="hidden" name="ans3" value="d" id="id_ans3_2">
<input type="hidden" name="method" value="regist" />
</div>
</div>
<input type="submit">
</form>