form libraries
TRANSCRIPT
Various Web Form Widget Toolkits
aodagPycon 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.deferreddef 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_resourcesegg = deformresource_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 Fanstaticfrom js.jqueryui import jqueryui
@wsgifydef 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