おみくじプログラムをパワーアップ
前回のおみくじプログラムは本当にシンプルなものだったので、少し物足りな かったかもしれません。今回は少し機能を増やしてみました。
おみくじデータを保存できるようにします。自分の好きな色をおみくじの選択 肢に加えられるようにしましょう。
Pythonパッケージを作る
前回同様、パッケージを先に作ってしまいましょう。パッケージ名はomikuji2 とします。
ディレクトリを作成して、その中に __init__.py, interfaces.py, tests.py, kuji.py を作ってください。
$ mkdir omikuji2
$ cd omikuji2
$ touch __init__.py interfaces.py tests.py kuji.py
(注) touchは空っぽのファイルを作るのに手間を省けるので使っているunixの コマンドです。本当はこういう用途には使いません。マニュアルを読んでくだ さい。
インターフェースを定義する
前回のチュートリアルであえて触れなかったZopeの非常に重要な要素がインター フェースです。
インターフェースはオブジェクトのAPIを定義する機能です。Zopeでは何らか のデータや機能を必ずインターフェースを使って定義します。インターフェー スを定義し、データや機能を相互に接続することでシステム全体を構築すると いう開発設計手法がコンポーネントアーキテクチャと呼ばれるものです。
zope.interfaceパッケージを使うとクラス定義のシンタックスを使ってインター フェースを定義できます。
インターフェースは必ずzope.interface.Interfaceを継承しなければいけませ ん。クラスのシンタックスであってもインターフェースはPythonのクラスでは ないので注意してください。
また、命名規則で名前の先頭は必ず大文字のIにする、と決まっています。こ れで名前をみればすぐにそれがインターフェースであると分かります。
さて、それではおみくじのインターフェースを定義しましょう。次のように書 いてみました。
"""Omikuji package interfaces.
$Id: 001177,v 1.39 2005/11/20 21:29:00 root Exp $
"""
from zope.interface import Interface
class IOmikuji(Interface):
def luckycolor():
"""get lucky color."""
def addcolor(color):
"""add your favorite color."""
このインターフェースには2つのメソッドがあります。luckycolorは色名を取 得するメソッド、addcolorは色の選択肢を追加するメソッドです。インター フェースの定義にはselfを書きません。注意しましょう。
インターフェースにはアトリビュートを定義できます。Zopeではここでスキー マ機能(zope.schema)が関連してきますが、話がややこしくなるので、今回は 触れません。
テストコードを書く
インターフェースの実装を書く前に、テストコードを準備しておきましょう。 Zope3ではテストが非常に重視されていて、非常に多くのテストコードが含ま れています。私たちもこれを見習いましょう。
今回はdoctestを行うことにして、doctestを実行するコードをtests.pyに書い ておきました。ちなみにテスト用モジュールにも命名規則があり、tests.pyか testsパッケージにするように決められています。
"""Test for the omikuji package.
$Id: 001177,v 1.39 2005/11/20 21:29:00 root Exp $
"""
import unittest
from zope.testing.doctest import DocTestSuite
def test_suite():
return unittest.TestSuite((
DocTestSuite('omikuji2.kuji'),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
doctestを知らない方に簡単に紹介しましょう。doctestはPythonのドキュメン テーション文字列にその関数やクラスの使い方(実例)を書き、それをそのまま unittestに使ってしまう、というテスト用パッケージです。人間が読めて、し かもテストに使えちゃうという一石二鳥のアイディアです。Example Driven Test なんて言う呼び方もあるようです。また、このパッケージはZope3の開発 の中で生まれたものらしいです。
ちなみに、Zope3にはpython2.4に付属しているdoctestと同等のものが zope.testingに含まれています。python2.3に付属しているdoctestは少し古い バージョンなので、python2.3を使っているなら、zope.testingのdoctestを使 いましょう。
tests.pyにはkuji.pyをdoctestの対象にするように書きました。
インターフェースを実装する。
ようやく機能を実装する準備が整いました。kuji.py に IOmikuji の実装を書 きましょう。
"""Fortune cookie
$Id: 001177,v 1.39 2005/11/20 21:29:00 root Exp $
"""
import random
from zope.interface import implements
from persistent import Persistent
from persistent.list import PersistentList
from interfaces import IOmikuji
color = ('white', 'black')
class Omikuji(Persistent):
"""Omikuji content class.
>>> from zope.interface.verify import verifyClass
>>> verifyClass(IOmikuji, Omikuji)
True
>>> kuji = Omikuji()
>>> kuji._color
['white', 'black']
>>> kuji.addcolor('green')
>>> kuji._color
['white', 'black', 'green']
>>> kuji.addcolor('red')
>>> kuji.addcolor('blue')
>>> kuji._color
['white', 'black', 'green', 'red', 'blue']
"""
implements(IOmikuji)
def __init__(self):
self._color = PersistentList(color)
def luckycolor(self):
"""get your lucky color."""
return random.choice(self._color)
def addcolor(self, color):
"""add color."""
self._color.append(color)
OmikujiクラスがIOmikujiの実装クラスです。
implements(IOmikuji)
とあるのが、このクラスはIOmikujiを実装しています、という宣言です。
def __init__(self):
self._color = PersistentList(color)
def luckycolor(self):
"""get your lucky color."""
return random.choice(self._color)
def addcolor(self, color):
"""add color."""
self._color.append(color)
コンストラクタ __init__ で永続リストを作成しています。ここに色の選択肢 を保存します。ZODBはリストを保存できません。保存したいときはタプルにす るか、永続リストを使いましょう。
luckycolorとaddcolorの実装は直截的で見ての通りです。
"""Omikuji content class.
>>> from zope.interface.verify import verifyClass
>>> verifyClass(IOmikuji, Omikuji)
True
>>> kuji = Omikuji()
>>> kuji._color
['white', 'black']
>>> kuji.addcolor('green')
>>> kuji._color
['white', 'black', 'green']
>>> kuji.addcolor('red')
>>> kuji.addcolor('blue')
>>> kuji._color
['white', 'black', 'green', 'red', 'blue']
"""
docstringの部分が、doctestでテストにも使われるドキュメントになっていま す。一番最初の部分がインターフェースのテストです。OmikujiがIOmikujiを 実装していることを確認しています。
次の部分がOmikujiの色の選択肢をちゃんと内部に保存できているかどうかの テストになっています。
これでおみくじアプリケーションの基本機能が完成です。
テストを実行してみる
それではテストを実行してみましょう。ちゃんと実装できていれば、問題ない はずです。
$ python tests.py
.
----------------------------------------------------------------------
Ran 1 test in 0.005s
OK
テストはOmikujiクラスのdocstringだけなので1つです。OKと出ればテスト成 功です。
ZCMLを書く
肝心の機能が完成したので、ZCMLを書いてZope特有の部分を設定し、Zopeが読 み込めるようにしましょう。configure.zcmlに次の内容を書いてください。
<configure
xmlns="http://namespaces.zope.org/zope"
i18n_domain="omikuji">
<interface
interface=".interfaces.IOmikuji"
type="zope.app.content.interfaces.IContentType"
/>
<content class=".kuji.Omikuji">
<require
permission="zope.View"
attributes="luckycolor"
/>
<require
permission="zope.ManageContent"
attributes="addcolor"
/>
</content>
<include package=".browser" />
</configure>
まず最初にconfigureディレクティブをみてください。
<configure
xmlns="http://namespaces.zope.org/zope"
i18n_domain="omikuji">
xmlnsでこれから使うネームスペースを宣言しています。ここではzopeネーム スペースにあるディレクティブを標準で使うようにしています。
そしてi18n_domainで、このディレクティブ内で読み込んだ設定に含まれる国 際化可能なテキストのドメインを指定しています。ドメインはアプリケーショ ン固有のものにします。
次にinterfaceディレクティブを書いています。
<interface
interface=".interfaces.IOmikuji"
type="zope.app.content.interfaces.IContentType"
/>
インターフェースを明示的に宣言しています。こうしておくと確実にapidocで インターフェースが出てくるので便利です。typeを指定しておくとapidoc上で 分類して表示されます。
その次はclassディレクティブです。
ここでIOmikujiインターフェースで定義した二つのメソッドをアクセス可能に するパーミッションを設定しています。luckycolorは誰でもアクセスできるよ うにzope.Viewパーミッション、addcolorには管理者だけが使えるように zope.ManageContentパーミッションを設定しています。
Zope3のパーミッションは全てZCMLでpermissionディレクティブを使って予め 宣言しておかなければ使うことができません。ということは、ZCMLでアプリケー ション固有のパーミッションを定義できるということでもあります。
今回使ったzope.Viewとzope.ManageContentは src/zope/app/security/configure.zcmlで宣言されています。
<content class=".kuji.Omikuji">
<require
permission="zope.View"
attributes="luckycolor"
/>
<require
permission="zope.ManageContent"
attributes="addcolor"
/>
</content>
最後に
<include package=".browser" />
ウェブブラウザ用のビューを提供するbrowserパッケージ内のconfigure.zcml を読み込むようにincludeディレクティブを書いています。が、まだbrowserパッ ケージを書いていませんでした。
browserパッケージを作る
おみくじの主要機能が完成し、それをZopeで実行できるようにZCMLを書きまし た。あとはウェブブラウザ用のビューを作れば完成です。browserパッケージ を作って、ウェブブラウザ向けの機能を実装しましょう。
まずはじめにディレクトリと最低限必要なファイルを作成しましょう。
$ mkdir browser
$ cd browser
$ touch __init__.py
$ touch configure.zcml
ZMIでOmikujiインスタンスを追加できるようにする
上で作ったOmikujiクラスのインスタンスをZMI(Zopeの管理画面)でZODBに登録 するための設定が必要です。
browser/configure.zcmlに書いてください。
<configure
xmlns="http://namespaces.zope.org/browser">
<addform
label="Omikuji"
name="addomikuji.html"
schema="omikuji2.interfaces.IOmikuji"
content_factory="omikuji2.kuji.Omikuji"
permission="zope.ManageContent"
/>
<addMenuItem
class="omikuji2.kuji.Omikuji"
title="Omikuji"
permission="zope.ManageContent"
view="addomikuji.html"
/>
</configure>
configureディレクティブでbrowserネームスペースのディレクティブを使える ように宣言しています。
<configure
xmlns="http://namespaces.zope.org/browser">
addformディレクティブはオブジェクトの登録フォームを自動生成してくれま す。このディレクティブは入力用HTML、入力された値のチェック処理、登録処 理を提供します。
<addform
label="Omikuji"
name="addomikuji.html"
schema="omikuji2.interfaces.IOmikuji"
content_factory="omikuji2.kuji.Omikuji"
permission="zope.ManageContent"
/>
labelは入力フォームの見出しです。nameはURLになります。schemaは入力フォー ムを生成するためのもので、追加したいオブジェクトのインターフェースを指 定します。content_factoryにはオブジェクトを作るファクトリ関数を指定し ます。ここではクラス自身を指定しています。permissionにはこのフォームに アクセスするためのパーミッションを指定します。
次に上で作ったaddformへのナビゲーション(リンク)をZMIに表示するための addMenuItemディレクティブを書きます。
<addMenuItem
class="omikuji2.kuji.Omikuji"
title="Omikuji"
permission="zope.ManageContent"
view="addomikuji.html"
/>
addMenuItemのclassにはメニューのリンク先で作成されるオブジェクトのクラ スを指定します。viewにはaddformに付けたnameを書いてください。
各ディレクティブの詳細は、apidocのZCMLリファレンスで参照できます。
ビューはマルチアダプタ
上で説明したaddform、このあとに出てくるpageなどのbrowserネームスペース で出てくるディレクティブのほとんどは、マルチアダプタを登録しています。
マルチアダプタとは、ある複数のインターフェースから、他の一つのインター フェースに変換するアダプタのことです。んー、この説明では難しいかもしれ ません。
アダプテーションの詳細についてここで説明するのは難しいので省きます。 正確にはsrc/zope/interface/adapter.txtを読んでください。apidocからも読 むことができます。
もっと具体的に説明すると、ビューはIRequestインターフェースとコンテンツ タイプのインターフェースのマルチアダプタなんです。これを登録する処理を 行っているのがZCMLのディレクティブで、その実体はアダプタ登録関数になっ ています。
Zope3は複数のプロトコルに対応しているため、何種類かのRequestインター フェースがあります。
browserネームスペースはウェブブラウザ向けなので、このネームスペースの ディレクティブで登録されるマルチアダプタはIBrowserRequestとのマルチア ダプタになります。
ブラウザ以外で、例えば、XMLRPCでアクセスしたときに使われるのは IXMLRPCRequestとのマルチアダプタになります。
このマルチアダプタの説明を踏まえて、次に進みたいと思います。
おみくじの結果を表示する
ようやくここまでやってきました。おみくじの結果をHTMLにしてブラウザに表 示させるための機能を作りましょう。
view.ptファイルを作成し、以下の内容を書いてください。
<html metal:use-macro="views/standard_macros/view">
<head>
<title>Lucky color</title>
</head>
<body metal:fill-slot="body">
Your lucky color is <span tal:replace="context/luckycolor">red</span>!!
</body>
</html>
このテンプレートの中で肝心な部分は一箇所はここです。
<span tal:replace="context/luckycolor">red</span>
contextはOmikujiクラスのインスタンスです。Omikujiクラスのluckycolorメ ソッドを実行し、色名を埋めこむ仕組みです。
ところでZopeのPageTemplateをご存知ですよね?HTMLタグのアトリビュートに 動的処理をさせるための命令文を書くタイプのテンプレート言語です。この言 語の利点は処理前でもHTMLとして解釈可能なことです。PageTemplateがはじめ ての方はZope2用のチュートリアルがあるのでそちらを参照してください。
テンプレートができたので、これを使えるようにするため、ZCMLを書きましょ う。configure.zcmlに次のディレクティブを追加してください。
<page
for="omikuji2.interfaces.IOmikuji"
name="omikuji.html"
permission="zope.View"
template="view.pt"
title="Show color"
menu="zmi_views"
/>
前述のビューとマルチアダプタの話を思い出してください。forでこのページ を適用する対象のインターフェースを指定しています。ここではIOmikujiです。
つまり、IOmikujiとIBrowserRequestに対するマルチアダプタですよ、という 宣言になるわけです。
forはディレクティブのアトリビュートとして頻繁に出てきます。そのときは ディレクティブがアダプタの登録であることを思い出してください。
次にnameです。アダプタの登録時に名前を付けることができます。名前を付け ることで、同じインターフェースの間のアダプタを複数登録できます。名前で 区分するわけです。
この考え方を、ある種のインターフェースを実装しているオブジェクトに対し てブラウザでアクセスしたときの処理にもってくると、Zope3のビューの仕組 みになります。
IOmikujiとIBrowserRequestに対するアダプタの関連づけにomikuji.htmlとい う名前を付ける、これがpageディレクティブの行っている中身です。これで ブラウザがオブジェクトにアクセスするときに、アダプテーションの検索をし て、該当する名前(URLで示された名前)のアダプタを見つけ、それを処理して HTMLが生成されるという流れになります。
templateはこのページで使うPageTemplateのファイルを指定します。ここでは 上で書いたview.ptを使いましょう。
titleとmenuは、ZMIでナビゲーション用のリンクを表示するために使います。 menuをzmi_viewsにすると管理画面の上のタブにリンクが表示されます。title はそのメニューで表示する名前です。
これでおみくじの結果が表示できるようになりました。
おみくじの色を追加できるようにする
おみくじは最初にblackとwhiteだけしか選択肢をもっていません。これではつ まらないので、色を追加できるようにしましょう。
色を追加する入力フォーム用にadd.ptを作ってください。
<html metal:use-macro="views/standard_macros/view">
<head>
<title>Add color</title>
</head>
<body metal:fill-slot="body">
<div tal:condition="exists:request/message" tal:content="request/message"></div>
<form action="add">
Add color
<input type="text" name="color">
<input type="submit">
</form>
</body>
</html>
一番注目してほしいのは formタグのactionアトリビュートです。入力フォー ムのサブミットボタンをクリックしたら、addというURLに値を送信するように しました。次に作るViewクラスで用意する色追加処理関数の名前をaddにしま しょう。
divタグでrequest/messageがあれば表示するようにしました。これはメッセー ジを表示するために付けました。
テンプレートができたので、ZCMLに設定を書いてアダプタを登録しましょう。 やっていることは前と同じです。
<page
for="omikuji2.interfaces.IOmikuji"
name="add.html"
permission="zope.ManageContent"
template="add.pt"
title="Add color"
menu="zmi_views"
/>
ちなみに、もっと複雑な入力フォームを作成するためのformlibというパッケー ジがあります。今回はそれを使わずに作ったので、入力値のチェックなどは行っ ていません。formlibはいつかコラムで触れたいと思います。
それでは次にformのサブミット先であるaddの実装をしたいと思います。addで 行うことは完全な手続きプログラムであり、HTML生成ではないので、 PageTemplateは使いません。こういうときはPythonを使ってビュークラスを書 きます。ビュークラスは表示関連の処理を受け持つクラスで、Zope2であれば、 PythonScriptが担当していた部分です。通常のPythonクラスをビュークラスと して表示用ロジックに使うことができるようになり、飛躍的にプログラムの書 きやすさ、保守のしやすさが向上しました。
ビュークラスをview.pyに書いてください。
"""Browser view component for fortune cookie
$Id: 001177,v 1.39 2005/11/20 21:29:00 root Exp $
"""
class KujiView:
def add(self, color):
self.context.addcolor(color)
self.request.response.redirect('./add.html?message=added')
KujiViewがビュークラスです。addが色を登録する関数で、サブミットしたと きの値の送り先になります。だから引数にcolorがあります。
ビュークラスもまたIOmikujiとIBrowserRequestのマルチアダプタです。アダ プタのコンストラクタにはこのアダプテーションの対象になったIOmikujiと IBrowserのそれぞれのインターフェースを実装したクラスのインスタンスが渡 されます。ビュークラスはそれを__init__でselfにセットしているので、 self.contextでOmikujiインスタンス、self.requestでBrowserRequestインス タンスが取得できます。
それを利用して、引数のcolor(ユーザがブラウザで入力した色の名前)を addcolorで追加し、requestを使って入力フォームにメッセージ付きでリダイ レクトしています。
この設定をZCMLに書きましょう。
<page
for="omikuji2.interfaces.IOmikuji"
name="add"
permission="zope.ManageContent"
class=".view.KujiView"
attribute="add"
/>
上の2つのpageディレクティブと違うのは、classとattributeです。classでは ビュークラスを指定しています。attributeはビュークラスを指定したときに、 そのクラスのどのアトリビュートをページとしてURLでアクセスできるように するかを設定します。ここではIOmikujiインターフェースを実装したオブジェ クトにaddという名前でアクセスしたときにビュークラスのaddメソッドにアク セスするように設定しました。
最後にIOmikuji用のデフォルトページを設定しましょう。これもZCMLで書きま す。
<defaultView
for="omikuji2.interfaces.IOmikuji"
name="omikuji.html"
/>
defaultViewは対象のインターフェースのデフォルトビュー(名前なしで直にオ ブジェクトにアクセスしたときに使われる)にする登録済みのビューを設定す るディレクティブです。
ここでは、おみくじの結果のラッキーカラーを表示するビューomikuji.htmlを デフォルトにしました。
以上でウェブブラウザ用の設定が完成しました。完成したbrowserパッケージ のconfigure.zcmlを以下に示します。
<configure
xmlns="http://namespaces.zope.org/browser">
<addform
label="Omikuji"
name="addomikuji.html"
schema="omikuji2.interfaces.IOmikuji"
content_factory="omikuji2.kuji.Omikuji"
permission="zope.ManageContent"
/>
<addMenuItem
class="omikuji2.kuji.Omikuji"
title="Omikuji"
permission="zope.ManageContent"
view="addomikuji.html"
/>
<page
for="omikuji2.interfaces.IOmikuji"
name="omikuji.html"
permission="zope.View"
template="view.pt"
title="Show color"
menu="zmi_views"
/>
<page
for="omikuji2.interfaces.IOmikuji"
name="add.html"
permission="zope.ManageContent"
template="add.pt"
title="Add color"
menu="zmi_views"
/>
<page
for="omikuji2.interfaces.IOmikuji"
name="add"
permission="zope.ManageContent"
class=".view.KujiView"
attribute="add"
/>
<defaultView
for="omikuji2.interfaces.IOmikuji"
name="omikuji.html"
/>
</configure>
完成
以上でおみくじアプリケーションのバージョン2は完成です。 package-includesにZCMLを書いて、Zopeの起動時に読み込まれるようにして、 実際に動かしてみてください。
package-includesに書くZCMLは分かりますよね。
まとめ
今回とにかく何度もしつこく説明したのがアダプタです。アダプタ、これが Zope3のもっとも重要な概念であることに間違いありません。アダプタが分か れば、ZCMLも楽勝でしょう。
Zope3を使いこなすにはこれを頭にしっかり入れておく必要があります。アダ プタを理解するにはsrc/zope/interface/adapter.txtを読むのが一番。ぜひ目 を通しておいてください。