2012年9月11日火曜日

web2py SQLFORM あれこれ ① SQLFORM基本

久々の投稿です。あまり記事を書かないのも寂しいので、今回、web2py の SQLFORMのテクニックの「あれこれ」を書いてみたいと思います。今回紹介する設定は、特別なものではありません。

SQLFORMはモデル定義(テーブル定義)を元にフォームを自動作成します。
同じような機能で、HTMLヘルパークラスのFORMがあります。FORMはコンストラクタで、HTMLフォームを生成します。また、入力値をバリデータチェックするメソッドが用意されています。

内部的には、SQLFORMはFORMの機能を使用しています。しかしSQLFORMは、FORMの様にフィールドを定義する必要がなく、モデルから入力用フィールドを生成します。またデータベースからレコードを取得して表示し、バリデータチェック後に入力値をデータベースに保存します。

参考: web2py book - フォームとバリデータ

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)
記述した関数を実行すると、SQLFORM が生成するフォームが表示される。

processメソッドを使うのではなく、accepts メソッドを利用することも可能だ。しかし、

  • acceptsメソッドの戻り値はブール値で、process の様に自分自身のオブジェクトを返さない。
  • flashメッセージをデフォルトでは設定できない。
といった制約のため、acceptsメソッドを使う場合、次のようなコードを記述する必要がある。
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)
実は、web2py book 第三版 では accepts メソッドを紹介していたが、第四版からは process メソッドを紹介するようになった。これは、process メソッドを利用した方が簡潔に記述できる、からだと思われる(実際、ショートカット機能として紹介されている)。

また 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番号をURLパラメータで渡す。
http://127.0.0.1:8000/myapp/default/image/3
このURLパラメータは ID番号が3のレコードを対象にする。更新画面は次のようなイメージになる。

該当する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 に extensionsmaxsizeパラメータを設定する。

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 のエラーメッセージもアップロードファイルに対しては少しおかしいため、カスタマイズのエラーメッセージを設定する。


取り敢えずここまでは、基本的な事を書いてみました。本当はもっと複雑な事を書きたかったのですが、記事が長くなったため、次回 に持ち越します。