Plone 开发接口(API)

关于Plone的API应用的简单介绍 作者Emanuel Sartor( emanuel@menttes.com ),翻译Natalia B. Bidart( nbidart@except.com.ar ) 谢谢CZUG,谢谢贡献人,柯铁军 翻...

关于Plone的API应用的简单介绍
作者Emanuel Sartor(emanuel@menttes.com),翻译Natalia B. Bidart(nbidart@except.com.ar)

谢谢CZUG,谢谢贡献人,柯铁军 翻译, 潘俊勇

1   介绍

本文档描述了如何通过Plone/Zope的API来使用Plone中的对象。

我们经常通过Python脚本来操作Plone中的对象。学习使用Plone/Zope的API,可以达到以下目的:

  • 控制content对象.
  • 使用Plone的catalog.
  • 控制用户和组.

2   控制content对象

创建、删除、修改content对象的脚本样例。

content对象,是指能够在Plone界面中添加的那些对象,比如页面、图片、链接、文件夹、新闻和文件。

content对象可以通过Python脚本来操纵。换句话说,content对象可以被创建、修改和删除;它的状态也可以修改,对象所拥有的信息也能访问。

2.1   例1:如何通过Python脚本访问content对象

首先,通过Plone界面创建一个content对象。然后,我们根据下面步骤来通过一个脚本对它进行访问。

  • 添加一个document(Page),id取名为my_document.在Plone2.1.1中,这一步稍有不同,因为page的添加表单只要求page的title,所以取名My Document(id会自动生成)。

  • 从ZMI界面转移到你的Plone站点的文件夹下面,在其中的portal_skins/custom中,添加一个脚本(Script (Python)),其中内容如下:

    from Products.CMFCore.utils import getToolByName
    
    urltool = getToolByName(context, "portal_url")
    portal = urltool.getPortalObject()
    document = getattr(portal, "my_document")
    print document.Title()
    print document.CookedBody()
    return printed
    

在上面的例子中,可以看到getToolByName和getattr两个函数以及getPortalObject、Title和CookedBody三个方法的使用。更深一层:

  • getToolByName(obj, name, default=[])

    这个函数是从CMF模块中导入的. 通过给定的name和obj返回tool。

  • getattr(object, name[, default])

    是zope中的函数,它返回object指定属性名字的值。其中属性的名字必须是string类型。如果该对象没有属性与给定的名字相符,就会触发一个AttributeError,除非设定了第三个可选的参数。

  • hasattr(object,name)

    是zope中的函数,如果参数name字符串是指定对象object的属性,该函数就返回True,否则返回False。

  • getPortalObject(self)

    是从Products.CMFCore.URLTool.URLTool导入的类的方法。它返回Portal对象.

2.2   例2:如何使用Python脚本修改一个document的内容

通过修改例1中的脚本即可实现, 在print块中加这么一句话:

document.edit(text_format="html",
            text="<div><b>This is a new text...</b></div>")

edit方法使用如下:

  • edit(self, text_format, text, file="", safety_belt="")

    方法属于CMFDefaults.Document.Document类.其中参数text_format可以是html, structured-text或是plain格式. 参数text是文档的body内容.

2.3   例3:如何通过Python脚本复制和粘贴content对象

  • 创建一个文件夹,其id为my_folder. 注意这个操作过程在Plone2.1.1中稍有不同(Plone2.1.1中只须新文件夹的名字)

  • 下面的脚本,其功能是把刚才创建的my_document文档复制到新文件夹my_folder里:

    from Products.CMFCore.utils import getToolByName
    
    urltool = getToolByName(context, "portal_url")
    portal = urltool.getPortalObject()
    cb_copy_data = portal.manage_copyObjects(["my_document"])
    folder = getattr(portal, "my_folder")
    folder.manage_pasteObjects(cb_copy_data)
    

现在,应该能了解如何使用这两个(三个)重要的方法:

  • manage_copyObjects(self, ids=None, REQUEST=None,RESPONSE=None)

    从OFS.CopySupport.CopyContainer类中导入的.对对象进行引用,这些对象由粘贴面板中的id列表识别。

  • manage_cutObjects(self, ids=None, REQUEST=None)

    方法同上

  • manage_pasteObjects(self, cb_copy_data=None, REQUEST=None)

    从OFS.CopySupport.CopyContainer类中导入的.它将参数cb_copy_data所引用的对象进行粘贴,这个对象不是manage_copyObjects方法就是manage_cutObjects方法执行后的结果。

2.4   例4:如何通过Python脚本删除centent对象

下面的脚本将删除例1中创建的文档:

