Personal tools
You are here: Home 博客群 蜻蜓点水 举重若轻 Plone Cook Book v0.0.2
Log in


Forgot your password?
Recent Comments
 还好, (Anonymous User) 2008-08-29
 Trolltech 从一开始就是开源的 (cavendish) 2007-09-26
 Boa 铁杆用户? (Anonymous User) 2007-09-24
 创世纪 (Anonymous User) 2007-05-18
 lihao (Anonymous User) 2007-05-14
 
Document Actions

Plone Cook Book v0.0.2 Plone Cook Book v0.0.2

Submitted by eishn. on 2007-01-08 00:13. PlonePythonZope
本书主要面向 ArcheTypes/Plone 开发者。读者不一定必须是中高级 Plone 开发人员, 但是必须要掌握一定的 ArcheTypes 基础知识。 如果您尚未有 ArcheTypes 的开发经历, 那么直接阅读本文档会有一定的困难。但这并不妨碍您通读本文档、迅速掌握其中各个知识点, 并能在实际开发中迅速索引到本文档的相关章节。 请时刻握好您的工具, Google 以及 Plone.org 右上角的 ZCatalog 接口。事实上本文档不会面面俱到, 因为它总是假定您是一名出色的黑客。 -寻找源码, 追随源码, 模仿源码, 洞察源码, 成为别人的范例源码。

Plone Cook Book

2006 年 5 日, Version 0.0.2

魏中华 沈崴 庄上林










适应人群 & 注意事项

本书主要面向 ArcheTypes/Plone 开发者。读者不一定必须是中高级 Plone 开发人员, 但是必须要掌握一定的 ArcheTypes 基础知识。

如果您尚未有 ArcheTypes 的开发经历, 那么直接阅读本文档会有一定的困难。但这并不妨碍您通读本文档、迅速掌握其中各个知识点, 并能在实际开发中迅速索引到本文档的相关章节。

请时刻握好您的工具, Google 以及 Plone.org 右上角的 ZCatalog 接口。事实上本文档不会面面俱到, 因为它总是假定您是一名出色的黑客。

寻找源码, 追随源码, 模仿源码, 洞察源码, 成为别人的范例源码。




ATField & ATWidget 本纪


身世


ATField 常用属性

  • required
  • default
  • searchable
  • accessor, 取值函数。
    • 如果没有设置该属性, ArcheTypes 会自动生成一个默认函数。
    • 通常默认的命名规则是 "get" + "字段名(首字母大写)"。
    • 如果恰好字段是元数据, 那么默认取值函数是 "字段名(首字母大写)", 比如 "Title"。
    • 可以在 ZPT 或者 Python Script 中通过 here/getXXX (ZPT) 或者 context.getXXX (Script) 调用。
  • mutator, 设值函数。请参考 accessor, 不同的是设值函数的默认名称永远是 "set" + "字段名(首字母大写)"。
  • validators, 验证器。
    • Plone 已经提供了 isEmail、isURL、isInt 等多种验证器。具体请参见 Plone.org 上的相关文档
    • 请注意, 黑客使用 ArcheTypes 是为了避免将时间浪费在无聊的事情上, 本文档认为默认的验证器已经基本够用, 如果您发现自己总是在痛苦地编写 Validator 以适应应用, 那么无非是两种可能, 您可能搞错了, 或者您的应用搞错了。或许您需要的是一个带自定义验证的 Plone 问卷产品, 而第三方开发者早已为您准备好了。
  • index, 索引类型。FieldIndex、KeywordIndex 或者 TextIndex。
  • vocabulary, 可选项。您需要下载几个带下拉框的范例来理解这个字段。
  • widget


