Django、フォームの表示方法まとめ

Python Django Bulma Bootstrap4


フォームオブジェクトを作成したら、それをテンプレートへ渡します。{{ form }}{% for field in form %}など様々な表示方法がありますが、今回はそれを紹介していきます。

ウィジェットの変更やclass属性変更については、Django、モデルフォームのウィジェットを変更する幾つかの方法をご覧ください。

Bootstrap4を使う場合の例だったり、柔軟な表示方法についても紹介していきます。

次のフォームを例に使っていきます。

from django import forms

RATING_CHOICES = [
    (1, '★1'),
    (2, '★2'),
    (3, '★3'),
    (4, '★4'),
    (5, '★5'),
]

GOOD_POINT_CHOICES = [
    (1, '丁寧な対応'),
    (2, '良い人そう'),
    (3, 'お値段以上'),
    (4, '高品質'),
    (5, '素早い対応'),
]


class ReviewForm(forms.Form):
    """レビュー用フォーム"""
    name = forms.CharField(
        label='お名前', max_length=50,
        required=False, help_text='※匿名が良い場合は空欄にしてください'
    )
    rating = forms.ChoiceField(label='評価', choices=RATING_CHOICES, initial=3)
    good_point = forms.MultipleChoiceField(
        label='良かったとこ', choices=GOOD_POINT_CHOICES,
        required=False, help_text='CTRLキーで複数選択できます。'
    )

ちょっとした評価用のフォームです。実際には、モデルを定義しそのモデルフォームを使ったほうが集計処理などもしやすいです。

今回はモデルでの保存処理ではなくフォームの表示方法がメインなのと、通常のフォームのサンプルもかねて、forms.Formとして作っています。

手作業で取り出す

ちょっと面倒ですが、一番確実な方法です。

{{ form.non_field_errors }}

{{ form.name.label_tag }}
{{ form.name }} 
{{ form.name.help_text }}
{{ form.name.errors }}
<hr>

{{ form.rating.label_tag }}
{{ form.rating }}
{{ form.rating.errors }}
<hr>

{{ form.good_point.label_tag }}
{{ form.good_point }}
{{ form.good_point.help_text }}
{{ form.good_point.errors }}
<hr>

フォームエラーがなければ、以下のようなHTMLになります。

<label for="id_name">お名前:</label>
<input type="text" name="name" maxlength="50" id="id_name">
※匿名が良い場合は空欄にしてください
<hr>

<label for="id_rating">評価:</label>
<select name="rating" id="id_rating">
    <option value="1">★1</option>
    <option value="2">★2</option>
    <option value="3" selected>★3</option>
    <option value="4">★4</option>
    <option value="5">★5</option>
</select>
<hr>

<label for="id_good_point">良かったとこ:</label>
<select name="good_point" id="id_good_point" multiple>
    <option value="1">丁寧な対応</option>
    <option value="2">良い人そう</option>
    <option value="3">お値段以上</option>
    <option value="4">高品質</option>
    <option value="5">素早い対応</option>
</select>
※CTRLキーで複数選択できます。
<hr>

エラーがあった場合は、後で紹介しますが<ul><li>エラー内容1</li></ul>といった要素が挿入されます。

見た目は次のような感じです。

form.フィールド名

手作業でフォームのフィールドを取り出す場合は、{{ form.フィールド名 }}とします。これで<input><select>といった要素を取り出すことができます。 画像でいうと、次の部分です。

例えば{{ form.name }}ならば、<input type="text" name="name" maxlength="50" id="id_name">として生成されます。name属性はモデルやフォームのフィールド名、id属性はその先頭にid_をつけたものです。

form.フィールド名.label_tag

これを使うかは任意ですが、使っておいたほうがユーザーに優しい入力欄になります。次の丸で囲んだ部分です。

HTML上では、次のようなlabel要素になります。

<label for="id_name">お名前:</label>

これを使うと何が良いかというと、「お名前」といった部分をクリックすると入力欄にフォーカスされ、すぐに入力できるようになります。入力欄と項目名を紐づけてくれます。

スマホのように、各入力欄が小さくてタップしずらい場合等は、ラベル部分をクリックするほうが楽です。

label要素をもう少しカスタマイズしたい場合もあります。例えばclassに指定をしたり、label要素の中に入力欄がネストされるような場合もあるかもしれません。

そういった場合のために、項目名だけを取り出すこともできます。