from Products.CMFCore.utils import getToolByName

urltool = getToolByName(context, "portal_url")
portal = urltool.getPortalObject()
portal.manage_delObjects(["my_document"])

该例中manage_delObjects是一个关键方法:

  • manage_delObjects(self, ids=[],REQUEST=None)

    从CMFPlone.PloneFolder.BasePloneFolder类中导入.该方法删除id列表中id所指的所有对象。

2.5   例5:如何通过Python脚本创建文档、文件和事件.

思考下面的脚本:

from Products.CMFCore.utils import getToolByName
urltool = getToolByName(context, "portal_url")
catalogtool = getToolByName(context, "portal_catalog")
portal = urltool.getPortalObject()
#创建一个文档
doc = portal.invokeFactory("Document", "test_doc")
document = getattr(portal, "test_doc")
#document.setTitle("Test document")
#document.setDescription("This is the description of a test document")
document.editMetadata(title="Test document",
                     description="This is the description of a test document",
                     subject="")
document.edit(text_format="html",
             text="<b>This is a test document!<b>")
#创建一个文件夹
fld = portal.invokeFactory("Folder", "test_folder")
folder = getattr(portal, "test_folder")
folder.setTitle("My Folder")
folder.setDescription("This is the description of a test folder")
catalogtool.refreshCatalog()
#创建一个事件
evt = folder.invokeFactory("Event", id="event")
event = getattr(folder, "event")
event.edit(title = "Foo",
          start_date="2003-09-18",
          end_date="2003-09-19",
          location="home",
          description="This is the description of a test event")
event.editMetadata(subject="Appointment")

接下来对上面例子中使用到的一些方法进行解释说明:

  • editMetadata(self, obj, allowDiscussion=None, title=None, subject=None, description=None, contributors=None, effective_date=None, expiration_date=None, format=None, language=None, rights=None, **kwargs)

    此方法定义在CMFPlone.PloneTool.PloneTool类中.

  • invokeFactory(self, type_name, id, RESPONSE=None, args, *kw)

    参数type_name将要创建的内容类型的名字.

    需要注意的是,函数中的参数id必须和portal_types中的一样(但有可能和Plone用户界面中显示的类型名字不一样).参数id是新对象的标识(简称):这个名字在以后生成URL地址用得着.这个方法还接受其它一些参数,这些参数是传递到对象的构造方法中的.其中最普遍的就是title参数.

  • refreshCatalog(self, clear=0)

    此方法对自身所包含的所有对象重新进行索引.

2.6   例6:如何通过Python脚本改变私有文档的状态

看下面的例子:

from Products.CMFCore.utils import getToolByName
urltool = getToolByName(context, "portal_url")
portal  = urltool.getPortalObject()
document = getattr(portal, "my_document")
review_state = document.portal_workflow.getInfoFor(document, "review_state", "")
print "The initial state is: " + review_state + "\n"

if not review_state in ("rejected", "retracted", "private"):
document.portal_workflow.doActionFor(document, "hide", comment="")
review_state = document.portal_workflow.getInfoFor(document, "review_state", "")
print "The final state is: " + review_state + "\n"
return printed

其中有两个需要注意的方法:

  • getInfoFor(self, ob, name, default=[], wf_id=None, args,*kw)

    这个方法从CMFCore.WorkflowTool.WorkflowTool类中导入.它返回与指定对象obj工作流相关的一个特别属性(由参数name指定).

  • doActionFor(self, ob, action, wf_id=None, args,*kw)

    此方法从CMFCore.WorkflowTool.WorkflowTool类中导入.它基于指定对象obj的工作流而执行一个行为.

3   使用Plone的catalog

Plone提供了一个叫Catalog的工具,可以在指定的Portal里搜索有关内容对象。

这一节开始学searchResults方法使用。首先,先介绍其使用方法,然后,举一个其通用的使用样例.

  • searchResults(self, REQUEST=None, **kw)

    这个方法定义在CMFCore.CatalogTool.CatalogTool类中,它通过调用 ZCatalog.searchResults方法执行操作.为根据用户的权限显示相应结果需要传递特定的参数。这些参数定义了如何对catalog实例进行查询。查询可以是关键词、映射或是REQUEST对象的一部分(比如从一个HTML文档发过来的请求).这个目录查询的索引可以是关键字名字、映射的关键字或是record对象的一个属性。

    • record对象的属性
      • query: 可以是对象的序列,也可以是单值传递给索引进行查询(强制性的).
      • operator: 当对值的序列进行查询时,指定查询结果的组合方法(可选,默认值:OR).值的范围:当关键字是索引和路径索引时是"AND,OR";文本索引时范围是" AND, OR, ANDNOT, NEAR".
      • range: 在一个索引上定义搜索范围(optional, default: off).可选值: min、max以及minmax。对对象进行搜索时,传递给查询的值中有大有小,min指定搜索的值比其中最大值都大,而max指定搜索的值不能超过值中的最小值,minmax是二者的结合.
      • level: 只用于路径索引.该属性指定了开始执行搜索的文件目录层次(可选,默认值: 0).

