2011/12/29

KeyError: 'constrainTypesMode'

Here is an excerpt of error I run into:

URL: plone/app/layout/viewlets/contentactions.pt
Line 32, Column 8
Expression: <StringExpr u'plone.contentmenu'>
...
URL: plone/app/contentmenu/contentmenu.pt
Line 1, Column 0
Expression: <PathExpr standard:u'view/menu'>
...
KeyError: 'constrainTypesMode'

It turns out from my typo in content/mytype.py, by the time when I hesitate whether if using a folderish type:

-ArticleSchema = folder.ATContentTypeSchema.copy()
+ArticleSchema = folder.ATFolderSchema.copy()

Files to Edit for Adding Archetypes Type

除了用 zopeskel 來建立 Content Type 的方法,手動編輯檔案的話,下列是相關檔案的順序列表:

interfaces/__init__.py
interfaces/mytype.py
content/configure.zcml
content/mytype.py
profiles/default/factorytool.xml
profiles/default/types.xml
profiles/default/types/mytype.xml
config.py
browser/configure.zcml
browser/mytype.py
browser/templates/mytype.pt
locales/

2011/12/27

typesUseViewActionInListings in Plone PropertiesTool

In my archetypes-based project, I create a folderish type Book to contain Chapter type. One of the last steps is to add Chapter type in typesUseViewActionInListings field.

For GenericSetup, it is in profiles/default/propertiestool.xml:

<property name="typesUseViewActionInListings" type="lines">
 <element value="Image"/>
 <element value="File"/>
 <element value="Chapter"/>
</property>

Note that if Book added to that field, it will be bothering when you browse Book items in Contents tab.

2011/12/19

Collective TinyMCE Templates

collective.tinymcetemplates is a TinyMCE Plugin for templates and snippets. It works with Plone 4.1.3. You can see a "Insert predefined template content" icon added:

Transmogrifier Export in Action

transmogrifier 可以匯入或匯出 Plone 網站的內容,之前的經驗以匯入 CSV 或 PostgreSQL 資料為主,現在完成匯出到 PostgreSQL 的實作。

以 develop.cfg 為例,要修改的內容如下:

eggs +=
    psycopg2
    SQLAlchemy == 0.6.5
    zope.sqlalchemy
    plone.app.transmogrifier

客製 myproj.transmogrifier 的內容摘要如下:

myproj.transmogrifier/configure.zcml

  <transmogrifier:registerConfig
    name="myproj.transmogrifier.exportContent"
    title="MyProject Export Content"
    description="Transmogrifier Pipeline config to export contents."
    configuration="confs/export.cfg"
    />

  <!-- register our blueprints -->
  <utility
    component=".contentexport.ContentExporterSection"
    name="myproj.transmogrifier.contentexporter"
    />

myproj.transmogrifier/browser/configure.zcml

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    xmlns:zcml="http://namespaces.zope.org/zcml">

    <browser:page
        for="*"
        class=".contentexport.ContentExport"
        name="content-export"
        permission="cmf.ManagePortal"
        />
<configure>

myproj.transmogrifier/browser/contentexport.py

class ContentExport(BrowserView):

    def __call__(self):
        transmogrifier = ITransmogrifier(self.context)
        transmogrifier('myproj.transmogrifier.exportContent')
        self.request.response.write('exported')

myproj.transmogrifier/confs/export.cfg

[transmogrifier]
pipeline =
    contentexport

[contentexport]
blueprint = myproj.transmogrifier.contentexporter
dsn = postgresql://myuser:mysecret@localhost:5432/mydb
portal-types = News Item, Event
review-states = published

另外,還有 myproj.transmogrifier/models.py 和 myproj.transmogrifier/contentexport.py 兩個主檔,分別負責資料庫連線和匯出工作。

2011/12/18

Search Portlet for Current Section

By default, Plone provides a searchbox viewlet and a search portlet. With the viewlet, you can decide whether to search only within the current section. However, the porlet does not apply this behavior. My requirement could be illustrated by this screenshot:

Here are the files to be modified:

$ diff plone/app/portlets/portlets/search.py
55a56,61
>     def folder_path(self):
>         context_state = getMultiAdapter((self.context, self.request),
>                                          name=u'plone_context_state')
>         folder = context_state.folder()
>         return '/'.join(folder.getPhysicalPath())
>

$ diff plone/app/portlets/portlets/search.pt
29a30,44
>
>         <div class="searchSection">
>             <input id="searchbox_currentfolder_only"
>                    class="noborder"
>                    type="hidden"
>                    name="path"
>                    tal:attributes="value view/folder_path"
>                    />
>             <label for="searchbox_currentfolder_only"
>                    i18n:translate="label_searchbox_currentfolder_only"
>                    style="cursor: pointer">
>                 only in current section
>             </label>
>         </div>
>

2011/12/16

Table View in Contents Tab

Here is the default view for Plone.

I want Published column instead of Modified column. By 'grep -r listing-table' I find the target file is at plone.app.content/browser/table.pt. Related python files are batching.py, container.py and browser/tavleview.py. My naive guess of changing keyword modified to effective, does not work, showing LocationError.

Display Logos Based on Paths

Here is a customization in plone.app.layout/viewlets/common.py that can display customized logos based on tab paths.

class LogoViewlet(ViewletBase):
    index = ViewPageTemplateFile('logo.pt')

    def update(self):
        super(LogoViewlet, self).update()

        portal = self.portal_state.portal()
        bprops = portal.restrictedTraverse('base_properties', None)
        if bprops is not None:
            logoName = bprops.logoName
        else:
            logoName = 'logo.jpg'
        plone_url = getToolByName(self.context, 'portal_url')()
        plone_url_len = len(plone_url)
        request = self.request
        url = request['URL']
        path = url[plone_url_len:]
        if path.startswith('/news'):
            logoName = 'logo-news.png'
        if path.startswith('/events'):
            logoName = 'logo-events.png'

To illustrate how the path variable works, add a Python Script in ZMI and use the following sample code:

from Products.CMFCore.utils import getToolByName

plone_url = getToolByName(context, 'portal_url')()
print "plone_url = %s\n" % (plone_url),
plone_url_len = len(plone_url)
request = context.REQUEST
url = request['URL']
path = url[plone_url_len:]
print "url = %s, path = %s" % (url, path)
return printed

2011/12/15

portal_tab globalnav customization

portal_tab, or globalnav, or global_section in Plone, provides clickable links. Let's say, we want a tab to open in new browser window when clicked. The trick is in plone.app.layout/viewlets/sections.pt.

<ul id="portal-globalnav"
    tal:define="selected_tab python:view.selected_portal_tab"
    ><tal:tabs tal:repeat="tab portal_tabs"
    ><li tal:define="tid tab/id;
                     turl tab/url"
         tal:attributes="id string:portaltab-${tid};
                        class python:selected_tab==tid and 'selected' or 'plain'"
        ><a href=""
           tal:content="tab/name"
           tal:attributes="href tab/url;
                           title tab/description|nothing;
                           target python: (turl.endswith('/my-url') and '_blank' or '')">
        Tab Name
        </a></li></tal:tabs></ul>

2011/11/30

Workteam with oDesk

感謝 pofeng 的安排,有機會到 CloudTW 聚會分享 oDesk 的使用經驗。

簡報檔: http://www.slideshare.net/marr/workteam-with-odesk

討論過程中,有人回應「這是個讓 developer 找 developer 的服務」,沒錯,所以適合在技術人員聚會裡介紹 oDesk 服務,用它來累積專案管理的經驗,oDesk 試著促成一個良性循環,讓雇主和工程師都願意重視評價結果,彼此長期建立良好名聲。

自己的使用經驗,只以 employer 身份發包 Plone web development,值得找機會以 developer 角色登入,了解專業技能認證的流程。

2011/11/29

Joyent Cloud Computing

CloudTW 有段 SmartOS 的討論,於是查了相關資訊。Joyent 成立於 2004 年,業務內容與日俱進,目前已發展為 cloud computing software stack 提供者,據說公司接收了 OpenSolarisIllumos 的開發人員,併購 LayerBoom 的案例,也能跟台灣教改挫敗扯上關係,總之,這些都算是藉由 cloud computing 時代造英雄的例子。

網路上找到的既有資料,都顯示 Joyent 是家強調技術本位的公司,看了 Community Manager 也就是技術公關,心想,台灣何時會有這種角色?

2011/11/26

POSKeyError: 'No blob file'

執行 Plone migration 時,遇到 POSKeyError 錯誤,發現某個目錄裡的 File 內容出了問題,刪除這些 File 之後,就可以順利從 Plone 4.0.5 昇級到 4.1.2。

2011/11/23

Content Not Existing?

遇到 Plone instance 透過 http://mysite.com/news-events 之類的網址,出現「網頁並不存在」的訊息,透過 http://mysite.com/news-events/folder_contents 網址是可以看到內容,最後發現用 http://mysite.com/news-events/selectViewTemplate?templateId=folder_listing 之類的方式,就能解決問題。發生原因是之前選用了 lead image 擴充模組的顯示方式,當這個顯示方式不存在時,就會造成網頁無法正常顯示的問題。

