"BOKU"のITな日常

BOKUが勉強したり、考えたことを頭の整理を兼ねてまとめてます。

チェックボックスとラジオボタン。初期化と値の受取など。/Django3.

f:id:arakan_no_boku:20190320212948j:plain

今回は「CheckBox」と「RadioButton」です。

環境は Windows + anaconda です。

目次

MultipleChoiceField」一択でやります

3パターンのFieldを使い分ける方法がよく紹介されてます。

などですが、いちいち使い分けるのが面倒くさいので、自分はチェックボックスも、Radioボタンも、チェックボックスの複数選択もまとめて「MultipleChoiceField」一択でやってます。

チェックボックスラジオボタンを使うパターンとしては5つあります。

 それぞれの定義の仕方です。

forms.pyに定義します。

パターン1:単一のチェックボックス

かなりの力技なんですが。

class ChkForm(forms.Form):
     labels = ['チェック','複数チェック','ラジオボタン','動的選択肢1','動的選択肢2']
     SINGLE_CHOICE = [('1','OKなららチェック')]

     one = forms.MultipleChoiceField(
          label=labels[0],
          required=False,
          disabled=False,
          initial=[],
          choices=SINGLE_CHOICE,
          widget=forms.CheckboxSelectMultiple(attrs={
               'id': 'one','class': 'form-check-input'}))

複数選択と同じだけど、選択肢をひとつだけ渡せば「単一チェックだよ」という理屈。

値を受け取る側のロジックをシンプルにできるメリットがあります。

パターン2:複数選択可能なチェックボックス(選択肢は静的)
class ChkForm(forms.Form):
     labels = ['チェック','複数チェック','ラジオボタン','動的選択肢1','動的選択肢2']
     CHOICE = [
          ('1','選択肢<1>'),
          ('2','選択肢<2>'),
          ('3','選択肢<3>')]
     
     two = forms.MultipleChoiceField(
          label=labels[1],
          required=False,
          disabled=False,
          initial=[],
          choices=CHOICE,
          widget=forms.CheckboxSelectMultiple(attrs={
              'id': 'two','class': 'form-check-input'})) 

Selectと同じで「キー、バリュー」のタプルのりすとを、choicesに渡して選択肢を設定します。

initial=[]なら、すべて未選択状態でチェックボックスを表示します。

CSSはBootstrap4を使う前提なので、「'class': 'form-check-input'」の指定が必要です。

これを忘れると、きれいに表示されません。 

パターン3:複数選択可能なチェックボックス(選択肢は動的)
class ChkForm(forms.Form):
     labels = ['チェック','複数チェック','ラジオボタン','動的選択肢1','動的選択肢2']

     four = forms.MultipleChoiceField(
          label=labels[3],
          required=False,
          disabled=False,
          widget=forms.CheckboxSelectMultiple(attrs={
               'id': 'four','class': 'form-check-input'}))

上記の2つとの違いは、initialとchoicesがないこと。

選択肢と初期状態(選択・非選択)は、views.pyの中で実行時に渡します。

DBから値をとってきてセットするイメージです。 

パターン4:ラジオボタン(Radio)(選択肢は静的す) 
class ChkForm(forms.Form):
     labels = ['チェック','複数チェック','ラジオボタン','動的選択肢1','動的選択肢2']
     CHOICE = [
          ('1','選択肢<1>'),
          ('2','選択肢<2>'),
          ('3','選択肢<3>')]
     
     three = forms.MultipleChoiceField(
          label=labels[2],
          required=False,
          disabled=False,
          initial=['2'],
          choices=CHOICE,
          widget=forms.RadioSelect(attrs={
               'id': 'three','class': 'form-check-input'}))
    

チェックボックスとの違いは、RadioSelectを使うところだけです。 

パターン5:ラジオボタン(Radio)(選択肢は動的す) 
class ChkForm(forms.Form):
     labels = ['チェック','複数チェック','ラジオボタン','動的選択肢1','動的選択肢2']

     five = forms.MultipleChoiceField(
          label=labels[4],
          required=False,
          disabled=False,
          widget=forms.RadioSelect(attrs={
               'id': 'five','class': 'form-check-input'}))

なので。

動的に選択肢と初期状態(選択)を渡すパターンは、choicesとinitialがないだけです。 

動的に選択肢等を渡す&選択した値取得

まずは、ソースコードから。

views.pyです。

from django.http.response import HttpResponse
from django.shortcuts import render, render_to_response
from django.contrib.staticfiles.templatetags.staticfiles import static
from . import forms
from django.template.context_processors import csrf