<label for="{{ form.name.id_for_label }}">{{ form.name.label }}:</label>

ちょっと長いですが、これで<label for="id_name">お名前:</label>と同じ内容になります。後は好きにカスタマイズしましょう。

form.フィールド名.help_text

モデルやフォームフィールドでのhelp_text属性の部分です。画像でいうと、次の丸で囲んだ部分です。

help_textがない場合もあるので、使うかどうかは自由です。とはいえ、テンプレートでは属性が見つからなかった等のエラーは無視されて何も表示されないので、とりあえずつけておいても大丈夫でしょう。

help_textの名の通り、その欄のヘルプ的なテキストです。入力欄に関する補足や説明に使います。{{ form.name.help_text }}のように、 form.フィールド名.help_textで取り出すことができます。

たまにあるのですが、help_textにhtmlを含めたいことがあります。例えばヘルプテキスト中の空欄の部分を太字にしたいのかもしれません。

help_text='※匿名が良い場合は<b>空欄</b>にしてください'

テンプレートへ渡される文字列にhtmlが含まれていればそれはエスケープ処理されますので、htmlとして表示させたいならば少し追加の処理が必要です。

from django.utils.safestring import mark_safe
...
...
help_text=mark_safe('※匿名が良い場合は<b>空欄</b>にしてください')

mark_safeとするか、テンプレートにてsafeフィルタを使いましょう。

{{ form.name.help_text | safe }}

form.フィールド名.errors

そのフィールドに紐づくエラー内容です。基本的には表示させることになるでしょう。フォームのis_valid()メソッドが呼ばれた際に設定されます。空欄ダメだよと言ってるのに空欄で送信されたとか、EmailFieldなのに入力されたのはメールアドレスっぽくないとか、そういうのを表示してくれます。

試しに表示させてみました。

このエラーはどうやって作ったかというと、ReviewFormに以下のメソッドを追加することで作れます。今回のフォームの表示とは関係ないのであまり触れないようにしますが、このように入力欄のチェック処理を自分で作ることも可能です。

    def clean_name(self):
        name = self.cleaned_data.get('name')
        if name in ('ばか', 'あほ', 'まぬけ', 'うんこ'):
            self.add_error('name', '名前に暴言を含めないでください。')
            if name == 'ばか':
                self.add_error('name', 'ばかは特にダメです。')
        return name

話を戻して、form.フィールド名.errorsとした場合は上の画像のような感じで表示されます。htmlとしては、次のようなものを生成してくれます。

<ul class="errorlist">
    <li>名前に暴言を含めないでください。</li>
</ul>

<ul class="errorlist">という要素を作ってくれます。よくやるのは、●部分を消して赤く表示するとかですね。以下のようなCSS設定で簡単にできます。

ul.errorlist {
    list-style-type: none;
    padding: 0;
    color: red;
}

次のように表示されます。

errorsという名前や上のリスト要素で察しているかもしれませんが、エラー内容が複数ある場合もあります。

