ご無沙汰しました。公私ともちょっと忙しくしておりました。

 

今日の内容は、タイトルの通りなのですが、Djangoのフォーム・セレクトメニューのOptionタグにclass指定する方法についてです。

 

具体的には、こんなソースを出力したい訳です。

<form method="post" action="index">
 <select name="menu">
  <option value="banana" class="fruits">バナナ</option>
  <option value="apple" class="fruits">りんご</option>
  <option value="tomato" class="vegetables">とまと</option>
  <option value="carrot" class="vegetables">にんじん</option>
 </select>
 <input type="submit" value="send" />
</form>

 

何故そのようなことがしたいかと言えば、ここで指定したclassに応じて、jQueryで各optionタグの表示/非表示を切り替えたいからです。

 

さて、私のケースではデータベースに保存したレコードを元にメニューを作成したいので、forms.ModelChoiceFieldを使ってセレクトメニューを出力することにします。

このforms.ModelChoiceFieldのルートクラスは、forms.Fieldです。forms.ModelChoiceFieldに限らず、django.formsでの各種フィールドは、forms.Fieldのサブクラスです。forms.Fieldでコアな処理を行い、それと関連づけられたforms.widgets.WidgetのサブクラスでHTML出力を行うという構成らしい。

 

forms.ModelChoiceFieldクラスのHTML出力クラスを確認すると、forms.widgets.Selectクラスだと分かりました。

 

そこで、forms.widgets.Selectクラスのoptionタグ出力を行っているソースコードを確認します。

 widgets.py
def render_option(self, selected_choices, option_value, option_label):
  option_value = force_text(option_value)
  if option_value in selected_choices:
    selected_html = mark_safe(' selected="selected"')
    if not self.allow_multiple_selected:
      # Only allow for a single selection.
      selected_choices.remove(option_value)
    else:
      selected_html = ''
    return format_html('<option value="{0}"{1}>{2}</option>',
                       option_value,
                       selected_html,
                       force_text(option_label))

残念ながら、引数でclass指定とかは出来ない内容。

 

そこで、

  1. forms.ModelChoiceFieldのサブクラスを作成
  2. forms.widgets.Selectのサブクラスを作成
  3. #2のrender_option関数をオーバーライト。まずは元のrender_otpionのソースを丸々コピーして、class属性を出力する内容に書き換える
  4. #1のinit処理において、super.__init__を呼び出す。その引数のkwargsで#2で作成したクラスをwidget(=HTML出力クラス)として追加する。

のようなフローで実装することに決定。

 

myapp.forms

 

これをviews.pyから

form = MyForm()

とインスタンス生成すればOKです。