Django FormのChoiceFieldでBootstrapのbtn-groupを使いたい


やりたいこと


・こんな感じのことをRadioSelectのwidgetを使ってやりたかった。

環境

  • Django 3.1.2
  • Bootstrap 4.3.1

前提

forms.py
class CustomForm(forms.Form):
    interval = forms.ChoiceField(
        choices=((0, "DAY"), (1, "HOUR"), (2, "MINUTE"),),
        widget=forms.RadioSelect
    )
    ...

こんなFormがあるとして、このintervalを上のようなBootstrapのbtn-groupを使って表示したい。

流れ

  1. settingsを変更しRadioSelectで独自Templateを使えるように。
  2. Templateを調整。
  3. TestFormで独自Templateを設定。

1. 設定の変更

settings.py
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
INSTALLED_APPS [
    ...,
    "django.forms",
]

デフォルトのレンダラーだとtemplates以下のファイルを読み込めないためTemplateSettingに変更。
そして、このままだと今度は組み込みTemplateを読み込めないためINSTALLED_APPSにdjango.formsを追加。

2. 独自Templateの作成。

今回はDjangoデフォルトのものをベースに radio.html と radio_options.html の2種類のTemplateを使う。

widget/radio.html
{% with id=widget.attrs.id %}
    <div{% if id %} id="{{ id }}"{% endif %}
                    class="btn-group btn-group-toggle {% if widget.attrs.class %}{{ widget.attrs.class }}{% endif %}" data-toggle="buttons">
        {% for group, options, index in widget.optgroups %}
            {% for option in options %}
                {% include option.template_name with widget=option %}
            {% endfor %}
        {% endfor %}
    </div>
{% endwith %}
widget/radio_options.html
{% if widget.wrap_label %}<label{% if widget.attrs.id %}
    for="{{ widget.attrs.id }}"{% endif %}
    class="btn btn-sm btn-outline-dark">{% endif %}{% include "django/forms/widgets/input.html" %}
{% if widget.wrap_label %} {{ widget.label }}</label>{% endif %}

やっていることはClassの追加とHTMLタグの修正くらい。

3. form.pyの修正

forms.py
class CustomForm(forms.Form):
    interval = forms.ChoiceField(
        choices=((0, "DAY"), (1, "HOUR"), (2, "MINUTE"),),
        widget=forms.RadioSelect
    )
    interval.widget.template_name = "widget/radio.html"
    interval.widget.option_template_name = "widget/radio_options.html"
    ...

[field_name].widget.template_name と option_template_name に作ったTemplateを設定。

完成!

Tips

初期値を設定したい。

forms.py
class CustomForm(form.Form):
    def __init__(self, *args, **kwargs):
        super(GiftFormSearch, self).__init__(*args, **kwargs)
        self.initial["interval"] = 0

init内で設定できます。 views.pyで設定したい場合は以下の通り。

views.py
def hogehoge(request):
    ...
    form = CustomForm({"interval": 0})

ただし、inputにcheckedが追加されるだけでClassには何も追加されないため見た目で判別できないことに注意。
クライアント側のjsでとりあえず対応しています。