def demo3(request):
    labels = ['チェック','複数チェック','ラジオボタン','動的選択肢1','動的選択肢2']
    # 入力結果を格納する辞書
    results = {}
    radios = {}
    ret = ''
    if request.method == 'POST':
        # 入力されたデータの受取
        results[labels[0]] = request.POST.getlist("one")
        results[labels[1]] = request.POST.getlist("two")
        results[labels[2]] = request.POST.getlist("three")
        results[labels[3]] = request.POST.getlist("four")
        results[labels[4]] = request.POST.getlist("five")
        ret = 'OK'
        c = {'results': results,'ret':ret}
    else:    
        form = forms.ChkForm()
        choice1 = []
        choice1.append(('1','動的選択肢1'))
        choice1.append(('2','動的選択肢2'))
        choice1.append(('3','動的選択肢3'))
        choice1.append(('4','動的選択肢4'))
        form.fields['four'].choices = choice1
        form.fields['four'].initial = ['2']    
        form.fields['five'].choices = choice1
        form.fields['five'].initial = ['3']    
        c = {'form': form,'ret':ret}
        # CFRF対策(必須)
        c.update(csrf(request))
    return render(request,'demo03.html',c)

ポイントを補足します。

ポイント1:動的に選択肢と初期状態を渡す 

この部分です。

form = forms.ChkForm()
choice1 =
choice1.append(('1','動的選択肢1'))
choice1.append(('2','動的選択肢2'))
choice1.append(('3','動的選択肢3'))
choice1.append(('4','動的選択肢4'))
form.fields['four'].choices = choice1
form.fields['four'].initial = ['2']
form.fields['five'].choices = choice1 

 選択肢のリストを生成して、choicesとinitialにセットしてます。

initial=['2']だと、2番目の「動的選択肢2」にチェックだけにチェックがつきます。

ポイント2:選択された値の受取 

この部分です。

results[labels[0]] = request.POST.getlist("one")

results[labels[1]] = request.POST.getlist("two")

results[labels[2]] = request.POST.getlist("three")

results[labels[3]] = request.POST.getlist("four")

results[labels[4]] = request.POST.getlist("five")

基本、Multipleなので、全部getlistで受け取るわけです。

チェックが一つもついてなければ(空のリスト}が返ります。

チェックがついていれば、そのキーが返ります。

['1']  とか、['2','3']みたいな感じで。

なので、単一チェックでも複数チェックでもラジオボタンでも、みんな同じロジックで処理ができるわけで・・これが、自分がこのやり方を気に入っている最大の理由です。

注意点:MultipleChoiceFieldのレイアウトの癖

 DjangoのMultipleChoiceFieldは、難儀な癖があります。

そのまま使うと<ul>~</ul>で囲まれてしまうのです。

本家のドキュメントから引用します。

Widgets | Django documentation | Django

Selectと似ていますが、<li>タグ内のラジオボタンのリストとしてレンダリングされます。

<ul>
    <li><input type='radio' name='...'></li>
...
</ul>

これはレイアウトの自由度が制限されてしまうので、実に具合が悪いです。

なので、HTML側で自分でレイアウトしてやる必要があります。

条件分岐に必要な「inputのtype名」を取得する方法はこうです。

 field.field.widget.input_type == "radio"

 チェックボックスなら。

 field.field.widget.input_type == "checkbox"

type名識別を考慮したHTMLの例

以下のようになります。

% for field in form %}
	{% if field.field.widget.input_type == "radio" %}
		<tr>
			<td>{{ field.label_tag }}</td>
			<td>
    			{% for r in field %}
    			{{ r }}</br>
    			{% endfor %}
			</td>
    	</tr>    			
    {% else %}
		<tr>
       		<td>{{ field.label_tag }}</td>
			<td>
    			{% for c in field %}
    				{{ c }}</br>
    			{% endfor %}
			</td>
    	</tr>
    {% endif %}
{% endfor %}

上記では開業で縦並びにしていますが、これをとると横並びになります。

サンプル画面の実行イメージです。

初期表示(縦並びパターン)から。

f:id:arakan_no_boku:20210504220058p:plain

これでチェックボックスを複数選んだりして、送信ボタンをおします。

f:id:arakan_no_boku:20210504220157p:plain

まあ、こんな感じで値を受け取れるということですね。

ソース全文:forms.py 
from django import forms

