Skip to content. | Skip to navigation

Personal tools

Navigation

You are here: Home / Tips / View and Template

View and Template

Plone 使用 View 機制來呈現動態資訊,它通常會包括 Python Class 和 Page 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

ZCML layer

範例實務介紹 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()

Members 目錄 index_html 程式碼呼叫

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 設定值。

指定 UTF-8 編碼

<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 問題。

isAdmin

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

Conditional Expressions

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 作結尾。

編輯 propertiestool.xml 可以自動載入

enhanced collection views

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>

Using Pdb in Templates

CSS

#form-widgets-description

tag cloud in colors: collective.vaporisation example

TAL Tips

TAL Condition for JavaScript

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)

redirection

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);

Pass Variables with Redirect

from urllib import urlencode

self.request.response.redirect('@@my-results?' + urlencode({'myvar': results['myvar']}))

Newline to BR

Description Field Line Break

<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

Title Customization

plone.batching: Products.Ploneboard

Some Date Values Not Displayed

Rendering a z3cform Wizard as a Standalone View

Get Viewlet by an Object URL

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>
Related content
Image Scaling