2011/11/19

Content Type Extension

SchemaExtender 可以動態調整 Archetypes 型別定義值,下列是一個 extender.py 範例:
from zope.component import adapts
from zope.interface import implements

from archetypes.schemaextender.interfaces import ISchemaExtender
from archetypes.schemaextender.field import ExtensionField
from plone.app.blob.field import BlobField

from Products.Archetypes import atapi

from example.blobattype.interfaces import IExampleATType
from example.blobattype import blobattypeMessageFactory as _


class ExtensionBlobField(ExtensionField, BlobField):
    """ derivative of blobfield for extending schemas """


class ExampleATTypeExtender(object):
    adapts(IExampleATType)
    implements(ISchemaExtender)

    fields = [
        ExtensionBlobField('afile',
            widget=atapi.FileWidget(
                label=_(u"A file"),
                description=_(u"Some file"),
            ),
            required=True,
            validators=('isNonEmptyFile'),
        ),

        ExtensionBlobField('secondfile',
            widget=atapi.FileWidget(
                label=_(u"Some other file"),
                description=_(u"Some other file"),
            ),
            required=True,
            validators=('isNonEmptyFile'),
        ),
    ]

    def __init__(self, context):
        self.context = context

    def getFields(self):
        return self.fields

2011/11/17

ATBTreeFolder vs ATFolder

最近把一個 Plone 網站從 3.3.5 昇級到 4.0.7,在 ZMI 裡看得到 ATBTreeFolder 和 ATFolder 兩種目錄型別,ATBTreeFolder 就是所謂的 Large Folder,在 Plone 3 之前的時代,用它來儲存大量的內容項目,到 Plone 4 之後,目錄型別被統合了

ATBTreeFolder 在 ZMI 裡提供 Security 設定頁籤,可以指定 Local Roles,不過 Plone 4 裡的 ATFolder 並沒有這樣的設定介面,必須搭配 plone.app.workflow 編輯 sharing.xml 來擴充權限設定介面

2011/11/08

CMF Action Condition

使用 Products.Carousel 時,會新增一個 Carousel tab,如果想在特定的目錄裡才出現它,可以在 ZMI portal_actions/object/carousel 的 Condition (Expression) 欄位輸入下列表示式:

python: (context.id in ['carousel']) and plone_context_state.is_folderish()

2011/11/06

Localization with i18ndude

以 develop.cfg 為例,先在 parts 裡加一行 i18ndude,再加一整段的 [i18ndude] 內容:

parts =
    ...
    i18ndude

[i18ndude]
recipe = zc.recipe.egg
eggs = i18ndude

執行 bin/buildout -c develop.cfg 會在 bin 目錄裡建立 i18ndude 工具程式。

以 my.example 模組專案為例,處理 .po 檔案的步驟如下:

$ cd src/my.example/my/example
$ vi configure.zcml
<configure
  xmlns:i18n="http://namespaces.zope.org/i18n"

  <i18n:registerTranslations directory="locales" />

$ mkdir -p locales/zh_TW/LC_MESSAGES
$ i18ndude rebuild-pot --pot locales/my.example.pot --create my.example .
$ touch locales/zh_TW/LC_MESSAGES/my.example.po
$ i18ndude sync --pot locales/my.example.pot locales/zh_TW/LC_MESSAGES/my.example.po
$ vi locales/zh_TW/LC_MESSAGES/myexample.po # do the translation work
$ msgfmt -o locales/zh_TW/LC_MESSAGES/my.example.mo locales/zh_TW/LC_MESSAGES/my.example.po

如果有手動增加的 po 內容,通常是寫在 manual.pot 裡,再用下列指令併進 pot 檔案裡。

$ i18ndude rebuild-pot --pot locales/my.example.pot --create my.example --merge manual.pot .

2011/10/20

Creating Windows Buildout Project

試著在 Windows 建立 Plone 4.1.2 的 Buildout 專案環境,事先要安裝 Python 2.6.7、PyWin32PIL 1.1.6easy_installSubversionGit。再下載安裝 zopeskel:

C:\> easy_install zopeskel

接著,可以用 zopeskel 來建立 Buildout 專案,範例指令如下:

C:\> mkdir Plone
C:\> cd Plone
C:\Plone> zopeskel basic_buildout

basic_buildout: A basic buildout skeleton
This creates a basic skeleton for a buildout.
Enter project name: myproj

If at any point, you need additional help for a question, you can enter
'?' and press RETURN.

Expert Mode? (What question mode would you like? (easy/expert/all)?) ['easy']:

如果遇到下列訊息,可以予以略過不理會:

UserWarning:
You don't have the C version of NameMapper installed!
I'm disabling Cheetah's useStackFrames option as it is painfully slow
with the Python version of NameMapper.
You should get a copy of Cheetah with the compiled C version of NameMapper.
You don't have the C version of NameMapper installed!

剛建立的 myproj 專案目錄裡,只有 bootstrap.py 和 buildout.cfg 兩個檔案,執行 python bootstrap.py 之後,會建立必要的目錄架構,特別是 bin 目錄裡會建立 buildout 工具程式,額外的撇步是指定 zc.buildout 版本是 1.4.4,不然預設裝的 1.5.2 版本會造成衝突,範例訊息如下:

C:\Plone\myproj> python bootstrap.py -v 1.4.4
Downloading http://pypi.python.org/packages/2.6/s/setuptools/setup
tools-0.6c11-py2.6.egg
Creating directory 'C:\\Plone\\myproj\\bin'.
Creating directory 'C:\\Plone\\myproj\\parts'.
Creating directory 'C:\\Plone\\myproj\\eggs'.
Creating directory 'C:\\Plone\\myproj\\develop-eggs'.
Getting distribution for 'setuptools'.
Got setuptools 0.6c12dev-r88846.
Generated script 'C:\\Plone\\myproj\\bin\\buildout'.

預設的 buildout.cfg 檔案內容如下:

[buildout]
parts =
    templer

[templer]
recipe = zc.recipe.egg
eggs =
    PasteScript
    templer.core

不過,我並沒使用這個 buildout.cfg 內容,而是直接複製 Linux 環境上安裝的 *.cfg 檔案,修改 base.cfg 和 lxml_static.cfg 的 eggs-directory 和 download-cache 配合 Windows 環境的目錄,再執行 buildout:

C:\Plone\myproj> bin\buildout -c develop.cfg
mr.developer: Creating missing sources dir C:\Plone\myproj\src.
Getting distribution for 'zc.recipe.egg==1.2.2'.
Got zc.recipe.egg 1.2.2.
Getting distribution for 'plone.recipe.zope2instance==4.1.9'.
Got plone.recipe.zope2instance 4.1.9.
Getting distribution for 'Zope2==2.13.10'.
Installing Zope2 2.13.10
Caused installation of a distribution:
zope2 2.13.10
with a different project name.
Got zope2 2.13.10.
Getting distribution for 'mailinglogger==3.3.3'.
Got mailinglogger 3.3.3.
Getting distribution for 'nt-svcutils==2.13.0'.
Got nt-svcutils 2.13.0.
Getting distribution for 'Products.StandardCacheManagers==2.13.0'.
Installing Products.StandardCacheManagers 2.13.0
Caused installation of a distribution:
products.standardcachemanagers 2.13.0
with a different project name.
Got products.standardcachemanagers 2.13.0.
Getting distribution for 'Products.PythonScripts==2.13.0'.
...

安裝 Pillow 時,需要安裝 Visual Studio 2008 Express,據說 2008 之外的版本並不行:

error: Setup script exited with error: Unable to find vcvarsall.bat
An error occured when trying to install Pillow 1.7.2. Look above this message fo
r any errors that were output by easy_install.
While:
  Installing instance.
  Getting distribution for 'Pillow==1.7.2'.
Error: Couldn't install: Pillow 1.7.2

編譯 lxml 如果遇到錯誤,要補足需要的檔案,以 Plone 4.1.3 為例,要下載 lxml 2.3.2 的原始碼。如果沒成功的話,還可以試試直接安裝較低的 lxml 版本

2011/10/17

Search Events and News via Calendar Portlet

Plone 的月曆方框預設是搭配 Event 來顯示,如果某一天是在 Event 發生的期間,就可以在月曆上點選來顯示,它會先利用類似 search?review_state=published&start.query:record:list:date... 網址格式來搜尋,如果想要增加其他內容型別到月曆方框,例如新聞的話,就要到 ZMI 的 portal_calendar 新增型別,並為型別新增 start 和 end 屬性值。

2011/10/15

RelStorage Installation

RelStorage 是 ZODB 的後端儲存工具,能夠將 pickle 資料存到關聯式資料庫,也就是說,經由 RelStorage 和 ZODB 的合作,Plone 裡的內容可以直接轉存到 MySQL、PostgreSQL 之類的資料庫裡,讓資料庫的類型選擇和轉換更具彈性。

經過原作者 Shane Hathaway 不斷的改進,安裝 RelStorage 變得很簡單,以 Ubuntu 和 PostgreSQL 為搭配範例,先要建立適當的帳號和資料庫:

