2010/11/30

Plone 3.x Migration to Plone 4.0

migration 至少分成資料與模組的昇級,兩者相關,但處理的重點不同。前者可參考 quintagroup.transmogrifier 的範例,後者則要先閱讀 Upgrade Guide

找到一個 ethnomed project 當作模組昇級範例,它已具備 buildout policy theme contenttypes extrafields reviewlist 等模組,透過 svn co https://svn.hsl.washington.edu/repos/ethnomed/ethnomed.buildout/trunk 可以取得原始碼。

開發過程有使用 Products.CacheSetup 之類的相依關係,在 Plone 4 的場合會遇到問題,可參考這個 chageset 的處理方式。另外 ethnomed/contenttypes/browser/viewlets.py 裡的 membership = self.tools.membership() 要改成 membership = getToolByName(self.context, 'portal_membership') 才能正常顯示多位作者。

額外的範例還有 PloneBooking 的 changeset

2010/11/29

Title-to-Id Behavior

title-to-id 是 Plone 的行為特色,在建立文件的過程,會把 title 欄位的值,轉換成 URL 的值。事實上,也可以事後轉換,例如寫成 External Method 來批次執行

Archetypes 預設就使用 title-to-id 機制,相關的程式碼片段整理如下:

Products/Archetypes/BaseObject.py
# Import conditionally, so we don't introduce a hard dependency
try:
from plone.i18n.normalizer.interfaces import IUserPreferredURLNormalizer
from plone.i18n.normalizer.interfaces import IURLNormalizer
URL_NORMALIZER = True
except ImportError:
URL_NORMALIZER = False

class BaseObject(Referenceable):
...
def generateNewId(self):
"""Suggest an id for this object.
This id is used when automatically renaming an object after creation.
"""
title = self.Title()
# Can't work w/o a title
if not title:
return None

# Don't do anything without the plone.i18n package
if not URL_NORMALIZER:
return None

if not isinstance(title, unicode):
charset = self.getCharset()
title = unicode(title, charset)

request = getattr(self, 'REQUEST', None)
if request is not None:
return IUserPreferredURLNormalizer(request).normalize(title)

return queryUtility(IURLNormalizer).normalize(title)

def _renameAfterCreation(self, check_auto_id=False):
"""Renames an object like its normalized title.
"""
old_id = self.getId()
if check_auto_id and not self._isIDAutoGenerated(old_id):
# No auto generated id
return False

new_id = self.generateNewId()
plone/i18n/normalizer/__init__.py
class URLNormalizer(object):
...
def normalize(self, text, locale=None, max_length=MAX_URL_LENGTH):
...
text = baseNormalize(text)
base = text # use text.lower() to make it lower-case

把原本 base = text.lower() 改成 base = text。

另外,Dexterity 則利用 Name From Title 的行為設定來啟用,參考 plone.app.content 裡 interfaces.py 的 INameFromTitle。

2010/11/24

Archetype Content Customization

在 Plone 4 初期,Archetype 還活著,它仍是自製表單的常見方式,即使日後要改用 Dexterity,也有機會透過 transmogrifier轉移內容。想要客製化 Archetype 表單,常見的工作是修改 view 和 edit 的程式碼,以 MyType 的 view 為例,它是由 base_view.pt 來管理 js、css、header、body、folderlisting、footer 六個 macro 設定值,預設會去拉 mytype_view.pt 來顯示,找不到的話就使用 base.pt 來顯示,base.pt 提供四個 macro 設定值,能夠滿足小規模的調整需求。原則上不需要修改 base_view.pt 內容,想要大規模地修改 view 動作,可以在 browser/configure.zcml 裡註冊新的顯示方式。
使用 ReferenceField 的話,要搭配 ReferenceBrowserWidget,它要從 archetypes.referencebrowserwidget 載入,利用 allowed_types=('Document',) 之類的設定值,可以限定項目種類,利用 allow_search=True 可以決定是否提供搜尋欄位。
想要修改 schemata 的話,是使用類似 schema.changeSchemataForField('related_category', 'reference') 這樣的語法。預設的 Dublin Core metadata 相關欄位,設定在 Products/Archetypes/ExtensibleMetadata.py 檔案裡,例如 rights 欄位的資訊:
TextField(
'rights',
accessor="Rights",
default_method='defaultRights',
widget=TextAreaWidget(
label=_(u'label_copyrights', default=u'Rights'),
description=_(u'help_copyrights',
default=u'Copyright statement.'),
)),
透過 default_method 可以指定預設的內容,例子裡的 defaultRights 就是呼叫 portal_metadata 的 listSchemas() 來比對是否存在預設值,而且在 Products/ATContentTypes/content/schemata.py 檔案裡,利用 finalizeATCTSchema() 來指派各欄位的 schemata 位置。
另外,使用 Plone 4 的朋友,記得參考新的網頁管理方式

2010/11/05

Transmogrifier - Import/Export Made Easy

collective.transmogrifier 是處理資料轉換或匯出匯入的工具,常被用來支援 Plone 網站資料的匯出匯入,但其他網站同樣可以引用。

成功測試的是使用 Plone 3.3.5 UnfiedInstaller 來安裝,修改 buildout.cfg 內容如下:
eggs =
Pillow
Plone
collective.transmogrifier
plone.app.transmogrifier
transmogrify.sqlalchemy
pysqlite
argparse
iw.debug

zcml =
collective.transmogrifier-meta
collective.transmogrifier
plone.app.transmogrifier
transmogrify.sqlalchemy
iw.debug

[versions]
Pillow = 1.2
SQLAlchemy = 0.6.5
argparse = 1.1
distribute = 0.6.14
iw.debug = 0.3
plone.app.transmogrifier = 1.1
pysqlite = 2.6.0
transmogrify.sqlalchemy = 1.0.1
collective.transmogrifier = 1.2
ipdb = 0.2
ipython = 0.10.1

