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 的設定值。