2021-01-25: 追記
ModelFormでModelの1つのカラムを複数のフォーム要素(inputとか)で表現したかったので調べてみた。フォーム要素を適当に作って、clean_xxxの時にくっつけるとかそんな実装の仕方も出来るみたいだけど、MultiWidgetを使うと、Modelの1つのカラムに対して複数のinput要素を使うことが出来るみたいなので、使ってみた。名前のカラムに対して姓、名の入力要素を作るとかそんなのが出来るみたい。
まずはMultiWidgetを継承したクラスを作る
# models.py
from django.forms.widgets import MultiWidget
class NameWidget(MultiWidget):
次にクラスに対して、__init__をオーバーライドし、widgetとしてTextInputのフォームを2つ登録してみた。コードはこんな感じ。
# models.py
class NameWidget(MultiWidget):
def __init__(self,attrs=None):
widgets = (
forms.TextInput(attrs=attrs.update({"placeholder":"姓"})),
forms.TextInput(attrs=attrs.update({"placeholder":"名"}))
)
super(NameWidget,self).__init__(widgets,attrs)
widgetsはリストでもタプルでも良さげ?ともかくやることはwidgetのリストかタプルを作って、親クラス(MultiWidget)のinitを呼び出すこと。
次に、decompress、value_from_datadictメソッドを書く。decompressはカラムの値を複数のフォームに振り分ける手順、value_from_datadictはModelFormの入力データからModelのカラムに入れる値を作る手順を記述する。姓名を半角スペースで繋げて、半角スペースで分割するように作ってみた。
# models.py
class NameWidget(MultiWidget):
def __init__(self,attrs={}):
attrsSei = attrs.copy()
attrsMei = attrs.copy()
widgets = (
forms.TextInput(attrs=attrsSei.update({"placeholder":"姓"})),
forms.TextInput(attrs=attrsMei.update({"placeholder":"名"}))
)
super(NameWidget,self).__init__(widgets,attrs)
def decompress(self,value):
if value:
names = value.split(' ')
return (names[0],names[1])
return (None,None)
def value_from_datadict(self,data,files,name):
ulist = [widget.value_from_datadict(data,files,name+'_{0}'.format(i)) for i, widget in enumerate(self.widgets)]
return u"{0} {1}".format(ulist[0].replace(u' ',''),ulist[1].replace(u' ',''))
あとはこのWidgetを使いたい要素にwidgetとして指定すればおk
# models.py
from django import forms
...(上のコードなど)...
class HogeForm(forms.ModelForm):
name = forms.CharField(max_length=100,label=u'名前',widget=NameWidget())
このコードだと姓、名のフォームが2行に表示されている。1行にしたい場合はインライン指定とかそんなのを付けたクラスをattrsで指定して後はcssでやっちゃえばいいんじゃないかなと
# models.py
name = forms.CharField(max_length=100,label=u'名前',widget=NameWidget(attrs={"class":"inline"}))
とかそんな感じ
他にももっと細かくhtmlを記述出来るformat_outputとかあるみたいだけど、試してないので省略。なくても取りあえず出来る。
2021-01-25追記
上のコードではplaceholderを姓、名で分けることが出来なかった。下のコードのように一旦superで親クラスをinitしておいて、その後、widgetに設定する必要がある。修正したコードはこんな感じ
# models.py
class NameWidget(MultiWidget):
def __init__(self,attrs={}):
widgets = (
forms.TextInput(),
forms.TextInput()
)
super(NameWidget,self).__init__(widgets,attrs)
self.widgets[0].attrs.update({"placeholder":u"姓"})
self.widgets[1].attrs.update({"placeholder":u"名"})
def decompress(self,value):
if value:
names = value.split(' ')
return (names[0],names[1])
return (None,None)
def value_from_datadict(self,data,files,name):
ulist = [widget.value_from_datadict(data,files,name+'_{0}'.format(i)) for i, widget in enumerate(self.widgets)]
return u"{0} {1}".format(ulist[0].replace(u' ',''),ulist[1].replace(u' ',''))
参考:How to set different placeholders for DateFromToRangeFilter? Ask Question
またvalue_from_datadictは「入力」→「確認」→「完了」みたいな遷移をさせる場合に、確認から完了への遷移時にデータが入らずにexceptionを吐くので注意。try catchしてcatch側で通常処理
return self.data.get(name)
とかしておけば良いと思う。