View Component
View 是新式 Python 網站框架的重要元件,通常由下列兩者組成:
- Python Class (使用 .py 檔名)
- Page Template (使用 .pt 檔名)
如果有需要,也會搭配 JavaScript 或 CSS 等資源檔,可以用來顯示統計表單,或是上傳檔案的提示頁面。除了產生 HTML 檔案外,也可以輸出成 JSON 或 Excel 檔案格式,或是匯出資料到 SQL 資料庫。
在 Plone 環境裡,Browser View 是最常見的應用方式,通常也會搭配 Viewlet 或 Layer,設定進階的控制細節。
背景知識
Skin Layer 是 Plone 2 時代,顯示動態網頁的傳統機制,它通常透過 Restricted Python 來執行程式邏輯,包括副檔名是 .cpy 的 Controller Script 檔案,再由 Page Template 顯示畫面。但這類 Python Script 並不是一般的 Python 程式,它們只允許執行特定功能,如果存取超過安全範圍的功能,就會出現 Unauthorized: The container has no security assertions 的錯誤。
另一個常見問題是,Skin Layer 的 Script 或 Template 可以在任何 context 裡執行。以 plone_content 的 document_view 為例,通常它該和 Page 搭配,但在 /some-news-item/document_view 的情況下,Zope 仍會試圖執行它,這樣很容易遇到 AttributeError 的錯誤。
當然,也有些 Script 或 Template 屬於通用類型的設計,在多種 context 裡都適合執行,以 plone_forms 裡的 content_status_history 為例,只要內容型別實作 Workflow 功能,就適合搭配它來執行。無論如何,到了 Plone 5 之際,Skin Layer 將被 View 機制取代。
從技術底層來看,View 是 Zope Component Architecture (ZCA) 的一種 Component,它是 Multi Adapter。在 Plone 5 之際,包括 plone_content 在內的 Skin Layer 被改寫成 Browser View 了。
從範例認識運作方式
從 Plone 網站根目錄,可以看到「目錄 (Folder)」包括好幾種「顯示方式 (Display)」:
- Standard view
- Summary view
- Tabular view
- All content
- Album view
- Event Lising
- Select a content item as default view
上述畫面裡的 Item: Welcome to Plone 表示顯示方式是「Select a content item as default view」而且「選了 front-page 這個 Page 當作 default view」。
具體的 View 和 Template 檔案,預設在 browser 目錄裡,檔案通常是 view.py 或 mytype.pt 命名,其中的 .pt 指的就是 Page Template 檔案。以 Page 的顯示功能為例,它有用到 Page Template,具體的檔案位置在 plone/app/contenttypes/browser/templates/document.pt
檔案裡的 <div tal:...></div> 和 <metal></metal> 就是 Page Template 裡的標籤語法。
新增一行 Page Template 程式碼,測試下列的語法效果:
試試把 <span tal:content=”string:Hello World!”></span> 改成:
<span tal:content="context/title"></span>
或是:
<span tal:content="context/created"></span>
從上述範例,可以知道 context/title 或 context/created 是動態存取的變數值,更多關於變數值的控制細節,可參考 View and Template 文件說明。
View Class Example
檔名範例是 browser/views.py,內容範例如下:
from Products.Five.browser import BrowserView class MyView(BrowserView): def __init__(self, context, request): self.context = context self.request = request # def __call__():
除了 context 和 request 當作基本參數設定外,不要在 __init__() 裡執行程式邏輯,它無法知道 View 的 parent 或 hierarchy,就算 helper 也無法生效,如果有錯的話,zope.component 會把它對應到 View not found 或 Traversal Error,慣例是使用 setup() 之類的程式碼,再由 __call__() 去呼叫執行。__call__() 是 View 的進入點,它會執行 self.index() 對應 ViewPageTemplateFile 來讀取 Template 內容。
下列是 index 在 View Class 裡的設定範例,同樣的功能也能透過 ZCML 達成:
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile class MyView(BrowserView): index = ViewPageTemplateFile("my-template.pt")
程式碼範例: pretaweb.healthcheck
Registration
使用 Browser View 之前,要向系統註冊必要的資訊,包括作用對象、負責程式邏輯的檔案位置,這點和 Skin Layer 的運作方式不同。它使用 ZCML 語法和 XML 格式,檔名慣例是 browser/configure.zcml,註冊範例如下:
<browser:page name="list-contents" for=".interfaces.IMyType" permission="zope2.View" class=".views.MyView" />
上述 index 指定 my-template.pt 的範例,與下列註冊方式同義:
<browser:page name="list-contents" for=".interfaces.IMyType" permission="zope2.View" class=".views.MyView" template="my-template.pt" />
也就是說,即使搭配最簡化的 View Class 程式碼:
class MyView(BrowserView): pass
它預設也會執行下列內容:
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile class MyView(BrowserView): # This may be overridden in ZCML index = ViewPageTemplateFile("my-template.pt") def render(self): return self.index() def __call__(self): return self.render()
在 Products/CMFPlone/browser/configure.zcml 檔案裡,找得到更多範例:
<browser:page for="*" name="plone" class=".ploneview.Plone" permission="zope.Public" allowed_interface=".interfaces.IPlone" />
BrowserView 定義在 Zope2 的 Products.Five/browser/__init__.py 裡,在 Products.Five/__init__.py 有提供載入介面,因此,透過 from Products.Five 或 from Products.Five.browser 都能 import BrowserView。
Manual View Look-up
View 是 ZCA 裡的 multi-adapter registration,可以透過名稱 (@@view-name 形式) 來查找,而不是 Traversing 機制。解析過程會用到的 Interface 包括:
- context: 預設使用 zope.interface.Interface (相當於 for="*" 註冊方式)
- request: 使用 zope.publisher.interfaces.browser.IBrowserRequest
- layer: 預設使用 zope.publisher.interfaces.browser.IDefaultBrowserLayer
進階技巧包括:動態指定新的 Template 內容 指派多個 Template 檔案 覆蓋模組裡的 View Class 重複利用 View Template 或嵌入在其他 View 裡
利用 manage_propertiesForm 來管理 layout 設定
http://plone.org/documentation/kb/customization-for-developers/zope-3-browser-views
http://plone.org/documentation/kb/creating-a-custom-template-for-a-plone-content-type
Example: Products.PloneKeywordManager
Every attempt to access a content named "_foo" using a browser lead to a NotFound error:
if name[0] == '_': # Never allowed in a URL. raise NotFound, name
collective.folderishtraverse is an alternative to a default page
Listing Registered Browser Resources
get_size nocall:... fileSize python: get_size(chapter)
quick search and issue button: Products.Poi
ImageView: Products.RoleAwarePortlet
BrowserView 舊的 __of__ 不再推薦使用: collective.geo.bundle 範例 AttributeError: 'MapWidgets' object has no attribute '__of__'