2012/07/27

Plone 4.2 Installation Tips

Plone 4.2 的 Unified Installer 上架一段時日了,安裝用到的參數並沒改變,它搭配 Python 2.7.3 版本,在 Ubuntu 12.04 環境裡,接近完整的編譯訊息如下:

$ ./install.sh standalone --target=/home/marr/plone420
Stand-Alone Zope Instance selected

Detailed installation log being written to
 /home/marr/Downloads/Plone-4.2-UnifiedInstaller/install.log

Rootless install method chosen. Will install for use by system user marr

Installing Plone 4.2 at /home/marr/plone420

Skipping libjpeg build
Skipping readline build
Installing Python-2.7.3. This takes a while...
Installing distribute...
Python build looks OK.
Unpacking buildout cache to /home/marr/plone420/buildout-cache
Compiling .py files in egg cache
Copying Plone-docs
Your platform's xml2/xslt are up-to-date. No need to build them.
Copying buildout skeleton
Fixing up bin/buildout
Skipping static libxml2/libxslt build.
Building Zope/Plone; this takes a while...

如果 xml2/xslt 需要編譯的話,就會顯示下列訊息:

Your platform's xml2/xslt are missing or out-of-date. We'll need to build them.

對於初學者,第一個叮嚀是,不要用 root 權限安裝 Plone 會比較單純。

從 4.0.7 昇級到 4.2 的過程,遇到幾個值得留意的訊息:

INFO PythonScripts Some of your Scripts have stale code cached.
Since Zope cannot use this code, startup will be slightly slower
until these Scripts are edited.
You can automatically recompile all Scripts that have this problem
by visiting /manage_addProduct/PythonScripts/recompile
of your server in a browser.

Converting to IIBTree for index `end`.
Converting to IITreeSet for index `object_provides`.
Converting to IITreeSet for index `Subject`.
Converting to IITreeSet for index `getRawRelatedItems`.
Converting to IIBTree for index `effective`.
Converting index `UID` to UUIDIndex.
Converting index `is_folderish` to BooleanIndex.

INFO plone.app.upgrade Ran upgrade step:
Add Member role to 'Portlets: View dashboard' permission
INFO plone.app.upgrade Ran upgrade step:
Install the CMFEditions component registry bases modifier

把 readline 支援放進 Python 是很重要的事,記得事先安裝 libreadline-dev 套件。

Warning: This Python does not have readline support.
It may still be usable for Zope, but interacting directly with Python will be painful.

通常 Plone 開發團隊會等到主要平台的 Unified Installer 都就緒,才會在網頁發佈新版號正式釋出的消息,我關心的是 Linux 版本進度,必要時自行到 http://dist.plone.org/release/ 找檔案就行。

2012/07/17

GeoPDF: Consumable GIS File

GeoPDFTerraGo 公司開發的軟體,可以處理具備地理資訊的 PDF 檔案,包括顯示地圖、座標、計算距離面積等功能,並可使用 Adobe Reader 之類常見軟體來開啟。它的特點包括:

  1. PDF 格式讓一般使用者也能簡單操作。
  2. 現場人員可輸入數據和其他人員共享。
  3. 縮小檔案容量方便散佈與攜帶。
  4. 使用期間可以不依賴網路。
  5. 可附帶位置訊息寫入註釋等。

目前在美國有超過 900 個組織使用 GeoPDF 工具,大多屬於政府單位,常見的使用場合包括:

  • 災後重建領域:用於災情掌握、確認避難路徑、分發救援資訊。
  • 警戒護送領域:維安工作的場合,可顯示特定行進路途的重要警戒地點、布置警衛人員、利用實況報告來推斷逃跑路線等。
  • 基礎設施管理領域:用於電力公司、上下水道、交通設施等的設施管理。
  • 保險理賠領域:保險公司事故調查人員在事故發生現場將發生的事故資訊登記到 GeoPDF 文件中,作為理賠審查時的調查表。

GeoPDF 是以 ISO32000 中規定的 PDF 1.7 作為基礎,目前正朝向標準化的準備工作邁進。

2012/07/13

IOError: decoder jpeg not available

Currently PIL is not easy to work with Plone, that is why there is a PIL fork for this issue. The common error message looks like this:

ERROR root could not scale ImageField "image" ...
...
IOError: decoder jpeg not available
...
AttributeError: image_mini

Here is my environment running into this issue:

  • Plone 4.0.7
  • Ubuntu 12.04

A quick search leads me to an answer. My working fix is like this:

  • edit buildout.cfg to pin version: Pillow = 1.7.5
  • remove the installed Pillow 1.7.2 and re-run buildout

If everything goes well, you should see JPEG support available after downloading Pillow.

2012/07/10

ValueError: Unable to find update_version_before_edit

