読者です 読者をやめる 読者になる 読者になる

Blogを作る(4)記事の投稿・編集

Pylons

 記事の投稿、編集を行えるようにします。

入力値の検証

 入力値の検証には、FormEncodeのSchemaの派生クラスを使います。まず、記事(Post)の検証に使う、PostSchemaクラスを定義します。

# -*- coding: utf-8 -*-
from formencode import validators
from formencode.schema import Schema

class PostSchema(Schema):
    # allow_extra_fields=Trueにしないと、
    # <input type="submit" value="Submit" />によって送信された値も
    # 検証対象になってしまうので、Trueに設定しておく。
    allow_extra_fields = True

    # エントリのタイトル
    # 文字数に上限なしだが、空白文字列はダメ。
    title = validators.String(not_empty=True, strip=True)

    # エントリ本文
    # 同じく文字数に上限なし。
    content = validators.String(not_empty=True)

class EntriesController(BaseController):
    # ... 略

 PostSchemaの定義は、blogtutorial/controllers/entries.pyの先頭付近に行いました。タイトルに入力された値は空白文字列を不可(自動的に前後の空白文字列がstripされる)、本文は空白文字OKにしましたが、FormEncodeの挙動を確認してみたかっただけで、それ以上の意図はありません。あと、SQLiteで動作を確認しましたが、OracleではNOT NULL制約に引っかかって動作しないかもしれません。

検証、新規作成、更新

 新規作成の場合も、更新の場合も、根本は同じなので、まとめて紹介します。
 blogtutorial/controllers/entries.pyのEntriesControllerのnew, createメソッド(新規作成)、edit, updateメソッド(更新)を実装します。検証には、validateデコレータを使用しました。

class EntriesController(BaseController):
    def __before__(self):
        model.session_context.current.clear()

    # ... 略

    @validate(schema=PostSchema(), form='new')
    def create(self):
        """POST /: Create a new item."""
        # url_for('entries')
        current = model.session_context.current

        obj = Post(**self.form_result)
        current.save(obj)
        current.flush()
        redirect_to('/entries')
    
    def new(self, format='html'):
        """GET /new: Form to create a new item."""
        # url_for('new_entry')
        return render_response('entries/new.myt')

    @validate(schema=PostSchema(), form='edit')
    def update(self, id):
        """PUT /id: Update an existing item."""
        # Forms posted to this method should contain a hidden field:
        #    <input type="hidden" name="_method" value="PUT" />
        # Or using helpers:
        #    h.form(h.url_for('entry', id=ID), method='put')
        # url_for('entry', id=ID)
        entry = model.session_context.current.query(Post).get_by(id=id)
        entry.title = self.form_result['title']
        entry.content = self.form_result['content']
        model.session_context.current.flush()
        redirect_to('/entries')

    def edit(self, id, format='html'):
        """GET /id;edit: Form to edit an existing item."""
        # url_for('edit_entry', id=ID)
        c.entry = model.session_context.current.query(Post).get_by(id=id) or abort(404)
        c.form_result = {
            'title'   : c.entry.title,
            'content' : c.entry.content
        }
        return render_response('entries/form.myt')

 上のコードのnew, editメソッドが、フォーム表示画面に対応するアクションで、validateでデコレートされているcreate, updateが実際にレコードを挿入、更新するアクションです。

    @validate(schema=PostSchema(), form='new')
    def create(self):
        pass

 validateデコレータのschemaキーワード引数に、上で定義したPostSchamaクラスのインスタンスを与え、formキーワード引数に検証失敗した場合のフォーム表示アクション名を指定します。検証やエラーメッセージの表示といった仕事は、validateデコレータと、その背後にあるformencode.htmlfillが面倒を見てくれます。

 検証が成功した場合、create, updateメソッドからは、入力値をself.form_resultで参照できるので、これを利用して、挿入、更新を行います。正常終了時には、トップ画面(/entires)にリダイレクトさせるようにしました。

フォームのテンプレート

 続いて、新規作成、更新用のテンプレートをそれぞれ用意します。

 新規作成用のblogtutorial/templates/entries/new.myt

<h2><% _('Post a new entry') %></h2>
<% h.form(h.url_for('entries'), method='post') %>
<p><label><% _('Title') %></label>
<% h.text_field('title') %>
</p>
<p><label><% _('Text') %></label>
<% h.text_area('content', size='30x10') %>
</p>
<p>
<% h.submit(value=_('Create')) %>
</p>
<% h.end_form() %>

<% h.link_to(_('Back'), h.url(action='list', id=None)) %>

 更新用のblogtutorial/templates/entires/form.myt

<h2><% _('Update the post') %></h2>
<% h.form(h.url_for('entry', id=c.entry.id), method='put') %>
<p><label><% _('Title') %></label>
<% h.text_field('title', value=c.form_result['title']) %>
</p>
<p><label><% _('Content') %></label>
<% h.text_area('content', c.form_result['content'], size='30x10') %>
</p>
<p>
<% h.submit(value=_('Update')) %>
</p>
<% h.end_form() %>

<% h.link_to(_('Back'), url=h.url_for('entry', id=c.entry.id)) %>


 routing.pyでmap.resourceを使って、REST風のURLマッピングを行っている場合は、新規作成にはHTTP POST、更新にはHTTP PUTを使わなくてはなりません。が、現実世界のブラウザでは、HTTP PUTでフォーム入力値を送信することは難しいので、Pylonsでは_method=PUTというパラメータを使って代用しています。このパラメータのHiddenフィールドを追加しているのが、以下の箇所です。

<% h.form(h.url_for('entry', id=c.entry.id), method='put') %>

 実際の出力結果は次のようになります。

<form action="/entries/2" method="POST">
<input id="_method" name="_method" type="hidden" value="put" />


 最後に、新規作成画面、編集画面へのリンクを作ります。

<!-- 新規作成画面へ -->
<% h.link_to(_('Post a new entry'), url=h.url_for('new_entry')) %>
<!-- 編集画面へ -->
<% h.link_to(_('Edit'), h.url_for('edit_entry', id=c.entry.id)) %>


 編集フォームで入力エラーがあった場合、こんな感じになります。


 相変わらず見栄えが悪かったり、Blogなのに誰でも編集できたりと、まだまだ先は長いですが、おいおい修正していきます。