[9]オブジェクト生成をコントロールする
オブジェクトが正しい内容を伴って生成されることを保証するため,portal_factoryとrename-after-creationフックを利用します
設計がよくないせいで,CMFとArchetypesはオブジェクト生成の仕組みがあまりよくできていません。ユーザがメニューを使いコンテンツを追加する際,追加フォームが表示され,"保存"や"キャンセル"ボタンを押して操作を確定します。しかし実際はコンテンツはすでに存在しています。ユーザが"キャンセル"ボタンを押したり,追加フォームの外に遷移したりすると,空のオブジェクトが残ってしまいます。
この問題を解決するため,Ploneではportal_factoryというハックを追加しました(Geoffさん,ありがとう)。portal_factoryは「temporary folder」上にオブジェクトを作り,最初に「保存」されたときに実際のフォルダにオブジェクトを移動します。
portal_factoryを有効にするのは簡単です。Install.pyに,以下のようなコードを書きます
# portal_factoryを有効にする
factory = getToolByName(self, 'portal_factory')
types = factory.getFactoryTypes().keys()
if 'RichDocument' not in types:
types.append('RichDocument')
factory.manage_setPortalFactoryTypes(listOfTypeIds = types)
ImageAttachmentやFileAttachmentについては同様の処理をする必要がないことに注意して下さい。portal_factoryはWebインターフェースを通じて生成されるオブジェクトについてのみ利用するようにします。画像や添付ファイルをアップロードするためのウィジェットはフォームを持っていて,RichDocumentの内部に追加されるオブジェクトの一貫性を保証してくれます。
残念なことに,portal_factoryは,すでに内部にオブジェクトを持っているフォルダ風のオブジェクト(たとえばRichDocumentのような)ものをうまく扱えません。したがって,widget_imagesmanager_uploadとwidget_attachmentsmanager_uploadいうスクリプトがportal_factory.doCreate()を呼んで,アップロードされたオブジェクトが直ちに生成されるように処理をしています。幸いなことに,このような処理をしても問題はほとんど起こりません。というのは,オブジェクトの生成を途中でやめるケースは,ほとんどの場合生成時にエラーが発生する場合だからです。
タイトルからショートネームを得る
Plone 2.1の新機能にrename-after-creation(オブジェクト生成後のリネーム)フックがあります。デフォルトでは,コンテンツの編集画面ではID(ショートネーム)のフィールドは表示されません。かわりに,コンテンツが保存されたとき,タイトルから生成されたショートネームにリネームされます。
この機能を利用するのはとても簡単です。クラスに以下のように記述します:
_at_rename_after_creation = True
IDの生成をより細かくコントロールしたい場合は,'Archetypes/BaseObject.py'の_renameAfterCreation()メソッドをオーバーライドします。
security.declarePrivate('_renameAfterCreation')
def _renameAfterCreation(self, check_auto_id=False):
"""Renames an object like its normalized title.
"""
plone_tool = getToolByName(self, 'plone_utils', None)
if plone_tool is None or not shasattr(plone_tool, 'normalizeString'):
# Plone toolが使えないか,古すぎるようです
# XXX ロギングした方がいいかな?
return None
title = self.Title()
if not title:
# titleがないと処理を続けられない
return False
old_id = self.getId()
if check_auto_id and not self._isIDAutoGenerated(old_id):
# IDは自動生成されたものではない
return False
new_id = plone_tool.normalizeString(title)
invalid_id = False
check_id = getattr(self, 'check_id', None)
if check_id is not None:
invalid_id = check_id(new_id, required=1)
else:
# check_idが利用できない場合は,IDの重複をチェックするだけにする
parent = aq_parent(aq_inner(self))
invalid_id = new_id in parent.objectIds()
if not invalid_id:
# portal_factoryを使っているときは,subtransactionをコミットしてから
# リネームすること!
get_transaction().commit(1)
self.setId(new_id)
return new_id
return False
標準の方法では,plone_utils.normalizeString()という関数を使って,文字列がID(例えば,オブジェクトやCSSクラス,アンカーのname)に利用できることを保証します。_isIDAutoGenerated()を確認し,check_id()を呼び出していることにも注意して下さい。この2つの処理を行うことで,リネーム前に自動生成されたIDのみをリネームし,IDが有効でかつ一意であることを保証できます。
RichDocumentはPlone標準のドキュメント/ページが持っているデフォルトのタイトル - ID生成の手法を利用します。フォルダ内での通し番号をIDとして利用したい場合もあるでしょう。「Poi」という進捗管理ツールが,この方法を使って進捗管理の項目IDに通し番号を利用しています:
def _renameAfterCreation(self, check_auto_id=False):
parent = self.aq_inner.aq_parent
maxId = 0
for id in parent.objectIds():
try:
intId = int(id)
maxId = max(maxId, intId)
except (TypeError, ValueError):
pass
newId = str(maxId + 1)
# portal_factoryを使っているときは,subtransactionをコミットしてから
# リネームすること!
get_transaction().commit(1)
self.setId(newId)
多くのチェックを必要とせず,処理がより単純になっています。
以下のURLにあるHow Toの翻訳です。
http://plone.org/documentation/tutorial/richdocument/controlling-creation