Ploneプログラミングレシピ
- 対象バージョン: Plone 2.5.x、Plone 2.1.x
- 対象読者: 開発者
- 原文: Common Plone programming recipes by Mikko Ohtamaa - last modified June 21, 2007 - 00:23
- 翻訳者: TAKAGI Masahiro
PloneのオブジェクトをPythonで操作するための基本的な方法をまとめました。
導入
前書き
このチュートリアルでは、Plone/ZopeのオブジェクトをPythonで操作する基本的な手法を説明します。あくまでも基本的な部分だけです。
何か新しい項目(あなたの血と汗と涙の結晶 (^o^))をここに追加したいという場合はコメントをください。私が書き足します。あるいは、ここで説明している内容よりももっとわかりやすい書き方があれば、ぜひ教えてください。
アイテムの作成とコピー
サイトにデータを追加する方法
コンテンツオブジェクトの作成
Ploneのコンテンツオブジェクトを作成するには、親フォルダのinvokeFactoryメソッドを使用します。フォルダ風の(folderish)コンテンツタイプはすべてこのメソッドを持っています。invokeFactoryの引数に渡すのは、コンテンツタイプとそのIDです。さらに、任意の数のオプション引数を渡すことができます。これは、アイテムを作成する際にフィールドのデフォルト値として設定されます。
# employeesというフォルダを作成します。 # オプションの初期値としてtitleを設定しています。 folder = self.portal.invokeFactory("Folder", id="employees", title="Employees")
Ploneのウェブインターフェイス上からオブジェクトを作成する場合には、PortalFolder?.createObjectというメソッドが呼び出されます。createObjectはオブジェクトに一時的なIDを与え、最初にそのオブジェクトが保存されたときにオブジェクトのタイトルにもとづいたIDを設定します。
invokeFactoryのセキュリティチェックや型チェックを迂回する方法
invokeFactoryメソッドは、そのオブジェクトの作成が許可されているかどうかをまずチェックします。ここでチェックするルールは、エンドユーザを念頭に置いたものです。時には、このセキュリティチェックをうっとおしく感じることもあるでしょう。- for example, workaround the global_allow flag in portal types. このような場合に使用するメソッドがCMFPlone.utilsにあります。
from Products.CMFPlone.utils import _createObjectByType # invokeFactory barks about global_allow flag # _createObjectByTypeで、invokeFactoryのチェックを回避します #self.invokeFactory(CheckoutTool.meta_type, CheckoutTool.id) _createObjectByType(CheckoutTool.meta_type, self, CheckoutTool.id)
Zopeオブジェクトの作成
Ploneは、Zopeで動く数多くのプロダクトのうちのひとつに過ぎません。Plone以外にもさまざまなZopeプロダクトが存在します。Zopeオブジェクトの作り方は、Ploneのオブジェクトとは多少異なります。
通常、Zope 2.xのオブジェクトにはmanage_addという特別なメソッドがあります。オブジェクトを作成する際にはこのメソッドをコールします。以下に例を示します。
# ZMySQLDAを使用して、MySQLとの接続を作成します # ここでは、self = portal rootです from Products.ZMySQLDA.DA import manage_addZMySQLConnection if not "mysql_connection" in self.objectIds(): manage_addZMySQLConnection(self, "mysql_connection", "MySQL connection", "toholampi@localhost root", check=True)
コンテンツオブジェクトのコピー
オブジェクトのコピーは、思っているほど簡単ではありません。実際には以下のような点に注意する必要があります。
- 一意なオブジェクトID
- コンテンツオブジェクトの参照
- 検索インデックスのデータ
ごく一般的なオブジェクトを別のフォルダにコピーする手順は、次のようになります。
# 元のオブジェクトのコピーを作成します classgroup_folder.manage_pasteObjects(proto_folder.manage_copyObjects([sourceId])) # コピーできたら、そのidを修正します classgroup_folder.manage_renameObjects([sourceId], [targetId]) # titleを修正します object = classgroup_folder[targetId] object.setTitle(targetTitle) # ナビゲーションツリーや検索インデックスのデータを更新します object.reindexObject()
ウェブ上でのコンテンツの作成
Ploneのウェブインターフェイスで新しいオブジェクトを作成する際には、そのオブジェクトのcreateObjectメソッドがコールされます。createObjectは自動的にデフォルトのオブジェクトIDを作成し、"作成中"状態 (object._at_creation_flag = True) にします。
Pythonスクリプト内からcreateObjectを使用したい場合は、次のようにします。
## Script (Python) "add_expertise_area" ##title=Add expertise area button handler ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind state=state ##bind subpath=traverse_subpath ##parameters=id='' ## # このスクリプトは、自前の「保存」ボタンが押されたときに実行されます。 # # これは現在のオブジェクトを保存し、子オブジェクトを作成して # 編集ビューで開きます。 # # 作者: Mikko Ohtamaa <mikko@redinnovation.com> res = context.content_edit_impl(state, id) context.plone_log("Got res:" + str(res)) # Add new expertise area and move to its edit form if res.status == "success": # Override default "changes saved" message state.kwargs["portal_status_message"] = u"Please fill in expertise area details" context.plone_log("Got state:" + str(state)) # Created returns URL for the new object # The state object is changed by createObject, so we can discard this information created = context.createObject(type_name = "expertiseArea")
フィールドの値の読み書き
コンテンツの状態にアクセスしたり変更したりする方法
フィールドへのアクセス
通常は、独自のコンテンツタイプを作成する場合にはArchetypesのスキーマを使用します。スキーマとは、フィールドとそのプロパティのリストのことです。
以下の例では、コンテンツのスキーマに"mySomeField"というフィールドを作成します。すると、Archetypesが以下のメソッド群を自動生成します(Archetypes/ClassGen.pyを参照ください)。
- accessor (get メソッド) getMySomeField()
- mutator (set メソッド) setMySomeField(value)
- raw accessor (edit accessor とも呼ばれる、文字エンコーディングに関する操作を一切しない get メソッド) getMySomeFieldRaw()
自動生成されたこれれのメソッドは、フィールドのプロパティで上書きすることができます。たとえば、titleにアクセスするにはgetTitle()でなくTitle()も使用できます。
value = myItem.getMagic() myItem.setMagic(newValue)
Field.getおよびField.setの使用法
accessorやmutatorに直接アクセスできない場合もあるでしょう。あるいは返り値をラップして文字コード変換などを行いたい場合もあるかもしれません。そんな場合にはFieldのgetメソッドおよびsetメソッドを使用します。
f = myItem.getField("myFunnyField") # Fieldのインスタンスは、同じコンテンツオブジェクト間で共有されます。 # Fieldのメソッドを使用する際には、対象となるオブジェクトをパラメータで指定しなければなりません。 oldValue = f.get(myItem) f.set(myItem, newValue)
詳細はArchetypes/Field.pyを参照してください。
インデックスの再構築
オブジェクトをすばやく見つけられるように、Ploneの内部では検索カタログとインデックスを管理しています。このインデックスには、オブジェクトの属性の内容が検索しやすい形式で格納されています。場合によっては、オブジェクトに対して行った操作の結果がインデックスに自動反映されないことがあります。このような場合、ナビゲーションツリーや検索機能などがうまく働かなくなってしまいます。
具体的に言うと、object.setTitle()は、PathIndexが作成するナビゲーションツリー内でのタイトルを変更しません。
このような状況を救済するには、変更を行った後にobject.reindexObject()をコールします。
schema = ATContentTypeSchema.copy() + Schema(
# フィールドリスト
(
TextField('someField',
widget=StringWidget(
label='Here is a text value',
description="Try to set this",
),
),
IntegerField('someIntegerField')
)
class MyType(ATCTContent):
"""
サンプルとして、いくつかコンテンツを宣言します
"""
schema = schema
typeDescription= 'MyType custom content type'
meta_type = 'MyType'
archetype_name = 'MyType'
# ...
# そして、そのフィールドを操作します。ここでは
# portal.myfolder.itemを作成したものとします
myItem = portal.myfolder.myitem
myItem.setSomeField("Moo was here")
# カウンタを加算します
intFieldValue = myItem.getSomeIntegerField()
myItem.setSomeIntegerField(intFieldValue + 1)
オブジェクトの型の取得
# オブジェクトの型を、クラスの宣言から取得します
type = object.portal_type
フォルダの中身の一覧、ポータルルートやサイトツールの使用
コンテンツツリーをたどり、サイト内の別の場所にあるオブジェクトを使用する方法
ポータルアイテムの格納方式
すべてのアイテムは、ポータル配下に階層的に格納されています。
オブジェクトのIDを通常のPythonの属性として扱えば、ポータル内のオブジェクトにアクセスすることができます。属性はZopeオブジェクトと透過的に対応し、オブジェクトのIDがそのオブジェクトのURLとなります。Pythonのインターフェイスでは、子オブジェクトとフィールドは区別しません。
たとえば、plone.orgで次のようなスクリプトを実行すれば、このHow-toを参照することができます。
# どうにかしてplone.orgのルートオブジェクトを取得し、それを # ローカル変数 "portal" に保存する必要があります documentation = portal.documentation howTos = getattr(portal, "how-to") # how-toのダッシュが構文上無効なので、getattrを使用する必要があります myHowTo = getattr(howTos, "manipulating-plone-objects-programmatically")
フォルダの内容の一覧取得
CMFCore/PortalFolder.pyにcontentItemsというメソッドが定義されています。詳細はソースコードをごらんください。
items = folder.contentItems() # 子オブジェクトの情報(id、object)を格納したタプルのリストを返します
このようにしてオブジェクトの一覧を取得するのは、非常に負荷のかかる処理となります。よっぽどのことがない限り、こんなやり方は避けるようにしましょう。詳細な情報は、Plone コアリファレンスの "waking up objects"の部分をごらんください。
特定の型のアイテム一覧の取得
listFolderContentsメソッドは、フォルダ内のオブジェクトを取得します (時間がかかります)。引数contentFilterにはディクショナリを渡します。ここで"portal_type"を指定すると、その型のオブジェクトのみを取得します。
# このフォルダ内にある、portal_typeが"CourseModulePage"であるすべての型を取得します return self.listFolderContents(contentFilter={"portal_type" : "CourseModulePage"})
フォルダのアイテムのIDの取得
IDのみがほしいのならobjectIds()メソッドを使用します。これは、パフォーマンス面でも効率的です。
# フォルダ内のオブジェクトのIDのリストを返します
ids = folder.objectIds()
特定のオブジェクトIDが存在するかどうかの確認
フォルダの中に特定のアイテムが存在するかどうかを調べるには、以下のようなコードを実行します。もし「BTreeFolderであるかどうか」を調べる必要がないのなら、もう少しこれを簡略化することもできます。
# 可能なら、BTreeFolderのAPIを使用します if base_hasattr(context, 'has_key'): # BTreeFolderのhas_keyは、数値を返します return context.has_key('index_html') and True or False elif 'index_html' in context.objectIds(): return True else: return False
獲得 (Acquisition)
Ploneでは、多くの場面で「獲得 (Acquisition)」という仕組みを使用しています。これは、クラスツリーにおける継承の概念と似ていますが、獲得の場合はオブジェクトのコンテキスト階層を用いて属性を受け取ります。子オブジェクトから、親コンテナのプロパティを上書きすることができます。獲得を使用する場面としてもっともよくあるのが、権限やプロパティの設定です。通常は、「獲得」用のメソッドを直接コールする必要はありません。
たとえば、
- left_slotsやright_slotsといったプロパティで表示させたポートレットに対しては「獲得」を用いてアクセスすることができます。left_slotやright_slotは、ポータルのルートで最初に宣言されます。子フォルダ側でこの設定を上書きすることで、これを上書きすることができます。
- Ploneの階層ごとに、異なる権限を設定することができます。この仕組みのもととなっているのが「獲得」です。
権限については、ZopeのAccessControlモジュールのドキュメント (ソースコード) を参照ください。
プロパティについては、ZopeのOFSモジュールにあるPropertyManagerクラスのドキュメント (ソースコード) を参照ください。
コンテンツの親コンテナの取得
コンテンツの階層をさかのぼる際にも「獲得」を使用することができます。
from Acquisition import aq_parent parent = aq_parent(context)
あるいは parent = ac_parent(ac_inner(context)) としたくなることもあるかもしれません。これらの使い分けについて、誰かわかりやすく説明できる人はいますか?
ポータルルートのハンドルの取得
ポータルルートは、Ploneのコード上ではportalオブジェクトとして表されます。
クイックインストールスクリプトの関数install(self)において、selfの中身がportalへのハンドルとなります。
ユニットテストの際にはself.portalを使用します。
portalに直接アクセスできない場合は、portal_urlツールと「獲得」を用いて取得することができます。
from Products.CMFCore.utils import getToolByName # "context"というオブジェクトがあることをご存知でしょう portal_url = getToolByName(context, "portal_url") portal = portal_url.getPortalObject()
Zopeアプリケーションサーバのハンドルの取得
Ploneサイトの内部を操作するだけでなく、Zopeアプリケーションサーバに対して直接アクセスしたくなることもあるでしょう。Zopeのルートを取得するには、次のようにします。
app = context.restrictedTraverse('/')
Portalツール
ポータルルート配下にあるツールやユーティリティ群に直接アクセスしなければならないこともあるでしょう。たとえばportal_types (型情報の取得のため) やportal_membership (ログイン情報の取得のため) などが例にあげられます。getToolByNameという関数を使用すると、これらのユーティリティのインスタンスを取得することができます。
from Products.CMFCore.utils import getToolByName # コンテンツのメソッドでは、このように使用します def getMySecretVariabl(self) plone_utils = getToolByName(self, "plone_utils") # あるいは、スキンのスクリプト内では、このように使用します plone_utils = getToolByName(context, "plone_utils")
オブジェクトの削除、名前の変更
Ploneのオブジェクトの削除や名前の変更を、プログラム上で行う方法
コンテンツオブジェクトの作成
コンテンツを削除するには、parent_container.manage_delObjectsを使用します。
Zopeでは、delキーワードを使用するとアイテムを削除することができます。しかし、これを使用してはいけません。delを使用すると、内部のインデックスに不要な情報が残ったままになり、整合性がとれなくなってしまいます。Ploneのオブジェクトを削除する際にhは、常にmanage_delObjectsメソッドを使用するようにしましょう。
# delObjectsは、IDのシーケンスをパラメータとして受け取ります self.portal.myfolder.manage_delObjects(["myitem"]) # 削除したいアイテムのIDのリストを、パラメータとして渡します
コンテンツオブジェクトの名前の変更
ID(URLとして見える部分)を変更するには、次のようにします。
folder.manage_renameObject(id='my_item_id', new_id='my_item_new_id')
オブジェクトのタイトルを変更するには、次のようにします。
folder.item.setTitle("My new content title")
folder.item.reindexObject()
セキュリティモデル
Zopeのセキュリティモデルの概要を説明し、Pythonのコードでセキュリティを設定する方法を説明します
セキュリティモデル
Pythonのメソッドは、Zopeのセキュリティマネージャによって保護されています。各メソッドは、それぞれ個別にアクセス権限を設定する必要があります。サンドボックス内のコンテキスト(HTTP URLやページテンプレート、あるいはサイトのスクリプト)からサンドボックス外に出るメソッドは、すべてPythonのセキュリティマネージャによるチェックの対象となります。サンドボックスの外部では、セキュリティマネージャによる自動チェックは行われません。毎回このチェックを行うのは、非常に負荷がかかるからです。
すべてのメソッドは、通常はURL経由でアクセスします(http://myhost/mycontent/getMyValue など)。そしていったんセキュリティチェックをクリアすれば、その後再度チェックが行われることはありません。非公開情報を操作したり取得したりするメソッドには、適切な権限を付与しておくことが重要です。
複数の権限を組み合わせて「ロール」として管理することができます。ロールは下位フォルダに継承されます。サブフォルダ側では、親フォルダから継承したロールに対して別の権限を与えることができます。
ロールは、ユーザやグループに対して適用します。ユーザは、サイト内の場所ごとに異なるロールを保持することができます。
メソッドやモジュールへのセキュリティの定義
Zopeにおいて、権限はPythonの文字列形式で設定します。権限の名前をいったん変数に格納した上で、その変数を用いて権限を設定することをお勧めします。そうすれば、名前のタイプミスによるエラーを発見しやすくなります。
Ploneで共通に用いられる権限を以下にまとめます。これらは、CMFCoreのpermissionsモジュールで宣言されています。以下の一覧は、「変数名=変数の中身」形式で表示しています。
- View = "View"
- この権限を持っているユーザは、アイテムを閲覧することができます。つまり、閲覧用のクラスメソッドやgetId()、getText()といったメソッドを使用できるようになります。
- ModifyPortalContent = 'Modify portal content'
- この権限を持っているユーザは、アイテムを編集することができます。つまり、編集用のクラスメソッドやsetId()、setText()といったメソッドを使用できるようになります。
- AddPortalContent = 'Add portal content'
- この権限を持っているユーザは、フォルダにアイテムを追加することができます。
- ListFolderContents = 'List folder contents'
- フォルダの中にどんなアイテムがあるのかを見ることができます。
- AddXXXContent = "Add XXX content"
- コンテンツタイプごとに、そのタイプのコンテンツを作成するための権限が定義されています。つまり、新しいアイテムを追加するためにはAddPortalContentとAddXXXContentの両方の権限が必要になるということです。
CMFCoreで定義されている権限を使用する例を以下に示します。
from CMFCore import permissions class REAgent(Member): """ Real-estate agent content type """ security = ClassSecurityInfo() archetype_name = 'REAgent' meta_type = 'REAgent' portal_type = 'REAgent' schema = schema security.declareProtected(permissions.View, "showImage") def showImage(self, blaa): """ このメソッドを使用するにはView権限が必要です。View権限を保持している(あるいは継承している)ユーザだけがこのメソッドをコールできます。""" ...
モジュールレベルの関数の宣言にはZopeのModuleSecurityを、そしてクラスメソッドの宣言にはClassSecurityを使用します。
- ClassSecurity.declarePublic("myMethodName") ウェブブラウザも含め、すべての場所からこの関数を使用できます。
- ClassSecurity.declarePrivate("myMethodName") セキュリティチェックを通過したもののみが使用できます。
- ClassSecurity.declareProtected(MY_PERMISSION_STRING_CONSTANT, 'myMethodName') 文字列MY_PERMISSION_STRING_CONSTANTで表される権限を保持しているユーザおよびセキュリティチェックを通過したユーザが使用できます。
クラスを宣言した後には、InitializeClassあるいはRegisterTypeをコールしてセキュリティを初期化することを忘れないようにしましょう!
モジュールのセキュリティについては、このチュートリアルも参照してください。
# Zope imports from AccessControl import ClassSecurityInfo, Unauthorized from Globals import InitializeClass # Plone imports from Products.Archetypes.public import registerType # Local imports from Products.MyProduct.permissions import ADD_ISSUES_PERMISSION class Issue(ATCTContent): """ Usability issue for an application """ security = ClassSecurityInfo() security.declareProtected(ADD_ISSUES_PERMISSION, 'initializeArchetype') def doStuff(self, **kwargs): """ Set voting enabled initially called by the generated addXXX factory in types tool """ ATCTContent.initializeArchetype(self, **kwargs) setattr(self, "enableRatings", True) setattr(self, "enableVoting", True) if(notAllowed == True): raise Unauthorized, "Your user can't do this" security.declarePublic('isVoteable') def isVoteable(self): """ Do not allow voting of sealed item """ workflowTool = getToolByName(self, 'portal_workflow') #print "Is voteable:" + str(workflowTool.getStatusOf("issue_workflow", self)) return (workflowTool.getStatusOf("issue_workflow", self)["review_state"] == "in_progress") registerType(Issue, PROJECTNAME) class MyCustomClass: security = ClassSecurityInfo() security.declarePublic("blaa") def blaa(self): pass InitializeClass(MyCustomClass)
パーミッション
Zopeの権限をPythonコードで操作する方法
権限のチェック
現在のユーザがそのコンテキストにおける権限を保持しているかどうかを調べる方法です。以下の例ではselfをコンテキストとして扱います。
# portal_membership ツールを使用して権限を確認します mtool = context.portal_membership checkPermission = mtool.checkPermission # checkPermissions は、その権限が付与されているときに true を返します if checkPermission('Modify portal content', context): return "変更できます" # # あるいは... # if not getSecurityManager().checkPermission(MANAGE_USABILITY_ITEMS_PERMISSION, self): raise Unauthorized, "ユーザ " + str(getSecurityManager().getUser()) + " は、アプリケーションフォルダを作成する権限がありません。必要な権限は:" + MANAGE_USABILITY_ITEMS_PERMISSION
オブジェクトの権限の設定
Normally one should never set context object permissions directly in Plone. 正しいやり方は、まずコンテキストオブジェクトの権限を設定するstateを持つワークフローを作成することです。
Zope の内部での権限管理は "権限が付与されたロール + 獲得 (acquisition) が有効な場合に獲得したロール" 形式になっています。AccessControl/Role.py のメソッド manage_permission で、権限を設定します。
def manage_permission(self, permission_to_manage, roles=[], acquire=0, REQUEST=None): """Change the settings for the given permission. If optional arg acquire is true, then the roles for the permission are acquired, in addition to the ones specified, otherwise the permissions are restricted to only the designated roles. """
たとえば次のように使用します。
# Plone の一般的な権限については、このモジュールで # 擬似変数が定義されています from Products.CMFCore import permissions def fix_assignment_permissions(context): """ student と tutor にのみ許可します """ # View は Plone 本体の権限のひとつです。これは、 # そのオブジェクトを誰が閲覧できるのかを表します。 # このオブジェクトは、studentとtutor、そしてmanagerのみが閲覧できるようにします context.manage_permission( permissions.View, roles = ["Student", "Tutor", "Manager"], acquire=False)
セキュリティのテスト
ユニットテストの際には、PloneTestCaseのメソッドsetRolesを使用します。
これは、アクティブなセキュリティロールをユニットテストのドライバに設定します。ユニットテストでは常にsetRolesを使うようにします。そうすると、セキュリティエラーも捕捉してくれるようになります。
def testCreateServiceRequest(self): """ Create service request and translate it through all states """ self.setRoles(("Member",)) self.portal.service_requests.invokeFactory("BuyerServiceRequest", id="testRequest") req = self.portal.service_requests.testRequest self.setRoles((REAGENT_ROLE,)) workflowTool = self.portal.portal_workflow workflowTool.doActionFor(req, "pick_request") workflowTool.doActionFor(req, "close_request") self.setRoles(("Manager",)) workflowTool.doActionFor(req, "reopen_request") self.setRoles((REAGENT_ROLE,)) workflowTool.doActionFor(req, "pick_request") workflowTool.doActionFor(req, "close_request")
権限の操作
The easiest way to set custom permission for roles is to do it via workflows. Please refer to this tutorial. Note that workflows cannot user permissions before permissions are declared at portal root level.
See Zope/AccessControl/Role.py for methods if you need to do it directly.
This might come in handy:
# Python imports import types from StringIO import StringIO # Zope imports from AccessControl.Permission import Permission def addPermissionsForRole(context, role, wanted_permissions): """ Add permissions for a role in the context Parameters: @param context Portal object (portal itself, Archetypes item, any inherited from RoleManager) @param role role name, as a string @param wanted_permissions tuple of permissions (string names) to add for the role All wanted_permissions lose their acquiring ability """ assert type(wanted_permissions) == types.TupleType #print "Doing role:" + role + " perms:" + str(wanted_permissions) for p in context.ac_inherited_permissions(all=True): name, value = p[:2] p=Permission(name,value, context) roles=list(p.getRoles()) #print "Permission:" + name + " roles " + str(roles) if name in wanted_permissions: if role not in roles: roles.append(role) p.setRoles(tuple(roles)) def removePermissionsFromRole(context, role, wanted_permissions): """ Remove permissions for a role in the context Parameters: @param context Portal object (portal itself, Archetypes item, any inherited from RoleManager) @param role role name, as a string @param wanted_permissions tuple of permissions (string names) to add for the role All wanted_permissions lose their acquiring ability """ assert type(wanted_permissions) == types.TupleType #print "Doing role:" + role + " perms:" + str(wanted_permissions) for p in context.ac_inherited_permissions(all=True): name, value = p[:2] p=Permission(name,value, context) roles=list(p.getRoles()) #print "Permission:" + name + " roles " + str(roles) if name in wanted_permissions: if role in roles: roles.remove(role) p.setRoles(tuple(roles))
ロール
ロールの作成、コンテンツオブジェクトやロールの操作
新しいロールの作成
The example below creates a role programmatically
From PloneInstallation RoleInstaller.py
def doInstall(self, context): """Creates the new role @param context: an InstallationContext object """ context.portal._addRole(self.role) context.logInfo("Added role '%s'" % self.role) if self.model: # Copies permissions from an existing role permissions = self._currentPermissions(context, self.model) context.portal.manage_role(self.role, permissions=permissions) context.logInfo("Give permissions of '%s' to '%s'" % (self.model, self.role)) if self.allowed: context.portal.manage_role(self.role, permissions=self.allowed) context.logInfo("Allowed permissions %s to '%s'" % (', '.join(["'" + p + "'" for p in self.allowed]), self.role)) if self.denied: permissions = self._currentPermissions(context, self.role) for p in self.denied: permissions.remove(p) context.portal.manage_role(self.role, permissions=permissions) context.logInfo("Denied permissions %s to '%s'" % (', '.join(["'" + p + "'" for p in self.denied]), self.role)) return
現在のユーザの、そのコンテキストにおけるロールの確認
user.getId() in context.users_with_local_role('Owner')
ロールの操作
Adding local rules for a user. The role is effective only in the context and nested child objects.
object.manage_addLocalRoles(username, ("My Custom Role",))
グループに対する、そのコンテキストにおけるロールの追加
ユーザ
ログインしているユーザの取得、ユーザデータベースの操作
ログインしているユーザの取得
現在ログインしているユーザとその名前を取得します。
from Products.CMFCore.utils import getToolByName mt = getToolByName(self, 'portal_membership') if mt.isAnonymousUser(): # そのユーザはログインしていません pass else: member = mt.getAuthenticatedMember() username = member.getUserName()
ユーザの削除
Plone 2.5での方法
try:
self.portal.acl_users.source_users.doDeleteUser("hr")
except KeyError:
# そのユーザは存在しません
pass
メンバーのフルネームの取得
mt = getToolByName(self, 'portal_membership') member = mt.getAuthenticatedMember() fullname" : member.getProperty('fullname')
メンバーのメールアドレスの取得
mt = getToolByName(self, 'portal_membership') member = mt.getAuthenticatedMember() fullname" : member.getProperty('fullname')
ユーザデータベースの操作
Manipulating users depends a bit what kind of user backend you have
- Zope internal user database
- CMFMember or other product which presents users as site content
- External user database through PlonePAS (e.g. LDAP Windows user accounts)
Ploneを直接使用する例
def createMember(self, id, pw, email, roles=('Member',)): pr = self.portal.portal_registration member = pr.addMember(id, pw, roles, properties={ 'username': id, 'email' : email }) return member
CMFMemberを使用する例
def createREAgent(self, id): md = self.portal.portal_memberdata tmp_id = id + '_tmp_id' md.invokeFactory(type_name='REAgent', id=tmp_id) return md._getOb(tmp_id)
すべてのメンバーに対するプロパティの設定
This example shows how to change the editor for all users.
Below code is used from an external method, it was placed as 'switchToKupu.py' inside a product's 'Extensions/' directory. This was used to move users from Epoz to Kupu:
def switchToKupu(self): out = [] # Collect members pm = self.portal_membership for memberId in pm.listMemberIds(): member = pm.getMemberById(memberId) editor = member.getProperty('wysiwyg_editor', None) if editor == 'Kupu': out.append('%s: Kupu already selected, leaving alone' % memberId) else: member.setMemberProperties({'wysiwyg_editor': 'Kupu'}) out.append('%s: Kupu has been set' % memberId) return "\n".join(out)
ワークフロー
ワークフローをプログラム上で扱う方法
デフォルトのワークフロー
For Plone stock workflow state ids and transition ids see DCWorkflow/Default.py
ワークフローの作成
To create or manipulate workflows please refer to this tutorial.
オブジェクトのワークフローの状態の取得
If you want to read the workflow state of an object, use the following snippet:
workflowTool = getToolByName(self.portal, "portal_workflow") # Returns workflow state object status = workflowTool.getStatusOf("plone_workflow", object) # Plone workflows use variable called "review_state" to store state id # of the object state state = status["review_state"] assert state == "published", "Got state:" + str(state)
ワークフローの状態の設定
To set workflow state programmatically, you need to use WorkflowTool
portal.invokeFactory("SampleContent", id="sampleProperty") workflowTool = getToolByName(context, "portal_workflow") workflowTool.doActionFor(portal.sampleProperty, "submit")
WorkflowTool also can list available actions. Note that there can be several workflows per object. This is important to know when retrieving the current workflow state.
インストールされているワークフローの取得
Gets the list of ids of all installed workflows. Test if there is one particular present.
# Get all site workflows ids = workflowTool.getWorkflowIds() self.failUnless("link_workflow" in ids, "Had workflows " + str(ids))
あるポータルタイプのデフォルトのワークフローの取得
# Get default workflow for the type chain = workflowTool.getChainForPortalType(ExpensiveLink.portal_type) self.failUnless(chain == ("link_workflow",), "Had workflow chain" + str(chain))
あるオブジェクトのワークフローの取得
How to test which workflow the object has
# See that we have a right workflow in place workflowTool = getToolByName(context, "portal_workflow") # Returns tuple of all workflows assigned for a context object chain = workflowTool.getChainFor(context) # there must be only one workflow for our object self.failUnless(len(chain) == 1) # this must must be the workflow name self.failUnless(chain[0] == 'link_workflow', "Had workflow " + str(chain[0]))
重要なメソッド
Getting content type, URL, workflow state, etc.
スキーマおよびフィールド
Getting Field from content
field = content.getField("myFieldName")
Getting schema
schema = content.getSchema()
Iterating through fields
for id in schema.keys(): field = schema[id]
アイテムの型
Getting item type
item.getTypeInfo().getId() # return factory type information id, e.g. portal_type attribute
URL
If you want to give URLs for your content object in page templates and page scripts
url = item.absolute_url()
or in page template
<a href="#" tal:attributes="href string:${here/absolute_url}">
コンテンツタイプの作成
Plone用の独自のコンテンツタイプを作成する方法
Martin Aspeliが、すばらしいRichDocumentチュートリアルを書いています。私が何を書いてもその二番煎じになってしまいそうなので、ここでは省略します。
ビューおよびテンプレート
Plone has different sets of views appearing for each content type. The basic views are "view" and "edit". This chapter tells how to add and manipulate views.
デフォルトのビューテンプレートの変更
The most common use case is that one wishes to have a customized view template for a new content type.
First, change "default_view" attribute in AT class definition. This attribute tells what page template is used to view the content object.
class consultantInformation(ATCTFolder): """ """ default_view = "consultant_view" schema = schema
動的ビューの追加
Dynamic views appear in the object's Display menu. The manager can override default view mode for the object. For example, Plone ships with "Album view" for folders which makes folders behave like photo albums, showing thumbnailed images.
- Add your custom template to supplied views of your content class
class WorkPackageFolder(ATFolder): """ Contains work package items """ schema = schema filter_content_types = True typeDescription= 'Work package folder' meta_type = 'WorkPackageFolder' archetype_name = 'Work package folder' # Generate user friendly id from item title during creation # Effective only for ATContentTypes based classes _at_rename_from_title = True suppl_views = ('my_template_name',)
- Create a custom view template my_template_name.pt. Good starting points are base_view.pt in Archetypes product and various templates in ATContentTypes product.
- Create my_template_name.pt.metadata file which will contain user readable label for your view and other information. This must be in the same folder with the view template.
[default]
title = My view name
デフォルトの編集テンプレートの上書き
The default edit template is called "base_edit.cpt". Here are instructions how you replace it for your AT class to add custom text on the template.
- Copy base_edit.cpt and base_edit.metadata to your skins directory
- Rename them to specific to your item, e.g. wnc_edit.cpt and wnc_edit.metadata
- Edit wnc_edit.cpt. The following exampe adds a new button Add expertise area next to Save. Note that <input> name must be in form of "form.button.xxx".
<metal:use_body use-macro="body_macro">
<metal:block fill-slot="buttons"
tal:define="fieldset_index python:fieldsets.index(fieldset);
n_fieldsets python:len(fieldsets)">
<input tal:condition="python:fieldset_index > 0"
class="context"
tabindex=""
type="submit"
name="form_previous"
value="Previous"
i18n:attributes="value label_previous;"
tal:attributes="tabindex tabindex/next;
disabled python:test(isLocked, 'disabled', None);"
/>
<input tal:condition="python:fieldset_index < n_fieldsets - 1"
class="context"
tabindex=""
type="submit"
name="form_next"
value="Next"
i18n:attributes="value label_next;"
tal:attributes="tabindex tabindex/next;
disabled python:test(isLocked, 'disabled', None);"
/>
<input class="context"
tabindex=""
type="submit"
name="form_submit"
value="Save"
i18n:attributes="value label_save;"
tal:attributes="tabindex tabindex/next;
disabled python:test(isLocked, 'disabled', None);"
/>
<input class="context"
tabindex=""
type="submit"
name="add_expertise_area"
value="Add expertise area"
/>
<input class="standalone"
tabindex=""
type="submit"
name="form.button.cancel"
value="Cancel"
i18n:attributes="value label_cancel;"
tal:attributes="tabindex tabindex/next"
/>
</metal:block>
</metal:use_body>
- Add following to your content type class
from Products.ATContentTypes.content.base import updateActions, updateAliases class WNCCompany(ATCTContent): """ Consultancy company listing entry """ security = ClassSecurityInfo() # This name appears in the 'add' box archetype_name = 'Consultancy company' meta_type = portal_type = 'WNCCompany' global_allow = True _at_rename_after_creation = True schema = schema # Override default edit view actions = updateActions(ATCTFolder, ( { 'id': 'edit', 'name': 'Edit', 'action': 'string:${object_url}/wnc_edit', 'permissions': (permissions.ModifyPortalContent,), }, ))
ページテンプレートおよびウィジェットのふしぎ
How to perform often requested tricks with Archetypes widgets and page templates
前書き
テンプレートの内容をKupuにコピペしたときに、フォーマットがくずれてしまっているかも……。
名前を動的に設定するマクロの作成
Try this code (also, example in the section below):
<tal:block tal:define="macro_path python: path('here/%s/macros' % page_template_name);
callable_macro macro_path/my_custom_macro_name;">
<tal:use-macro metal:use-macro="callable_macro" />
</tal:block>
フォルダ内の特定の型のコンテンツの順次処理
The following macro serves as a base how to iterate certain content types in a folder
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en">
<!-- per_content_type_renderer macro definition
List certain content types and calls a target macro for them.
Target macro is given as a page template filename which contains the macro.
Takes arguments:
wanted_item_type: string, content type id.
Be careful with space padding in template code.
view_macro: which macro to be called, template file basename,
file contains 'listing_core' macro
Author: Mikko Ohtamaa
http://www.redinnovation.com
-->
<body>
<metal:macro define-macro="per_content_type_renderer">
<tal:foldercontents
define="contentFilter contentFilter|request/contentFilter|nothing;
limit_display limit_display|request/limit_display|nothing;
more_url more_url|request/more_url|string:folder_contents;
contentsMethod python:test(here.portal_type=='Topic', here.queryCatalog, here.getFolderContents);
folderContents folderContents|python:contentsMethod(contentFilter, batch=True);
use_view_action site_properties/typesUseViewActionInListings|python:();
over_limit python: limit_display and len(folderContents) > limit_display;
folderContents python: (over_limit and folderContents[:limit_display]) or folderContents;
batch folderContents">
<tal:listing condition="folderContents">
<div tal:repeat="item folderContents">
<tal:block tal:define="item_url item/getURL|item/absolute_url;
item_type item/portal_type;
item_object item/getObject;
item_creator item/Creator;
macro_path python: path('here/%s/macros' % view_macro);
callable_macro macro_path/listing_core;">
<tal:activity tal:condition="python: item_type == wanted_item_type">
<tal:use-macro metal:use-macro="callable_macro" />
</tal:activity>
</tal:block>
</div>
</tal:listing>
</tal:foldercontents>
</metal:macro>
</body>
</html>
フォルダ一覧でのウィジェットのレンダリング
This is an often heard request - one wants to render a widget outside Archetypes rendering flow
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en">
<body>
<tal:define metal:define-macro="render_field">
<!-- Calling Archetypes widget renderer directly for a certain field
This snippet is useful if one wants to render AT widgets outside their context
object, e.g. in a folder summary view.
The following code takes an arbitary AT content object in item_object variable.
It checks whether this item object has a field "Staff" and then calls
Staff default widget renderer. We need to perform the trick of redeclaring
context variable which might confuse some code (e.g. permissions) so
be careful.
Takes arguments:
target_item: Object whose field we are rendering
field_name: Field name as a string
use_label: True or False whether label should be rendered
Author: Mikko Ohtamaa
www.redinnovation.com
-->
<tal:has-field tal:condition="python: field_name in target_item.schema">
<tal:field-context tal:define="context python: target_item;
field python: target_item.schema[field_name];
widget_view python: target_item.widget(field.getName(), mode='view', use_label=use_label);
field_macros here/widgets/field/macros;
label_macro view_macros/label | label_macro | field_macros/label;
data_macro view_macros/data | data_macro | field_macros/data;
">
<div tal:define="fieldtypename python:field.getType().split('.')[-1]"
tal:attributes="class string:field ArchetypesField-${fieldtypename};
id string:archetypes-fieldname-${field/getName}">
<tal:if_perm condition="python:'view' in widget.modes and 'r' in field.mode and field.checkPermission('r',here)">
<tal:if_use_label tal:condition="python: use_label">
<metal:use_label use-macro="label_macro" />
</tal:if_use_label>
<metal:use_data use-macro="data_macro|default" />
</tal:if_perm>
</div>
</tal:field-context>
</tal:has-field>
</tal:define>
</body>
</html>
Example how to use the page template above:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en">
<body>
<tal:define metal:define-macro="my_funny_macro">
<h1>Render item_object.Staff field</h1>
<tal:field-render-core
tal:define="target_item python: item_object;
field_name string:Staff;
use_label python: False">
<tal:call-renderer metal:use-macro="here/render_field/macros/render_field" />
</tal:field-render-core>
</tal:define>
</body>
</html>
Including a nested context in item
The following snippet will include code from an nested item in the context folder. The nested item will be rendered in a custom widget view macro.
<!-- VIEW -->
<metal:define define-macro="view">
<tal:no-fees condition="python: not 'course-fees' in context.objectIds()">
<p tal:condition="not: isAnon">
Please add Fees page with id "course-fees" to see it here.
You can use contents tab/rename button to change the id of the object.
This messages is visible for editors only.
</p>
</tal:no-fees>
<tal:has-fees condition="python:'course-fees' in context.objectIds()">
<tal:new-context define="context python:context['course-fees']; here python:context['course-fees']">
<metal:body use-macro="here/base/macros/body" />
</tal:new-context>
</tal:has-fees>
</metal:define>
クイックインストーラのサンプル
Each Plone product has quick installer script which prepares the portal for the product. Here are some useful snippets which you can reuse.
PloneInstallation
I sincerely recommend using PloneInstallation product in quick installer scripts. It has many classes needed not to reinvent the wheel every time one writes a quick installer script.
Enabling Large Plone Folders
Large Plone Folders (also known as BTreeFolders) use binary trees as the folder index. This makes folder look ups faster on large item counts. By default, creation of Large Plone Folders is disabled. To enable it, run this code
# Allow creation of large folders lpf = portal.portal_types.getTypeInfo("Large Plone Folder") lpf.global_allow = True
Hiding actions
If you use quick installer script to customize your Plone site you might want to hide certain actions from the end users
# Hide some actions actionsTool = self.portal_actions act = actionsTool.getActionInfo("document_actions/print") act.condition = "python: False" act = actionsTool.getActionInfo("document_actions/sendto") act.condition = "python: False"
Removing permissions and preventing anonymous registration
This code removes the permission to create new users from anonymous visitors. The wanted side effect is that Join link also disappears.
# Python imports import types # Zope imports from AccessControl.Permission import Permission # Plone imports from Products.CMFCore.permissions import * def removePermissionsFromRole(context, role, wanted_permissions): """ Remove permissions for a role in the context. Parameters: @param context Portal object (portal itself, Archetypes item, any inherited from RoleManager) @param role role name, as a string @param wanted_permissions tuple of permissions (string names) to add for the role All wanted_permissions lose their acquiring ability """ assert type(wanted_permissions) == types.TupleType #print "Doing role:" + role + " perms:" + str(wanted_permissions) for p in context.ac_inherited_permissions(all=True): name, value = p[:2] p=Permission(name,value, context) roles=list(p.getRoles()) #print "Permission:" + name + " roles " + str(roles) if name in wanted_permissions: if role in roles: roles.remove(role) p.setRoles(tuple(roles)) # Prevent registration at the site removePermissionsFromRole(self, "Anonymous", (AddPortalMember,))
Adding permissions for custom roles
The following snippets allows you to add permissions for roles
# Python imports import types # Zope imports from AccessControl.Permission import Permission # Plone imports from Products.CMFCore.permissions import * def addPermissionsForRole(context, role, wanted_permissions): """ Add permissions for a role in the context. Parameters: @param context Portal object (portal itself, Archetypes item, any inherited from RoleManager) @param role role name, as a string @param wanted_permissions tuple of permissions (string names) to add for the role All wanted_permissions lose their acquiring ability """ assert type(wanted_permissions) == types.TupleType #print "Doing role:" + role + " perms:" + str(wanted_permissions) for p in context.ac_inherited_permissions(all=True): name, value = p[:2] p=Permission(name,value, context) roles=list(p.getRoles()) #print "Permission:" + name + " roles " + str(roles) if name in wanted_permissions: if role not in roles: roles.append(role) p.setRoles(tuple(roles)) # Make anonymous link submitting possible addPermissionsForRole(self.link_pool, "Anonymous", (AddPortalContent,))
Adding external method
# Add external method # # External methods are Python code which lie in Zope content # structure. Each external method has a module in Extensions folder # and Zope object in Zope. You can call external methods by typing # in URL directly. External methods bypass Zope security mechanism. # # Usually external methods are used for automated maintenance tasks. # # # Following adds a function from poll.py which is in the product's Extensions folder # # self = portal root from config import PROJECTNAME from Products.ExternalMethod.ExternalMethod import manage_addExternalMethod if not "poll_sql" in self.objectIds(): manage_addExternalMethod(self, "poll_sql", "Poll external SQL source", PROJECTNAME + ".poller", "poll_sql")
開発者向けスクリプト
Command line scripts which are useful in product development
Windowsでのユニットテスト用のテストランナー
Since Plone 2.5.1 invoking per product unit test modules has become a major pain.
NOTE: This is only necessary on MS Windows. Linux, OSX and other *NIX platforms can run the tests normally from the commandline.
Here is a short .bat file which allows one to run unit tests within the context of one product.
@echo off REM Invoking unit test directly doesn't work anymore on Plone 2.5.1 REM See http://plone.org/documentation/error/attributeerror-test_user_1_ set PYTHON=d:\python24\python.exe set ZOPE_HOME=F:\workspace\plone-2.5.1\Zope-2.9.6\Zope set INSTANCE_HOME=F:/workspace/plone-2.5.1/instance set SOFTWARE_HOME=%ZOPE_HOME%\lib\python set CONFIG_FILE=%INSTANCE_HOME%\etc\zope.conf set PYTHONPATH=%SOFTWARE_HOME% set TEST_RUN=%ZOPE_HOME%\bin\test.py "%PYTHON%" "%TEST_RUN%" --config-file="%CONFIG_FILE%" --usecompiled -vp --package-path=%INSTANCE_HOME%/Products/lsmintra Products.lsmintra
ポータルカタログの問い合わせ
Portal catalog provides search indexing information for Plone site. Portal catalog queries are much faster than walking through objects manually,
Searching content objects by author and type
The following snippet will perform a search which returns all items for a certain type and a certain creator.
# Search site for a consultant profile whose creator the current # user is # from Products.CMFCore.utils import getToolByName portal_catalog = getToolByName(context, 'portal_catalog') mt = getToolByName(context, 'portal_membership') if mt.isAnonymousUser(): # the user has not logged in return None else: member = mt.getAuthenticatedMember() username = member.getUserName() # Please refer to portal_catalog tool # Zope management interface for default Plone seach indexes query = {} query["Creator"] = username query["Type"] = "Consultant Profile" # Return brain objects for search results brains = portal_catalog.searchResults(**query) context.plone_log("Got results:" + str(brains)) if len(brains) > 0: # Return the real object of the first search hit return brains[0].getObject() else: # Np hits - no profile created yet return None
Test existence of index and metadata colums
# Test if catalog has a search index if not "getFirmName" in catalog_tool.indexes(): catalog_tool.manage_addIndex("getFirmName", "ZCTextIndex", extra) # Test if catalog has a metadata column if not "getSummary" in catalog_tool.schema(): catalog_tool.manage_addColumn("getSummary")
オブジェクトのアクション
Actions are state changing triggers users perform on objects. For example, edit object, copy or print are actions. This chapter describes how to add new actions and manipulate existing actions.
Adding a tab to content type
あとで書く
Enabling and disabling actions site wide
def disable_actions(portal): """ Remove unneeded Plone actions @param portal Plone instance """ # getActionObject takes parameter category/action id # For ids and categories please refer to portal_actins in ZMI actionInformation = portal.portal_actions.getActionObject("document_actions/rss") # See ActionInformation.py / ActionInformation for available edits actionInformation.edit(visible=False)
Enabling and disabling actions for content type
A sample code to disable few stock Plone actions. The example product RTFExport available here.
from Products.ATContentTypes.content.base import updateActions, updateAliases class Employee(ATCTContent): """ Employee record """ # Add RTF export action icon for the object # Hide properties and sharings tabs # Hide cut and copy actions actions = updateActions(ATCTContent, ( { 'id' : 'export_rtf', 'name' : 'Export as RTF', 'action' : 'string:$object_url/export_rtf', 'permissions' : (View,), 'category' : "document_actions", }, { 'id' : 'metadata', 'visible' : False, }, { 'id' : 'local_roles', 'visible' : False, }, { 'id' : 'sendto', 'visible' : False, }, { 'id' : 'cut', 'visible' : False, }, { 'id' : 'copy', 'visible' : False, }, ) )
Overriding edit action
An usual use case is using custom edit form. Add the following snippet to your content class definition to use consultant_edit form as the edit form:
actions = updateActions(ATCTFolder, (
{ 'id': 'edit',
'name': 'Edit',
'action': 'string:${object_url}/consultant_edit',
'permissions': (permissions.ModifyPortalContent,),
},
))
プロパティ
Properties are flexible key-value pairs assigned to content types and tools. Properties are passed to child content objects via acquisition.
Properties
Properties are a special kind of acquired values. Properties have automatically generated user interface in Zope Management Interface to deal with them. If you hit any folder or object in ZMI it has properties tab were can fiddle around with these.
The most common properties one would want to change are probably left_slots and right_slots which control the appearing of portlets in Plone 2.5.x. (Plone 3.0 has reworked portlet system).
Setting properties
Setting properties to an object causes it to override parent properties in an acquisition chain. Properties must not exist on the object before calling _setProperty. _setProperty takes property type which can be found out on ZMI Properties tab.
Overriding portlet settings in a subfolder. No portlets are used for items inside this folder:
data_storage._setProperty('left_slots', [], 'lines') data_storage._setProperty('right_slots', [], 'lines')
Updating properties
Existing properties can be updated with _updateProperty. This only works if properties have been created using set before. Properties must exist on the target object itself, inherit properties are not count in.
Updating properties left_slots and right_slots for the portal root:
portal._updateProperty("left_slots", ["here/portlet_navigation/macros/portlet","here/portlet_login/macros/portlet"]) portal._updateProperty("right_slots", [])
Testing existence of a property
Use object.hasProperty.
The following example code will set or update a property.
# The following code will create or update property. # Update default view page template for assigments folder # default_page property tells the custom page # template used to render this particular content object # in view mode if not assignments.hasProperty("default_page"): # Create the property assignments._setProperty("default_page", "") # Override assigments value (old or new created) assignments._updateProperty("default_page", "assigments_view")
Site and navigation tree properties
There is a special tool portal_properties which manages most of Plone's site wide properties. Please refer to its content by peeking it in ZMI.
Example: Modifying a navigation tree behavior
self.portal_properties.navtree_properties._updateProperty("topLevel", 1)
ポートレット
How to deal with portlets
Activating a custom portlet
This is Plone 2.x way. Plone 3.0 has revamped portlet system. The item shows portlets which are defined in left_slots or right_slots properties.
# Activate shopping portlet # This can be done for any folder # self = portal root right_slots = self.right_slots new_portlet_macro = "here/portlet_shopper/macros/portlet" if not new_portlet_macro in right_slots: self._updateProperty("right_slots", right_slots + (new_portlet_macro,))
新しいコンテンツタイプの作成手順
The check list what you need to do when you create new content types for Plone.
This applies for Plone 2.1.x and still works in Plone 2.5.x. It is encouraged to use newer mechanisms provided by Five subsystem when you start new products from scracth. Please read Martin Aspeli's excellent tutorial about the subject.
Archetypes is a Plone subsystem to define new content types. Content types are Python classes which have special attributes described by Archetypes product. The most import of them is schema which defines what fields your content type has. Archetypes reference manual is handy.
File system product skeleton
You need a file system product where new content types is added. You can take the existing product and rip its flesh away or use examples provided by Archetypes manual.
Content type Python module
Create a Python module containing your content type class declaration
- Create a .py module to the "content" folder of the product.
- Add Necessary dependency imports for fields, widgets, parent schema and parent class
Example
# Plone imports from Products.Archetypes.public import * from Products.ATContentTypes.content.base import ATCTContent from Products.ATContentTypes.content.base import updateActions, updateAliases from Products.ATContentTypes.content.schemata import ATContentTypeSchema from Products.ATContentTypes.content.schemata import finalizeATCTSchema # Local imports from Products.MyCustom.BulletField import BulletField from Products.MyCustom.BulletWidget import BulletWidget from Products.MyCustom.config import *
- Create the schema definition for your content type. See Archetypes manual for available fields, widgets and their properties.
Example
schema=ATFolderSchema.copy() + Schema((
TextField('isItForMe',
default='',
searchable=True,
widget=RichWidget(
label='Is it for me?',
)
),
TextField('benefits',
default='',
searchable=True,
widget=RichWidget(
label='What are the benefits of taking this course?',
)
),
LinksField('whatDoILearn',
default='',
searchable=True,
widget=RichWidget(label='What do I learn?', macro="what_do_i_learn_widget.pt")
),
))
finalizeATCTSchema(schema)
The class definition defines security and general properties of your content type.
Example:
class CoursePage(ATFolder): """ Courses page content type """ # Internal programming id which Plone uses to refer this type portal_type = meta_type = 'CoursePage' # User readable name in "Add new content" drop down menu archetype_name = 'Course page' # Help text for Add new content drop down menu typeDescription = "A course description telling what students should expect for this course" # Fields used in this content type (as defined above) schema = schema # If true, this content type can be created anywhere at the site global_allow = True # We limit what kind of items are allowed in this folderish content type filter_content_types = True # List of allowed content types allowed_content_types = [ "GeneralLSMPage", "CourseModulePage", "CourseModePage", "GeneralLSMPage2", "CourseFeesModePage" ]
You need to use registerType to register your class definition for Zope security manager.
# CoursePage is your Python class # PROJECTNAME is the name of your product and it's usually declared in config.py registerType(CoursePage, PROJECTNAME)
Initializing the product
To make your content type available for Plone
- You need to import it during the Plone start-up sequence
- You need to run the quick installer script to create persistent portal_type entries for your content type
In init.py of your product, import the new content type module so that Zope security manager is initialized for it
""" Copyright 2006 xxx """ __author__ = 'Mikko Ohtamaa <mikko@redinnovation.com>' __docformat__ = 'epytext' from Products.Archetypes.public import process_types, listTypes from Products.Archetypes.ArchetypeTool import getType from Products.CMFCore.DirectoryView import registerDirectory from Products.CMFCore import utils as CMFCoreUtils from config import * from Permissions import * def initialize(context): """ Registers all classes to Zope @param context Zope/App/ProductContext instance """ # initialize security context from content import MyCustomContentTypeModule # Register our skins directory - this makes it available via portal_skins. registerDirectory(SKINS_DIR, GLOBALS) # helper function to go through all content types in your product content_types, constructors, ftis = process_types( listTypes(PROJECTNAME), PROJECTNAME) # Initialize content types CMFCoreUtils.ContentInit( PROJECTNAME + ' Content', content_types = content_types, permission = PermissionNameToCreateThisContent, extra_constructors = constructors, fti = ftis, ).initialize(context)
Then, you need to remember to register types in Extension/Install.py quick installer script. This creates persistent portal_type entries for your content type. Each time you change the general properties of your content type (everything except schema) you need to rerun the quick installer.
""" Extension/Install.py quick installer script Your Copyright line here """ __author__ = 'Mikko Ohtamaa <mikko@redinnovation.com>' __docformat__ = 'epytext' # Python imports from cStringIO import StringIO # Plone imports from Products.Archetypes.public import listTypes from Products.Archetypes.Extensions.utils import installTypes, install_subskin # Local imports from Products.MyProduct.Extensions.utils import * from Products.MyProduct.config import * def install(self): """ This is called by Plone quick installer tool @param self portal instance object @return String which is added for the installer log """ out = StringIO() # Register layout files in the portal install_subskin(self, out, GLOBALS) registerStylesheets(self, out, STYLESHEETS) registerScripts(self, out, JAVASCRIPTS) # Register Archetypes types in the portal installTypes(self, out, listTypes(PROJECTNAME), PROJECTNAME) print >> out, "Installation completed." return out.getvalue() def uninstall(self): # TODO out = StringIO()
添付ファイル
- security_model.png (43.4 kB) - m-takagi によって 2007/07/27 15:13:06 に登録されました。
- security_model_2.png (27.7 kB) - m-takagi によって 2007/07/27 15:13:17 に登録されました。


