2011/08/01

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 環境,讓我們可以體驗新介面的預覽版。
https://lh3.googleusercontent.com/-jnpfZ9f-6KI/Tg_WHllBbTI/AAAAAAAAC1k/avTn8KV-JkQ/s640/cmsui-frontpage.png
另一個改版重點,是導入新的 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 選項,讓它成為啟用的項目。

https://lh4.googleusercontent.com/-sjn9-Kzs9V8/TgAzjwrAi_I/AAAAAAAAC0I/s5uYBx36cgk/s800/collection-indexes.png

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 一倍以上的硬碟空間。