ATField 关键技巧

  • 对象标题总是和 Container 标题一样?

    这是 Plone 的一个 Bug, 解决办法是自定义 "title" 字段 (Field) 的 accessor 和mutator。

  • 当一个字段的 Widget 为 RichWidget 时, 请设置该 Field 的 default_output_type 属性的值, 如 "text/html"。否则无法使用 Plone 的编辑器 (比如 Kupu) 进行编辑。
  • 中文全文索引

    Plone 默认不带有中文全文索引。请首先安装润普公司开放的 ZopeChinaPak 和 CJKSplitter。

    最糟糕的解决方案 (如果您的产品已经不幸进入生产系统, 这时的补救方法) 是, 手工在 ZMI - portal_catalog 中删除原有索引, 然后添加一个支持中日韩全文索引的 ZCTextIndex 索引。毫无疑问, 您需要请尽快阅读 润普公司的官方说明

    在您的项目正式开发 (事情变糟糕) 之前阅读《Archetypes 中的内容索引的设置使用》是个好主意。

    CZUG 社区 当然有机会让 TextIndex 在开发时对中文全文索引透明, 为此也确有过争论。但是最后他们的决定是继续保持 Plone 在国际化上的平滑性。知道这点, 也许会让您心里暂时好过些。

  • FieldIndex:schema 的使用

    在利用 portal_catalog.searchResults 进行搜索时, 它返回一堆代表搜索结果的 brain 对象, 它们是真实对象的缩写版本 (在 ZCatalog 中实现)。

    得到查询结果: brains = portal_catalog.searchResults(...)

    调用 brain 的 getObject() 方法可以得到真正的目标对象。通过 brain 你可以访问到该对象的元数据集 (metadata), 而对 brain 进行 getObject() 之后, 你可以访问该对象的所有属性。

    得到真正的结果对象: obj = brain.getObject()

    如果在您的应用中, 需要访问元数据之外的属性, brain 就不够用了, 那么你需要对搜索结果进行 getObject() 操作。不幸的是 getObject() 取得的完整版结果对象过于庞大, 在搜索结果数巨大的情况下, 您的内存将迅速报销。

    如果把字段的 Index 类型设置为 FieldIndex:schema, 则可以直接通过 brain 对象来获取该字段的值。

    Field:schema 做了一件很简单的事情, 它将该字段像元数据一样保存在 ZCatalog 的索引中。因此, 你可以在 brain 里面直接获取到该字段。

  • ReferenceField

    该字段实际上是一个指向其他对象的指针, 用 get 方法所获取的字段值, 是其所指向的对象。

    我们经常会忘记 ArcheTypes 中还有这么一个好用的工具, 实在是不应该。


ATWidget 关键技巧

  • Widget 的主要属性有 label、description 以及 visible 等。
  • 可以设置 "visible" 属性来让字段不可见, 或者不可更改, 如:
    visible = {"view": None, "edit": None}
  • 在使用 SelectionWidget 时, 可以设置 Field 的 vocabulary 作为其选项。




ATContentType 本纪


身世

  • Plone 预定义了 8 种 Base Class, 见 Products.Archetypes.public 中的 BaseClass 部分。
  • 其中出场率最高的是 BaseContent、BaseFolder、BaseBTreeFolder, 以 Folder 结尾的, 都带有 Folder (文件夹) 的特性。
  • 不用理会对 BaseBTreeFolder 性能问题的扯淡, 它能够装下几千万对象 (保守估计)。但是如果你少写了 BTree 这两个字, 您的文件夹可能只能放下几千个对象 (乐观估计)。


ATContentType 关键技巧

  • 如果自定义 Folder 类型 (ContentType) 需要使用到 CMFContentPanels (润普公司开放的界面布局产品), 该 ContentType 必须同时继承 BrowserDefaultMixin。请阅读 CMFContentPanels 的具体说明, 非常地简单。
  • 如果一个文件夹下面需要放相当多的文件 (大于 1000), 请从 BaseBTreeFolder 继承, 但不要忘记您是工作在对象数据库下, 所以一个目录下请尽量不要存放太多的对象。
  • 忘记关系数据库在性能上超过对象数据库 (以及网状数据库) 的谣言。洞察您的应用, 区分复杂的逻辑数据 (树型结构) 和简单的平面数据, 分别存放在对象数据库和关系数据库下, 您将同时得到最高的开发效率和执行效率。


ATContentType 常见属性 (Attributes of ATContentType)

  • meta_type、portal_type、content_icon、schema、allow_discussion、global_allow、all_content_types、actions、security
  • 如果不想让您的产品变的无法控制, 或者仅仅是因为黑客不想了解太多 ArcheTypes 细节的时候, 请给 meta_type 和 portal_type 赋上一个相同的值。


和属性相关的关键技巧

  • archetype_name 属性。设置该 ContentType 的 title。
  • _at_rename_after_creation 属性。
    • 对象创建后, 系统会根据对象的 title 来改变对象的 id。
    • 对于中文, 如果安装了 ZopeChinaPak, id 将自动变成 title 的中文拼音。
  • 在 action 中, 重载 id 为 "view" 的视图, 可以改变默认视图
  • 设置 action 的 conditions 属性, 可以让不同权限的用户看到不同的 action 标签。比如:
    'condition': 'python: object.portal_membership.getAuthenticatedMember().has_role("Manager", here)'
  • 去除 "共享"、"属性" 标签
    #在 ContentType 中加入以下内容: class MyContentType(...):
    def modify_fti(fti):
    # hide unnecessary tabs (usability enhancement)
    for a in fti['action']:
    if a['id'] in ['metadata', 'sharing']:
    a['visible'] = 0
    return fti
  • 遗憾的是目前尚没有找到更好的方法来去除 "文件夹内容" 标签。可以仿照一个叫做 Poi 的第三方产品来做, 但会导致添加菜单的内容混乱。