3.1   例7:如何按id降序排列所有文档

下面的脚本将把当前Portal中的所有文档按其id降序列出来。注意其中的参数,portal_type限制内容对象的类型,而sort_on和sort_order则指定排序的依据以及排序的方式:

results = context.portal_catalog.searchResults(sort_on="id",
                                              portal_type="Document",
                                              sort_order="reverse")
print [i.getObject().id for i in results]
return printed

在Plone2.1.1中,如果按title对结果进行排序,则调用searchResults时需要由原来的给参数sort_on设定值直接改为sortable_title参数(好像不需要赋值,自己猜的:-))

还有一点需要注意,getObject方法是由catalog中的搜索结果调用的,因为目录搜索出来的对象并不是content对象本身,而是一个包含了catalog schema信息的一个小的brain。可能正如你所想的,getObject方法返回的结果才是真正的content对象。

3.2   例8:如何列出所有含有单词"texto"的私有对象。

每个content对象都定义了自己信息的哪部分可以参与搜索。参数SearchableText就是对每个对象的那部分信息进行查找的。而参数review_state则是用来根据对象的状态进行过滤:

results = context.portal_catalog.searchResults(SearchableText="texto",
                                              review_state="private")
print [i.getObject().Title() for i in results]
return printed

3.3   例9:如何列出这样一些对象,他们的创建日期晚于2005/11/30,而且创建者是用户admin。

下面的脚本说明了如何把一个record对象传递给searchResults方法。参数Date接受一record对象,并按其中给定的日期查找创建时间在其之后的那些对象。参数Creator则过滤掉那些不是由用户admin创建的对象:

results = context.portal_catalog.searchResults
           (Date={"query": DateTime("2005/11/30"), "range": "min"},
            Creator="admin")
print [i.getObject().Title() for i in results]
return printed

4   控制用户和组

这一节里,讲述了如何使用Plone的API来创建用户和组,以及如何把用户分配到某个组里。

4.1   例10:如何添加一个用户.

看下面的代码:

id = "user"
fullname = "Emanuel Sartor"
password = "changeme"
email = "eman...@menttes.com"
roles = ("Manager",)
status=""
props = {"username": id,
        "fullname": fullname,
        "password": password,
        "email": email}
# 在Portal中添加一个新成员
try:
   context.portal_registration.addMember(id, password, roles,domains="",
                                         properties=props)
   status+="The user "+fullname+" was successfully added.\n"
except:
   status+="The user "+fullname+" was not added.\n"
print status
return printed
  • addMember(self, id, password, roles, domains,properties)

    这个方法用来创建一个PortalMember。

更多信息请看 RegistrationTool-class.html

4.2   例11:如何创建一用户组,取名Group0.

看下面的代码:

groupname = "Group0"
status=""
# 添加一新租
try:
   context.portal_groups.addGroup(groupname,)
   status += "The group was successfully added.\n"
except:
   status += "Manager group couldn't be added\n"
print status
return printed

4.3   例12:如何向某个组里添加用户

看下面的代码:

id = "usuario"
groupname = "Group0"
status=""
# 将用户user添加到groupname组
try:
   group = context.portal_groups.getGroupById(groupname)
   group.addMember(id)
   status += "The user "+id+" was successfully added to "+groupname+".\n"
except:
   status += "The user "+id+" was not added to group "+groupname+".\n"
print status
return printed

5   练习

5.1   练习1:

编写一个脚本实现如下功能:脚本接受两个字符串变量doc1和doc2,并假设这两个参数对应当前文件夹下的两个文档标识。脚本能修改doc2的内容,使其变为doc1和doc2两个文档内容的连接。

5.2   练习2:

编写一脚本实现如下功能:

  • 在plone的portal的根目录下创建一文件夹,题目为Events.
  • 将所有portal的Event类型对象移动到刚才创建的文件夹.
  • 把刚才portal的所有event对象状态修改为Published.

提示:

  • obj.aq_inner.aq_parent是obj的父对象.
  • obj.objectValues()显示obj所有的孩子.