django.forms.ModelChoiceFieldをカスタマイズ
newformsは奥が深い。(1.0final前提。1.0rc1だとうまく動かない)
たとえば、django.contrib.auth.models.Userを拡張するこんなモデルがあったとして。(user.get_profile() で UserProfile が取れるように設定しておく)
class Department(models.Model): name = models.CharField(u'名称', max_length=100, unique=True) class UserProfile(models.Model): user = models.ForeignKey(User, verbose_name=u'ユーザー', unique=True) dept = models.ForeignKey(Department, verbose_name=u'部署') class Hoge(models.Model): user = models.ForeignKey(User, verbose_name=u'ユーザー', unique=True) remark = models.CharField(u'備考', max_length=100)
Userを条件にしてHogeを検索したい場合, フォームはこう書く。レンダリング結果はSelectタグになる。
class HogeSearchForm(forms.Form): user = ModelChoiceField(label='ユーザー', queryset=User.objects.all(), required=False)
この検索フォームで、ある特定の部署のユーザーだけを表示したい場合や、ユーザーの部署もレンダリング結果に反映したい場合、1.0未満ではどうしようもなかったと思う。
1.0ではちょっとしたカスタマイズで両方実現できてしまう。
肝は__init__内でself.querysetを初期化することと、label_from_instanceをオーバーライドすること。
class CustomChoiceField(forms.ModelChoiceField): def __init__(self, queryset, empty_label=u"---------", cache_choices=False, required=True, widget=None, label=None, initial=None, help_text=None, to_field_name=None, *args, **kwargs): super(CustomChoiceField, self).__init__(queryset, empty_label, cache_choices, required, widget, label, initial, help_text, to_field_name, *args, **kwargs) # 選択候補を絞り込み self.queryset = User.objects.filter( id__in=UserProfile.objects.filter(dept__id=1).values('user__pk').query) def label_from_instance(self, obj): """ 表示をカスタマイズ。 """ return u'%s:%s' % (obj.get_profile().dept.name, obj.name) class HogeSearchForm(forms.Form): user = CustomChoiceField(label='test', queryset=None, required=False)
ちなみに、optionタグのvalueをカスタマイズしたい場合は、CustomChoiceFieldの__init__でto_field_nameにフィールド名を指定する。
こいつが使われるのが、django.forms.ModelChoiceIteratorのchoiceメソッド。
def choice(self, obj): if self.field.to_field_name: try: key = getattr(obj, self.field.to_field_name).pk except AttributeError: key = getattr(obj, self.field.to_field_name) else: key = obj.pk return (key, self.field.label_from_instance(obj))
to_field_nameが未指定の場合、モデルのIDがvalueに設定される。指定した場合は、getattrで属性を取得して、PK属性があればそれをvalueに設定するようになっている。
表示はUserProfileを使いたいけど、form.cleaned_data['user']ではUserを取得したい場合とかに便利。