unoh.github.com

Rails風フレームワークPylonsで簡易Wikiを作ってみる

Tue Jul 17 23:50:38 -0700 2007

Yet Another Sakatokuです。今回はPyhonで書かれたRuby on Rails風のフレームワークPylonsの使い方を簡単に紹介したいと思います。

PylonsはPythonのWebフレームワークとしては、現在のところ、Django, TurboGearsにつぐ三番手(以下)と見なされていますが、TurboGears 2がPylonsと合流して、Pylons上に旧TurboGearsのAPIを提供していくことが表明されましたので、今後大きな勢力になっていくと思われます。

サンプル・アプリケーション

サンプルとして、簡易Wiki(CoCoWiki)を作ってみました。以下のURLからダウンロードできるので参考にしてみてください。

cocowiki.tar.gz

Pylonsの開発サーバ上で動作させるには、ダウンロードしたtar.gzを解凍し、cocowikiディレクトリで"paster serve"コマンドで実行します。

$ tar xzf cocowiki.tar.gz
$ cd cocowiki
$ paster serve development.ini

上手く動作していれば、http://localhost:5000/にアクセスすると次のような画面が表示されると思います。

cocowiki
cocowiki posted by (C)フォト蔵

動作確認はWindows版Python2.5.1, Linux版Python2.4.3で行いました。後述の通り、あらかじめPylonsとSAContextをインストールしている必要があります。また、Python2.4以前のバージョンの場合は、pysqlite2ライブラリも必要です。

Pylonsのインストール

例によってeasy_installを使ってインストールします。ただ、僕の環境ではsetuptoolsのバージョンが古いというエラーが発生したので、最初に以下のURLから最新版のsetuptoolsをダウンロードし、インストールしました。

http://cheeseshop.python.org/pypi/setuptools

次に、Pylonsをインストールします。現在配布されているバージョンは0.9.6RC1です。

$ easy_install -U Pylons

これで、Pylonsの依存するライブラリがすべてインストールされるはずです。

Pylons 0.9.6の大きな変更点として、データベース接続にSAContextというライブラリの使用が推奨されるようになったことがあります。今回作成するアプリケーションではこのSAContextを使うので、これもインストールしておきます。

$ easy_install -U SAContext

プロジェクトを作成する

新しくPylonsプロジェクトの作成するには"paster create"コマンドを使います。

プロジェクト名は、"cocowiki"としました。

$ paster create --template=pylons cocowiki
$ cd cocowiki

開発用のサーバを起動するには、"paster serve"コマンドを使います。

$ paster serve development.ini

アプリケーションの設定

まずdevelopment.inにアプリケーションの設定を記述します。

今回はSessionを使わないので、とりあえずはデータベースの設定のみです。

sqliteを使うことにしましたが、SQLAlchemyがサポートするデータベースならば何でも構いません。

sqlalchemy.default.uri = sqlite:///%(here)s/data/cocowiki.sqlite.db
sqlalchemy.default.echo = true
sqlalchemy.default.echo_pool = false
sqlalchemy.default.pool_recycle = 3600

SQLAlchemyのサポートするデータベースとURIの記述の仕方については、こちらのドキュメント(英語)を参照してみてください。

モデル定義とデータベースの初期化

次にモデルの定義をcocowiki/model/__init__.pyに記述します。

# -*- coding: utf-8 -*-
# cocowiki/model/__init__.py
from sqlalchemy import *
from sqlalchemy.ext.assignmapper import assign_mapper

from urllib import quote
from datetime import datetime

from sacontext import PylonsSAContext

# Pylons 0.9.6から以前のpylons.databasesがdeprecated扱いになりました。
# 代わりにSAContextのPylonsSAContextを使うことが推奨されています。
# 詳しくはhttp://sluggo.scrapping.cc/python/sacontext/を参照してください。
sac = PylonsSAContext()
sac.add_engine_from_config(None)