ATContentType 的常用方法 (Methods of ATContentType)

  • def initializeArchetype(self, *args, **kw), 对象创建时, 将会调用该函数, 这是给开发者提供的对象初始化接口。该接口已经淘汰, 也许你会遇到使用该接口的一些旧的第三方产品, 但是在通常情况下, 请使用 at_post_create_script(self) 接口代替。
    # 下面是通常的写法, 这里以 BaseFolder 为例 class MyContentType(BaseFolder):
    def initializeArchetype(self, *args, **kw):
    BaseFolder.initializeArchetype(self, *args, **kw)
    # ... 我的代码
  • def at_post_create_script(self), ATContentType 基类提供的一个 Hook (钩子), 在对象创建时会调用该函数。
  • def at_post_edit_script(self), 与 at_post_create_script(self) 不同的是这个钩子是在对象被修改 (点击保存) 的时候调用的。
  • def SearchableText(self), 对象在搜索中的描述
    # SearchableText 示范, 作者张炳凯
    def SearchableText(self):
    """ Return the information for indexing"""
    return "%s %s %s" % (self.Title(),
    self.Description(),
    self._cookedText )




Security (Plone Permission) 世家


查看权限

  • View, 在页面上显示对象, View 是必须的
  • Search in catalog, 事实上这也是一个重要的查看权限, 只是我们将它通常保持在默认状态。请勿随意设置。
  • List folder content, 顾名思义, 列出文件夹对象的权限。
  • Access content information, 在页面上显示对象, View 是必须的。同时这个权限控制着所有对对象的操作。


写权限 (增加、修改)

  • Delete Object
  • Copy and Move
  • Add portal content
  • Modify portal content


其他权限

  • Reply to item, 评注。
  • Add portal member, 可以用来禁止注册功能。


关键技巧

  • 请注意, 在 Plone 的默认配置下, 用户可以匿名查看站点上的资料。
  • 请注意, 在 Plone 的默认配置下, 用户可以自由注册 (有 Add portal member 权限)。
  • Plone 会默认添加一种名为 Authenticated 的角色。这种角色很容易造成权限配置的混乱, 建议禁止该角色。
  • 设置对象的默认访问权限
    security = ClassSecurityInfo()
    security.declareObjectProtected('View MyContentType')
    security.setPermissionDefault('View MyContentType', ('Manager', 'Owner', 'Member'))
  • 自动创建 (本地) 角色 (context._addRole 方法)
    for role in ['MyRole1', 'MyRole2']:
    self._addRole(role)
  • 控制对象方法的访问权限, 基本上在范例产品中, 类似 security.declarePublic('MyFunc')security.declareProtected(permissions.View, 'MyFunc') 这种写法随处可见。但是繁复的权限控制, 通常是由糟糕并且自相矛盾的需求, 或者控制狂引起的, 我们需要时刻保持清醒以避免一切失控。尽管人类历史上从未出现过如此强大而易配置的权限系统。




Workflow 世家


创建工作流

  • 安装产品 DCWorkflowDump, 进入 ZMI - portal_workflow 创建自己的工作流。点击 "Dump" 标签, 程序将自动生成该工作流的源码。
  • 我们都爱 DCWorkflowDump。


protal_workflow

  • State 标签页
  • Transitions 标签页, 其中 Expression 为 TALES 表达式。
    python: here.getUserName() == user.getUserName()
  • Scripts, 你可以让对象在状态改变时执行一个 Python Script。请参考润由 CZUG 合著的《Plone 资料汇编》第八章, 其中 "通常的任务与例子" 一节可以满足您居家旅行的大部分需要。


安装工作流

# Using permissions and workflow in your custom products 
# Author, Martin Aspeli
def install(self):
from Products.MyProduct.Extensions import MyWorkflow
wf_tool = getToolByName(self, 'portal_workflow')
if not 'my_workflow' in wf_tool.objectIds():
wf_tool.manage_addWorkflow('my_workflow (My custom workflow)',
'my_workflow')
wf_tool.updateRoleMappings()
wf_tool.setChainForPortalTypes(pt_names=['MyType'],
chain='my_workflow')


