はじめに
DjangoでWebアプリ開発をしている中で、1つの問題に直面しました。Djangoのテンプレートを素直に使用するだけでは、アコーディオンメニューを作れないという問題です。複数のチェックボックスを選択するフォームを作っている際に、選択肢が数百個に及ぶ可能性があり、見出しごとに選択肢を分割するアコーディオンメニューにしたいと考えました。この記事はその実現方法の備忘録です。
作戦
基本的な複数選択のチェックボックスを作るには、Djangoに用意されているCheckboxSelectMultipleというWidgetを利用します。このCheckboxSelectMultipleをオーバーライドすることで、アコーディオンメニューを実現することを目指しました。
分析
まずはDjango本体のソースコードを見ていきましょう。
上記のリポジトリを追っていくと、CheckboxSelectMultipleは次のようにクラスが継承されていることが分かります。
class CheckboxSelectMultiple(RadioSelect):
hogehoge
class RadioSelect(ChoiceWidget):
input_type = "radio"
template_name = "django/forms/widgets/radio.html"
option_template_name = "django/forms/widgets/radio_option.html"
use_fieldset = True
そして、ソースコードを読み解いていくと、option_template_nameが選択肢のチェックボックスひとつひとつのテンプレートであり、template_nameが選択肢全体のテンプレートであることが分かります。今回は複数のチェックボックスというロジックはそのままにアコーディオンメニューを作ろうとしているので、このtemplate_nameの部分をオーバーライドして変更すれば良いでしょう。
実装
分析をもとに次のような実装をしました。
まずは、新しいClassを作ります。今回オーバーライドするのはtemplate_nameだけであるため、CheckboxSelectMultipleを継承して、template_nameのみ記載します。
class CheckboxSelectMultipleWithHeading(CheckboxSelectMultiple):
template_name = "<テンプレートのパス>"
そして、使用するテンプレートを自分で作成します。今回はBootStrapを使用して、デフォルトで使用されているテンプレートを改変し、アコーディオンメニューにしました。
{% with id=widget.attrs.id %}
<div{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>
<div class="accordion" id="accordionExample">
{% for group, options, index in widget.optgroups %}
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse_{{ index }}" aria-expanded="true" aria-controls="collapseOne">
{{group}}
</button>
</h2>
<div id="collapse_{{ index }}" class="accordion-collapse collapse show p-2" aria-labelledby="headingOne" data-bs-parent="#accordionExample">
{% for option in options %}
<div>{% include option.template_name with widget=option %}</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endwith %}
これにより下図のようなフォームを作ることができました。