mizzsugar’s blog

Pythonで学んだことや読書録を書きます。

DjangoのフォームのChoiceFieldに画像を挿入する

苦労したので、備忘録に・・


環境

※今回投稿する方法は、Django1系では利用できません。 1系はこちらをご参照ください。 なお、下記の記事の中にあるRadioFieldRendererは2系では存在しません・・・

moqada.hatenablog.com

注文アプリを作成している時に、モデルから選択肢を作成し、モデルに格納しているimgを挿入したいという件がありました。


完成図

f:id:mizzsugar:20181205223208p:plain

models.py

class Curry(models.Model):
    name = models.CharField(max_length=30)
    price = models.IntegerField()
    image = models.FilePathField() 


class Order(models.Model):
    user_name = models.CharField(max_length=20, unique=True)
    curry = models.ForeignKey(Curry, on_delete=models.CASCADE)
    amount = models.IntegerField()

forms.py

def generate_curry_choice() -> Iterable[Tuple[int, str]]:
    @dataclasses.dataclass
    class MyLabel:
        name: str
        image: str
        price: int

        def __str__(self):
            return self.name

    return (
        (curry.id, MyLabel(curry.name, curry.image, curry.price))
        for curry in Curry.objects.all()
    )


class OrderForm(forms.Form):
    user_name = forms.CharField(label='名前', max_length=20)
    curry = forms.ChoiceField(
        label='カレー',
        choices=generate_curry_choice,
        widget=RadioSelect
    )

order.html

<form action="/order_form/{{ group.url_uuid }}" enctype="multipart/form-data" method="POST">
        {% csrf_token %}
        <div class="form-group">
          <label>{{ form.user_name.label }}</label>
          {{ form.user_name }}
          {{ form.user_name.errors }}
        </div>
        <div class="form-group">

            {% for radio in form.curry %}
            <div class="curry-select">
            {{ radio.tag }}
            </div>
            {% endfor %}
            {{ form.curry.errors }}
        </div>
        <button type="submit" class="btn btn-primary btn-lg btn-block">登録</button>
    </form>



画像をいれるスキがない!!!

widgetのRadioSelectについて公式ドキュメントで調べてみると・・・

class RadioSelect(ChoiceWidget):
    input_type = 'radio'
    template_name = 'django/forms/widgets/radio.html'
    option_template_name = 'django/forms/widgets/radio_option.html'

Djangoにあるhtmlを利用してフォームを生成しているっぽい。

ウィジェット | Django documentation | Django


ソースをみると・・・

radio_option.html

{% include "django/forms/widgets/input_option.html" %}


radio_option.htmlが継承している、input_option.htmlの中身は・・・

input_option.html

{% if widget.wrap_label %}
<label
        {% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}
    {% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label }}
</label>
{% endif %}


input_option.htmlが継承している、input.htmlの中身は・・・

input.html

<input 
        type="{{ widget.type }}"
        name="{{ widget.name }}"
        {% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}
        {% include "django/forms/widgets/attrs.html" %}
>


どうやら、input.htmlにて ラジオボタンを生成しているっぽいです。

django/django/forms/templates/django/forms/widgets at master · django/django · GitHub


input_option.htmlのlabel内の

inputの上辺りに画像を挿入したらいけるかも・・・?


しかし、RadioSelectでは実現できない。


カスタマイズしたwidgetを生成しよう。


htmlファイルが入った、templateディレクトリの中に、 widgetというディレクトリを生成し、 curry_radio_option.htmlというwidget用のhtmlファイルを作成します。

curry_radio_option.html

{% if widget.wrap_label %}
<label
{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}

<img src="{{ widget.label.image }}">
    <div class="block">
    <input
    type="{{ widget.type }}"
    name="{{ widget.name }}"
    {% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}
    {% include "django/forms/widgets/attrs.html" %}
>
    {{ widget.label.name }}
        </div>
{% if widget.wrap_label %}
</label>
{% endif %}


cssはこんな感じです。

.block{
    display:block;
    font-size: 20px;
    font-weight: bold;
    align: center;
}

.curry-select{
    display: inline-block;
    padding: 20px;
    margin: 20px;
}


forms.pyの中に、ImageSelectというカスタマイズしたwidgetクラスを作ります。

forms.py

class ImageSelect(forms.widgets.RadioSelect):
    template_name = 'widgets/curry_radio.html'
    option_template_name = 'widgets/curry_radio_option.html'


OrderFormの中のwidget

RadioSelectからImageSelectに変更します。

forms.py

class OrderForm(forms.Form):
    user_name = forms.CharField(label='名前', max_length=20)
    curry = forms.ChoiceField(
        label='カレー',
        choices=generate_curry_choice,
        widget=ImageSelect
    )


無事、画像が入りました〜。


htmlファイルをいじる方法、他のwidgetでも応用できそうです。

他にもっと良い方法があったらぜひ教えてください(>_<)