关键技巧

  • 获取当前对象的 state
    • 对于 brain 类型的对象 (比如搜索结果), 可以直接使用 brain.review_state 获得
    • 对于常规类型, 可以使用 getInfoFor 方法取得
      wf_tool = getToolByName(self, 'portal_workflow')
      st = wf_tool.getInfoFor(self, 'review_state')
  • 获取 State 的 Title
    wf_tool = getToolByName(self, 'portal_workflow')
    title = wf_tool.getTitleForStateOnType(st, self.portal_type)
  • 获取当前对象的工作流的所有状态
    def getAllStatesChainForCT(self, content_portal_type):
    """ Get all the state about the portal_type """
    wftool = getToolByName(self, 'portal_workflow')
    chain = wftool.getChainForPortalType(content_portal_type)
    workflow = getattr(wftool, chain[0])
    states = getattr(workflow, 'states')
    state_list = []
    for id, state in states.items():
    state_list.append((id, state.title))
    return state_list
  • 执行 Transition (状态跃迁)
    wf_tool = getToolByName(self, 'portal_workflow')
    wf_tool.doActionFor(self, 'transition_name')




Python Scripts 列传


Request 对象

用户提交的数据会以字典形式存储在 Request 对象中。

  • 在 ZPT 中, 该对象的名字是 request
  • 在 Python Script 中, 该对象为 context.REQUEST


Response 对象

RESPONSE = context.REQUEST.RESPONSE

  • 处理完毕后, 如果需要 redirect 到另一个页面, 则 Response 就可以派上用场。
    RESPONSE.redirect(URL)
  • 使用 Plone 系统的消息功能
    RESPONSE.redirect(context.absolute_url() + '?portal_status_message=发生错误,请与管理员联系')
  • 设置 Cookie
    RESPONSE.setCookie('变量名', 变量值, expires='Web, 19 Feb 2020 14:28:00 GMT')




评注·列传


添加评注

dtool = getToolByName(context, 'portal_discussion')
tb = dtool.getDiscussionFor(context)
id = tb.createReply(title='TITLE', text='TEXT', creator='CREATOR')
reply = tb.getReply(id)

获取对象所有评注

def getRs(obj, replies, counter):
rs = pd.getDiscussionFor(obj).getReplies()
# 对评注按修改日期排序
rs = container.sort_modified_ascending(rs)
for r in rs:
replies.append({'depth':counter, 'object':r})
getRs(r, replies, counter=counter + 1)
replies = []
pd = container.portal_discussion
from Products.CMFDefault.DiscussionTool import DiscussionNotAllowed
try:
pd.getDiscussionFor(page_obj)
except DiscussionNotAllowed:
# We tried to get discussions for an object that has not only
# discussions turned off but also no discussion container.
return []
getRs(page_obj, replies, 0)
return replies




角色·列传


pm = getToolByName(self, 'portal_memebership')


创建角色

self._addRole('ROLE')


获取角色列表

pm.getCandidateLocalRoles(context)


分配角色

pm.setLocalRoles(obj=self, member_ids=[...], member_role='Contributor')


删除用户角色

pm.deleteLocalRoles(obj=self, member_ids=[...])




港台音乐

手工 (强制) 提交事务

import transaction
transaction.commit()


获取当前登录用户

  • ZPT, user.getUserName()
  • ArcheTypes, portal.portal_membership.getAuthenticatedMember()


在 Skin 上显示 HTML 格式的文本

<span tal:content="structure python: here.CookedBody(stx_level=2)">

不重启, Refesh Products

  • 在你的 Product 目录下添加一个名为 "refresh.txt" 的文件
  • 进入 ZMI:ZOPE-ROOT/control_panel/products/MyProduct, 在 Refresh 页面下可对 Product 进行 Refresh 操作, 也可以设置 Auto Refresh Mode 来进行定时 Refresh


获取请求的 URL

context.REQUEST['URL']


对 searchResults 的查询结果按照对象的 title 进行排序

context.searchResult(sort_on = 'sortable_title', ...)


在自定义 form 里面调用 Kupu 编辑器

<metal:block tal:define="
editor python: here.portal_membership.getAuthenticatedMember().wysiwyg_editor;
wysiwyg python: test(editor and (editor!='None'), editor, 'None');
use_wysiwyg python: 1 ">
<metal:block tal:define="
inputname python:'my_input';
formname python:'my_from';
inputvalue python:'the content of the field';
width python:200;
height python:300;
tabindex tabindex/next; ">
<div metal:use-macro="here/wysiwyg_support/macros/wysiwygEditorBox">
The WYSIWYG code
</div>
</metal:block>
</metal:block>

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: