Various
Web Form Widget Toolkits
          aodag
     Pycon JP 2011
お前誰よ


@aodag
小田切篤

BeProud勤務
今別の部屋で発表しているianと同僚

Djangoきらいです(´・ω・`) 
PylonsとかPyramidとか、既存のコンポーネント組み合わせてるもの
のほうが好きです。
SQLAlchemy

● データマッパー
● すごく柔軟
WSGI (PEP-333, PEP-3333)

PythonのWebアプリケーション標準

def hello(environ, start_response):
  start_response("200 OK",
     [('Content-type', 'text/plain')])
  return ["Hello, world!"]
Adminアプリケーション

Djangoのadmin(だけ)はいいね!

SQLAlchemyにも同じようなものがほしい

WSGIアプリで全般的に使いたい(あまりフレームワークに依存したく
ない)

Ajaxばりばりである必要はないけど、DatePickerとかSuggestとか、入
力補助系のJSが利用できるとよい
Adminアプリケーション

● クラスごとにサブアプリケーション
● グリッド表示と検索
● 入力フォーム
● カスタムアクションを追加できる
フォームライブラリを調査

Form Libraryの役割
比較ライブラリ
Form Libraryの役割

● HTMLフォーム生成
● バリデーション
比較ライブラリ

● ToscaWidgets / Sprox
● FormAlchemy
● tw2.sqla
● WTForms
● deform/colander
比較のポイント

● SQLAlchemyとの親和性
● バリデータのカスタマイズ
● フィールドのカスタマイズ
● ウィジェットのカスタマイズ
ToscaWidgets / Sprox

● SproxはSQLAlchemyのスキーマからToscaWidgetsのフォームを
  作成します
● SQLAlchemyのスキーマ以外のフィールドをフォームに追加した
  り、スキーマのフィールドをフォームから削除したりできます。
● formencodeでカスタムバリデータを作成します
● tw.formsでカスタムフィールドを作成します
FormAlchemy

● SQLAlchemyだけでなく、zope.schemaなどにも対応
● SQLAlchemyのスキーマ対応は一番すぐれている
● デフォルトで用意されているウィジェットレンダラーが少ない
● 実行時にもフィールド定義を変更可能
● jqueryuiを使ったfa.jqueryのような追加スキンが存在する
tw2

● ToscaWidgetsの後継
● まだ a4がリリースされたばかり、 発展途上
● SQLAlchemyから自動生成するフォームがある
   ○ many-to-manyまでは対応できてない
● SQLAlchemyというよりElixirに対応している
wtforms

● SQLAlchemyからのスキーマ生成はしない
● SelectのoptionをSQLAlchemyのクエリで設定可能
● 機能は少なめ
● その分はまりどころが少なく枯れるのが早そう
● フォーム全体の生成はしない
● グリッド生成もしない
● ToscaWidgetsのtw.formsと名前が紛らわしい><
deform / colander

 ● colanderはスキーマ定義
 ● deformはcolanderに対応しているフォームライブラリ
 ● SQLAlchemyからのスキーマ生成できない
 ● ウィジェットが豊富
 ● ajaxとりこみに意欲的
 ● deferred bindingにより実行時にウィジェットやバリデーションを変
   更可能
 ● テンプレートにchameleon(zope page template)を使っている
 ● テンプレートをmakoに入れ替えるプロジェクトが進行中

http://deformdemo.repoze.org/
Sample Model

SQLAlchemy
 ● User
    ○ user_name
    ○ password
    ○ user_image
    ○ groups
 ● Group
    ○ name
    ○ users
    ○ permissions
 ● Permission
    ○ name
    ○ groups
ポイント

● 全部many-to-manyの関連付け
● User - Group - Permission
● User - Permissionの派生関連付け
● _password 直接見せたくないフィールド
● ユーザー画像はファイル保存
Userフォーム Sprox

class UserForm(AddRecordForm):
   __model__ = models.User
   __require_fields__ = ['user_name']
   __omit_fields__ = ['_password']
   __field_order__ = ['user_name', 'password', 'groups']

  password = tw.forms.PasswordField('password',
                     validator=tw.forms.validators.NotEmpty)
Userグリッド Sprox

class UserTable(TableBase):
   __model__ = models.User

user_table = UserTable(models.DBSession)

class UserTableFiller(TableFiller):
   __model__ = models.User

user_table_filler = UserTableFiller(models.DBSession)

user_table(user_table_filler.get_value())
Sprox雑感

● AddRecordFormとEditableFormをそれぞれ作らないといけない
● SQLAlchemy0.7で動かない!
● many-to-manyがうまくフォームに反映されない
● デフォルトでアルファベット順になってしまうので、いい感じの順
  番にするには、全部指定しなおさないといけません(´・ω・`) 
(´・ω・`) 
そろそろ
オワコン?
Userフォーム FormAlchemy

class UserForm(FieldSet):
   def __init__(self, **kw):
     super(UserForm, self).__init__(model=User, **kw)

    excludes = [self._password]
    # 追加フィールド
    self.insert_after(self.user_name,
       Field('password').password().required())
    self.configure(exclude=excludes) # フォーム全体の設定
User グリッド FormAlchemy

class UserGrid(Grid):
   def __init__(self, **kw):
     super(UserGrid, self).__init__(cls=models.User, **kw)

    # Edit用のリンク追加
    self.append(Field('edit_link', value=lambda u: '<a href="%s/edit"
>Edit</a>' % u.id))
    self.configure(readonly=True, exclude=[self._password])


users = models.DBSession.query(models.User).all()
grid = user_grid.bind(users)
FormAlchemy雑感

やっぱりSQLAlchemy0.7で動かない(´・ω・`) 
fa.jqueryはまだ安定していない
many-to-manyをしっかりおいかけてくれる
開発が活発なので、今後に期待できる
全体的には
(・∀・)イイ!
  と思う
Userフォーム tw2.sqla

class UserForm(tw2.sqla.DbFormPage):
   entity = models.User
   class child(tw2.forms.TableForm):
      user_name = tw2.forms.TextField(validator=tw2.core.Required)
      password = tw2.forms.PasswordField(validator=tw2.core.
Required)
      user_image = tw2.forms.FileField()
      groups = tw2.sqla.DbSingleSelectField(entity=models.Group)
● SQLAlchemyのスキーマから自動生成する機能が追加されてき
  ていますが、使い物になりませんでした。(´・ω・`) 
● あと、entityクラスのqueryメソッドを呼ぼうとしたり、Elixirを前提に
  しすぎです。
● 遅延評価できるselectウィジェットに複数選択可能なものがなく、
  many-to-manyの関連付けに困ります
(゚д゚)
マダマダ
Userフォーム WTForms

def group_factory():
  return models.DBSession.query(models.Group)

class UserForm(wtforms.Form):
   username = wtforms.TextField('User Name')
   password = wtforms.PasswordField('Password')
   groups = QuerySelectMultipleField(query_factory=group_factory )
WTForms表示

<form method="post">
<table>
${self.field_row(form.username)}
${self.field_row(form.password)}
${self.field_row(form.groups)}
</table>
<button type="submit">Add</button>
</form>
WTForms 表示

<%def name="field_row(field)">
<tr>
<td>
${field.label}
</td>
<td>
${field()}
</td>
</tr>
</%def>
WTForms 雑感

● やれることが少ない分、はまりどころはなさそうです
● でもフォームライブラリ使ってるのにHTMLテーブル書くのはやで
  す。
● B2Cサイトで複雑なHTMLに入れるのに向いてそうですが、そん
  なことは他のフォームライブラリでできます
機能
少なすぎね?
 (´・ω・`) 
Userフォーム deform

class UserSchema(c.MappingSchema):
   # colanderはSQLAlchemyから自動生成しない
   user_name = c.SchemaNode(c.String())
   password = c.SchemaNode(c.String(),
      widget=w.PasswordWidget())