$ sudo su - postgres
$ createuser --pwprompt zodbuser
$ createdb -O zodbuser zodb

編輯 /etc/postgresql/8.4/main/pg_hba.conf 存取控制規則,範例如下:

local  zodb  zodbuser  md5

接著可以修改 develop.cfg 的 eggs 設定區段:

eggs +=
    psycopg2
    RelStorage

修改 base.cfg 的 instance 設定區段,其中 blob-dir 參數指定一個目錄名稱,就是用來儲存 ZODB 的 BLOB 檔案:

rel-storage =
    type postgresql
    dbname zodb
    host localhost
    user zodbuser
    password mypass
    blob-dir var/blobs

執行 buildout 生效並啟動系統後,可以查看到資料庫的表單內容:

public | blob_chunk        | table    | zodbuser
public | commit_lock       | table    | zodbuser
public | current_object    | table    | zodbuser
public | object_ref        | table    | zodbuser
public | object_refs_added | table    | zodbuser
public | object_state      | table    | zodbuser
public | pack_object       | table    | zodbuser
public | pack_state        | table    | zodbuser
public | pack_state_tid    | table    | zodbuser
public | transaction       | table    | zodbuser
public | zoid_seq          | sequence | zodbuser

object_state 表單用來記錄 Page、Folder 等的內容,它的欄位包括:

zoid, tid, prev_tid, md5, state_size, state
其中 state 欄位記錄的就是內容項目的資料值。

2011/10/14

EEA Faceted Navigation Installation

EEA Faceted Navigation 已經正式支援 Plone 4.x 版本,測試成功的安裝方式是,先到 zinstance/src 目錄下載程式碼:

$ cd ~/Plone/zinstance/src
$ svn co http://svn.plone.org/svn/collective/eea.facetednavigation/trunk eea.facetednavigation
$ svn co http://svn.plone.org/svn/collective/eea.jquery/trunk eea.jquery
$ svn co http://svn.plone.org/svn/collective/eea.faceted.vocabularies/trunk eea.faceted.vocabularies

編輯 develop.cfg 內容,下列是範例:

[sources]
    eea.facetednavigation = fs eea.facetednavigation
    eea.jquery = fs eea.jquery
    eea.faceted.vocabularies = fs eea.faceted.vocabularies

[buildout]
eggs +=
    eea.facetednavigation

zcml +=
    eea.facetednavigation-meta
    eea.facetednavigation-overrides
    eea.facetednavigation

執行 buildout 生效:

$ cd ~/Plone/zinstance
$ bin/buildout -c develop.cfg

啟用模組後,在目錄的 Action 下拉選單,可以看到 Enable faceted navigation 選項,生效後可以再到 Faceted settings 和 Faceted criteria 設定細項。

2011/10/11

RichDocument SimpleAttachment Title

RichDocument 是個 content type 模組,它跟 Page 功能很像,但編輯介面提供特定欄位來上傳圖檔和檔案,這些特定欄位又搭配 SimpleAttachment 模組,讓上傳檔案的工作變得更容易擴充維護,SimpleAttachment 也是 Ploneboard 的相依模組

以前 Plone 3.x 時代,就試用過 RichDocument 和 SimpleAttachment,當時覺得成熟度不足,現在新版支援 Plone 4.x 的 blob 功能,再次試用,仍然遇到無法處理附檔中文標題的問題。

不過,它的設計概念和實作方式,是學習開發技巧時的參考,下列是幾個程式碼片段和錯誤訊息的記錄:

BooleanField('displayImages',
    default=False,
    languageIndependent=0,
    widget=ImagesManagerWidget(
        label="Display images download box"
    ),
),

BooleanField('displayAttachments',
    default=True,
    languageIndependent=0,
    widget=AttachmentsManagerWidget(
        label="Display attachments download box"
    ),
),
<tal:main-macro metal:define-macro="main"
 tal:define="text context/getText;
             images python:context.getFolderContents(
                        contentFilter = {'portal_type' : ['ImageAttachment']});
             firstImage python:images and images[0] or None;
             is_editable context/@@plone_context_state/is_editable;">
<div metal:use-macro="python:context.widget('displayImages')"/>
<div metal:use-macro="python:context.widget('displayAttachments')"/>
ERROR Zope.SiteErrorLog 1317974487.770.594350019308
 http://localhost:8080/mysite/Members/marr/my-page/richdocument_view_float
Traceback (innermost last):
  Module ZPublisher.Publish, line 126, in publish
  Module ZPublisher.mapply, line 77, in mapply
  Module ZPublisher.Publish, line 46, in call_object
  Module Shared.DC.Scripts.Bindings, line 322, in __call__
  Module Shared.DC.Scripts.Bindings, line 359, in _bindAndExec
  Module Products.CMFCore.FSPageTemplate, line 240, in _exec
  Module Products.CMFCore.FSPageTemplate, line 180, in pt_render
  Module Products.PageTemplates.PageTemplate, line 79, in pt_render
  Module zope.pagetemplate.pagetemplate, line 113, in pt_render
  Module zope.tal.talinterpreter, line 271, in __call__
  Module zope.tal.talinterpreter, line 343, in interpret
  Module zope.tal.talinterpreter, line 888, in do_useMacro
...
  Module zope.tal.talinterpreter, line 343, in interpret
  Module zope.tal.talinterpreter, line 531, in do_optTag_tal
  Module zope.tal.talinterpreter, line 513, in no_tag
  Module zope.tal.talinterpreter, line 343, in interpret
  Module zope.tal.talinterpreter, line 742, in do_insertStructure_tal
  Module Products.PageTemplates.Expressions, line 219, in evaluateStructure
  Module Products.PageTemplates.Expressions, line 264, in _handleText
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe8 in position 94:
 ordinal not in range(128)

2011/10/05

Link Integrity Checks

Plone Site Setup 裡的 Editing 設定介面,勾選 Enable link integrity checks 後,可以啟動連結檢查功能,也就是說,某個項目被刪除或搬移時,如果發現有其他項目連結到它,系統會提醒無法刪除或搬移,並在 var/log/instance.log 裡,記錄 TypeError: ('Could not adapt', None, <InterfaceCalss plone.uuid.interfaces.IUUID>) 的錯誤訊息。只要停用連結檢查功能,完成動作後再恢復即可。

另一種處理方式,是在 ZMI 裡,檢查項目的 Interfaces 內容,確認 IUUID 被選用,應該就可以在 ZMI 裡把它刪除。

UID (Unique identifier) 是系統唯一的識別碼,即使內容項目被改名或搬移,它的 UID 仍然維持一致。像 Archetypes、ReferenceField、Kupu 都有用到 UID 服務,不過,Dexterity 使用 integer id 來管理關連,並未使用 UID。

2011/10/04

PIL vs Pillow

安裝 Plone 4.x 的時候,通常會看到 Pillow 的身影,它是 PIL (Python Image Library) 的分支,因為 Plone 社群需要一份相容 setuptools 的版本,於是開始維護這個分支,考量點在於簡化安裝流程。

2011/09/30

Page Template Tip on Creators

Plone Archetype content type 可以存取 Dublin Core Metadata 內容,例如作者、著作權、發佈日期等資訊,從 Products/Archetypes/ExtensibleMetadata.py 可以看到作者欄位識別碼是 LinesField: creators,還有 Creator 和 listCreators 兩個函式,想要存取作者資訊的話,分成 Creator 和 Creators 兩個方式:例如 <tal:content="context/Creator"> 會顯示第一筆資料,<tal:content="context/Creator"> 則會以多筆資料方式顯示。常見的範例是:

Authos:
<metal:field use-macro="python:context.widget('creators', mode='view')">
John Smith, et al.</metal:field>

2011/09/29

Plone Theme Editor with Diazo

Plone 佈景主題的開發方式,自從 DeliveranceDiazo 問世之後,邁入新的里程碑,加上 Plone 4.1 開始搭配 plone.app.theming 模組,建立 Diazo 工作環境的門檻大幅降低。

覺得編輯規則檔 (rules.xml) 太麻煩嗎? 現在有個視覺編輯工具正在開發,預計在 Plone 4.3 之後納入,這段影片讓你先睹一快: http://www.screenr.com/b1Rs

2011/09/27

OFS.Uninstalled Could not import class

嘗試移除 Plone 模組時,經常會遇到這個警告訊息,這代表模組並未完全移除乾淨,至少我遇過 Add-on Configuration 還留有設定項目,或是 ZMI 根目錄裡看得到 broken object 的情況。

broken object 應該可以直接刪除,Add-on Configuration 的項目要到 ZMI portal_controlpanel 裡刪除。這裡有個 DropDownMenu 的範例畫面。

如果症狀比上述情況嚴重,就要參考 Removing a persistent local utility 的除錯步驟。

2011/09/23

Moving collective.geo.kml Viewlet

collective.geo.* 是一組提供 GIS 服務的 Plone 模組,它的地圖顯示區塊,預設是在 description 欄位下方,也就是在 <div id="viewlet-above-content-body"> 的下方,想要把它移到 <div id="viewlet-below-content"> 旁邊。 首先,用 firebug 得知顯示區塊被包在 <div id="kml-content-viewlet"> 裡,那麼它是由哪個檔案提供的呢?