# テーブル定義
pages = Table('pages', sac.metadata,
              Column('id', Integer, primary_key=True),
              Column('word', Unicode(250), nullable=False, unique=True),
              Column('content', Unicode, nullable=False),
              Column('created_at', DateTime, default=datetime.now()),
              Column('updated_at', DateTime, default=datetime.now(), onupdate=datetime.now()),
              mysql_engine='InnoDB',
              )

# Wikiのページに相当するドメインオブジェクト
class Page(object):
    def __init__(self, word=None, content=None):
        self.word = word
        self.content = content

    def __repr__(self):
        return '<cocowiki.model.Page "%s">' % self.word

    def get_absolute_url(self):
        """
        このオブジェクトのURLを取得します
        これはPylonsの流儀ではないかもしれませんが便利です!
        """
        return '/%s' % quote(self.word.encode('utf-8'))

# AssignMapperを使ってテーブルとドメインオブジェクトを
# マッピングします
assign_mapper(sac.session_context,
              Page,
              pages)

モデルの設計は、あっさりとしたもので済ませることにしました。

いくつか注釈を付け加えるならば、

データベースの初期化のコードは、cocowiki/websetup.pyに記述しておきます。

# -*- coding: utf-8 -*-
"""Setup the cocowiki application"""
from paste.deploy import appconfig
from pylons import config

from cocowiki.config.environment import load_environment

def setup_config(command, filename, section, vars):
    """Place any commands to setup cocowiki here"""
    conf = appconfig('config:' + filename)
    load_environment(conf.global_conf, conf.local_conf)

    from sqlalchemy import exceptions
    from cocowiki.model import sac, Page
    sac.metadata.create_all(checkfirst=True)

    try:
        start_page = Page('StartPage', u'ここうぃきの世界にようこそ')
        sac.session_context.current.flush()
    except exceptions.SQLError, e:
        pass

コマンドラインから、"paster setup-app"コマンドを使うと、このコードを実行することができます。

$ paster setup-app development.ini

コントローラの作成, ルーティングの設定

コントローラを作成します。

$ paster controller page

これで、cocowiki/controllers/pages.pyにPagesControllerというコントローラクラスが生成されます。このコントローラのメソッドに各処理(アクション)を実装していきます。

それらのアクションとURLを対応付けているのが、cocowiki/config/routing.pyです。ルーティングについて簡潔にまとまっているページがなかなか見つからないので、Routeのドキュメントにあたるのが一番早そうです。今回は、ドキュメントを参考に次のようなURL設計にしてみました。

/list
登録済みのページ一覧の表示
/WikiWord
WikiWordというページの表示
/WikiWord;edit
WikiWordというページの編集(新規登録)フォームの表示

"/list"で一覧表示というのが、あまりエレガントではないかもしれません。また、リソースの新規作成と更新をURL上でも、HTTPメソッドの上でも区別しないことにしています。"/WikiWord"というURLにPOSTすると、ページがすでにある場合は更新し、無い場合は新規作成します。

このような動作に対応するrouting.pyは、以下のようになります。

# cocowiki/config/routing.py

def make_map():	
	# ...略

    # 登録されている単語を表示するページです。
    map.connect('list', controller='pages', action='index')

    # Wikiの編集フォームのページ
    map.connect('*(word);edit', controller='pages', action='edit')

    # Wiki更新のアクションのマッピングです。
    # フォームの内容にエラーがある場合は、再度上のeditページを表示します。
    map.connect('*word', controller='pages', action='update', conditions={'method':['POST']})

    # Wikiの個々のページです。
    # 上記のform, edit, list以外の単語がWikiワードになります。
    map.connect('*word', controller='pages', action='show')

    return map

Wikiとは違って、リソースをデータベースのinteger型のキーで識別するようなアプリケーションでは、Rails風(REST風)のURLマッピングが使えます。この場合は、コントローラを"paster restcontroller"で作成します。

$ paster restcontroller object objects

"paster restcontroller"に関しては、別の場所でメモを残したことがあるので、興味がある方は参照してみてください。Pylons 0.9.6になっても、この部分は変わっていないと思います。

