Toscawidgets の Widget の作成例
Toscawidgets のサンプルが少なくて、ちょっと凝ったWidget を作るのに苦労したので共有しときます。
TwoStepSelectField
1番目のSingleSelectField で絞り込み、2番目のSingleSelectField でデータを選択するフィールドセットを実装しました。
twostep.py
# -*- coding: utf-8 -*- from tw.api import * from tw.forms.fields import * from formencode.schema import Schema from formencode.validators import * __all__ = ["TwoStepSelectField"] class TwoStepSelectFieldSchema(Schema): """ NULL チェックのために ignore_key_missing は False にする """ allow_extra_fields = True filter_extra_fields = False ignore_key_missing = False class VoiceTextValidator(FormValidator): """ DB保存の前処理 """ def _to_python(self, value, state): if "main" in value.keys(): value = value["main"] return value twostep_js = JSLink(modname=__name__, filename='static/twostep.js') initialize_twostep = js_function("initialize_twostep") class TwoStepSelectField(FieldSet): """ guide で絞り込みを行ってから main を選択する、リストボックスを2つ用いたフィールド field として代表して持つ値は main の値。 絞り込み要素を与えるために、初期化時に2次元の辞書を与える。 """ template = "myproj.forms.templates.twostep" params = ["filter_dict", "main_attr", "guide_attr"] javascript = [twostep_js] include_dynamic_js_calls = True validator = TwoStepSelectFieldSchema( chained_validators = [VoiceTextValidator()] ) def __new__(cls, id=None, parent=None, children=[], **kw): guide_attr = dict(label_text="guide", options=[], validator=Int) main_attr = dict(label_text="main", options=[], validator=Int) if "guide_attr" in kw.keys(): guide_attr.update(kw["guide_attr"]) if "main_attr" in kw.keys(): main_attr.update(kw["main_attr"]) class fields(WidgetsList): guide = SingleSelectField(**guide_attr) main = SingleSelectField(**main_attr) children = fields return super(TwoStepSelectField, cls).__new__(cls, id, parent, children, **kw) @property def groups(self): """ 初期化時に渡された filter_dict (dict or callable) を辞書化して返す """ if callable(self.filter_dict): return self.filter_dict() return self.filter_dict def adapt_value(self, value): """ value の加工 (フォーム表示の前処理) """ for k, v in self.groups.items(): if value in dict(v).keys(): return {"guide": k, "main": value} return value def update_params(self, d): """ template に渡す d の加工 """ super(TwoStepSelectField, self).update_params(d) main_value = d["value_for"](d["c"]["main"]) self.add_call(initialize_twostep(main_value, self.id, self.groups)) # "include_dynamic_js_calls = True" is required return d
twostep.js
function initialize_twostep(val, parent_id, groups) { var guide = document.getElementById(parent_id + "_guide"); var main = document.getElementById(parent_id + "_main"); function change_selects() { var idx = guide[guide.selectedIndex].value; for (var i = main.options.length - 1; i >= 0; i--) { main.options[i] = null; } if (groups[idx]) { for (var i = 0; i < groups[idx].length; i++) { var id = groups[idx][i][0]; var label = groups[idx][i][1]; main.options[i] = new Option(label, id); } } } // Add Event to guide field. if (guide.addEventListener) { guide.addEventListener("change", change_selects, false); // Not IE } else { guide.attachEvent("onchange", change_selects); // IE } // Initialize change_selects(guide[guide.selectedIndex].value); for (var i = 0; i < main.options.length; i++) { if (main.options[i].value == val) { main.selectedIndex = i; break; } } }
twostep.mak
## -*- coding: utf-8 -*- <%namespace name="tw" module="tw.core.mako_util"/> % for field in ifields: ${ field.label_text} ${ field.display(value_for(field), **args_for(field)) } % endfor
使い方
絞り込みのための辞書を用意して、以下のように使います。
foods = {"vegetable": [(1, "tomato"), (2, "")], "fruit": [(3, "orange"), (4, "apple")], } food = TwoStepSelectField( label_text="Food", guide_attr=dict(label_text = "Category", options = [(c, c) for c in foods.keys()], validator = String), main_attr=dict(label_text = "Food", options = [], validator = Int(not_empty=True)), filter_dict=foods, )
参考
公開メソッドと非公開メソッドが入り乱れていて分かりづらいですが、tw.core:Widget を読むことをお勧めします。
個人的には、 adapt_value の使い方に気づくまで時間がかかりました。
posted by id:junya_hayashi