使用時要搭配設定檔,例如建立一個 import_test.cfg 檔案,來處理資料庫內容的匯入。設定檔的格式跟 buildout.cfg 類似,第一個設定區段 [transmogrifier] 先指定要用到的 pipeline 項目,也就是轉換資料的過程,需要哪些分解動作。以匯入的例子來說,資料來源是資料庫的內容,存取的資料會依序在 pipeline 傳遞,最後寫進 ZODB 並且更新 Archetype 之類的表單內容。
[transmogrifier]
pipeline =
source
add_type
add_path
constructor
schemaupdater

每個 pipeline 項目又有對應的 section 設定值,其中的 blueprint 是 named adapter,而且慣例是以 package name 來命名,完全避免撞名的問題。以 [source] 的設定值為例,它是搭配 SQLite 來讀取資料,傳遞中的資料以 dict 格式來處理,以 [add_type] 為例,它會新增一組 '_type': 'MyContentType' 資料併入 dict 裡,再往下一個 pipeline 傳遞。
[source]
blueprint = transmogrify.sqlalchemy
dsn = sqlite:///database.sqlite
query = SELECT gid, name as title, organizer, address, tel FROM test

[add_type]
blueprint = collective.transmogrifier.sections.inserter
key = string:_type
value = string:MyContentType

[add_path]
blueprint = collective.transmogrifier.sections.inserter
key = string:_path
value = python:'/MyFolder/'+str(item['gid'])

[constructor]
blueprint = collective.transmogrifier.sections.constructor
required = True

[schemaupdater]
blueprint = plone.app.transmogrifier.atschemaupdater

資料來源並不限於資料庫,也可以是 CSV 檔案,除了匯出匯入資料外,也可以轉換文字編碼之類的動作。如果想套用 PostgreSQL 來源,安裝的模組檔案改為 psycopg2,並參考 transmogrify.sqlalchemy 的範例,改成 dsn = postgres://scott:tiger@localhost:5432/mydatabase 的設定值。

2010/10/24

Building Policy Package

客製化 Plone 網站的方式,有兩種途徑,一種是在網頁設定介面進行,稱為 TTW (Through The Web),另一種是在檔案系統裡撰寫程式碼,稱為 TTF (Through The Filesystem)。
撰寫程式碼的好處之一,是日後可以重覆沿用設定值,Plone 利用 GenericSetupZopeSkel 等機制,協助管理員建立客製化的 Policy Package 模組。以 UnifiedInstaller 安裝方式為例,開發中的模組程式碼要放在 src 目錄裡,建立工具是 paster 程式,步驟如下:
$ cd src
$ ../bin/paster create -t plone zopenfoundry.policy
Selected and implied templates:
ZopeSkel#basic_namespace A basic Python project with a namespace package
ZopeSkel#plone A project for Plone products

Variables:
egg: zopenfoundry.policy
package: zopenfoundrypolicy
project: zopenfoundry.policy
Expert Mode? (What question mode would you like?
(easy/expert/all)?) ['easy']:
Version (Version number for project) ['1.0']:
Description (One-line description of the project) ['']:
ZOpenFoundry Policy Package
Register Profile (Should this package register a GS Profile)
[False]: True
特別注意,Register Profile 要指定為 True。
再編輯 buildout.cfg 內容,在 develop = 指定 src/zopenfoundry.policy,並在 eggs = 和 zcml = 指定 zopenfoundry.policy,執行 bin/buildout 指令,通知系統已建立了新模組。要留意的是,試過把 package name 寫在 base.cfg 的結果,並不會讓系統找到新模組。
想要測試模組是否能被系統存取,可用 zopepy 工具來檢查:
$ bin/zopepy

>>> from zopenfoundry import policy
>>>
沒有發現 ImportError 的話,就是成功了。
接著,要在 profiles 目錄裡建立設定檔,如果 profiles 目錄還未被建立,可以手動完成:
$ cd src/zopenfoundry.policy/zopenfoundry/policy
$ mkdir profiles
$ mkdir profiles/default
在 profiles/default 目錄裡建立 properties.xml 檔案,內容如下:
<?xml version="1.0"?>
<site>
<property name="title">ZOpenFoundry Site Policy</property>
<property name="description">Welcome to ZOpenFoundry Site</property>
</site>
至此,就可以重啟系統,到 Plone Site Setup 把模組啟用。

2010/10/20

Permission to Search Member

預設在 Plone 的 Users 資料夾裡,未登入的使用者無法看到會員資訊,而被通知 You are not allowed to list portal members. 訊息,這是因為 Anonymous User 沒有 List portal members 的權限,處理方式之一,是到 ZMI 的 Security tab 調整。
http://lh3.ggpht.com/_BESgcgeL9eA/TL65nS0_HXI/AAAAAAAACak/TyQ9KImyqt4/s800/ListPortalMember.png
如果 Plone Site Setup 裡有選項,讓管理員能夠設定的話,會更理想。關於權限的基本說明,可參考 Basic Roles and Permissions in Plone

2010/10/07

EEA Faceted Navigation

Faceted Navigation (FacetedNav) 是 Plone 顯示搜尋結果的新方式,以搜尋結果為中心,使用者可以在週圍選擇查詢條件,結合 jQuery 後,它能夠更快速地回應搜尋結果。

完成測試的環境是 Plone 3.3.5,有人貢獻 Plone 4 patch,現在也有 collective repository for Plone 4

可以結合 Syndication (XML) 和 Cache (memcache) 提供進階應用。開發人員來自 EEA (European Environment Agency) 是歐盟提供環保政策資訊的單位,已有生產許多 Plone 模組