在 Plone 4.2 安裝 Products.Collage + collective.collage.megamenu 後,在新增 Collage 時遇到 ValueError: Unable to find update_version_before_edit 訊息,暫時還不清楚細節,但處理方式是到 portal_setup 的 Import 執行 CMFEditions 的所有步驟。

ContentMigration Example

When migrating Plone from 2.5 to 4.x, you might want to migrate the custom Archetypes packages. Here is one simple example. Note that this might not be best practice, but should be working if your migration case is simple as mine.

First create a migration.py file in myproj.atcontent/myproj/atcontent/Extensions/

#!/usr/bin/python
# -*- coding: utf-8 -*-

from Products.contentmigration.archetypes import InplaceATItemMigrator, ATItemMigrator
from Products.contentmigration.walker import CustomQueryWalker
from Products.contentmigration.archetypes import *
from Products.contentmigration.common import unrestricted_rename

from transaction import savepoint
from Products.CMFCore.utils import getToolByName


def getMigrationWalker(context, migrator):
    """ set up migration walker using the given item migrator """
    portal = getToolByName(context, 'portal_url').getPortalObject()
    return CustomQueryWalker(portal, migrator, use_savepoint=False)


class OldTypeToNewTypeMigrator(InplaceATItemMigrator):
    src_portal_type = 'OldType'
    src_meta_type = 'OldType'
    dst_portal_type = 'NewType'
    dst_meta_type = 'NewType'

    def last_migrate_reindex(self):
        self.new.reindexObject(idxs=['object_provides', 'portal_type',
            'Type', 'UID'])

    def renameOld(self):
        self.code_field = self.old.getCode_field()
        InplaceATItemMigrator.renameOld(self)

    def remove(self):
        if self.new.getId() != self.code_field:
            unrestricted_rename(self.new.aq_inner.aq_parent, self.new.getId(), self.code_field)

    fields_map = {
        'datasource': 'data_src',
        'old_field': 'new_field',
    }


def getOldTypeToNewTypeMigrationWalker(self):
    return getMigrationWalker(self, migrator=OldTypeToNewTypeMigrator)


def migrateOldType(self):
    walker = getOldTypeToNewTypeMigrationWalker(self)
    savepoint(optimistic=True)
    walker.go()
    return walker.getOutput()


class ImagesToPhotosMigrator(InplaceATItemMigrator):
    src_portal_type = 'Image'
    src_meta_type = 'ATBlob'
    dst_portal_type = 'Photo'
    dst_meta_type = 'Photo'

    def last_migrate_reindex(self):

        self.new.reindexObject(idxs=['object_provides', 'portal_type',
            'Type', 'UID'])

    fields_map = {
    }

def getImagesToPhotosMigrationWalker(self):
    return getMigrationWalker(self, migrator=ImagesToPhotosMigrator)

def migrateImages(self):
    walker = getImagesToPhotosMigrationWalker(self, {'path': '/my_folder'})
    #savepoint(optimistic=False)
    walker.go()
    return walker.getOutput()

Then, go ZMI and add an External Method in Plone Site root:

Id: myproj.atcontent.migration
Module Name: myproj.atcontent.migration
Function Name: migrateOldType

Click Test tab to run the External Method. If everything goes well, you might want go portal_catalog Advanced tab to Update Catalog.

2012/07/09

Responsive Design

已經有不少人討論 Responsive Design 並且找得到許多範例,甚至也有人提出它的缺點技術上它是用了 CSS3 media queries 的 W3C 規格,據說 IE 6-8 沒有支援,所以要另外找處理方法

同時間 twitter 在此風潮下,有推出一套稱為 bootstrap 的 CSS toolkit。已有 Plone 好手討論整合 bootstrap 這項工具。試過像 plonetheme.responsivetheme, plonetheme.responsive1140, beyondskins.reponsive 這些例子,但暫時最喜歡的是 collective.responsivetheme。實際的網站範例有: www.arpa.piemonte.it, netimpact.org。有空要測試 redomino.css3theme 的範例。

2012/07/06

Plone Member Approval Process

利用 pas.plugins.memberapprovalcollective.memberapprovalcollective.memberemails 三個模組的合作,可以擴充 Plone 的帳號審核功能,新增一個 Approval Status 欄位,也就是申請帳號後,經由管理員同意的話,才可以登入系統。

狀態分成 Pending、Approved、Disapproved 三種。

啟用三個模組後,會出現 Member creation email settings 設定項目,用來指定通知信要寄給誰。

接著,要到 Security 設定勾選 Enable self-registration 和 Let users select their own passwords,還有確認 Mail 項目裡完成 SMTP 設定。

自建 SMTP server 常見方法之一是安裝 postfix,現在 Python 粉絲多了 lamson 選項,用它進行簡易測試算是很方便。

2012/07/03

TinyMCE Output Transformations

