2009/02/25(水)Plone / Archetypes のプロダクトサンプル
ArchExampleをベースに製作したプロダクトサンプルを配布し、解説します。ArchetypesによるPlone3.x Product開発に資料はあるにはありますが、とても直感的には理解しにくいため、分かっている範囲で分かりやすく解説します。*1
Download
ファイル構成
プロダクトディレクトリ以下のファイル構成と役割です。
ファイル | 役割 |
---|---|
__init__.py | プロダクトの初期化スクリプト。Zope起動時、最初に呼ばれる。 |
config.py | C言語で言うところの main.h を記述する。プロダクト共通ヘッダファイル代わり |
■付属ファイル■ | |
README.txt | ファイルを置くと自動的に"プロダクトの説明"という欄に表示される。簡易書式機能付(詳細略)。 |
refresh.txt | プロダクトの自動リロード対応を示すため、システムに対して指示的な意味で置くファイル。0byteのファイルでよい。 |
■スクリプトファイル■ | |
*.py | その他のファイルは、すべてZope起動時に読み込まれメモリに展開される。 |
Article.py | 編集メニューに追加されるコンテンツタイプArticleを処理する本体。まだ良く分からないので今回は放置。 |
ExampleTool | Toolと呼ばれるテンプレート用インターフェイス。 |
tool.gif | Toolのアイコン。プロダクトのアイコンはまた別。 |
■特殊なスクリプト■ | |
Extensions/__init__.py | 初期化用。中身は空でよい。 |
Extensions/Install.py | インストール/アンインストールスクリプト |
■テンプレート■ | |
skins/archexample 以下。skins内のサブディレクトリ名は何でも良い。 | |
--archexample_control_panel.cpt | プロダクトコントロールパネル(ダミー) |
--article_view.cpt | コンテンツタイプArticleのテンプレート。今回は放置。 |
--cp_tool.gif | Ploneプロダクトアイコン |
解説
ソースファイル中に(分かっている範囲で)詳細に日本語コメントを書き込みました(UTF-8)。これをベースにプロダクト開発をすればさほど困らないとは思いますが、概念等を説明しておきます。
起動時
Ploneのプロダクトは、まずディレクトリ内に __init__.py を置くことではじまります。Ploneは(当然のように)イベントドリブンシステムですので、直接Ploneに対するプログラムを書くことはできません。__init__.py により、Ploneのどのような処理にどのような機能を割り込ませるか決めるわけです。
それ以外の.pyファイル必要な機能を必要なプロダクトディレクトリ内の *.py 自動的に読み込まれて処理されます。それら1つ1つのファイルは、1つ(以上の)クラスに対応付けられます。そして必要に応じて呼び出します。
インストール/アンインストール
Extensions/Install.py に install および uninstall というメソッドを記述して行わせます。Ploneの「サイト設定」→「アドオンプロダクト」でのインストール・アンインストール処理を記述します。Extensions/__init__.py はPythonの制約から(中身が空でも)必要です。
処理内容は、Ploneに対するコンテンツタイプやアドオンコントロールパネルの登録、スキンの登録などを行います。
コントロールパネルのinstall
- コントロールパネルのテンプレート : skins/archexample/archexample_control_panel.cpt
- コントロールパネルのアイコン : skins/archexample/cp_tool.gif
まず、config.py に登録情報を記述します。
controlpanel_configlet = { 'id': 'archexample', 'appId': PROJECTNAME, 'name': 'Archetypes Example', 'action': 'string:$portal_url/archexample_control_panel', 'category': 'Products', 'permission': (CMFCorePermissions.ManagePortal,), 'imageUrl' : 'cp_tool.gif', }
nameはコントロールパネルの項目名になります。idは適当につけてください。imageUrlはコントロールパネルのアイコンです。なくても構いません。
Extensions/Install.py において install/uninstall 時に、コントロールパネルの登録、削除を行うようにします。
# def install(self) 内 from Products.ArchExample.config import example_configlet cp_tool = getToolByName(self, 'portal_controlpanel') try: cp_tool.registerConfiglet(**controlpanel_configlet) except: pass
config.py 上で定義してある controlpanel_configlet をimportし、その情報をそのまま登録しています。削除ルーチンを書いておかないと、どんどん増えて大変なことになります。
# def uninstall(self) 内 try: cp_tool = getToolByName(self, 'portal_controlpanel') cp_tool.unregisterApplication(PROJECTNAME) except: pass
Toolの登録
テンプレート(ZPT=Zope Page Template)はそのままPloneのURLに対応させることができるのはわかりましたが、ではテンプレート側から任意のモジュールを操作するにはどうしたらいいのでしょうか?
テンプレートから特定モジュールを読み出すのは制約されているためほぼ無理です。色々調べてみると、Pythonの特定のクラスをToolという形でシステムに登録しテンプレートから利用するのが流儀のようです。ToolとはTemplateに対するインターフェイスといえます。ここでは、そのToolについて解説します。
- Toolの実体 - ExampleTool.py
- Toolの初期化 - __init__.py
- Toolの登録 - Extensions/Install.py
ToolはきちんとInstallしないと使えません*3。まずはToolの実体を見ましょう。
class ExampleTool(PropertyManager, UniqueObject, SimpleItem, ActionProviderBase): # (略) security = ClassSecurityInfo() id = 'portal_example' meta_type = 'ExampleTool' # (略) security.declareProtected(CMFCorePermissions.ManagePortal, 'testFunc') def testFunc(self): return "value"
security オブジェクトを使用して、本来は禁止されているオブジェクトメソッドへのアクセスを許可しています。idはテンプレートからアクセスするときに利用する名前になります。meta_typeはツールを識別するための名前になります。ユニークに付けます。
続いて __init__.py です。
from ExampleTool import ExampleTool ToolInit( '%s Tool' % PROJECTNAME, tools=(ExampleTool,), product_name=PROJECTNAME, icon='tool.gif').initialize(context)
最後にExtensions/Install.pyのToolの登録部を示します。
from Products.ArchExample.ExampleTool import ExampleTool add_tool = self.manage_addProduct[PROJECTNAME].manage_addTool if not self.objectIds(spec=ExampleTool.meta_type): add_tool(ExampleTool.meta_type)
これはインストールの作業です。アンインストールは特に不要なようです(?)。
この状態でプロダクトをインストールすると、Toolとしてプロダクトのモジュールを呼び出すことができます。サンプルでは、上で説明したコントロールパネル(skins/archexample/archexample_control_panel.cpt)にToolの呼び出しを記述してあります。
<div metal:fill-slot="main"
tal:define="example_tool python:container.portal_example;
errors python:request.get('errors', {});">
<h2>Control Panel Demo</h2>
<p tal:content="python:example_tool.testFunc()"></p>
</div>
example_tool python:container.portal_exampleで、exsample_tool に portal_example をロードします。
<p tal:content="python:example_tool.testFunc()"></p>
は<p>~</p>の中身をtestFunc()の実行結果に書き換えよという指示で、実際に表示されると書き換えられることが分かります。
日本語化
プロダクト以下に i18n/ というディレクトリを作り、XXXX-ja.po といったファイルを置いておけばいいようです。基本的に XXXX の部分はプロダクト名と一緒にします。
ArchExample-ja.po は他の言語ファイルを参考にコピーしたものですが、
"Domain: ArchExample\n"
だけ書き換えています。言語ファイルは Domain という構造を持ち、指定したDomainの中の言語ファイルを参照することができます。Plone本体は "plone" という domain を使用しています。
ZPTからの参照は次のようにします。
<div i18n:domain="ArchExample"> <p i18n:translate="cp_description"> Description </p> </div>
中身を置換する部分で直接 domain を指定しても構いませんが、親タグでdomain指定したほうが簡単です。
変数を埋め込むときは次のようにします。
<p i18n:translate="cp_fileinfo" i18n:data="python:{'file':'skin/archexample_control_panel.pt',}"> This template file is `${file}'. </p>
Pythonスクリプトからは次のように参照します。
from zope.i18nmessageid import MessageFactory _ = MessageFactory('ArchExample') msg1 = _("testMsg1", default="This message from testFunc() in Product.ArchExample.ExampleTool") msg2 = _("testMsg2", default="Message embedded variable var1=${var1}, var2=${var2}", mapping={'var1':var1, 'var2':var2}, )
Plone のソースを読んでいると _() という謎の記述をよく見かけるのですが、_ に対してメッセージ翻訳関数をロードするのがPlone流の書き方のようです。記述は読めば分かるぐらい簡単なので解説は省略します。mapping は変数埋め込みです。
なお発生条件はよく分かっていませんが、上の方法ではPOST後の処理などでメッセージの翻訳に失敗することがあります。なぜか指定したドメインを参照できず、常に"plone"ドメインを参照してしまうようです。
この場合は次にように回避します。
translate = getToolByName(self, 'translation_service').translate msg = translate(msgid=u"messageXXX", domain="ArchExample", default=u"Default Message", )
デバッグ出力
Zope/Plone をデバッグモード
$ bin/instance fg または $ bin/zopectrl fg
で起動したとき、コンソールにメッセージや変数を出力することができます。
from zLOG import LOG, DEBUG, INFO LOG("[ArchExample]", INFO, "This message from ArchExample/ExampleTool.py")
などとします。変数を直接出力することもできるため、動作把握する際に覚えておくと大変便利です。
参考資料
- Archetypes Developer Manual …… オフィシャル。Step by Stepの解説の割にあまり役に立ちません...。
その他Zopeメモ
- Pythonスクリプトから context オブジェクトを得る方法が不明なのですが、selfを見ればいいようです。
- ZPTでそのまま出力 tal:content="structure context"