$ grep -r kml-content-viewlet buildout-cache/eggs

collective/geo/kml/browser/kmlcontentviewlet.pt 檔案有我們要找的 div,照慣例,先到 collective/geo/kml/browser/configure.zcml 找線索:

  <browser:viewlet
      name="collective.geo.kml.kmlcontentviewlet"
      for="collective.geo.geographer.interfaces.IGeoreferenceable"
      class=".viewlets.ContentViewlet"
      manager="plone.app.layout.viewlets.interfaces.IAboveContentBody"
      template="kmlcontentviewlet.pt"
      layer="..interfaces.IGeoKmlLayer"
      permission="zope2.View"
  />

看來把 viewlet 的 manager 由 IAboveContentBody 改到 IBelowContent 就行。

2011/09/22

Transmogrifier in Action

最近用 collective.transmogrifier 將資料匯入 Plone 4.0.7,同時支援 CSV 和 PostgreSQL 兩種來源格式。

首先,用 paster 建立一個 my.importer 的專案,其中的 configure.zcml 內容範例如下:
<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:five="http://namespaces.zope.org/five"
    xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
    xmlns:transmogrifier="http://namespaces.plone.org/transmogrifier">

  <includeDependencies package="." />

  <genericsetup:registerProfile
      name="default"
      title="my.importer"
      directory="profiles/default"
      description="To set context for transmogrifier profiles."
      provides="Products.GenericSetup.interfaces.EXTENSION"
      />

  <include package="collective.transmogrifier" />
  <include package="collective.transmogrifier" file="meta.zcml" />
  <transmogrifier:registerConfig
    name="my.importer.importMyNews"
    title="My Importer for MyNews"
    description="Transmogrifier Pipeline config to import contents."
    configuration="confs/import_mynews.cfg"
    />

</configure>
在 confs 目錄裡,我們編輯 import_mynews.cfg 設定檔,範例內容如下:
[transmogrifier]
pipeline =
#    csvsource
    sqlsource
    type-inserter
    path-inserter
    folders
    constructor
    schemaupdater
    state-inserter
    workflowupdater
    reindexobject

#[csvsource]
#blueprint = collective.transmogrifier.sections.csvsource
#filename = my.importer:data/mynews.csv

[sqlsource]
blueprint = transmogrify.sqlalchemy
dsn = postgresql://username:secret@localhost:5432/dbname
query = SELECT id, title, description FROM my_table

[type-inserter]
blueprint = collective.transmogrifier.sections.inserter
key = string:_type
value = string:News Item

[path-inserter]
blueprint = collective.transmogrifier.sections.inserter
key = string:_path
value = string:/myfolder/${item/id}

[folders]
blueprint = collective.transmogrifier.sections.folders

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

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

[state-inserter]
blueprint = collective.transmogrifier.sections.inserter
key = string:_transitions
value = string:publish

[workflowupdater]
blueprint = plone.app.transmogrifier.workflowupdater

[reindexobject]
blueprint = plone.app.transmogrifier.reindexobject
來源是 SQL 資料時,使用的是 [sqlsource],來源是 CSV 資料時,就改用 csvsource,並在 data 目錄裡放好 mynews.csv 檔案,內容範例如下:
id,title,description
data1,My Data #1,Description of My Data #1
接著,編輯 my/importer/profiles/default/transmogrifier.txt 檔案,內容如下:
# this file contains transmogrifier profile names to run
# on GenericSetup transmogirifier's step
my.importer.importMyNews
以上就是 my.importer 專案的主要設定內容。最後,編輯 buildout.cfg 的內容如下:
eggs =
     SQLAlchemy == 0.6.5
    psycopg2
    collective.transmogrifier
    plone.app.transmogrifier
    transmogrify.sqlalchemy
   my.importer

zcml =
    collective.transmogrifier-meta
    collective.transmogrifier
    plone.app.transmogrifier
    transmogrify.sqlalchemy
上述 SQLAlchemy == 0.6.5 語法,代表要指定 (pin) 安裝 0.6.5 版本的 SQLAlchemy,不指定的話,可能會安裝太新的版本,未必合用,我的經驗是 SQLAlchemy 0.7.2 會遇到 ImportError: No module named exceptions 錯誤訊息。

一切搞定後,要讓上述設定值生效的話,可以直接到 ZMI 的 portal_setup,在 Import 頁籤裡,從 Select Profile or Snapshot 找尋 my.importer 選項,再找到 Run transmogrifier pipeline (第41項) 並勾選它,最後點選 Import selected steps 按鈕。

額外一提,PostgreSQL 裡的欄位資料如果更新為空值,執行 transmogrifier 後,也會將 Plone 表單欄位清空。

2011/09/21

argument 2 to map() must support iteration

使用 collective.transmogrifier 和 transmogrify.sqlalchemy 讀取 PostgreSQL 內容匯入 Plone 表單時,遇到 TypeError: argument 2 to map() must support iteration 錯誤訊息,原因是表單所需要的 id 是 string 型別,來自 SQL 的 gid 內容則是 number 型別,想法子傳支援 iteration 的型別就行了。

AttributeError: getLocallyAllowedTypes

import 一個 folder zexp 到 Plone 4.1 根目錄後,遇到 AttributeError: getLocallyAllowedTypes 錯誤訊息,查到 #11950 討論這個問題,並附有 patch 檔案,預計在 Plone 4.1.1 會修掉這問題。

2011/09/19

Document Byline

有人並不喜歡看到網頁的「作者」「最近修改」資訊,這些資訊被 Plone 稱為 Byline,想要不顯示 Byline 資訊,除了在 portal_skinsCSS 可以修改外,在 ZMI 的 portal_view_customizations 裡,也可以修改 plone.belowcontenttitle.documentbyline 來達到效果: 原則上就是從 <tal:creator /> 開始,拿掉這些 page template 內容。

2011/09/15

pkg_resources.DistributionNotFound

執行 paster addcontent 時,遇到 pkg_resources.DistributionNotFound 訊息,處理方式是在搜尋哪個 .cfg 檔案包含 [zopeske] 設定區段,在裡面加上 ${instance:eggs} 設定值,例如:

[zopeskel]
recipe = zc.recipe.egg
eggs =
   ...
   ${instance:eggs}

重新執行 buildout 後,就能正常使用。

2011/09/08

Macro Expansion Failed

在 ZMI portal_skins 裡,想要客製 newsitem_view 時,看到一個訊息:
Macro expansion failed
<type 'exceptions.AttributeError'>: widget
這只是個煩人的訊息,直接修改內容並存檔,還是能夠運作。如果去處理它的話,會造成 page template 變得太複雜,在 Ticket #7173 被歸為暫不處理的項目。

cmf.ManagePortal ComponentLookupError

在 Plone 4.1 遇到下列訊息:

File "/home/marr/Plone/buildout-cache/eggs/
AccessControl-2.13.4-py2.6-linux-i686.egg/AccessControl/security.py",
 line 165, in protectClass
  permission = getUtility(IPermission, name=permission_id)
File "/home/marr/Plone/buildout-cache/eggs/
zope.component-3.9.5-py2.6.egg/zope/component/_api.py",
 line 169, in getUtility
  raise ComponentLookupError(interface, name)
zope.configuration.config.ConfigurationExecutionError:
 <class 'zope.component.interfaces.componentlookuperror'="">:
 (<interfaceclass zope.security.interfaces.ipermission="">, 'cmf.ManagePortal')
in:
File "/home/marr/Plone/buildout-cache/eggs/
p4a.subtyper-1.2.0-py2.6.egg/p4a/subtyper/configure.zcml",
 line 49.2-55.8
  <browser:page
      name="subtyper"
      for="*"
      permission="cmf.ManagePortal"
      class=".browser.SubtyperView"
      allowed_interface=".browser.ISubtyperView"
      />

臨時的解法是修改 p4a/subtyper/configure.zcml 內容,加上:

<include package="Products.CMFCore" 
file="permissions.zcml" />

以上,應該是 add-on 昇級的議題。另外,類似的訊息還有 ComponentLookupError: (<InterfaceClass plone.keyring.interfaces.IKeyManager>, '') 的錯誤,在 ZMI portal_setup 重新載入所有的 plone.keyring KeyManager Registration 步驟就行。

2011/08/18

AddRemoveWidget and ComboBoxWidget

AddRemoveWidget permits to add self-entered data, which makes it a good replacement for KeywordWidget. Example code looks like:
from Products.AddRemoveWidget.AddRemoveWidget import AddRemoveWidget

...
atapi.LinesField(
'watershed',
storage=atapi.AnnotationStorage(),
widget=AddRemoveWidget(
label=_(u"Watershed"),
description=_(u"Enter one tag per line, multiple words allowed."),
),
vocabulary_factory='watershed',
),


AddRemoveWidget works with multiple-value fileds, if you need just single-value fields, try ComboBoxWidget instead. Example code looks like:
from Products.AddRemoveWidget.ComboBoxWidget import ComboBoxWidget

