FormAlchemy のカスタマイズ

FormAlchemy には PostgreSQL の範囲型や配列に対応する renderer が 用意されていない。しかしコードを読んだらカスタマイズはそれほど 難しくはなさそうだったので書いてみた。

要点は、 formalchemy.fields.FieldRenderer をベースにして render(), render_readonly(), deserialize() 等をオーバーライドする。 フォーム出力用に formalchemy.helpers.text_field() というヘルパー函数が 用意されているため、これを利用すれば手間が省ける。 deserialize() はフォームの入力内容をパースする時に呼ばれるメソッドで、 入力内容を解釈して適切な Python オブジェクトに変換する機能を持たせる。

init4range と array 用のコードは以下のようになる。 これを forms.py に追加する。

from formalchemy import FieldSet
from formalchemy.helpers import text_field
from sqlalchemy.dialects.postgresql import INT4RANGE, ARRAY
from formalchemy.fields import FieldRenderer
from psycopg2.extras import NumericRange

class Int4RangeRenderer(FieldRenderer):
  def _range_string(self):
    # self.value は u"NumericRange(115, 889, '[)')" という文字列になっている
    # self.raw_value に NumericRange インスタンスがあるのでそれを利用
    # PostgreSQL 内には "[lower, upper+1)" という形式で格納されるようだが
    # 解り難いので "[lower, upper]" という形式で入出力する
    if isinstance(self.raw_value, NumericRange):
      value = "[{},{}]".format(self.raw_value.lower, self.raw_value.upper-1)
    else:
      value = ""
    return value

  def render(self, **kwargs):
    return text_field(self.name, value=self._range_string(), **kwargs)

  def render_readonly(self, **kwargs):
    return self._range_string()

FieldSet.default_renderers[INT4RANGE] = Int4RangeRenderer


class ArrayRenderer(FieldRenderer):
  def render(self, **kwargs):
    value = " ".join(self.raw_value)
    return text_field(self.name, value=value, **kwargs)

  def deserialize(self):
    # スペース区切りの文字列として受け取る
    data = self.params.getone(self.name)
    data = data.split()
    return data

FieldSet.default_renderers[ARRAY] = ArrayRenderer

FormAlchemy の設計では配列型の中身のデータ型に応じて default_renderer を切り替えるようにはできないので、上記コードでは 配列型を一緒くたに扱っている。