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

Blogを作る(3) 記事の一覧・詳細

Pylons

 記事の一覧、詳細を表示するところまで作ります。

【追記】
 出力時のHTMLエスケープ処理がなかったので、テンプレートを修正しました。以下、言い訳。Pylonsの公式のチュートリアルで勉強しながら書いているのですが、公式のドキュメントで出力エスケープについて、何も言及していないし、登場するテンプレート例でも使っていないのは、いかがなものかと。当然のように、使用していないので、<% %>ブロックは自動的にエスケープされるのだと、勝手に思い込んでいました。言い訳終わり。

paster restcontroller

 前々回のエントリで"paster controller"を使って"BlogController"というコントローラを作りましたが、その後"paster restcontroller"という、もっと便利なコントローラジェネレータが用意されていることを知ったので、こちらを使ってみます。


 プロジェクトディレクトリ内で、次のコマンドを実行します。

$ paster restcontroller entry entries

 restcontrollerの後に、通常は、「リソースの単数型」「複数形」を指定します。このコントローラのアクションに対応付けられるURLとHTTPメソッドは、最終的に次のようになります。

エントリの一覧
GET /etries
エントリの詳細
GET /entries/1
新規作成フォーム
GET /entries/new
エントリの作成
POST /entries
更新フォーム
GET /entries/1;edit
エントリの更新
PUT /entries/1
エントリの削除
DELETE /entries/1

 このURLマッピングは、標準では有効にならないので、blogtutorial/config/routing.pyを変更して、手動で設定を行う必要があります。標準のルーティングを無効にし、さらに"map.resouce('entry', 'entries')"という一行を追加しました。

# -*- coding: utf-8 -*-
"""
Setup your Routes options here
"""
import os
from routes import Mapper

def make_map(global_conf={}, app_conf={}):
    # ...略
    # 次の二行が有効なままだと、map.resourceが上手く動かないので、
    # コメントアウトするか、削除すること!
    #map.connect(':controller/:action/:id')
    #map.connect('*url', controller='template', action='view')

    # 追加
    map.resource('entry', 'entries')

    return map

 restcontrollerコマンドによって作られるコントローラには、"paster controller"で作ったコントローラと違って、リソースのCRUDを行うための「足場」が用意されています。
 コマンドを実行すると、blogtutorial/controllers/entries.pyというモジュールが作成されるので、これを開いてみると、index, create, new, update, delete, edit, showというメソッドをもつEntiresControllerが作られているのが分かると思います。ここにモデルとビューを関連づけるコードを記述していきます。

コントローラの初期化

 まず初めに、定義したモデルオブジェクト(Post)をインポートし、SQLAlchemyのSession(SessionContext)の初期化を行うコードを追加します。

# -*- coding: utf-8 -*-
from blogtutorial.lib.base import *
from blogtutorial.models import Post

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


 __before__というメソッドは、各アクションを実行する前に必ず呼び出されるメソッドです。ここで、コントローラ共通の初期化、認証、ログといった処理を行うことができます。他に、アクション実行後に呼び出される__after__というメソッドもあります。
 modelは先に定義したblogtutorial.modelsへのエイリアスで、model.session_contextはSQLAlchemyのSessionContextオブジェクトです。SessionContextについては、SQLAlchemyのドキュメントを参照してください。

一覧

 次に、エントリの一覧(index)を実装します。

    def index(self, format='html'):
        """GET /: All items in the collection."""
        # url_for('entries')
        c.entries = model.session_context.current.query(Post).select()
        return render_response('entries/list.myt')


 SQLAlchemyのORMを通して、Postオブジェクトを取得し、ビューのコンテキスト変数(c.entries)に、その値を割り当てます。そして、render_response関数で、'entries/list.myt'というテンプレートを使った応答を返します。


 テンプレートはblogtutorial/templatesから読み込まれるので、上で指定したテンプレートを、blogtutorial/templates/entries/list.mytとして用意します。

% for entry in c.entries:
<h3><% entry.title | h %></h3>
<span><% entry.posted_at | h %></span>
<div>
<% entry.content | h %>
</div>
<div>
<% h.link_to(_('Show'), url=h.url_for('entry', id=entry.id)) %>
</div>
% #endfor
<% h.link_to(_('Post a new entry'), url=h.url_for('new_entry')) %>


 コントローラのコメント部分に書いてあるので察しがつくとは思いますが、各アクションのURLは、url_for関数を使って取得できます。

一覧
url_for('entries')
詳細
url_for('entry', id=entry.id)
新規作成フォーム
url_for('new_entry')

 テンプレートから参照する場合は、変数hを通してアクセスします。


 後でgettextで国際化を行えるように、固定メッセージはすべて、gettext(), _()を使うようにしました。ここで、http://localhost:5000/entriesにアクセスすると、次のような画面が表示されるはずです。

詳細

 続いて、詳細画面を作成します。まず、コントローラのshowメソッドを実装します。

    def show(self, id, format='html'):
        """GET /id: Show a specific item."""
        # url_for('entry', id=ID)
        c.entry = model.session_context.current.query(Post).get_by(id=id)
        if not c.entry:
            abort(404)
        return render_response('entries/detail.myt')


 データベースから指定されたIDの記事が取得できない場合は、abort(404)を使って、例外を発生させています。


 次に対応するテンプレート(blogtutorial/templates/entries/detail.myt)を作成します。

<h2><% c.entry.title | h %></h2>
<p><% c.entry.modified_at | h %></p>
<div>
<% c.entry.content | h %>
</div>
<% h.link_to(_('Back'), h.url_for('entries')) %>


 http://localhost:5000/entries/1にアクセスすると、次のような画面が表示されます。


つづく。