久々の投稿です。あまり記事を書かないのも寂しいので、今回、web2py の SQLFORMのテクニックの「あれこれ」を書いてみたいと思います。今回紹介する設定は、特別なものではありません。
SQLFORMはモデル定義(テーブル定義)を元にフォームを自動作成します。
同じような機能で、HTMLヘルパークラスのFORMがあります。FORMはコンストラクタで、HTMLフォームを生成します。また、入力値をバリデータチェックするメソッドが用意されています。
内部的には、SQLFORMはFORMの機能を使用しています。しかしSQLFORMは、FORMの様にフィールドを定義する必要がなく、モデルから入力用フィールドを生成します。またデータベースからレコードを取得して表示し、バリデータチェック後に入力値をデータベースに保存します。
SQLFORMの基本
まず、サンプルを示してみる。最初はモデル定義(db.py など)だが、次のように category と image という2つのテーブルを定義する。
1 2 3 4 5 6 7 8 9 10 11 | db.define_table('category', Field('name', 'string', length=32, notnull=True), format='%(name)s') db.define_table('image', Field('name', 'string', length=64, notnull=True), Field('url', 'string', length=256), Field('file', 'upload', autodelete=True), Field('category', db.category)) db.image.category.requires=IS_IN_DB(db,'category.id','%(name)s', zero=T('choose one')) |
ここでは image テーブルに対するデータ操作のため、コントローラファイル(default.pyなど)に image というSQLFORMを使用した関数を記述する。 関数の2行目では SQLFORMインスタンスを生成すると共に、process メソッドを呼び出す。SQLFORMコンストラクタでは第一パラメータに、対象のテーブルオブジェクトを指定する。
1 2 3 | def image(): form = SQLFORM(db.image).process() return dict(form=form) |
processメソッドを使うのではなく、accepts メソッドを利用することも可能だ。しかし、
- acceptsメソッドの戻り値はブール値で、process の様に自分自身のオブジェクトを返さない。
- flashメッセージをデフォルトでは設定できない。
1 2 3 4 5 6 7 8 9 | def image(): form = SQLFORM(db.image) if form.accepts(request.vars, session): response.flash = T('form accepted') elif form.errors: response.flash = T('form has errors') else: response.flash = T('please fill out the form') return dict(form=form) |
syntax2html |
特に注意点として、acceptsメソッドでは request.vars(request.vars, request.get_vars, request.post_vars, request のどれかで指定)とsessionをパラメータで指定する必要がある。session を指定しなくても動作するのだが、指定するとフォームキーを生成し二重投稿をサーバ側で防止する(通常はブラウザ側でsubmitキーにロックがかかるため二重投稿はできない)。
(sessionパラメータが抜けていたので、requestパラメータの記述と併せて訂正・追記しました)
同一コードで accepts の代わりに、processメソッドを利用することも可能だ。processメソッドの戻り値はブール値でないため、次のように accepted インスタンス変数を利用して if 判定を行う。processメソッドではパラメータは指定する必要がない。
1 2 3 4 5 6 7 8 9 | def image(): form = SQLFORM(db.image) if form.process().accepted: response.flash = T('form accepted') elif form.errors: response.flash = T('form has errors') else: response.flash = T('please fill out the form') return dict(form=form) |
また processメソッドでは次のように flashメッセージがデフォルト設定されている。
- message_onsuccess = T("Success!")
- message_onfailure = T("Errors in form, please check it out.")
これらのメッセージを変更したい場合、次のように processメソッドのパラメータで指定する。
1 2 3 4 | def image(): form = SQLFORM(db.image).process(message_onsuccess=T("form accepted"), message_onfailure=T("form has errors")) return dict(form=form) |
ちなみに process は FORM クラスで定義されているメソッドだ。このため SQLFORM だけでなく、FORM インスタンスに対しても利用可能だ。
レコードの更新及び削除
今まで説明したコードでは、レコードの新規登録しかできない。レコードの更新もしくは削除を行うためには、SQLFORMコンストラクタの第二(もしくは record)パラメータに、レコードのRowオブジェクト、もしくはレコードのid番号を指定する必要がある。サンプルを示してみる。
1 2 3 | def image(): form = SQLFORM(db.image, request.args(0)).process() return dict(form=form) |
該当するID番号のレコードが存在しない場合、HTTP 404 エラーが発生する。普通は手動でID番号を指定しないだろうが、404エラーを発生させないようにするために事前にレコードをチェックし、次のようにRowオブジェクトを第二パラメータに渡してもよい。
1 2 3 4 | def image(): record = db.image(request.args(0)) or None form = SQLFORM(db.image, record).process() return dict(form=form) |
レコードの削除オプションについては、SQLFORMコンストラクタの第三(もしくは deletable)パラメータに True で渡してあげればよい。このため最終的に、更新・削除では次のようなコードになる。
1 2 3 | def image(): form = SQLFORM(db.image, request.args(0), True).process() return dict(form=form) |
次のイメージが、削除オプションが追加された画面だ。
今回、更新・削除画面ではURLパラメータにレコードIDを渡す方法を取った。これは比較的テストを実施し易いためだが、番号を渡しさえすればよいので、URLクエリー変数やセッション変数で渡すのもOKだ。
アップロードファイルの確認
アップロードしたファイルを更新する場合、「ファイル選択」ボタンを押して更新をしたいファイルを選択すればよい。しかし更新画面で、アップロードしたファイルを確認したい場合、どうすればよいだろうか?。
SQLFORMコンストラクタには upload パラメータがあり、設定するとダウンロード用のリンクを表示してくれる。さらに画像フィルの場合はプレビューを表示する。設定方法は default.py コントローラファイルにアプリケーション生成時にデフォルトで定義される download 関数のURLを、パラメータに設定するだけだ。 upload パラメータを設定したコードは、次のようになる。
1 2 3 4 | def image(): form = SQLFORM(db.image, request.args(0), True, upload=URL(c='default', f='download')).process() return dict(form=form) |
設定した関数を実行すると、次のイメージになる。
アップロードしたファイルが画像のため、プレビュー画面が表示されている。また file というリンクが表示されている。このリンクをクリックすると、アップロードファイルをダウンロード可能だ。さらに、delete というラベルが付いたチェックボックスも表示される。このチェックボックスをクリックすると、アップロードファイルを削除できる。
もしアップロードファイルにイメージファイルしか許可しないのであれば、最初に設定したモデル定義(db.py など)で次のバリデータを追加した方がよい。
db.image.file.requires=IS_IMAGE()
IS_IMAGE はイメージ形式以外のファイルアップロードを禁止する。png 形式でかつ、最大 200×200ピクセルまでの画像ファイルのみアップロード可能にするには、次のように IS_IMAGE に extensions と maxsizeパラメータを設定する。
db.image.file.requires=IS_IMAGE(extensions=('png'), maxsize=(200, 200))
最大サイズをピクセルではなく、ファイルの大きさで規制を行いたい場合は、IS_LENGTH で最大バイト数を指定する。次の例では、ファイル形式は jpeg 及び png で、最大ファイルサイズは 256kbyte で規制を行う。
db.image.file.requires=[IS_IMAGE(extensions=('jpeg', 'png')), IS_LENGTH(maxsize=262144)]
アップロードファイルに対するこれらのバリデータを設定して、SQLFORMのレコード更新画面を見た場合、妙な点に気づかないだろうか?。
そう、deleteチェックボックスが表示されなくなる。つまりこれらのバリデータはファイルの入れ替えは許可するが、アップロードファイルの削除は許可しない。もしアップロードファイルを削除を行いたい場合、次のようにバリデータの設定を行う。
db.image.file.requires=IS_EMPTY_OR([IS_IMAGE(extensions=('jpeg', 'png')), IS_LENGTH(maxsize=262144, error_message=T('max %(max)g bytes'))])
IS_EMPTY_OR バリデータで、今まで設定した条件を囲む。IS_EMPTY_OR は、空もしくは何かの条件、という意味のバリデータなので、アップロードフィールドに何も設定しないという条件も許可される。さらに IS_LENGTH のエラーメッセージもアップロードファイルに対しては少しおかしいため、カスタマイズのエラーメッセージを設定する。
取り敢えずここまでは、基本的な事を書いてみました。本当はもっと複雑な事を書きたかったのですが、記事が長くなったため、次回 に持ち越します。