[Python][TurboGears] TurboGearsでBlog作ってみる その3 入力チェック

http://d.hatena.ne.jp/aodag/20060518/1147968387
http://d.hatena.ne.jp/aodag/20060521/1148210554

えらく長く時間が開いてしまった。
TurboGears勉強会で続きはどうなってるのだという突っ込みがありましたので、続き。
その1, その2 を書いた当時のソースが残っていなかったため微妙にメソッド名や処理が異なる場合があります。
ある程度書き終わったら、いったんまとめるのでご容赦を。

では入力チェックを入れよう。
今回チェックする内容は以下のとおり

  • 日付チェック
  • 必須入力チェック

TurboGearsの入力チェックはFormEncodeを使う。
また、TurboGearsで入力チェックが追加されているので、そちらも使う。
turbogears.validators はformencode.validatorsをインポートしているので、以下のようにcontroller にインポートする

from turbogears import validators

また、入力チェックをさせるためのデコレータにvalidate、エラー発生時の遷移を指定するためのerror_handlerをインポートする

from turbogears import validate, error_handler

ブログ保存時に入力チェックを行うため、saveBlog メソッドにバリデータの設定を付け加える

    @expose()
    @error_handler(editBlog)
    @validate(validators={'date':validators.DateTimeConverter(format="%Y/%m/%d"),
                          'title':validators.UnicodeString(not_empty=True),
                          'description':validators.UnicodeString(not_empty=True)})
    @identity.require(identity.not_anonymous())
    def saveBlog(self, id=None, date=None, title=None, description=None):

validatorsを通すと、メソッドで受け取る時に、文字列からPythonオブジェクトに変換されている。
DateTimeConverterはdatetime.datetime になる。
他はユニコード文字列のままだ。
SQLiteを使っている場合、DateCol にdatetime を保存することはできるが、読み出しでエラーになってしまう。(時刻部分の文字列をパースできなくなるため)
DateCol に代入するときには、dateメソッドを使ってdatetime.date オブジェクトにする。

        if id is not None:
            b = Blog.get(id)
            b.set(date=date.date(), title=title, description=description)
        else:
            b = Blog(date=date.date(), title=title, 
                     description=description, 
                     authorID=identity.current.user.id)

エラーが発生した場合は、error_handlerで設定したeditBlogが呼び出される。
このとき、元の入力内容とエラー内容も引数で渡されてくる。
これらを受け取るため、引数を追加する。

    def editBlog(self, id=None, title=None, date=None, description=None, tg_errors=None):

メソッド内の処理はこんな感じ。

        inputs = {}
        if tg_errors is None and id is not None:
            blog = Blog.get(id)
            inputs['id'] = blog.id
            inputs['title'] = blog.title
            inputs['date'] = blog.date.strftime('%Y/%m/%d')
            inputs['description'] = blog.description
        else:
            inputs['id'] = id
            inputs['title'] = title
            inputs['date'] = date
            inputs['description'] = description
        if tg_errors is None:
            tg_errors = {}
        return dict(inputs=inputs, tg_errors=tg_errors)

入力エラー処理時、更新時、新規時によって入力値をフォームに出すため、tg_errorsとidの有無で切り替える。

HTMLフォームは以下のように修正する。

  <form action="saveBlog" method="POST">
    <input type="hidden" name="id" value="${inputs['id']}" py:if="inputs.get('id', None)"/>
    <table>
      <tr>
        <td>
          <label for="date">日付</label>
        </td>
        <td>
          <input type="text" id="date" name="date" value="${inputs.get('date', None)}"/>
          <div py:if="tg_errors.has_key('date')" 
               py:content="tg_errors['date']"/>
        </td>
      </tr>
      <tr>
        <td>
          <label for="title">タイトル</label>
        </td>
        <td>
          <input type="text" name="title" value="${inputs.get('title', None)}"/>
          <div py:if="tg_errors.has_key('title')" 
               py:content="tg_errors['title']"/>
        </td>
      </tr>
      <tr>
        <td>
          <label for="description">内容</label>
        </td>
        <td>
          <textarea name="description" py:content="inputs.get('description', None)"/>
          <div py:if="tg_errors.has_key('description')" 
               py:content="tg_errors['description']"/>

        </td>
      </tr>
    </table>
    <button type="submit">保存</button>
    </form>

idを隠しフィールドにもたせるが、Noneの場合はエレメントごと表示させない。(フォームがある時点で、id='' と解釈されてしまうのが気持ち悪いため)
また、各入力フィールドの下にエラーメッセージを出す。
tg_errorsのキーがエラーのあったフィールド名になっている。

次は、ウィジェットか。