...

atapi.StringField(
'vote',
storage=atapi.AnnotationStorage(),
widget=ComboBoxWidget(
label=_(u"Best Band Ever"),
description=_(u"Single Choice."),
),
vocabulary=['The Beatles', 'The Smiths', 'Led Zeppelin', 'Joy Division'],
),


Look into the source code for more config options.

2011/08/17

Editing Plone Short Name

Short Name 可以譯成「識別碼」,它是構成 URL 的一部份,這個欄位在編輯時,預設是不顯示。如果網站包含整批具備流水編號的內容,例如想用 ISBN 當作識別碼,這類場合就適合在編輯時顯示識別碼欄位。
在 Plone Setup 的 Editing 設定項目,可以指定是否顯示 Short Name 欄位。

系統設定值勾選啟用後,還要確定使用者偏好設定,必須同步設定啟用才真的生效。

生效後的範例如下圖:

以 Archetype 的表單為例,Short Name 在程式碼內部使用 id 這個欄位名稱,剛新增時的網址會使用 book.2011-08-17.8631510722 之類的字串,如果 Short Name 欄位留空未填,那麼預設會使用 title-to-id 行為方式來更改 id 欄位值。

2011/08/09

Diazo Installation

在 Plone 裡安裝 Diazo 最簡單的方法,是搭配安裝 plone.app.theming,它需要 Plone 4.1 之後的環境,使用 4.1 版本的 Unified Installer 是最簡單的方法。
$ diff -Nu develop.cfg.orig develop.cfg

--- develop.cfg.orig 2011-08-09 22:21:25.495261571 +0800
+++ develop.cfg 2011-08-09 22:23:31.063243116 +0800
@@ -110,6 +110,7 @@
# we're extending buildout.cfg
extends =
buildout.cfg
+ http://good-py.appspot.com/release/plone.app.theming/1.0b8

extensions +=
mr.developer
@@ -117,6 +118,7 @@
eggs +=
Products.DocFinderTab
plone.reload
+ plone.app.theming

parts +=
test

$ bin/buildout -c develop.cfg