Plone 4.0.x 到 4.1.x 之際,針對 custom output transformation 進行程式碼重構,以往各個編輯器需要個別實作 filter 功能,改由獨立的模組來統一處理。在這個過程中,搭配的 Products.TinyMCE 由 1.1.x 改為 1.2.x,許多檔案受到調整,原本的 resolveuid.pyparser.py 被幹掉了,其中的 filter 功能由 plone.outputfilters 取代,並盡量相容於 Plone 4.1 之前的程式碼

2012/07/02

Linux Succeeded Thanks to Selfishness and Trust

BCC 前陣子刊載 Linus Torvalds 的訪談稿,其中有個回應如下:

@GeKaTiek

然而值得討論的是,Linus 的說法,恐怕無法為他的論點提出有力的支持。上面讀來,Linus 認為,開源之所以能貢獻於社會公益,乃是因為程子們「追求寫程式的樂趣」,然而,顯而易見的,那些待在封閉的公司當中的人,就無法追求這種樂趣麼?或者說,真的只有開源才能體現這種樂趣麼?顯然不是的。
我們其實可以從當中窺探到所謂樂趣背後的可望,應該是受人景仰。於是,自私地追求樂趣的下場,程式碼越寫越「讓人看不懂」,就好像很厲害一樣、給別人看程式碼的心態就成為「炫耀」,於是大家追逐這種自私的結果,使得程式碼的品質下降、難以維護,更關鍵的是,如同 Linus 說的,「不是只有我有這種想法」,於是沒有人真的要讀你的 code,因為「大家都想自己來」,在這種自私的驅動下,開源只會更糟,不會更好。也正因為亞當斯密看透這種驚駭的思維(別忘了,他除了《國富論》之外,還有另外一本巨著《道德情操論》)我們才能在現代發現名言的新詮釋:因為屠夫與釀酒師、麵包師父自私自利,於是我們只能吃到黑心食品。
真正推動開放世界能夠持續進步的,還是得回到本初的心意:「樂」於分享。不可諱言,因為追求分享本身帶來的樂趣,這種「自私XD」,才是促使開放能夠持續前進的動力。

Linus 並不是第一次接受訪問,他已經多次闡述「當初會在 Usenet 公佈自己作品的初衷」,我也相信當中並沒有太多了不起的念頭。當他意識到 BBC 的提問想要導引出「某種預期規劃」時,他便順勢「扯到更一邊」去,如果你了解科學研究社群的文化,就會知道開源只是必要的程序之一,它跟自私或公益都沒太大直接關係。我猜 Linus 體諒 BBC 記者並不是科學圈的人,才懶得提這段原因。

自私追求樂趣的下場,會讓程式碼越寫越亂嗎? 首先,讓自己去讀一套具有上萬行程式碼的軟體,然後試著加上自己喜歡的功能,最後問自己是否能體驗到樂趣? 答案通常是否定的。於是,大家都想自己來囉,所以開源還是只會更糟? 看看這年頭,是否每個人都想實作自己的 kernel 吧 ^^

我在好久以前問過 Eric Raymond 這個問題,他的答案至今仍然有效:軟體的品質來自程式員願意投注的心力。當程式員形成社群,願意長期投注心力時,軟體的品質就更容易獲得提昇。形成社群的關鍵,又在於「信任」,只靠少數程式員自爽來支撐的軟體,很難長期提昇品質。

比較少人去理解:同樣是開源社群,GNU/Linux/BSD 就展現很不同的文化和發展模式,光憑這個實例,就能知道開源並不是影響軟體品質的重要因素,當然,開源肯定是培養信任的重要基礎之一。進一步地看,同樣是使用 GPL 的 GNU 和 Linux,仍然呈現不同的社群文化,可以窺見 license 也只是形塑社群文化的一部份因素。Linus Torvalds 在意別人是否公平地對待 contribution,Richard Stallman 在意別人是否公平地對待 credit,顯然地,說教式地要求公平,成效總是有限。

Linus 嘴裡的「樂趣」,就是他現在仍在參與及帶領的程式員社群,他們可以在社群裡持續追求程式員的聖杯:共同累積最棒的創意和實作。時至今日,Linux kernel 大約每隔三個月推出新版,包括超過 1,000 位核心程式員的協同心力,如果每一行程式碼都要讀過,恐怕很難有樂趣可言。如果 Linux kernel 全然變成某個公司的產品,那麼這項樂趣很可能就會變質或消失。那怕你是 FSF, IBM, Apple, Linux Foundation,都不能影響這個初衷,Linus Torvalds 並不希望有個委員會來決定 kernel 的方向,他信任的是那群在意軟體品質的程式員們,同時,他也自我要求,不讓信任他的程式員們感到擔心,有天 kernel 的發展方向會被特定單位把持。它變成一個全球規模的信任關係。