class ChkForm(forms.Form):
     labels = ['チェック','複数チェック','ラジオボタン','動的選択肢1','動的選択肢2']
     SINGLE_CHOICE = [('1','OKなららチェック')]
     CHOICE = [
          ('1','選択肢<1>'),
          ('2','選択肢<2>'),
          ('3','選択肢<3>')]
     
     one = forms.MultipleChoiceField(
          label=labels[0],
          required=False,
          disabled=False,
          initial=[],
          choices=SINGLE_CHOICE,
          widget=forms.CheckboxSelectMultiple(attrs={
               'id': 'one','class': 'form-check-input'}))

     two = forms.MultipleChoiceField(
          label=labels[1],
          required=False,
          disabled=False,
          initial=[],
          choices=CHOICE,
          widget=forms.CheckboxSelectMultiple(attrs={
              'id': 'two','class': 'form-check-input'}))
     
     four = forms.MultipleChoiceField(
          label=labels[3],
          required=False,
          disabled=False,
          widget=forms.CheckboxSelectMultiple(attrs={
               'id': 'four','class': 'form-check-input'}))
     
     three = forms.MultipleChoiceField(
          label=labels[2],
          required=False,
          disabled=False,
          initial=['2'],
          choices=CHOICE,
          widget=forms.RadioSelect(attrs={
               'id': 'three','class': 'form-check-input'}))
    
     five = forms.MultipleChoiceField(
          label=labels[4],
          required=False,
          disabled=False,
          widget=forms.RadioSelect(attrs={
               'id': 'five','class': 'form-check-input'}))
    
ソース全文:views.py
from django.http.response import HttpResponse
from django.shortcuts import render, render_to_response
from django.contrib.staticfiles.templatetags.staticfiles import static
from . import forms
from django.template.context_processors import csrf

def demo3(request):
    labels = ['チェック','複数チェック','ラジオボタン','動的選択肢1','動的選択肢2']
    # 入力結果を格納する辞書
    results = {}
    radios = {}
    ret = ''
    if request.method == 'POST':
        # 入力されたデータの受取
        results[labels[0]] = request.POST.getlist("one")
        results[labels[1]] = request.POST.getlist("two")
        results[labels[2]] = request.POST.getlist("three")
        results[labels[3]] = request.POST.getlist("four")
        results[labels[4]] = request.POST.getlist("five")
        ret = 'OK'
        c = {'results': results,'ret':ret}
    else:    
        form = forms.ChkForm()
        choice1 = []
        choice1.append(('1','動的選択肢1'))
        choice1.append(('2','動的選択肢2'))
        choice1.append(('3','動的選択肢3'))
        choice1.append(('4','動的選択肢4'))
        form.fields['four'].choices = choice1
        form.fields['four'].initial = ['2']    
        form.fields['five'].choices = choice1
        form.fields['five'].initial = ['3']    
        c = {'form': form,'ret':ret}
        # CFRF対策(必須)
        c.update(csrf(request))
    return render(request,'demo03.html',c)    

 

ソース全文:HTML(exsample3.html)
{% extends 'base.html' %} 
{% load static %} 
{% load widget_tweaks %} 
{% block header %} 
<meta name="dummy" />{
% endblock %} 
{% block title %} やあ。こんにちは。 {% endblock %} 
{% block content %}
<tr>
	<td colspan="2"><img src="{% static "images/k.png" %}" /></td>
</tr>
<form action="" method="post">{% csrf_token %}
    <h1>ビルトインフォームサンプル</h1>
        {% if ret %}
        	{% for key,val in results.items %}
				<tr>
            		<td><h4>{{ key }}</h4></td>
            	   	<td><h4>{{ val }}</h4></td>
    			</tr>
           	{% endfor %}
        {% else %}
        	{% for key,val in errors.items %}
				<tr>
        			<td colspan="2">{{ key }} : {{ val }}</td>
				</tr>
        	{% endfor %}
        	{% for field in form %}
        	{% if field.field.widget.input_type == "radio" %}
				<tr>
				<td>{{ field.label_tag }}</td>
				<td>
    			{% for r in field %}
    			{{ r }}</br>
    			{% endfor %}
				</td>
    			</tr>    			
    		{% else %}
				<tr>
            		<td>{{ field.label_tag }}</td>
					<td>
    					{% for c in field %}
    						{{ c }}</br>
    					{% endfor %}
					</td>
    			</tr>
    		{% endif %}
			{% endfor %}
		<tr>
			<td colspan="2">
				<button type="submit" class="btn btn-primary">送信する</button>
			</td>
		</tr>
         {% endif %}
     </form>
{% endblock %}
 

HTMLは共通テンプレートを使う前提の差分のみです。

arakan-pgm-ai.hatenablog.com

ではでは。