エラー1つ1つを取り出して自分なりに表示したいかもしれません。幸いにもforで取り出すことができます。これはul要素は生成しないので、好きにカスタマイズできます。`

{% for error in form.name.errors %}
    {{ error }}<br>
{% endfor %}
<hr>

form.non_field_errors

以外に忘れがちですが、この{{ form.non_field_errors }}の存在を忘れないようにしましょう。これは特定のフィールドには紐づかなかったり、複数のフィールドに跨るエラー内容が詰まっています。

例えばですが、「良かったとこ」が2つ以上あるのに「★1」をつけるのがシステムにそぐわないとしましょう。この場合、2つのフィールドに関連しているのでnon_field_errorに格納されるのが素直な実装に思います。

他によくあるのは、パスワードと確認用パスワードの欄がありそれらが一致しないとか、IDとパスワードのどちらかが間違っているよ、と教える場合ですね(IDが間違っています、パスワードが間違っています、という内容は情報を教えすぎなので良くないです)。Djangoが提供しているログイン用フォームやユーザー作成用フォームでは、実際にそのようにしています。

次のようにclearn()メソッドを定義してnon_field_errorsにエラーを格納しておきます。

    def clean(self):
        good_point = self.cleaned_data.get('good_point')
        rating = self.cleaned_data.get('rating')
        if rating == '1' and len(good_point) >= 2:
            self.add_error(None, '良いところが2つ以上あれば、★1はつけれません。')
        return self.cleaned_data

次のように表示されます。

以下のようなul要素を出力します。classにnonfieldとありますので、フィールドのエラーリストと区別したい場合に活用してください。

<ul class="errorlist nonfield">
    <li>良いところが2つ以上あれば、★1はつけれません。</li>
</ul>

non_field_errrosもforで取り出すことが可能です。

form.errorsでエラーをまとめる

form.フィールド名.errorsとform.non_field_errorsは、{{ form.errors }}とすることで一緒くたにすることができます。まずhtmlを修正しましょう。

{{ form.errors }}

{{ form.name.label_tag }}
{{ form.name }}
{{ form.name.help_text }}
<hr>

{{ form.rating.label_tag }}
{{ form.rating }}
<hr>


{{ form.good_point.label_tag }}
{{ form.good_point }}
{{ form.good_point.help_text }}
<hr>

見た目は次のような感じです。 ちゃんとまとまっていますが、nameや__all__などの部分が気になりますね。

エラー表示部分を少し変えましょう。forで取り出せるのですが、少々複雑です。

{% for errors in form.errors.values %}
    {% for error in errors %}
        {{ error }}<br>
    {% endfor %}
{% endfor %}

見た目がちょっと悪くて紛らわしいのですが、ちゃんと全てのエラーのテキストが表示できました。後は、かっこよくカスタマイズしましょう!

一括で取り出す

一行でフォームを出力できるので、非常に手軽です。3種類あります。

form or form.as_table

<table>
    {{ form }}
</table>

tableタグで囲むことを忘れないようにしましょう

{{ form }}、又は{{ form.as_table }}とすると、次のように出力されます。

<tr>
    <th><label for="id_name">お名前:</label></th>
    <td><input type="text" name="name" maxlength="50" id="id_name"><br><span class="helptext">※匿名が良い場合は空欄にしてください</span>
    </td>
</tr>

<tr>
    <th><label for="id_rating">評価:</label></th>
    <td><select name="rating" id="id_rating">
        <option value="1">★1</option>
        <option value="2">★2</option>
        <option value="3" selected>★3</option>
        <option value="4">★4</option>
        <option value="5">★5</option>
    </select></td>
</tr>

<tr>
    <th><label for="id_good_point">良かったとこ:</label></th>
    <td><select name="good_point" id="id_good_point" multiple>
        <option value="1">丁寧な対応</option>
        <option value="2">良い人そう</option>
        <option value="3">お値段以上</option>
        <option value="4">高品質</option>
        <option value="5">素早い対応</option>
    </select><br><span class="helptext">※CTRLキーで複数選択できます。</span></td>
</tr>

なので、{{ form }}table要素で囲めばテーブルレイアウトで表示できます。 tableのclassだとかを好きに指定できるわけです。

フォームにエラーがあると次のようになります。エラー関係も全て表示されますね。

form.as_ul

こちらはli要素でそれぞれの欄が生成されます。

<ul>
    {{ form.as_ul }}
</ul>

{{ form.as_ul }}は次のようなHTMLとなります。

<li>
    <label for="id_name">お名前:</label>
    <input type="text" name="name" maxlength="50" id="id_name">
    <span class="helptext">※匿名が良い場合は空欄にしてください</span>
</li>

<li>
    <label for="id_rating">評価:</label>
    <select name="rating" id="id_rating">
        <option value="1">★1</option>
        <option value="2">★2</option>
        <option value="3" selected>★3</option>
        <option value="4">★4</option>
        <option value="5">★5</option>
    </select>
</li>

<li>
    <label for="id_good_point">良かったとこ:</label>
    <select name="good_point" id="id_good_point" multiple>
        <option value="1">丁寧な対応</option>
        <option value="2">良い人そう</option>
        <option value="3">お値段以上</option>
        <option value="4">高品質</option>
        <option value="5">素早い対応</option>
    </select>
    <span class="helptext">※CTRLキーで複数選択できます。</span>
</li>

以下のような見た目になります。

エラーがある場合です。 今回の例だと 見た目的にわかりづらいですが、名前欄のエラーは名前部分の<li>の中にあります。全てのエラーを最初に表示している訳ではありません。

form.as_p

これはp要素ですね。

{{ form.as_p }}

次のようなHTMLになります。

<p>
    <label for="id_name">お名前:</label>
    <input type="text" name="name" maxlength="50" id="id_name">
    <span class="helptext">※匿名が良い場合は空欄にしてください</span>
</p>

<p>
    <label for="id_rating">評価:</label>
    <select name="rating" id="id_rating">
        <option value="1">★1</option>
        <option value="2">★2</option>
        <option value="3" selected>★3</option>
        <option value="4">★4</option>
        <option value="5">★5</option>
    </select>
</p>

<p>
    <label for="id_good_point">良かったとこ:</label>
    <select name="good_point" id="id_good_point" multiple>
        <option value="1">丁寧な対応</option>
        <option value="2">良い人そう</option>
        <option value="3">お値段以上</option>
        <option value="4">高品質</option>
        <option value="5">素早い対応</option>
    </select>
    <span class="helptext">※CTRLキーで複数選択できます。</span>
</p>

見た目は次のようになります。

エラーがある場合です。

forループで取り出す

それなりに記述も短く済み、そこそこ柔軟なのがこの方法です。

{{ form.non_field_errors }}

{% for field in form %}
    {{ field.label_tag }}
    {{ field }}
    {{ field.help_text }}
    {{ field.errors }}
    <hr>
{% endfor %}

手作業で取り出す場合と同じです。違うのは、{{ form.name }} のように書いていた部分がforループ内の{{ field }}として使える点です。

Bootstrap4の例

Bootstrap4のフォーム例をいくつか紹介します。

テキストボックス、セレクト、テキストエリア

type="text"なinput要素とselect要素、あとはtextareaも簡単に扱えます。class属性にform-controlをつけるだけで、テンプレートでは素直にフィールドを取り出せます。

今回の例は単純なので、フォームの__init__で一括してclass属性を設定します。

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'

{{ form.as_p }}での表示です。それなりには良く見えます。

<table class="table">{{ form.as_table }}</table>も良い感じです。

forで取り出すのも簡単です。non_field_errorsalertで表示してみました。

{% if form.non_field_errors %}
    <div class="alert alert-danger alert-dismissable alert-link" role="alert">
        <button class="close" type="button" data-dismiss="alert" aria-label="Close">&#215;</button>
        {% for error in form.non_field_errors %}
            {{ error }}<br>
        {% endfor %}
    </div>
{% endif %}

{% for field in form %}
    <div class="form-group">
        {{ field.label_tag }}
        {{ field }}

        {% if field.help_text %}
            <small class="form-text text-muted">{{ field.help_text }}</small>
        {% endif %}

        {% for error in field.errors %}
            <small class="form-text text-danger">{{ error }}</small>
        {% endfor %}
    </div>
{% endfor %}

良い感じですね!

forで、更にグリッドなフォームにしてみます。labelのclass属性をカスタマイズしなきゃなので、少し難しいlabelの書き方をしていることに注意です。

{% if form.non_field_errors %}
    <div class="alert alert-danger alert-dismissable alert-link" role="alert">
        <button class="close" type="button" data-dismiss="alert" aria-label="Close">&#215;</button>
        {% for error in form.non_field_errors %}
            {{ error }}<br>
        {% endfor %}
    </div>
{% endif %}

{% for field in form %}
    <div class="form-group row">
        <label for="{{ field.id_for_label }}" class="col-sm-2 col-form-label">{{ field.label }}</label>
        <div class="col-sm-10">
            {{ field }}

            {% if field.help_text %}
                <small class="form-text text-muted">{{ field.help_text }}</small>
            {% endif %}

            {% for error in field.errors %}
                <small class="form-text text-danger">{{ error }}</small>
            {% endfor %}
        </div>
    </div>
{% endfor %}

これも良いですね。

チェックボックス、ラジオ(独自ウィジェットの作成)

Djangoで、ウィジェットを変更するには次のようにします。これはselectinput type="radio"にする例です。

    rating = forms.ChoiceField(
        label='評価', choices=RATING_CHOICES, initial=3,
        widget=forms.RadioSelect
    )

Bootstrap4のラジオボタンは、class属性にform-check-input等とつける必要があるので、その設定も必要です。これはforms.RadioSelect(attrs={'class': 'form-check-input'})とすれば解決します。こういったウィジェットの変更やclassの追加は、Django、モデルフォームのウィジェットを変更する幾つかの方法に書いています。

しかし、これを動作させるには骨が折れます。

まず押さえておきたいのは、ラジオボタンに変更した場合、{{ form.rating }}のようにすると以下のように出力されることです。

<ul id="id_rating" class="form-check-input">
    <li>
        <label for="id_rating_0">
            <input type="radio" name="rating" value="1" class="form-check-input" required id="id_rating_0">
            ★1
        </label>
    </li>

    <li>
        <label for="id_rating_1">
            <input type="radio" name="rating" value="2" class="form-check-input" required id="id_rating_1">
            ★2
        </label>
    </li>

    <li>
        <label for="id_rating_2">
            <input type="radio" name="rating" value="3" class="form-check-input" required id="id_rating_2" checked>
            ★3
        </label>
    </li>

    <li>
        <label for="id_rating_3">
            <input type="radio" name="rating" value="4" class="form-check-input" required id="id_rating_3">
            ★4
        </label>
    </li>

    <li>
        <label for="id_rating_4">
            <input type="radio" name="rating" value="5" class="form-check-input" required id="id_rating_4">
            ★5
        </label>
    </li>

</ul>

ul li要素が作られました。

本当に欲しかったのは、次のようなHTMLなのです。

<div class="form-check">
    <input class="form-check-input" type="radio" name="rating"
           id="id_rating_0" value="1">
    <label class="form-check-label"
           for="id_rating_0">★1</label>
</div>

<div class="form-check">
    <input class="form-check-input" type="radio" name="rating"
           id="id_rating_1" value="2">
    <label class="form-check-label"
           for="id_rating_1">★2</label>
</div>

<div class="form-check">
    <input class="form-check-input" type="radio" name="rating"
           id="id_rating_2" value="3">
    <label class="form-check-label"
           for="id_rating_2">★3</label>
</div>

<div class="form-check">
    <input class="form-check-input" type="radio" name="rating"
           id="id_rating_3" value="4">
    <label class="form-check-label"
           for="id_rating_3">★4</label>
</div>

<div class="form-check">
    <input class="form-check-input" type="radio" name="rating"
           id="id_rating_4" value="5">
    <label class="form-check-label"
           for="id_rating_4">★5</label>
</div>

これはえらいことです。何もかもが違います。どこをどう変更すればいいのでしょうか。

方法は幾つかありますが、今回はエレガントな方法を紹介します。ウィジェットクラスを自分で作ってしまうのです。

forms.pyを修正していきます。

class BS4RadioSelect(forms.RadioSelect):
    input_type = 'radio'
    template_name = 'app/widgets/bs4_radio.html'


class BS4CheckboxInline(forms.CheckboxSelectMultiple):
    input_type = 'checkbox'
    template_name = 'app/widgets/bs4_checkbox_inline.html'

これらが、Bootstrap4のラジオやチェックボックスを作る専用ウィジェットです。そして、ReviewFormのフィールドでこれらのウィジェットクラスを使うように指定します。

    rating = forms.ChoiceField(
        label='評価', choices=RATING_CHOICES, initial=3,
        widget=BS4RadioSelect
    )
    good_point = forms.MultipleChoiceField(
        label='良かったとこ', choices=GOOD_POINT_CHOICES,
        required=False, help_text='※CTRLキーで複数選択できます。',
        widget=BS4CheckboxInline
    )

Djangoの各ウィジェットは、それがどういうHTMLを作るかをテンプレートで指定しています。これから、そのテンプレートを作っていきます。

ちなみにですが、Djangoデフォルトのものや、他サードパーティ製アプリのウィジェットテンプレートを上書きするとかもできます。

まず、bs4_radio.htmlです。この手のは、templates→アプリケーションの中にwidgetsのようなディレクトリを作っておくと、わかりやすいです。

{% for group, options, index in widget.optgroups %}
    {% for option in options %}
        <div class="form-check">
            <input class="form-check-input" type="{{ option.type }}" name="{{ option.name }}"
                   id="{{ option.attrs.id }}" value="{{ option.value }}" {% if option.attrs.checked %}checked{% endif %}
                   {% if widget.required %}required{% endif %}>
            <label class="form-check-label"
                   for="{{ option.attrs.id }}">{{ option.label }}</label>
        </div>
    {% endfor %}
{% endfor %}

そしてbs4_checkbox_inline.html

{% for group, options, index in widget.optgroups %}
    {% for option in options %}
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="{{ option.type }}" name="{{ option.name }}"
                   id="{{ option.attrs.id }}" value="{{ option.value }}" {% if option.attrs.checked %}checked{% endif %}
                   {% if widget.required %}required{% endif %}>
            <label class="form-check-label"
                   for="{{ option.attrs.id }}">{{ option.label }}</label>
        </div>
    {% endfor %}
{% endfor %}

フォームを表示するテンプレートは、おかげでシンプルになります。この例ならば、forループで取り出すこともできますね。

{% if form.non_field_errors %}
    <div class="alert alert-danger alert-dismissable alert-link" role="alert">
        <button class="close" type="button" data-dismiss="alert" aria-label="Close">&#215;</button>
        {% for error in form.non_field_errors %}
            {{ error }}<br>
        {% endfor %}
    </div>
{% endif %}

<div class="form-group">
    <label for="{{ form.name.id_for_label }}">{{ form.name.label }}:</label>
    {{ form.name }}

    <small class="form-text text-muted">{{ form.name.help_text }}</small>

    {% for error in form.name.errors %}
        <small class="form-text text-danger">{{ error }}</small>
    {% endfor %}
</div>

<div class="form-group">
    <label>{{ form.rating.label }}:</label>
    {{ form.rating }}

    {% for error in form.rating.errors %}
        <small class="form-text text-danger">{{ error }}</small>
    {% endfor %}
</div>

<div class="form-group">
    <label>{{ form.good_point.label }}:</label>
    {{ form.good_point }}

    <small class="form-text text-muted">{{ form.good_point.help_text }}</small>

    {% for error in form.good_point.errors %}
        <small class="form-text text-danger">{{ error }}</small>
    {% endfor %}
</div>

ちゃんと作れています。

ウィジェットクラスが作れるようになると、フォームの入力欄に関してはどれだけ複雑なものでも作ることができるようになります。覚える価値ありです。

Bulmaの例

Bootstrap4と同様で、チェックボックスやラジオボタンを作る場合は独自ウィジェットにしたほうが楽です。

テキストボックスとセレクトは比較的楽にできます。やってみましょう。 forms.py

    name = forms.CharField(
        label='お名前', max_length=50,
        required=False, help_text='※匿名が良い場合は空欄にしてください',
        widget=forms.TextInput(attrs={'class': 'input'})
    )

ratinggood_pointは、特にcssのclassを追加する必要はありません。selectはその要素ではなく、親のdiv要素にclassを追加することになります。

そして、テンプレート

<!-- non_field_errorsの表示。全てのエラーをこれで表示してもよいかもしれない -->
{% if form.non_field_errors %}
    <div class="notification is-danger">
        <button class="delete" type="button"></button>
        {% for error in form.non_field_errors %}
            <p>{{ error }}</p>
        {% endfor %}
    </div>
{% endif %}

<!-- 名前欄 -->
<div class="field">
    <label class="label" for="{{ form.name.id_for_label }}">{{ form.name.label }}</label>
    <div class="control">
        {{ form.name }}
    </div>

    <p class="help">{{ form.name.help_text }}</p>

    {% for error in form.name.errors %}
        <p class="help is-danger">{{ error }}</p>
    {% endfor %}
</div>

<!-- 評価欄 -->
<div class="field">
    <label class="label" for="{{ form.rating.id_for_label }}">{{ form.rating.label }}</label>
    <div class="control is-expanded">
        <div class="select is-fullwidth">
            {{ form.rating }}
        </div>
    </div>

    {% for error in form.rating.errors %}
        <p class="help is-danger">{{ error }}</p>
    {% endfor %}
</div>

<!-- 良いところ欄 -->
<div class="field">
    <label class="label" for="{{ form.good_point.id_for_label }}">{{ form.good_point.label }}</label>
    <div class="control is-expanded">
        <div class="select is-fullwidth is-multiple">
            {{ form.good_point }}
        </div>
    </div>

    <p class="help">{{ form.good_point.help_text }}</p>

    {% for error in form.good_point.errors %}
        <p class="help is-danger">{{ error }}</p>
    {% endfor %}
</div>

次のようになります。良い感じですね。

Bulmaでの表示例

上のnon_field_errors欄にあるエラーを、×閉じしたい場合は次のようなjsを書いておきましょう。Bulmaにはjsファイルは付属していないので、こういった処理は自分で書く必要があります。とはいえ、簡単です。

<script>
    for (const element of document.querySelectorAll('.notification > .delete')) {
        element.addEventListener('click', e => {
            e.target.parentElement.classList.add('is-hidden');
        });
    }
</script>

記事にコメントする