Django2で動的にformを作る2-MultipleChoiceFieldに対応させる-

# 背景
前回「[Django2で動的にformを作る](https://qiita.com/imp555sti/items/4aaf4c24146d21e2b53a)」でも回答がシングルとなるような`CharField`や`ChoiceField`などは対応できていますが
回答が複数となるような値。つまり`MultipleChoiceField`には対応できていませんでした。

eclipseのDEBUGで見ると、`request.POST`自体には値が複数入っているんですが
`request.POST['変数名']`とすると最後の値のみ取得されてしまうんですよね…。

また入力フォームって入力画面→確認画面→登録って流れが多いですよね。
それらに対応しようという訳で、前回のものをアップデートしていきます。

# views.py

```python: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を定義しました。

```python
    # 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オブジェクトを生成していきます。

```python
    # 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などの複数回答を取得する部分を記述します。

```python
    # 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`を代入し、それによって確認画面の識別をする設計です。
今回は説明のため登録画面ははぶいて初期フォーム画面に戻りますけどね(笑)

# 登録確認画面の実装
登録確認画面には設問とその回答を画面表示させたいですよね。
その部分の処理は以下の様に実装しました。

```python
                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の修正
今回は登録画面、確認画面を用意したのでテンプレートもそれに合わせて修正します。

```html:dynamic.html
<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'`つまり確認画面のテンプレートとなります。

```html
  {% 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が増えます。

# 実行画面

色気はありませんが、こんな感じで登録画面→確認画面が表示されます。

![01.png](https://qiita-image-store.s3.amazonaws.com/0/324255/d922d707-504b-47a8-3656-4af19907ff58.png)

登録画面に値を入力してSubmitします。
![02.png](https://qiita-image-store.s3.amazonaws.com/0/324255/b3046c23-2370-6add-b9c3-abf4016799cf.png)

確認画面はこんな感じ
![03.png](https://qiita-image-store.s3.amazonaws.com/0/324255/86f0ac91-80df-3be4-cbc5-bb6701190b2e.png)

確認画面のソースはこんな感じになります。

```html
<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>&lt;script&gt;alert(&#39;test&#39;)&lt;/script&gt;</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="&lt;script&gt;alert(&#39;test&#39;)&lt;/script&gt;" 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>
```