ModelFormのような内部のMetaを見るFormを自分で作る
ModelFormのように、内部のMetaを見るクラスを自分で作って見たいと思った。
概要
djangoでは、以下のようにMetaを使ってmetaclassに情報を渡して、Formクラスを作れる。
class ArticleForm(ModelForm): class Meta: model = Article
また、以下のようにfieldを直接書けるのもdjangoが用意しているmetaclassの機能*1。
class ContactForm(forms.Form): topic = forms.ChoiceField(choices=TOPIC_CHOICES) message = forms.CharField(widget=forms.Textarea()) sender = forms.EmailField(required=False)
同じようにMetaをみるクラスを作ってみたいと思った。
今回作る機能
フォームの以下の機能を分けてみる。
- 人間が見やすいようなhtmlのレイアウトをする機能
- 入力(POST)されたデータを検証する機能
それぞれ「Layoutフォーム」、「Validateフォーム」と呼ぶことにする。
この時のValidateフォームを作ってみることにする。
注意
入力値の検証に失敗した時、再入力を促したい。
なので、ValidateフォームからLayoutフォームへ変換できる必要がある。
使い方
以下のようにして使う。
formの定義
class LayoutForm(forms.Form): ## どういう風に表示するかなどたくさん書く。 item = forms.ChoiceField(choices=[(i, i) for i in xrange(3)]) date = forms.DateTimeField(required=False) class ValidateForm(ValidateForm): ## 値の検証方法をたくさん書く item = forms.IntegerField() date = forms.DateTimeField() class Meta(object): layout_form = LayoutForm #form.layout_form()が呼べるようになる.
定義しようとしているFormクラスが大きすぎない限り煩雑になるだけであまり意味がないもだけれど。
viewでの利用
#... form = ValidateForm(dict(item=3), prefix="foo") if not form.is_valid(): lform = form.layout_form() #formで生成されたerror.listを持っている #...
ValidateFormが行っている作業
- form.layout_form()の生成
- layoutフォームとValidateフォームのフィールドの差分を検証
- Validateフォームで定義された全てのフィールドをLayoutフォームが持っていないとエラー
作り方
- Metaに含まれた情報を格納するValidateFormOptionsというクラスを作る
- ValidateFormMetaclassというmetaclassを作る
- __metaclass__に定義したmetaclassを渡したクラスを作る
import django.forms.forms as forms class ValidateFormOptions(object): def __init__(self, options=None): self.layout_form = getattr(options, "layout_form", None) self.translate = getattr(options, "translate", None) def layout_form_function_fuctory(formclass): def layout_form(self): form = formclass(auto_id = self.auto_id, prefix = self.prefix, error_class = self.error_class, label_suffix = self.label_suffix, empty_permitted = self.empty_permitted) form._changed_data = self._changed_data form.data = self.data form._errors = self._errors return form return layout_form class ValidateFormMetaclass(forms.DeclarativeFieldsMetaclass): def __new__(cls, name, bases, attrs): new_class = super(ValidateFormMetaclass, cls).__new__(cls, name, bases, attrs) opts = new_class._meta = ValidateFormOptions(getattr(new_class, "Meta", None)) if opts.layout_form: ## validate layout_form_fields = opts.layout_form.base_fields for k in new_class.base_fields.keys(): if layout_form_fields.get(k) is None: raise AssertionError("%s is not found in %s class" % (k, opts.layout_form.__name__)) if opts.translate: new_class.layout_form = opts.translate else: new_class.layout_form = layout_form_function_fuctory(opts.layout_form) class ValidateForm(forms.BaseForm): __metaclass__ = ValidateFormMetaclass