form = Form(UserSchema(), buttons=('save',))
colanderのdeferred bind 定義

実行時に、ウィジェット、やバリデータを切り替える仕組み

@c.deferred
def group_select_widget(node, kw):
  groups = kw['groups']
  return w.SelectWidget(values=[
     (g.id, g.group_name)
     for g in groups
     ])
colander deffered binding

class Group(c.MappingSchema):
   # groupを選択するためのスキーマ
   group_id = c.SchemaNode(c.String(),
        widget=group_select_widget)

class Groups(c.SequenceSchema):
   # groupを複数選択するためのスキーマ
   group = Group()

class UserSchema(c.MappingSchema):
   ......
   groups = Groups()
colanderのdeferred bind バインディング


schema = UserSchema()

groups = DBSession.query(Group)

# バインド
schema = schema.bind(groups=groups)

form = Form(schema, ....)
User フォーム deform バリデーション

try:
   params = form.validate(controls)
except ValidationFailure, e:
   e.render()
deform 雑感

● フォームでやりたいことは、おそらくなんでもできます。
● シーケンススキーマやマッピングスキーマを組み合わせることで、
  複雑な階層を持つスキーマも作成可能。
● その分ライブラリの構造が複雑です。
(´  > ω < )
  むずかしー!
ひとまずSQLAlchemyのデータ管理ツールを作るなら、FormAlchemy
が一番サポートされている。fa.jqueryは様子見たほうがいい。

1リクエストで複数のモデルを扱う場合は、colander / deform がほぼ
どんな構造でも対応できる。MongoDBなどスキーマレスDBを使う場
合は、こちらをおすすめする。
スタティックファイルの管理

deformやfa.jqueryはjquery.jsやjqueryui.js、その他cssなどが必要

フロントのApacheやnginexに任せてしまいたいが、
フォームライブラリが使うスタティックファイルはどこにあるのか?

ウィジェットライブラリが依存するjsなどをどう管理していくか?
Paste deployでがんばr

[app:deform_static]
use = egg:paste#pkg_resources
egg = deform
resource_name = deform/static

[composite:deform]
use = egg:paste#urlmap
/ = deform_app
/static = deform_static

[pipeline:deform_demo]
pipeline =
   egg:repoze.tm2#tm
   deform
Fanstatic

 ● スタティックファイルをホスティングするWSGIアプリ
 ● スタティックファイルを管理するユーティリティ、ミドルウェア
 ● fa.jqueryは今後これを使うようになる予定

http://pypi.python.org/pypi?%
3Aaction=search&term=fanstatic&submit=search
Fanstatic 例

from fanstatic import Fanstatic
from js.jqueryui import jqueryui

@wsgify
def app(request):
  jqueryui.need()
  return Response(body)

app = Fanstatic(app)
Fanstatic 例

body = """
<html>
<head>
</head>
<body>
Hello
</body>
</html>
"""
Fanstatic 例 実行結果

<html>
<head>
  <script type="text/javascript" src="/fanstatic/jquery/jquery.js"
></script>
<script type="text/javascript" src="/fanstatic/jqueryui/ui/jquery-ui.js"
></script>

</head>
<body>
Hello
</body>
</html>
参考

● SQLAlchemy http://www.sqlalchemy.org/
● ToscaWidgets http://toscawidgets.org/
● formencode http://formencode.org/
● Sprox http://sprox.org/
● tw2 http://toscawidgets.org/documentation/tw2.core/
● formalchemy http://docs.formalchemy.org/formalchemy
● fa.jquery http://docs.formalchemy.org/fa.jquery/
● WTForms http://wtforms.simplecodes.com/
● colandar https://docs.pylonsproject.org/projects/colander/dev/
● deform https://docs.pylonsproject.org/projects/deform/dev/
● fanstatic http://www.fanstatic.org/en/0.11.2/index.html

Form libraries