DjangoのフォームのChoiceFieldに画像を挿入する
苦労したので、備忘録に・・
環境
※今回投稿する方法は、Django1系では利用できません。 1系はこちらをご参照ください。 なお、下記の記事の中にあるRadioFieldRendererは2系では存在しません・・・
注文アプリを作成している時に、モデルから選択肢を作成し、モデルに格納しているimgを挿入したいという件がありました。
完成図
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でも応用できそうです。
他にもっと良い方法があったらぜひ教えてください(>_<)