コントローラの実装

いよいよコントローラにロジックを書いていくわけですが、すべてのアクションについて解説していくと長くなるので、ページの取得の部分(showメソッド)を例にとって、Pylonsの基本を説明していきます。

class PagesController(BaseController):
    def show(self, word=None, format='html'):
        """
        Wikiワード"Word"を表示します。
        WordがNoneの場合、つまりURLが"/"の場合は、/StartPage
        にリダイレクトさせます。
        """
        if not word:
            redirect_to('/StartPage')

        c.page = Page.get_by(word=word)
        if c.page is None:
            # wordでページが登録されていない場合は、
            # 新規登録のフォームを表示します。
            return self.edit(word)

        # 更新順に10件のページを取得します。
        # limit=11で最大11件表示しているのは、「もっと見る」の
        # リンクの表示する条件を簡略化するためです。
        c.words = Page.select(order_by=desc(Page.c.updated_at),
                              limit=11)
        return render('show')

他のメソッドは、サンプルアプリケーションのコードを参照してください。Pylonsの流儀に慣れてしまえば、難しいことは何もしていないことがお分かりになると思います。

テンプレートの作成

テンプレートは、cocowiki/templatesに配置します。現在のPylonsの標準テンプレートエンジンはMakoで、上述のrenderメソッドを使った場合、render('show')ならばcocowiki/templates/show.makが使用されます。

Makoの構文は以前簡単に紹介したとおりですが、今回は継承機能を使ってみました。Makoの継承機能についてはこちらのメモもご覧下さい。

実際のcocowiki/templates/show.makは次のようになっています。

<%inherit file="base.mak" />
<%def name="title()">${c.page.word | h}</%def>
<h2>${title()}</h2>
${ h.cocowiki_markup(c.page.content) }
<h3>${ _('Menu') }</h3>
<ul>
  <li>${ h.link_to(_('Edit this page'), h.url_for(word=c.page.word, action='edit')) }</li>
</ul>

<h3>${ _('Recent changes') }</h3>
% if c.words:
<ul>
% for word in c.words[:10]:
  <li><a href="${word.get_absolute_url()}">${word.word | h}</a></li>
% endfor
</ul>
  % if len(c.words) > 10:
    <span>» ${ h.link_to(_('See more'), h.url_for(word=None, action='index')) }</span>
  % endif
% else:
  <p>${_('No pages')}</p>
% endif

Pylonsのテンプレートについてまず知っておくべきことは、次のようなことです。

独自のビュー・ヘルパー関数を定義する

今回作成したアプリケーションでは、cocowiki/lib/helpers.pyに簡易Wiki記法をHTMLに変換するcocowiki_markupという関数を定義しています。

# -*- coding: utf-8 -*-
from webhelpers import *
from urllib import quote
import re
from cgi import escape

WIKI_WORD_RE = re.compile(r'\[\[(.+?)\]\]')

def cocowiki_markup(value):
    """

    WIKIのページデータをHTMLに変換します。
    """
    def repl(matcher):
        word = matcher.group(1)
        return '<a href="%s">%s</a>' % (quote(word.encode('utf-8')),
                                        escape(word))

    # markdown記法で変換
    value = markdown(value)

    # http://example.com, mail@example.comをリンクに変換
    value = auto_link(value)

    # [[word]]という表記をWikiワードとして扱う
    value = WIKI_WORD_RE.sub(repl, value)

    return value

静的なファイルの配信

cocowiki/publicに置いたファイルは静的なファイルとして配信することができますので、ここにスタイルシートや画像をおきました。

まとめと謝辞

以上、Pylonsの概要を駆け足で紹介しました。サンプルアプリケーションと合わせて参照していただくことを念頭に執筆しましたので、かなり説明を省略しているところもあるかと思います。

分かりにくいところや、間違いがあった場合は、お気軽にコメントやトラックバックをいただけましたら幸いです。

また、サンプルアプリケーションのテンプレートには、stansさんが配布されているテンプレートを利用させて頂きました。ありがとうございました。