Uninstalling zopepy.
Uninstalling instance.
Installing _mr.developer.
Generated script '/home/marr/plone410/zinstance/bin/develop'.
Installing instance.
Getting distribution for 'plone.app.theming==1.0b8'.
Got plone.app.theming 1.0b8.
Getting distribution for 'repoze.xmliter==0.4'.
Got repoze.xmliter 0.4.
Getting distribution for 'plone.resource==1.0b5'.
Got plone.resource 1.0b5.
Getting distribution for 'plone.subrequest==1.6.1'.
Got plone.subrequest 1.6.1.
Getting distribution for 'diazo==1.0rc3'.
Got diazo 1.0rc3.
Getting distribution for 'experimental.cssselect==0.1'.
Got experimental.cssselect 0.1.
Generated script '/home/marr/plone410/zinstance/bin/instance'.
Installing zopepy.
Generated interpreter '/home/marr/plone410/zinstance/bin/zopepy'.
Updating zopeskel.
Installing omelette.
Updating backup.
Updating chown.
chown: Running echo Dummy references to force this to execute after referenced parts
echo /home/marr/plone410/zinstance/var/backups
chmod 600 .installed.cfg
find /home/marr/plone410/zinstance/var -type d -exec chmod 700 {} \;
chmod 744 /home/marr/plone410/zinstance/bin/*
Dummy references to force this to execute after referenced parts
/home/marr/plone410/zinstance/var/backups
Installing test.
Generated script '/home/marr/plone410/zinstance/bin/test'.
Updating repozo.
Updating unifiedinstaller.
*************** PICKED VERSIONS ****************
[versions]

*************** /PICKED VERSIONS ***************



2011/08/03

Development Eggs

When using paster to create "development eggs", there will install Paste, PasteScript, PasteDeploy eggs in Plone src directory. These eggs are for development purpose, will enable paster options like local commands. However, along with my.package.egg-info, they are not into version control repository.

2011/08/02

Vocabulary Import Tips

Run into this error:

ImportError: No module named schema.vocabulary

Fix it by changing the imports in vocabulary.py, from:

from zope.interface import implements
from zope.app.schema.vocabulary import IVocabularyFactory
from zope.schema.vocabulary import SimpleVocabulary

to:

from zope.interface import implements
from zope.schema.interfaces import IVocabularyFactory
from zope.schema.vocabulary import SimpleVocabulary

zope.app.schema 3.5.0 removes deprecated vocabulary directive.

2011/08/01

Transmogrifier Tips

SQLAlchemy: No module named exceptions

After running buildout, my Plone fails to start and show this message -- ImportError: No module named exceptions.
Adding a pin for SQLAlchemy to version 0.6.8 fixes the issue. For details see SQLAlchemy changelog

Named Transmogrifier Pipelines

In collective/transmogrifier/genericsetup.py, 'transmogrifier.txt' is assigned to be read. So place the file in profiles/default directory, pipeline identifiers are read one per line, for example 'mysite.migration.importFromCSV'. In ZMI portal_setup 'Import' tab, you can run the selected steps.

CSV Import with Transmogrifier

假設你的欄位資料,都放在 mydata.csv 檔案裡,想要批次匯入來建立 MyContentType 內容,下列是主要的步驟範例。

以自製模組 mysite.migration 為例,它的 configure.zcml 內容範例如下:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
xmlns:i18n="http://namespaces.zope.org/i18n"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
xmlns:transmogrifier="http://namespaces.plone.org/transmogrifier"
i18n_domain="mysite.migration">

<!-- Include zcml files of all required packages -->
<includeDependencies package="." />

<genericsetup:registerProfile
name="default"
title="mysite.migration"
directory="profiles/default"
description="MySite transmogrifier profile."
provides="Products.GenericSetup.interfaces.EXTENSION"
/>

<!-- Register Transmogrifier Profile -->
<include package="collective.transmogrifier" />
<include package="collective.transmogrifier" file="meta.zcml" />
<transmogrifier:registerConfig

name="mysite.migration.importFromCSV"
title="Import Content Data From CSV"
description="Transmogrifier config to import contents from CSV"
configuration="confs/import_from_csv.cfg"
/>

<!-- -*- extra stuff goes here -*- -->

</configure>

在 confs/import_from_csv.cfg 內容範例如下:
[transmogrifier]
pipeline =
csvsource
type-inserter
path-inserter
folders
constructor
schemaupdater
state-inserter
workflowupdater
reindexobject


[csvsource]
blueprint = collective.transmogrifier.sections.csvsource
filename = mysite.migration:data/mydata.csv

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

[path-inserter]
blueprint = collective.transmogrifier.sections.inserter
key = string:_path
value = string:/myfolder/${item/id}

[folders]
blueprint = collective.transmogrifier.sections.folders

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

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

[state-inserter]
blueprint = collective.transmogrifier.sections.inserter
key = string:_transitions
value = string:publish

[workflowupdater]
blueprint = plone.app.transmogrifier.workflowupdater

[reindexobject]
blueprint = plone.app.transmogrifier.reindexobject

profiles/default/transmogrifier.txt 的內容範例如下:
# this file contains transmogrifier profile names to run
# on GenericSetup transmogrifier's step
mysite.migration.importFromCSV

以上模組檔案內容就緒後,在 buildout.cfg 加上:
[buildout]
...
develop =
src/mysite.migration

[instance]
...
eggs =
mysite.migration

執行 bin/buildout 之後,進入 ZMI 的 portal_setup 設定畫面,從 "Select Profile Or Snapshot" 下拉選單,點選 mysite.migration 項目,再點選 "Run transmogrifier pipeline" 項目,最後點選 "Import selected steps" 按鈕。

2011/07/19

Bio by Pictures

第一台電腦是 PC XT 8088 的時代,當時是買 Acer 但找不到照片,只好用這張類似的畫面。全機黑色的 Acer XT 比這漂亮太多了。

中學時代的電腦,主要是遊戲機的功能,學 BASIC 的目的,也是想寫出自己的遊戲。

自己是搖滾迷,id nickname 是取自英國樂團吉他手的名字。

第一個網站,內容是介紹搖滾樂。

架站總是要學 Unix 知識,後來發現歷史故事也充滿趣味。

現在是 Python 愛好者一枚。

2011/07/03

New User Interface Preview

Plone 操作介面的發展規劃,經過 Plone UI Sprint 的努力工作後,終於有了 plone.app.cmsui 測試版實作,搭配 Plone 4.1 環境,讓我們可以體驗新介面的預覽版。

另一個改版重點,是導入新的 page layout 管理機制,底層是一個稱為 Block 的引擎,它將網頁分成結構式和動態的顯示元件,經過組合再呈現。短期內,Plone 則推出 plone.app.page 讓 Folderish Page 取代 Folder,許多測試正在演進中

2011/06/30

Change Archetypes CreationDate

建立 Plone 內容時,系統會自動指定建立時間,想要竄改這個時間,可以用 bin/plonectl debug 存取 Data.fs,以 app.mysite.news['my-news'] 為例,使用 created() 這個 accessor 可以查詢建立時間,使用 setCreationDate() 這個 mutator 可以指定建立時間,完成之後要記得 transaction.commit()。再進 ZMI 的 portal_catalog 重新執行 created 的索引動作。
在 Products/Archetypes/ExtensibleMetadata.py 可以看到程式碼細節。

2011/06/28

Plone BlobStorage and Image

下列是使用 Plone 4.0.7 實測 Blob 功能的記錄。

新增一個 Image,附的圖檔約 1.4MB,看到 var/blobstorage 裡出現一個 0x00/0x00/0x00/0x00/0x00/0x00/0x20/0x5b/0x038f4c3af3551d22.blob 檔案,大小 1486990,驗證圖檔放進 blobstorage 裡,值得注意的是,同時伴隨產生一個 0x00/0x00/0x00/0x00/0x00/0x00/0x20/0x66/0x038f4c3af6acde66.blob 檔案,大小 40615。

在 Plone 裡把上述圖檔刪除,原本 blobstorage 對應的檔案仍然存在,並未馬上被刪除,直到執行 pack 之後,blobstorage 裡面剩下 0x00/0x00/0x00/0x00/0x00/0x00/0x20 目錄,底下的檔案都被刪除。

接著,即使重複上述的圖檔新增動作,上傳同樣圖檔,blobstorage 會產生新的對應檔名,例如 0x00/0x00/0x00/0x00/0x00/0x00/0x20/0x6e/0x038f4c4d455447cc.blob (size 1486990) 和 0x00/0x00/0x00/0x00/0x00/0x00/0x20/0x75/0x038f4c4d482af9ee.blob (size 40615)。

新增一個 File,附的檔案指定上述的圖檔,blobstorage 則會產生 0x00/0x00/0x00/0x00/0x00/0x00/0x20/0x7d/0x038f4c7f2e73e400.blob (size 1486990),但是沒有產生大小為 40615 的伴隨檔案。

如果編輯舊的 File,上傳新的檔案,則會對應產生 0x00/0x00/0x00/0x00/0x00/0x00/0x20/0x88/0x038f4cd0ba71df00.blob (size 11080872) 新檔案,原本的 0x00/0x00/0x00/0x00/0x00/0x00/0x20/0x7d/0x038f4c7f2e73e400.blob (size 1486990) 仍然存在,直到執行 pack 之後,才會只剩 0x00/0x00/0x00/0x00/0x00/0x00/0x20/0x88/0x038f4cd0ba71df00.blob (size 11080872)。

另外,新增 News Item 時,預設有兩種方式來上傳圖檔。一種是從 Image 欄位上傳圖檔,這種方式並不會在 blobstorage 裡產生對應的檔案,相同目錄裡也看不到對應的 ATImage 檔案,暫不清楚跟 show_content_type 是否相關。另一種方式是從 RichWidget 欄位上傳圖檔,這種方式會在 News Item 相同的目錄裡,建立對應的 ATImage 檔案,同時也在 blobstorage 裡產生 0x00/0x00/0x00/0x00/0x00/0x00/0x20/0xd1/0x038f4cde1e238bee.blob (size 1486990) 和 0x00/0x00/0x00/0x00/0x00/0x00/0x20/0xd6/0x038f4cde20a1d711.blob (size 6285) 的對應檔案,效果等同先建立 ATImage 再於 RichWidget 裡指定連結。

額外一提的是,如果 RichWidget 欄位上傳的圖檔,目錄已存在相同檔名的檔案,則會自動更名為 copy_of_SomeImage.jpg 之類的識別碼。

使用 blobstorage 後,即使所有的 Image File 都清掉,在檔案系統裡還是會留下之前建立過的目錄:
var/blobstorage/
├── 0x00
│   └── 0x00
│   └── 0x00
│   └── 0x00
│   └── 0x00
│   └── 0x00
│   ├── 0x20
│   └── 0x21
└── tmp

2011/06/21

Add Custom Field to Criteria in ATTopic

以 Plone site 的 events/aggregator 為例,點選 Criteria 頁籤,可以編輯選集的條件,這個編輯頁面使用 Products/ATContentTypes/skins/ATContentTypes/criterion_edit_form.cpt 程式碼,摘要如下:

<div class="field" style="float:left">

<label for="field">Field name</label>

<div class="formHelp"
id="fieldHelpSort">
List Available Fields
</div>

<select name="field"
id="sortfield"
tal:define="fields context/listSortFields">

<option value="no_sort"
tal:attributes="selected python:not context.hasSortCriterion();"
>No sort order</option>

<tal:block tal:repeat="field fields">
<option value=""
tal:define="explanation python:field[2]"
tal:attributes="value python:field[0];
selected python:context.hasSortCriterion()
and context.getSortCriterion().field==field[0];
title explanation"
tal:content="python:field[1]"
i18n:attributes="title"
i18n:translate="">Field</option>
</tal:block>
</select>

</div>
其中的 listSortFields 定義在 content/topic.py 裡。
在條件裡新增自製欄位的話,最簡單的方式,是到 Site Setup 的 Collections 裡設定,例如點擊 All fields 後,再勾選 Contributors 選項,讓它成為啟用的項目。

2011/06/01

Migration In Action

看過昇級的背景資訊後,可以真的動手了。

Plone 3.3.5 昇級到 4.0.x,算是相對容易的情境,原則上,把 Plone 3.3.5 選用或自製的模組版本,盡量先更新到合適的號碼,這樣就可以準備一份夠好的 Data.fs。再次提醒,實際昇級前,一定要把 Data.fs 備份好,完成昇級步驟後,還要進行許多測試工作,途中遇到錯誤的話,還是可能要用到 Data.fs 備份檔。

Plone 3.3.5 和 4.0.x 都有 buildout 工具,利用 buildout 就可以把許多昇級工作搞定,昇級 buildout.cfg 最簡單的方式,是先用 Unified Installer 安裝系統,再把 egg 和其他設定值搬到 buildout.cfg 裡。

依據上述的原則,下列是實戰過程的記錄。

舉例來說,原本 Plone 3.3.5 搭配 Ploneboard 2.0,先昇到 Ploneboard 2.2 之後,就可以同時在 Plone3 和 Plone4 使用。不過,Ploneboard 搭配的 python-dateutil,要使用 1.5 版本,處理方式是在 [versions] 設定區段,指定為 python-dateutil = 1.5,如果安裝 python-dateutil 2.0 的話,會造成 Ploneboard 無法執行,處理方法是刪掉 python-dateutil 2.0 再裝 1.5 版本。

webcouturier.dropdownmenu 模組也有類似的情況,它的 2.0 版本是搭配 Plone 3.x,到 2.1 版本就同時支援 Plone 3 和 4。另外,我是昇級 Plone4 後才發現 webcouturier 有 2.1 版,事後再昇級 webcouturier 並沒問題。

設定 Plone 4.0.5 的 buildout.cfg 時,記得把 Products.PloneHotfix 加上去,執行 bin/buildout 之後,把舊系統的 Data.fs 放到 var/filestorage 目錄,執行 bin/plonectl debug 來啟動新系統,總之,要等完成昇級測試後,再改用 bin/plonectl start。

進 ZMI 的昇級畫面後,先勾選 dry run 執行模擬,接著有一系列的昇級動作,其中的 plone.app.upgrade 在 v40/betas.py 檔案裡,定義了 convertToBlobs() 函式,會把 File 和 Image 轉存為 Blob 檔案,這個過程會把舊檔刪除,再建立新檔,如果舊站已存在許多 File 和 Image 的話,這個步驟會花很長的時間。

手邊一個 9.6G Data.fs 建立 6.5G blobstorage,最後 Data.fs 剩下 278.9M,因此,執行昇級前最好留有 Data.fs 一倍以上的硬碟空間。

2011/05/22

The Python Paradox

This is a zh-tw translation of The Python Paradox by Paul Graham.

在某次演講中,我的話語惹毛不少人,那段話是這樣說的 -「使用 Python 的專案,會比 Java 的專案,容易吸引到更聰明的程式人員。」

我可沒說 Java 程式員是笨的,那句話的焦點在於 Python 程式員是聰明的。學習一個新的程式語言,需要花費不少工夫,因此,人們不會為了混口飯吃,而去學 Python,那些學 Python 的程式員,是基於對編程的熱愛,而且他們對已知的語言都感到不滿意。

這類的程式員,應該是公司想要雇用的。由於找不到好的形容詞,我姑且稱這種現象是 Python 的弔詭 - 如果公司的軟體專案使用密教式的語言來開發,他們會雇用到較好的程式員,因為這樣的條件會吸引那些願意學這類語言的好手。對程式人員而言,下列的說法,更是無庸置疑:如果你想要找到好工作,該學的語言,必然不是那些只能混口飯吃的語言。

迄今,只有很少的公司夠聰明,能明白這樣的道理,不過,還有另一種演化趨勢,來自於那些最受程式員喜愛的公司,例如 Google,當他們要找 Java 工程師時,也會要求同時具備 Python 經驗。

我有個朋友,熟知所有廣被使用的語言,他手邊的專案,多數就用 Python 開發,而選用 Python 的最大原因,是程式碼看起來舒服。比較程式語言的選用條件時,這樣的理由聽起來可能好笑,但搭配下列的情境,這句話就不像鬧著玩了:動手編程時,你會花很多時間在讀程式碼,而不是寫程式碼。編修程式碼的過程,就像雕塑師在作品上捏塑泥巴的過程,如果語言讓程式碼變醜,嚴謹的程式員看了可會抓狂,就像糊成一團的泥巴,會讓雕塑師無法忍受一樣。

提到醜的程式碼,人們很容易想到 Perl,不過,Perl 表面上的醜,或是生硬語法所帶來的醜,並不是我想講的,錯誤概念所造成的醜,才是真的讓人無法接受的事。有時候 Perl 程式碼看起來像卡通人物幹譙時的一串符號,但有些情況下,Perl 在概念上比 Python 來得優異。

話說回來,這兩個語言仍然各自演化中,和 Ruby (Icon, Joy, J, Lisp, Smalltalk) 一樣,投身它們的程式員,都是熱愛編程的族群,而且是更有機會搞好編程的一群。

2011/05/14

Related Items and ReferenceField

預設的 ATContentType 提供 relatedItems 欄位,可以用來建立或指定關連項目,想在程式裡指定的話,可以參考下列的資訊:

>>> app.mysite['front-page'].setRelatedItems(app.mysite.news)
>>> app.mysite['front-page'].getRelatedItems() [<ATFolder at /mysite/news>]

setRelatedItems() 每次只接一個參數,因此後來接的參數,原則上就是蓋掉之前的設定值。想要清空內容的話,就是使用 setRelatedItems(''),想要指定多值,或是加入新值的話,可用串列的技巧:

>>> app.mysite['front-page'].setRelatedItems
  ([app.mysite.news,app.mysite.events])

還有個 getRawRelatedItems() 可以傳回 UID:

>>> app.mysite['front-page'].getRawRelatedItems()
['f8a2a96821f6e3df8dafd19d16b68fb2', '04b2e1267906691beccdff58dd861176']

可以試試 getRelatedItems() + UID() 是否跟 getRawRelatedItems() 結果是否相同。另外,在plone.app.layout/viewlets/content.py 裡的 ContentRelatedItems 程式碼片段,示範如何存取及顯示:

class ContentRelatedItems(ViewletBase):
    index = ViewPageTemplateFile("document_relateditems.pt")

    def related_items(self):
        context = aq_inner(self.context)
        res = ()
        if base_hasattr(context, 'getRawRelatedItems'):
            catalog = getToolByName(context, 'portal_catalog')
            related = context.getRawRelatedItems()
            if not related:
                return ()
            brains = catalog(UID=related)
            res = list(brains)
        return res

2011/05/12

Values for Archetypes Field and Widget

StringField 搭配 StringWidget 或 SelectionWidget 是最簡化的情況,儲存的資料是 string,如果 StringField 搭配 MultiSelectionWidget 或 InAndOutWidget,儲存的資料就是 list。前者的空值是 '',後者的空值是 [''],使用 page template 顯示時,就要額外處理它們的差異。例如:
<div tal:condition="context/myfield">
My Field:
<span metal:use-macro="python:context.widget('myfield', mode='view')" />
</div>
當資料是空的 string 時,整個 <div /> 並不會顯示,當資料是空的 list 時,還是會出現 My Field: [''] 之類的畫面。
一個簡單的處理方式是:
<div tal:condition="python:context.myfield != ['']">
My Field:
<span metal:use-macro="python:context.widget('myfield', mode='view')" />
</div>

2011/05/10

Couldn't find index page for 'PasteScript'

在 Plone 4.0.2 正常運作一段時間後,因為修改 src 目錄裡的自製模組,需要重新執行 bin/buildout,遇到錯誤訊息:
Download error: (-2, 'Name or service not known') -- Some packages may not be found!
Couldn't find index page for 'PasteScript' (maybe misspelled?)

distutils.errors.DistutilsError:
Could not find suitable distribution for Requirement.parse('PasteScript')
While:
Installing.
Processing develop directory '/home/marr/mysite/zinstance/src/my.package'.
檢查 my.package/setup.py 裡面有一行 setup_requires=["PasteScript"], 設定值,註解掉暫時有排除問題,但仍不清楚原理。
另外在 Problems running Buildout to install a them created with Paster 有類似的討論,或許會有正確解答的線索。

2011/04/28

eea.jquery datepicker localization

eea.jquery 的 UI 模組,已經把 localization 需要檔案準備好,在 ui/development-bundle/ui/i18n 目錄裡找得到。想要將 datepicker 日曆裡的月份星期以中文顯示,在 Plone 裡至少要修改兩個檔案,首先是利用 jsregistry.xml 註冊需要的 javascript resource,接著在 ui/configure.zcml 裡註冊實際的檔案位置。



eea/jquery/profiles/ui/jsregistry.xml
  <javascript id="++resource++jquery.ui-1.7.js"
cacheable="True" compression="safe" cookable="True" enabled="True" inline="False"
expression="" position-after="jquery.js" />
+ <javascript id="++resource++ui.datepicker-zh-TW.js"
+ cacheable="True" compression="safe" cookable="True" enabled="True" inline="False"
+ expression="" position-after="jquery.js" />
</object>

eea/jquery/ui/configure.zcml

+ <browser:resource
+ name="ui.datepicker-zh-TW.js"
+ file="development-bundle/ui/i18n/ui.datepicker-zh-TW.js"
+ permission="zope.Public"
+ />
+
<browser:resource
name="jquery-ui-1.7.custom.css"
file="css/smoothness/jquery-ui-1.7.custom.css"
permission="zope.Public"

2011/04/24

Diazo Theming Tool

Diazo 能夠讀取設計師的網頁藍圖,搭配內容管理系統,即時產生網站的佈景主題,同時可以佈署數個網站,快速提供一致的操作介面。它是改善使用經驗的強大工具,整個過程不需要存取系統原有的程式碼。這項技術概念,早期由 Deliverance 和 XDV 提供實作,Diazo 著眼在簡化流程,讓佈署的門檻降得更低。

為了使用 Diazo,通常要進行下列的準備工作:
1. 找出佈景主題的檔案裡,哪些是動態顯示的部份,最好使用 HTML 的 id 識別碼,將它們標識出來。
2. 編寫規則設定檔的內容,使用 replace 或 copy 的語法,用來產生上述網頁裡的動態內容。
3. 找出佈景主題的檔案裡,哪些是共用的部份,指的通常是 <head /> 裡的 CSS 或 JavaScript 的程式碼,使用 append 或 prepend 的語法,將它們寫到規則設定檔裡。
4. 找出佈景主題的檔案裡,哪些部份是多餘的,在規則設定檔裡使用 drop 語法來刪除它們。

規則設定檔使用 XML 格式,可以利用 CSS3 或 XPath 的 selector,來指定負責顯示和內容的元件。

有了佈景主題的 HTML 檔案,還有規則設定的 XML 檔案,就可以利用 Diazo 的編譯器,把它們轉成一個 XSLT 檔案,接著佈署在網站應用程式裡,透過 Apache mod_transform 之類的 XSLT 處理器,就能動態產生搭配佈景主題的網站內容。

XSLT 的轉換動作很快,靜態的網頁資源,例如圖檔、CSS、JavaScript 等,也可以直接由靜態網站提供服務。

實用的情境之一,是使用一份佈景主題的 HTML 檔案,配合不同的網站應用程式,產生不同的 XSLT 檔案,再用網址比對的技巧,依照網頁請求來回應不同的 XSLT 檔案。

2011/04/22

GenericSetup Circular Dependency

使用 Plone 4.0.5 import 一個 theme 檔案時,遇到 GenericSetup There are unresolved or circular dependencies. 訊息,因為相依關係造成 import 無法正常完成,似乎只是 warning 而不是 error。之前已有討論,而且應該已經解決

2011/04/21

Blob Storage Restore

前幾天執行了 Plone 4.0 到 Plone 4.0.5 的昇級,記錄幾個心得。
主機在 vm 裡,新的 Plone instance 只能先從 localhost 存取,又由於想要整頓帳號,因此使用 w3m http://localhost:8080/ 先建立 Plone Site,再從前台逐步把帳號和資料恢復。
線上直接執行 cp -a ~/plone400/zinstance/var/blobstorage/0x00 ~/plone405/zinstance/var/blobstorage 可能不會正確回復資料,根據討論資訊,配合備份 Data.fs 的時間,也要同時備份 var/blobstorage 的資料。
以 File 為例,舊的檔案如果上傳新的來覆蓋,在 blobstorage 裡會存在兩筆資料,使用 pack 讓 ZODB 更新後,則會只留一筆。這代表 blobstorage 裡的關連,是動態維護的。
目前找到的文件資訊,建議使用 collective.recipe.backup,再找時間繼續測試。

2011/04/19

DateIndex Patch for DateError

在 Plone 4.0.2 ZMI portal_catalog/Indexes 裡,點選 DateIndex 後,想要 Browse 時,偶而會遇到 DateError: Invalid date: (1915, 0, 7, 4, 59, 0, 'UTC') 的錯誤,雖然前台還沒遇到問題,但這樣的狀況總叫人不安。

plone IRC 有人提到,這是 manage_browse 的臭虫,使用 browseIndex.dtml DateIndex 當關鍵字,找到 Browse DateIndex Broken for edge cases,將 Zope2 的 Products/PluginIndexes/dtml/browseIndex.dtml 加上 patch 之後,有暫時處理這個臭虫。不過,後來發現搭配 eea.jquery 的 datepicker 功能時,還是會有月份數字少1的問題,例如輸入 1999-01-01 會變成 1999-00-01,看來沒有一勞永逸。

2011/04/13

Custom Folder Listing Tips

常見的 Plone 目錄頁面,可以顯示標題、作者、日期、摘要描述,不過,從 /mysite/@@security-controlpanel 進入 Security settings 設定畫面,有個「Allow anyone to view 'about' information」選項,可以決定一般訪客是否看得到作者和日期資訊。

完整資訊都呈現的效果如下:

希望登入後,還是不顯示作者、日期嗎? 這裡有幾個暴力修改法。
我先用 firebug 查看 HTML 的部份內容,找到「<span class="documentByLine">」的關鍵字,再到 Plone/buildout-cache/eggs 目錄,用 grep -r documentByLine | grep '.pt' 指令,找到 Products/CMFPlone/skins/plone_content/folder_listing.pt 檔案。

<tal:byline condition="show_about">
show_about 是控制的變數,我們只要調整 condition 條件值,就可以改變它顯示的行為。

<tal:byline condition="python: show_about and (context.getId() == 'events')">
在手邊的例子裡,打算讓它在 events 裡才顯示。
使用 context.id 很可能也行,另外 context.getId() in ('events', 'news') 則可以指定數個目錄值。

如果自製的 content type 裡,有個 my_field 欄位,在 的迴圈段落,加上 item_myfield item/getMy_field;,就可以用 <span tal:content="item_myfield"> 來顯示。

不過,我遇到的例子 my_field 是個 list,顯示結果是 ['\xe6\xb1\x89\xe6\x97\x8f'],如果只是想要顯示 list 裡的第一個值,最簡化的處理方式是使用 context.my_field[0]。

2011/02/02

Archetypes SelectionWidget

建立 Archetypes 表單時,可用的 widget 很多種,這裡特別整理跟選項有關的設定方式。對於「單選」的欄位,使用 SelectionWidget 就行,頂多只需要指定 format 設定值。下列範例提示了值得留意之處:
from Products.Archetypes.Field import StringField
from Products.Archetypes.Widget import SelectionWidget
...
StringField(
name='myfield',
vocabulary='_get_selection_vocab',
widget=SelectionWidget(
label_msgid='label_myfield',
format='select',
),
),
...
def _get_selection_vocab(self):
"""sample selection voc
"""
return DisplayList((('item01','Item One'),('item02','Item Two'),))

對於「複選」的欄位,使用 MultiSelectionWidget 就可搞定,如果選項過多,就要考慮是否改用 InAndOutWidget。
更多細節,從 Archetypes Developer Manual 找得到說明。

2011/01/29

EEA Vocabulary Setup

了解 vocabulary 的運作原理後,可以參考 Products.EEAContentTypes 的設定範例,在 Products/EEAContentTypes/setup/vocabularies.py 檔案裡看得到變數設定值:
vocabs = {}
vocabs['themes'] = (
('default', 'Default'),
...
('various', 'Various other issues'),
('waste', 'Waste'),
('water', 'Water'),
)
在 Products/EEAContentTypes/setup/ConfigurationMethods.py 裡看得到設定方法:
def setupATVocabularies(self, portal):
""" Installs all AT-based Vocabularies """

from Products.ATVocabularyManager.config import TOOL_NAME \
as ATVOCABULARYTOOL
from vocabularies import vocabs

vkeys = vocabs.keys()
atvm = getToolByName(portal, ATVOCABULARYTOOL, None)
if atvm is None:
return

for vkey in vkeys:

if hasattr(atvm, vkey):
continue

print "adding vocabulary %s" % vkey

atvm.invokeFactory('SimpleVocabulary', vkey)
vocab = atvm[vkey]
for (ikey, value) in vocabs[vkey]:
vocab.invokeFactory('SimpleVocabularyTerm', ikey)
vocab[ikey].setTitle(value)
不過,ConfigurationMethods.py 裡用的 SetupWidget 是舊式方法,新版已改用 GenericSetup 方式安裝。另外,collective.realestatebroker 使用 unicode_vocabulary 來取代 SimpleVocabularycollective-docs 裡說明 vocabulary 的新式建立方式也和 Archetypes 不同,目前使用 dexterity 來搭配測試。

2011/01/21

Drop Down Menu

目前試過三種 Plone 的 dropdown menu 模組,最早用的是 webcouturier.dropdownmenu,最近再裝了 quintagroup.dropdownmenuProducts.qPloneDropDownMenu,初步看來 quintagroup.dropdownmenu 可以配合 Plone 4 環境,Products.qPloneDropDownMenu 則有兩個版本分支,分別配合 Plone 3 和 Plone 4 環境。
安裝 quintagroup.dropdownmenu 的方式之一,是到 src 目錄下載原始碼:
$ cd src
$ svn co \
http://svn.quintagroup.com/products/quintagroup.dropdownmenu/trunk \
quintagroup.dropdownmenu
再編輯 buildout.cfg 內容,在 eggs, zcml, devel 變數加上 quintagroup.dropdownmenu 設定值,最後執行 buildout 讓設定值生效,我用的是指定 develop.cfg 來當執行參數:
$ bin/buildout -c develop.cfg
過程遇到版本衝突的訊息:
While:
Installing instance.
Error: There is a version conflict.
We already have: zope.schema 3.5.4
but z3c.form 2.4.1 requires 'zope.schema>=3.6.0'.
再次編輯 buildout.cfg 裡的 versions 設定區段,加上 z3c.form = 2.2.0 後,重新執行上述指令就行。


安裝後的設定方式,是到 ZMI portal_actions 裡的 portal_tabs 新增 CMF Action 和 CMF Action Category,由於它用了 RAM Cache,想要馬上看到更改後的效果,從 http://localhost:8080/mysite/@@ramcache-controlpanel 之類的網址,清除 cache 記錄即可。

2011/01/16

Study on Vocabulary

Dynamic Vocabularies in Plone Archetypes 文件裡,介紹幾種常見的 vocabulary 範例。最簡單的設定方式,是在 config.py 檔案列出清單:
HOUR = DisplayList((
('1', '1'),
('2', '2'),
etc...
))
再於 content type 的定義檔案裡,指定 vocabulary 變數值:
StringField(
'hour',
vocabulary=HOUR,
widget=SelectionWidget(
label='Hour',
),
required=1,
),
這個例子的清單,還可以用程式碼來產生:
class Event(ATContent):
def getHours(self):
dl = DisplayList()
for x in range(1, 13):
dl.add(str(x))
dl.add(str(x))
return dl
這樣就可以改用 method 存取:
StringField(
'hour',
vocabulary="getHours",
widget=SelectionWidget(
label='Hour',
),
required=1,
),
進一步來看,想要動態存取目錄裡的物件,可以借助 Acquisition 的服務:
from Acquisition import aq_parent
程式碼範例如下:
def getLinks(self):
dl = DisplayList()
linklist = aq_parent(self).contentValues(
filter={'portal_type': 'Link'}
)
for link in linklist:
dl.add(link['id'], link['title'])

return dl
接著,還可以參考 eea.faceted.vocabularies 的範例:
eea/faceted/vocabularies
|-- README.txt
|-- __init__.py
|-- catalog.py
|-- configure.zcml
|-- faceted.py
|-- portal.py
|-- position.py
|-- section.py
|-- simple.py
|-- tests.py
|-- types.py
|-- utils.py
`-- version.txt
以 faceted.py 為例,裡面的 FacetedPortalTypesVocabulary 會回傳 portal_types 和 portal_facedted 裡定義的項目,而 portal_facedted 是由 eea.faceted.tool 提供,如果要調整項目的排序方式,可以查詢 items.sort(key=operator.itemgetter(1), cmp=compare) 程式碼片段。

測試過程遇到 ValueError: Supplied vocabulary values resulted in duplicate term tokens 錯誤訊息,狀況是混用 Products.ATVocabularyManager 和 vocabularies.zcml 兩種方式,把 vocabulary name 都設成一樣,結果在進入 facetednavigation 的 configlet 畫面,它要把所有 vocabulary 清單列出時,發生這樣的錯誤。

更底層的細節,則可參考 zope.schema.vocabulary 和 zope.app.schema.vocabulary 的內容:
SimpleVocabulary : implements(IVocabularyTokenized)
SimpleTerm : implements(ITokenizedTerm)
FacetedPortalTypesVocabulary : implements(IVocabularyFactory)