6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-01-27

背景

前回「Django2で動的にformを作る」でも回答がシングルとなるようなCharFieldChoiceFieldなどは対応できていますが
回答が複数となるような値。つまりMultipleChoiceFieldには対応できていませんでした。

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

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

views.py

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リテラシーのある方には良いのでしょうが
一般受けするであろうチェックボックスタイプCheckboxSelectMultiplewidgetに指定しています。

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の修正

今回は登録画面、確認画面を用意したのでテンプレートもそれに合わせて修正します。

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

  {% 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

登録画面に値を入力してSubmitします。
02.png

確認画面はこんな感じ
03.png

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

<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>
6
7
0

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
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?