View and Template
Plone 使用 View 機制來呈現動態資訊,它產生的結果通常是 HTML 內容,也可以是 JSON 格式,或是 Excel 之類可供下載的檔案。View 通常會包括 Python Class 和 Page Template 兩類程式碼,前者負責處理程式邏輯,後者負責網頁的呈現效果,為了讓呈現效果更具彈性,常用的技巧包括 TAL、METAL 語法,也牽涉 CSS 和 JavaScript 的搭配。
在起始階段,View 會伴隨 context 和 request 資訊,這些資訊在 Page Template 裡也用得到,即使沒用到 context 的屬性值,通常也會利用 context 作為查詢其他 view 或 tool 的起始點,而且,Zope 安全機制也要查詢 context 來決定使用者的現行角色值。
從 ZCML 註冊方式來看 View 與 Template 的關係
Adventures In Theming: 傳統 main_template.pt 方式
Plone5 Products.CMFPlone/browser/main_template.py main_template.pt
<browser:page for="*" name="main_template" class=".main_template.MainTemplate" permission="zope.Public" />
Editable Tagline Viewlet Toolbar Menu
範例實務介紹 another example Browser View vs Adapter 效能討論
David Glick: restrictedTraverse is implemented in terms of getMultiAdapter. The implementation of "restrictedTraverse" is something like this: 1. Does the name start with '@@'? If so, fetch the view using getMultiAdapter like you showed above. 2. Otherwise, try to get the name as an attribute of the object being traversed. 3. If that fails, try to get the name as an view using getMultiAdapter 4. If that fails, try to get the name using item access (e.g. obj[name]) on the object being traversed. 5. If that fails, try to acquire the name as an attribute from parent objects.
# plone.app.users/browser/account.py @property def portal(self): return getToolByName(self.context, 'portal_url').getPortalObject()
member_search = context.restrictedTraverse('member_search_form') return member_search() from Products.CMFCore.utils import getToolByName utool = getToolByName(context, 'portal_url') portal = utool.getPortalObject() return portal.restrictedTraverse('contact-info')()
kss_generic_macros.pt 控制 Title 與 Description 欄位的顯示效果。
foldertextfield 讓 Folder 新增 Text 欄位。
目錄存在,但無法正確顯示,訊息是「找不到目錄」,先在目錄網址後面加上 /selectViewTemplate?templateId=folder_listing,或是在目錄網址後面加上 /manage-portlets 取消 portlet 設定值。
<tal:def define="dummy python: request.RESPONSE.setHeader('content-type', 'text/html; charset=utf-8')" />
Global Template Variables
tal:define="template_id template/getId; normalizeString nocall:context/@@plone/normalizeString; toLocalizedTime nocall:context/@@plone/toLocalizedTime; portal_properties context/portal_properties; site_properties context/portal_properties/site_properties; here_url context/@@plone_context_state/object_url; portal context/@@plone_portal_state/portal; isAnon context/@@plone_portal_state/anonymous; member context/@@plone_portal_state/member; actions python:context.portal_actions.listFilteredActionsFor(context); mtool context/portal_membership; wtool context/portal_workflow; wf_state context/@@plone_context_state/workflow_state; default_language context/@@plone_portal_state/default_language; is_editable context/@@plone_context_state/is_editable; isContextDefaultPage context/@@plone_context_state/is_default_page; object_title context/@@plone_context_state/object_title; putils context/plone_utils; ztu modules/ZTUtils; acl_users context/acl_users; ifacetool context/portal_interface; syntool context/portal_syndication;"
注意: 使用 context/@@plone_portal_state/portal 遇過 err_too_many_redirects 問題。
Products.CMFPlone/browser/jsvariables.py JSVariables(BrowserView) 在 Diazo 環境難以提供同等功能
Hide default view from getFolderContents results with TALES
class MyView(BrowserView): def dstrct_number(self, arg): switcher = { u'\u4e2d\u897f\u5340': '2', ... } return switcher.get(arg, '2') <div tal:condition="context/dstrct"> <iframe src="#" tal:define="url string: http://somesite.com/geoexplorer/viewer/#maps/"; tal:attributes="src python: url + view.dstrct_number(context.dstrct())"></iframe> </div>
Products.CMFPlone/browser/author.py: is_anonymous, is_owner
判斷現行位置是否在 /folder_contents: request.ACTUAL_URL
<p i18n:translate="">Open reset requests: <i18n:span name="n">${view/stats/open}</i18n:span></p> <p i18n:translate="">Expired reset requests: <i18n:span name="n">${view/stats/expired}</i18n:span> (expired requests deleted after 10 days)</p>
範例
<fieldset tal:repeat="group view/groups" tal:attributes="id python:''.join((group.prefix, 'group.', group.__name__)).replace('.', '-')"> <legend tal:content="group/label" /> <div class="field" tal:repeat="widget group/widgtets/values"> <label tal:content="widget/label" /> <br /> <div tal:content="structure widget/render" /> </div> </fieldset>
folder_contents 提供檔案總管式的顯示功能
absolute_url() vs getURL()
在 View 裡使用 absolute_url() 通常比較理想,它會回傳 canonical URL,另一方面,因為 acquisition 之類的影響,getURL() 可能會傳回錯誤的網址。
<form method="post" tal:attributes="action view/absolute_url"> <input type="hidden" name="filename" value="" tal:attributes="value item/filename" /> <input type="submit" name="form.action.convert" value="Convert" /> </form>
舊的 getFolderContents.py 新的 @@folderListing
New Features in Plone 4
Plone 4 的改進之處,包括使用新的 master template 檔案,如果使用 Plone 3 的 theme 模組,需要比對其中 main_template.pt 的差別,另外,也有辦法可以沿用 DTML 的程式碼。
Important Slots in Plone 4
- content-title - 顯示標題
- content-description - 顯示摘要描述
- content-core - 包括文件的內容及子目錄
- main - 相容於舊版並用於不需要 viewlet manager 的場合
<body> +<metal:title fill-slot="content-title"> +<h1 i18n:translate="">My Title</h1> +</metal:title> +<metal:description fill-slot="content-description"> +<div class="documentDescription"></div> +</metal:description> + <metal:content-core fill-slot="content-core"> <metal:content-core define-macro="content-core">
eea.facetednavigation description override
Page Template
tal:repeat 不要出現在 <ol>, <ul>, <dt> 裡,通常是接續在它們之後,利用 <tal:block repeat=""> 來建立迴圈。
先建立 View 再使用 Template : <div metal:use-macro="view/main_macro" />
Pagination Design pagination via template sorting and pagination Plone5 Mockup Pagination pagination custom example by AndyFung Pagination with AJAX request using Larvel and Vue.js
Variable Keys in Dictionaries with Page Templates TALES syntax
Condition for Types
set TAL condition to check the file type and accordingly render the template
Making '/folder_contents' tab visible in Ploneboard
python: object.displayContentsTab() or object.portal_type in ['PloneboardForum', 'PloneboardConversation',]
Macro 與 Slot
Viewlet Managers
和文件內容有關的 viewlet manager 都由 main_template.pt 來管理,也就是說,使用到 viewlet manager 的場合,並不需要在自製模組的 template 裡指定 viewlet manager,以 document_view.pt 為例,只需要選用 slot 就行。
<metal:content-core fill-slot="content-core"> <metal:content-core define-macro="content-core"> <metal:field use-macro="python:context.widget('text', mode='view')"> Body text </metal:field> </metal:content-core> </metal:content-core>
Left / Right Column Disable
eea.facetednavigation example:
<tal:left define="hidden python:request.set('disable_plone.leftcolumn', view.hide_left_column)" />
JavaScript Head Slot
eea.facetednavigation example:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US" metal:use-macro="here/main_template/macros/master" i18n:domain="eea"> <metal:jsslot fill-slot="javascript_head_slot" <script type="text/javascript" tal:content="string:jQuery(document).ready(function(evt){ Faceted.Load(evt, '${context/absolute_url}/'); });"></script> <script type="text/javascript" tal:content="string:jQuery(window).unload(function(){ Faceted.Unload(); });"></script> </metal:jsslot>
fill slot 的時候,不要使用 <div>,而是要用 <metal:meaningful-name> 的語法:
<metal:content-core fill-slot="content-core"> Content core </metal:content-core>
override
http://stackoverflow.com/questions/11904155/disable-advanced-in-workflow-status-menu-in-plone
http://stackoverflow.com/questions/11991225/how-could-i-render-just-the-form-from-an-autoform
collective.quickupload tab + viewlet
plone.app.themeplugin plone.app.discussion
typesUseViewActionInListings
<tal:block define="site_properties context/portal_properties/site_properties; use_view_action site_properties/typesUseViewActionInListings|python:();> ... <a href="#" tal:attributes="href python:item_type in use_view_action and item_url+'/view' or item_url;>
在 Content Listing 列表裡,預設 Image 和 File 的連結網址會使用 /view 作結尾。
getInfoFor
參考 changeset 22120 的內容,從
tal:define="getInfoFor python:wtool.getInfoFor;"
變成
tal:define="tools context/@@plone_tools; getInfoFor python:tools.workflow().getInfoFor;"
getIconFor
參考 changeset 22120 的內容,從
tal:define="getIconFor nocall:putils/getIconFor"
變成
tal:define="getIconFor nocall:context/plone_utils/getIconFor"
ViewPageTemplateFile vs PageTemplateFile
ZCML 和 ViewPageTemplateFile 所定義的 template 不同處
參考 collective.plonetruegallery 和 plone.formwidget.querystring collective.z3cform.wizard 修改歷程
$ svn diff collective/plonetruegallery/browser/views/display.py -r 110621:110623 + from zope.pagetemplate.pagetemplatefile import PageTemplateFile - class PloneTrueGalleryPageTemplate(ViewPageTemplateFile): + class PloneTrueGalleryPageTemplate(PageTemplateFile):
Use a skin based template in a Five view
from Acquisition import aq_base, aq_acquire from Products.Five.browser import BrowserView class TelescopeView(BrowserView): """ Renders an object in a different location of the site when passed the path to it in the querystring. """ def __call__(self): path = self.request["path"] target_obj = self.context.restrictedTraverse(path) # Strip the target_obj of context with aq_base. # Put the target in the context of self.context. # getDefaultLayout returns the name of the default # view method from the factory type information return aq_acquire(aq_base(target_obj).__of__(self.context), target_obj.getDefaultLayout())()
Listing Available Views
from plone.app.customerize import registration from zope.publisher.interfaces.browser import IBrowserRequest # views is generator of zope.component.registry.AdapterRegistration objects views = registration.getViews(IBrowserRequest)
Listing All View of Certain Type
from plone.app.customerize import registration from zope.publisher.interfaces.browser import IBrowserRequest # views is generator of zope.component.registry.AdapterRegistration objects views = registration.getViews(IBrowserRequest) # Filter out all classes which do not filter a certain interface views = [ view.factory for view in views if locksView.implementedBy(view.factory) ]
Get a Content Item's View for Page Rendering Explicitly
def viewURLFor(item): cdtate = getMultiAdapter((item, item.REQUEST), name='plone_context_state) return cstate.view_url()
Case: Proposal Item with Review Items
__init__() method special cases
View 的 constructor method 是 __init__(),它很特別,你不應該放進自己的程式碼,而是利用 helper method 之類的方式,來建立起始變數。
Template Engine
zope.tal 和 chameleon 都是 Template Engine 的範例,有人嘗試透過 Template Engine Chooser 來提供建議,由於 Template Engine 跟 Framework 的結合程度日益緊密,單獨針對 Template 的選用建議變得意義不大。
Chameleon: 取代 TAL 語法:
<a tal:attributes="href href" tal:content="text" />
換成
<a href="${href}">${text}</a>
CSS
#form-widgets-description
tag cloud in colors: collective.vaporisation example
TAL Tips
contactinfo = python: item.email_address if item.email_address else item.phone_number
Accessing priviledged JavaScript APIs from your web page in Firefox with Selenium
Redirect
How to set Plone to redirect to came_from after logout
A simple way: customize logged_out, transform it to a Python Script that redirect to whatever page you want:
context.REQUEST.RESPONSE.redirect(url)
request = container.REQUEST; response = request.response; ## construct the redirection URL if "id" not in request.form: id = '0'; else: id = request.form["id"]; redirectURL = 'detallemaqueta'+id+'.htm'; response.redirect(redirectURL,301);
from urllib import urlencode self.request.response.redirect('@@my-results?' + urlencode({'myvar': results['myvar']}))
Newline to BR
<div metal:define-macro="description-field-view" id="parent-fieldname-description" tal:define="kss_class python:getKssClasses('description', templateId='kss_generic_macros', macro='description-field-view'); pps modules/Products.PythonScripts.standard" tal:condition="context/Description" tal:attributes="class string:documentDescription$kss_class;"> <span metal:define-slot="inside" tal:replace="structure python:pps.newline_to_br(pps.html_quote(context.Description()))">Description</span> </div>
Linkable URLs in Description Field
plone.batching: Products.Ploneboard
Some Date Values Not Displayed
Rendering a z3cform Wizard as a Standalone View
plone.app.layout Related Items Viewlet Bug
Pass Template Text Without Using File Zope-compatible page template engine based on Chameleon
PLIP: Reuse code among Portlets, Tiles and Views
traverse_subpath for BrowserViews: @@view/some/more/path
Easy Template email content rule
tal:condition="python:request.URL.find('@@sharing') != -1" http://www.catapultsolutions.net/resources/tal-condition-based-on-url-in-a-plone-site.html
<div class="tileBody" tal:define="oneline python:len(item_title) < 15"
tal:condition="python: oneline and item_description">
<span class="description" tal:content="item_description">
description
</span>
</div>