"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

チェックボックスとラジオボタンを使う。初期化と値の受取など。/Django2.0+Bootstrap4

django2.0のFieldについて、毎回、調べ直すのは面倒なので、自分用を兼ねて、サンプルを書いておきます。

今回は「CheckBox」と「RadioButton」。

環境は Windows + anaconda です。

f:id:arakan_no_boku:20190320212948j:plain

 

DjangoでRadioとCheckboxはなかなかくせ者

 

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

などです。

まあ。

これでも良いのですが、いちいち使い分けるのが面倒くさいのですね。

自分的には。

だから、自分はチェックボックスも、Radioボタンも、チェックボックスの複数選択もまとめて「MultipleChoiceField」一択でやってます。

正解なのかどうかはわかりませんが、悩むことが少なくて、さっさとデモ画面のようなものを作る用途にはとても向いていると、自分は思ってます。

 

forms.pyの定義

 

パターンとしては5つあると思ってます。

 

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

 

複数選択可能なチェックボックス(選択肢は静的に渡す)
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'」の指定が必要です。

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

 

単一のチェックボックス

 

正直。

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

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'}))

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

何故、こんなことをしているのか?・・ですが、値を受け取る側のロジックをシンプルにしたいということで・・、ま、後で説明します。

 

複数選択可能なチェックボックス(選択肢は動的に渡す)
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から値をとってきてセットするイメージです。

 

ラジオボタン(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を使うところだけです。

 

ラジオボタン(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)

ポイントを補足します。

 

動的に選択肢と初期状態を渡す 

この部分です。

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」にチェックだけにチェックがつきます。

 

選択された値の受取

 

この部分です。

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']みたいな感じで。

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

 

HTML側が実は今回の肝なんです

 

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

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

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

Widgets | Django documentation | Django

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

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

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

なので、HTML側で自分でDIVタグの中に収めてやる必要があります。

かつ。

全フィールドをループで回しているわけなので、input_typeを判断して、radioボタンならこう・・、チェックボックスならこう・・という感じで切り分けて処理をする必要があります。

 

HTMLのソースです
{% for field in form %}
    {% if field.field.widget.input_type == "radio" %}
    	<div class="form-group row my-4">
    	   	<div class="col-lg-2 col-form-label">
            		
            </div>
    		<div class="col-lg-2  form-check">
    			{% for r in field %}
    			{{ r }}
    			{% endfor %}
    		</div>    			
    	</div>
    {% else %}
    	<div class="form-group row my-4">
    	    <div class="col-lg-2 col-form-label">
            	{{ field.label_tag }}
            </div>
    		<div class="col-lg-2 form-check">
    			{% for c in field %}
    			{{ c }}
    			{% endfor %}
    		</div>
    	</div>
    {% endif %}
{% endfor %} 

ポイントを補足します。 

 

type名の取得

 

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

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

 チェックボックスなら。

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

ですね。

 

ラジオボタンの縦並び

 

で・・ラジオボタンの表示はこう。

<div class="col-lg-2 form-check">

    {% for r in field %}

        {{ r }}

    {% endfor %}

</div>

表示されたイメージです。

f:id:arakan_no_boku:20190211224600j:plain



チェックボックスの縦並び

 

 チェックボックスはこう。

<div class="col-lg-2 form-check">
    {% for c in field %}
        {{ c }}
    {% endfor %}
</div>

こうした時の表示例がこうです。

f:id:arakan_no_boku:20190211224533j:plain

 

ラジオボタンの横並び(インライン展開)

 

Divタグがループの内側にはいるだけです。

{% for r in field %}
    <div class="col-lg-2 form-check">
        {{ r }}
    </div>
{% endfor %}

表示されたイメージ。

f:id:arakan_no_boku:20190211225154j:plain

 

チェックボックスの横並び(インライン)

 

これも、Divタグがループの内側にはいるだけです。

{% for c in field %}
    <div class="col-lg-2 form-check">
        {{ c }}
    </div>
{% endfor %}

表示されたイメージはこう。

f:id:arakan_no_boku:20190211225415j:plain

 

Bootstrap4のレイアウト機能を使ってるだけなんですが

 

上記の例は、Bootstrap4のRow・Colのレイアウト機能を使ってるだけです。

DjangoのWidgetはほぼ無視してます。

でも、そのおかげでレイアウトの自由度は高いです。

まあ。

手間さえかければ・・ですけど。

 

今回の記事の前提

 

django、bootstrapのインストールや利用設定、および、djangoで入力画面を作る部分は、以下の記事の内容を前提にしています。

arakan-pgm-ai.hatenablog.com

arakan-pgm-ai.hatenablog.com

arakan-pgm-ai.hatenablog.com

 

今回の記事の前提

 

django、bootstrapのインストールや利用設定、および、djangoで入力画面を作る部分は、以下の記事の内容を前提にしています。

arakan-pgm-ai.hatenablog.com

arakan-pgm-ai.hatenablog.com

arakan-pgm-ai.hatenablog.com

 

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

 

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

f:id:arakan_no_boku:20190211231117j:plain

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

f:id:arakan_no_boku:20190211231230j: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
{% load static %}
{% load bootstrap4 %}
{% load widget_tweaks %}

{% block header %}
<link rel="stylesheet" href="{% static "css/dj_talk.css" %}"></link>
{% endblock %}

{% block title %}
  CharFieldのサンプルいろいろ
{% endblock %}

{% block content %}
<div class="container bg-light">
    <form action="" method="post">{% csrf_token %}
        <h1>ビルトインフォームサンプル</h1>
        {% if ret %}
        	{% for key,val in results.items %}
        	<div class="form-group row my-4">  
            	<label class="col-lg-3 col-form-label">
            		<h4>{{ key }}</h4>
            	</label>
       			<div class="col-lg-6">       
	            	<h4>{{ val }}</h4>
    			</div>
    		</div>        	
        	{% endfor %}
        {% else %}
        	{% for key,val in errors.items %}
        		<p>{{ key }} : {{ val }}</p>
        	{% endfor %}
        	{% for field in form %}
        	{% if field.field.widget.input_type == "radio" %}
    		<div class="form-group row my-4">
    		   	<div class="col-lg-2 col-form-label">
            		
            	</div>
    			<div class="col-lg-2  form-check">
    			{% for r in field %}
    			{{ r }}
    			{% endfor %}
    			</div>    			
    		</div>
    		{% else %}
    		<div class="form-group row my-4">
    		    <div class="col-lg-2 col-form-label">
            		{{ field.label_tag }}
            	</div>
    			<div class="col-lg-2 form-check">
    			{% for c in field %}
    			{{ c }}
    			{% endfor %}
    			</div>
    		</div>
    		{% endif %}
			{% endfor %}
			
		   	<div class="form-group row my-4">	
    			<div class="col-lg-3">       
	        	    <button type="submit" class="btn btn-primary">送信する</button>
    			</div>
        	</div>
         {% endif %}
     </form>
</div>  
{% endblock %}    

ではでは。