ZCA 在Plone 中应用之一:对象发布和遍历
ZCA 在Plone 中应用之一:对象发布和遍历
http://www.315ok.org/blogfolder/573
http://www.315ok.org/logo.png
ZCA 在Plone 中应用之一:对象发布和遍历
ZCA 在Plone 中应用之一:对象发布和遍历
ZCA 在Plone 中应用(professinal plone4 development第九章 Zope[font=宋体][size=10.5pt]编程的核心概念[/size][/font])之一:对象发布
[align=left]既然我们已经学会了如何安装和定制化[font=Calibri]Plone[/font],那么就准备好了为我们的应用程序开发新的功能。这常常意味着要写新的内容类型,并以习惯的角度和形式来管理它们。有时,我们也需要创建表格和页面,这些表格和页面却与任何特定的内容类型没有关联。[/align]
[align=left]然而,在我们继续探讨[font=Calibri] OptiluxCinemas[/font]应用之前,我们先看看一些支撑[font=Calibri] Zope[/font]编程的核心原则。为[font=Calibri] Zope [/font]编写软件与其它[font=Calibri]web[/font]编程平台例如[font=Calibri]PHP[/font]或[font=Calibri]ASP.NET[/font]编写软件是有一点不同的。对[font=Calibri]Zope[/font]的核心概念有一个宏观的认识将会帮助你理解本书源码的一些例子,这些例子展现在本书的后面部分,并帮助把你所学的东西应用于你自己的程序。[/align]
[align=left]在本章节,我们将会学习:[/align]
[align=left]l 对象发表和遍历([font=Calibri]Objectpublishling and traversal[/font]),讲述[font=Calibri] Zope [/font]如何把[font=Calibri] URLs [/font]与内容对象关联起来[/align]
[align=left]l [font=Calibri]Persistence [/font]协议,[font=Calibri]Zope [/font]主要的数据存储机制[/align]
[align=left]l 目录,用来索引和查询内容[/align]
[align=left]l 接口,用来描述组件和它们的行为[/align]
[align=left]l 用[font=Calibri] ZCML [/font]的组件配置或者[font=Calibri]Grok[/font]配置技术[/align]
[align=left]l [font=Calibri]Utilities[/font],用来注册全局服务或者相关组件集合[/align]
[align=left]l 适配器,作用是在不改变对象本身的前提下,把附件的功能、行为应用于对象或者对象类[/align]
[align=left]l [font=Calibri]Views [/font]和[font=Calibri] viewlets[/font],[font=Calibri]Zope/Plone[/font]的显示表述组件[/align]
[align=left]l [font=Calibri]Events[/font],当内容发生增加、修改等操作时,它被用来执行惯例逻辑([font=Calibri]custom logic[/font])[/align]
[align=left][font=Calibri] [/font][/align]
[align=left]关于例子[/align]
[align=left]本章中的例子被故意的琐碎化,同时这些例子比较独立的解释一些概念。它们使用[font=Calibri] Python doctest [/font]句法,而且大多数可以在与本书相一致的[font=Calibri] optilux.codeexamples [/font]发布版中找到。[/align]
[align=left]通过下述方式可以建立和运行这些测试实例:[/align][font=宋体]
[align=left][size=3]对象发表和遍历([font=Calibri]Objectpublishling and traversal[/font])[/size][/align][size=3]
[align=left]参考:[font=Calibri]http://example.org/guitars/fender/strat.html[/font][/align]
[align=left]如果有一台服务于 HTML 页面的 Apache 服务器,那么 guitars 和fender 很有可能是目录,start.html 会是一个 HTML 的静态页面。如果服务器在使用 PHP,那样 start.html 也可能被称作 start.php,它将由 php代码构建的HTML页面组成,包括分支、迭代和其他逻辑。像ASP.NET 和Cold Fusion 的语言都是相似的。其他框架,诸如在Rails上的Ruby或者以URL为基础的Django,模仿于向一个指定的控制器发送一个请求。[/align]
[align=left]在Zope中,URL中的不同部分与对象发表相关联。例如,guitars 和fender 可以是容器对象,start.html 可以是 fender容器中一个对象的名字。start.html 也可以是一个属性名称或fender上的一个方法,一个视图(more on those in a littlewhile)或者一个方法别名。正如在第四章(定制化基础)中所学,部分路径也可以从 skin layer或父文件夹中获取。[/align]
[align=left]笔记:Jon Stahl,前Plone FoundationBoard首席曾说:“Data in Zope is like a flowchart. Data in other web applicationframeworks is like a spreadsheet”。[/align]
[align=left]决定发布哪一个对象的过程被称作“[font=Calibri]URL traversal[/font]”。[font=Calibri]Zope [/font]会在应用程序的根目录([font=Calibri]ZODB[/font]中的根节点,通常是[font=Calibri]Plone[/font]页面所在的地方)启动,并找出与[font=Calibri]URL[/font]第一部分相匹配的项目,在这种情景中,一个对象被称为[font=Calibri]guitars[/font]。然后[font=Calibri]Zope [/font]会匹配[font=Calibri]URL[/font]的下一个部分——[font=Calibri]fender[/font],并通过下述逻辑方法尝试找到它:[/align]
[align=left][font=Calibri]1.[/font] 如果[font=Calibri]fender[/font]是[font=Calibri]guitars[/font]对象上的一个属性,遍历将会从这个属性继续。[/align]
[align=left][font=Calibri]2.[/font] 如果[font=Calibri]fender[/font]能够被检索出是一个为[font=Calibri]guitars[/font]注册的视图,这个结果将会使用。[/align]
[align=left][font=Calibri]3.[/font] 如果[font=Calibri]guitars[/font]支持[font=Calibri]Python[/font]字典接口,同时[font=Calibri]fender[/font]是一个其中一个有效的键,那么这个键所对应的值将会被使用。[/align]
[align=left][font=Calibri]4.[/font] 如果[font=Calibri]fender[/font]不能被找到,一个[font=Calibri] NotFound [/font]异常将会被抛出,在[font=Calibri]Plone[/font]中将产生一个错误页面和[font=Calibri]404[/font]回应。[/align]
[align=left]当一个对象被找到时,[font=Calibri]Zope[/font]将会尝试调用它,并把它当做一个数据流,而这个数据流将会被发送给浏览器。页面模板和其他项实现了方法[font=Calibri]__call()__[/font]来处理这些数据流。事实上,一些对象实现了一个[font=Calibri]index_html()[/font]方法,它与[font=Calibri]__call()__[/font]相比,将优先调用。[/align]
[align=left][font=Calibri] [/font]当一个[font=Calibri]URL[/font]不明确包含一个页面模板、脚本、或者方法别名的名称来使用时,[font=Calibri]Plone [/font]有一些额外的逻辑来为一个内容对象决定默认视图。它以下种方式工作:
[font=Calibri] 1. [/font]如果一个对象提供指定的[font=Calibri]IBrowserDefault [/font]接口([font=Calibri] [/font]通常使用[font=Calibri]Products.CMFDynamicViewFTI.browserdefault.BrowserDefaultMixin[/font]作为一个基类),然后当前选定的[font=Calibri]layout[/font](在[font=Calibri]Plone[/font]中由[font=Calibri]Display[/font]菜单管理)或者,如果有可能的话,当前默认的页面(一个文件夹的默认视图的一个对象,也可以来自于显示菜单)将会被使用。[/align]
[align=left] [font=Calibri]2. [/font]如果对于一个对象的内容类型有一个(默认)方法别名,而且这个对象正被显示中,那么这个方法别名将会被查询去找到一个页面模板或者视图来显示。方法别名被定义在一个对象的[font=Calibri]FactoryType Information(FTI)[/font]中,而它在[font=Calibri]ZMI[/font]中的[font=Calibri]portal_types[/font]中。[/align]
[align=left] [font=Calibri]3. [/font]如果没有(默认)别名,但在[font=Calibri]FTI[/font](当在[font=Calibri]Plone[/font]中查看时,这个操作被视为一个对象的视图)中有一个视图操作,这将被理解为找出哪一个模板页面来显示。[/align] [font=宋体][size=10.5pt]因为在[/size][/font]Plone[font=宋体][size=10.5pt]中这个逻辑已经存在于基类中,这些基类从虚拟的角度看已经被所有的内容对象所使用,你几乎没有必要担心那些细节,但是,对于理解“属性和限制能够用来影响显示的内容”这一点很重要。[/size][/font]
Containment and URLs
[align=left]URL遍历通过containment hierarchy,这在ZODB中被称为对象图(object graph)。每一个对象都很明确的有一个父节点,所有的对象最终都是一个应用程序根的子节点。在所有常规内容对象中,有3种方法被用来引用containment hierarchy:[/align]
[align=left]l absolute_url() 返回一个对象的绝对URL值,这个绝对URL值能够安全地以一个连接向用户提供。它将考虑那些被服务器管理的URL值,包括任何虚拟机运行的URL。[/align]
[align=left]l absolute_url_path()返回上述方法的部分值(路径段),那就是说,该方法的值不含有主机名和协议。[/align]
[align=left]l getPhysicalPath() 返回一个元组,它包含路径的每一个元素,所有通向应用程序根的通道。
[/align]
[align=left]笔记:注意到在大多数主机场景中,应用程序以这种方式存在:Plone网站以服务器的根出现,甚至对于Zope,it lives one level down,在Zope 应用程序根的内部。因此,从物理路径来手工构造URLs或者由方法absolute_url()获取URLs值是危险的。然而,物理路径作为对象的标识是有用的(直到那些对象被用户删除),同时在构造目录查询时被高频的使用。转换物理元组的一个通常构造是‘/’.join(context.getPhysicalPath()) 。特别地,当搜索一个物理路径的路径索引时,我们所传递给目录的就是上述值。[/align]
[align=left] 因此,如果有一个对象taylor在一个Plone站点(guitarsrus)的文件夹(acoustic)中找到,那么它的绝对路径就是http://localhost:8080/ guitarsrus/ acoustic/taylor。正如在第17章(安装一个Production服务器)中我们将要学习到,这台Production服务器也可以明确地是一台虚拟主机。例如假设URL值http://example.org/acoustic/taylor。在这两种情景中,物理路径将会是/guitarsrus/acoustic/taylor/,因此taylor.getPhysicalPath()将会返回一个元组(‘’,‘guitarsrus’,‘acoustic’,‘taylor’)。[/align]
[align=left] 在ZODB对象图中,节点(或者能够充当容器的对象)被当作folderish对象,叶子被称为non-folderish对象。一个folderish对象有一个被设置为True的特殊属性isPrinicipiaFolderish;non-folderish对象将这个属性设置为False。[/align]
[align=left]笔记:为什么是isPrincipiaFolderish?很早很早之前,Zope被认为是Principia。[/align]
[align=left] Plone也引进了非结构化文件夹的概念。它其实仅仅是一个folderish对象,但在Plone的用户接口中不表现为一个文件夹。非结构化文件夹通过提供接口Products.CMFPlone.interfaces.INonStructualFolder来发布。有时你将会看到结构化文件夹被用来引用真正的文件夹。[/align]
[align=left] 在所有事物之前,为那个属性重新调用遍历检索。在Zope2中,folderish对象,尤其是那些继承于OFS.ObjectManager,以这种方式来实现__getattr__():任何获取一个属性的尝试,如果它没有在folderish对象本身中直接找到,那么这将会导致检索contained 对象发生。每一个containable 对象对于它所属的文件夹都有一个自己唯一的ID。这个值可以在这个对象的getId()方法中管理。[/align]
[align=left] 或许你已经猜想到,这在文件夹中有时候会导致contained对象、方法、属性间的命名冲突。在Plone中,那些会导致冲突的名字被处理成“保留”,同时任何添加的对象将会被重命名来产生一个没有二义性的id。在最新的代码中,更优先使用mapping semantics,即__getitem()__,而不是attribute semantics,对于model containment。[/align][font=宋体] 这里有一个实例,使用[/font]mapping semantics和getId()从一个对象所属的容器来检索这个对象。首先,在portal中,我们用ID guitars 创建一个文档,然后将这个ID作为那个文件夹的一个属性。方法invokeFactory(),它来自于Products.CMFCore.PortalFolder,可被用于任何标准文件夹, 并拿一个portal类型名字(在portal_types工具中找到)和一个ID作为参数,还有其它初始化的属性,以键值参数形式传递。
[align=left][font=Calibri]request = context.REQUEST[/font][/align]在这里,这个REQUEST变量是从内容中获取。先前的构造随着时间会被否决,尽管,支持使用aq_get():
[align=left]有时,我们需要直接控制acquisition链。当我们尝试使一个对象表现的像是从其他地方而不是从它本身所存在的地方(作为一个安全模式的一部分或是fool implicit acquisition)呈现时,这种需求(直接控制acquisition)就会发生。为了完成这个,我们可以使用任何acquisition-aware 对象都包含的__of__()方法。[/align]
[align=left] 在接下来的例子中,我们在内存中实例化一个临时内容对象,不把它连接至containment hierarchy,然后将它打包进portal的acquisition内容中:[/align][font=宋体]
[align=left]在页面模板或行为(即有[font=Calibri]TALES[/font]表达式出现的地方),有时你会看到诸如[font=Calibri]context/guitars/fender[/font]的表达式。这就是使用与特定对象相关联的路径遍历,同时给那个为[font=Calibri]URL[/font]遍历而描述的表达式雇佣一个类似的[font=Calibri]heuristic[/font]。[font=Calibri]Acquisition [/font]在这里也被考虑进来。[/align]
[align=left][font=Calibri] [/font]从一个与内容对象相关联的[font=Calibri]Python[/font]代码中,能够调用路径遍历,使用如下句法:[/align]
[/size]
[/font]
[align=left]既然我们已经学会了如何安装和定制化[font=Calibri]Plone[/font],那么就准备好了为我们的应用程序开发新的功能。这常常意味着要写新的内容类型,并以习惯的角度和形式来管理它们。有时,我们也需要创建表格和页面,这些表格和页面却与任何特定的内容类型没有关联。[/align]
[align=left]然而,在我们继续探讨[font=Calibri] OptiluxCinemas[/font]应用之前,我们先看看一些支撑[font=Calibri] Zope[/font]编程的核心原则。为[font=Calibri] Zope [/font]编写软件与其它[font=Calibri]web[/font]编程平台例如[font=Calibri]PHP[/font]或[font=Calibri]ASP.NET[/font]编写软件是有一点不同的。对[font=Calibri]Zope[/font]的核心概念有一个宏观的认识将会帮助你理解本书源码的一些例子,这些例子展现在本书的后面部分,并帮助把你所学的东西应用于你自己的程序。[/align]
[align=left]在本章节,我们将会学习:[/align]
[align=left]l 对象发表和遍历([font=Calibri]Objectpublishling and traversal[/font]),讲述[font=Calibri] Zope [/font]如何把[font=Calibri] URLs [/font]与内容对象关联起来[/align]
[align=left]l [font=Calibri]Persistence [/font]协议,[font=Calibri]Zope [/font]主要的数据存储机制[/align]
[align=left]l 目录,用来索引和查询内容[/align]
[align=left]l 接口,用来描述组件和它们的行为[/align]
[align=left]l 用[font=Calibri] ZCML [/font]的组件配置或者[font=Calibri]Grok[/font]配置技术[/align]
[align=left]l [font=Calibri]Utilities[/font],用来注册全局服务或者相关组件集合[/align]
[align=left]l 适配器,作用是在不改变对象本身的前提下,把附件的功能、行为应用于对象或者对象类[/align]
[align=left]l [font=Calibri]Views [/font]和[font=Calibri] viewlets[/font],[font=Calibri]Zope/Plone[/font]的显示表述组件[/align]
[align=left]l [font=Calibri]Events[/font],当内容发生增加、修改等操作时,它被用来执行惯例逻辑([font=Calibri]custom logic[/font])[/align]
[align=left][font=Calibri] [/font][/align]
[align=left]关于例子[/align]
[align=left]本章中的例子被故意的琐碎化,同时这些例子比较独立的解释一些概念。它们使用[font=Calibri] Python doctest [/font]句法,而且大多数可以在与本书相一致的[font=Calibri] optilux.codeexamples [/font]发布版中找到。[/align]
[align=left]通过下述方式可以建立和运行这些测试实例:[/align][font=宋体]
$cd optilux.codeexamples $python2.6 bootstrap.py --distribute $bin/buildout $bin/test[align=left][size=3]如果你对[font=Calibri] Zope [/font]和[font=Calibri]Plone [/font]编程还是新手,那么你会在第一次阅读本章时感觉有些不易。如果是这样的话,请不要灰心!可以先跳过本章,当你在接下去的章节中再次遇到不明白的概念时,可以返回本章来解决。或者,你可能想去研究并学习[font=Calibri] optilux.codeexamples [/font]包。[/size][/align]
[align=left][size=3]对象发表和遍历([font=Calibri]Objectpublishling and traversal[/font])[/size][/align][size=3]
[align=left]参考:[font=Calibri]http://example.org/guitars/fender/strat.html[/font][/align]
[align=left]如果有一台服务于 HTML 页面的 Apache 服务器,那么 guitars 和fender 很有可能是目录,start.html 会是一个 HTML 的静态页面。如果服务器在使用 PHP,那样 start.html 也可能被称作 start.php,它将由 php代码构建的HTML页面组成,包括分支、迭代和其他逻辑。像ASP.NET 和Cold Fusion 的语言都是相似的。其他框架,诸如在Rails上的Ruby或者以URL为基础的Django,模仿于向一个指定的控制器发送一个请求。[/align]
[align=left]在Zope中,URL中的不同部分与对象发表相关联。例如,guitars 和fender 可以是容器对象,start.html 可以是 fender容器中一个对象的名字。start.html 也可以是一个属性名称或fender上的一个方法,一个视图(more on those in a littlewhile)或者一个方法别名。正如在第四章(定制化基础)中所学,部分路径也可以从 skin layer或父文件夹中获取。[/align]
[align=left]笔记:Jon Stahl,前Plone FoundationBoard首席曾说:“Data in Zope is like a flowchart. Data in other web applicationframeworks is like a spreadsheet”。[/align]
[align=left]决定发布哪一个对象的过程被称作“[font=Calibri]URL traversal[/font]”。[font=Calibri]Zope [/font]会在应用程序的根目录([font=Calibri]ZODB[/font]中的根节点,通常是[font=Calibri]Plone[/font]页面所在的地方)启动,并找出与[font=Calibri]URL[/font]第一部分相匹配的项目,在这种情景中,一个对象被称为[font=Calibri]guitars[/font]。然后[font=Calibri]Zope [/font]会匹配[font=Calibri]URL[/font]的下一个部分——[font=Calibri]fender[/font],并通过下述逻辑方法尝试找到它:[/align]
[align=left][font=Calibri]1.[/font] 如果[font=Calibri]fender[/font]是[font=Calibri]guitars[/font]对象上的一个属性,遍历将会从这个属性继续。[/align]
[align=left][font=Calibri]2.[/font] 如果[font=Calibri]fender[/font]能够被检索出是一个为[font=Calibri]guitars[/font]注册的视图,这个结果将会使用。[/align]
[align=left][font=Calibri]3.[/font] 如果[font=Calibri]guitars[/font]支持[font=Calibri]Python[/font]字典接口,同时[font=Calibri]fender[/font]是一个其中一个有效的键,那么这个键所对应的值将会被使用。[/align]
[align=left][font=Calibri]4.[/font] 如果[font=Calibri]fender[/font]不能被找到,一个[font=Calibri] NotFound [/font]异常将会被抛出,在[font=Calibri]Plone[/font]中将产生一个错误页面和[font=Calibri]404[/font]回应。[/align]
[align=left]当一个对象被找到时,[font=Calibri]Zope[/font]将会尝试调用它,并把它当做一个数据流,而这个数据流将会被发送给浏览器。页面模板和其他项实现了方法[font=Calibri]__call()__[/font]来处理这些数据流。事实上,一些对象实现了一个[font=Calibri]index_html()[/font]方法,它与[font=Calibri]__call()__[/font]相比,将优先调用。[/align]
[align=left][font=Calibri] [/font]当一个[font=Calibri]URL[/font]不明确包含一个页面模板、脚本、或者方法别名的名称来使用时,[font=Calibri]Plone [/font]有一些额外的逻辑来为一个内容对象决定默认视图。它以下种方式工作:
[font=Calibri] 1. [/font]如果一个对象提供指定的[font=Calibri]IBrowserDefault [/font]接口([font=Calibri] [/font]通常使用[font=Calibri]Products.CMFDynamicViewFTI.browserdefault.BrowserDefaultMixin[/font]作为一个基类),然后当前选定的[font=Calibri]layout[/font](在[font=Calibri]Plone[/font]中由[font=Calibri]Display[/font]菜单管理)或者,如果有可能的话,当前默认的页面(一个文件夹的默认视图的一个对象,也可以来自于显示菜单)将会被使用。[/align]
[align=left] [font=Calibri]2. [/font]如果对于一个对象的内容类型有一个(默认)方法别名,而且这个对象正被显示中,那么这个方法别名将会被查询去找到一个页面模板或者视图来显示。方法别名被定义在一个对象的[font=Calibri]FactoryType Information(FTI)[/font]中,而它在[font=Calibri]ZMI[/font]中的[font=Calibri]portal_types[/font]中。[/align]
[align=left] [font=Calibri]3. [/font]如果没有(默认)别名,但在[font=Calibri]FTI[/font](当在[font=Calibri]Plone[/font]中查看时,这个操作被视为一个对象的视图)中有一个视图操作,这将被理解为找出哪一个模板页面来显示。[/align] [font=宋体][size=10.5pt]因为在[/size][/font]Plone[font=宋体][size=10.5pt]中这个逻辑已经存在于基类中,这些基类从虚拟的角度看已经被所有的内容对象所使用,你几乎没有必要担心那些细节,但是,对于理解“属性和限制能够用来影响显示的内容”这一点很重要。[/size][/font]
Containment and URLs
[align=left]URL遍历通过containment hierarchy,这在ZODB中被称为对象图(object graph)。每一个对象都很明确的有一个父节点,所有的对象最终都是一个应用程序根的子节点。在所有常规内容对象中,有3种方法被用来引用containment hierarchy:[/align]
[align=left]l absolute_url() 返回一个对象的绝对URL值,这个绝对URL值能够安全地以一个连接向用户提供。它将考虑那些被服务器管理的URL值,包括任何虚拟机运行的URL。[/align]
[align=left]l absolute_url_path()返回上述方法的部分值(路径段),那就是说,该方法的值不含有主机名和协议。[/align]
[align=left]l getPhysicalPath() 返回一个元组,它包含路径的每一个元素,所有通向应用程序根的通道。
[/align]
[align=left]笔记:注意到在大多数主机场景中,应用程序以这种方式存在:Plone网站以服务器的根出现,甚至对于Zope,it lives one level down,在Zope 应用程序根的内部。因此,从物理路径来手工构造URLs或者由方法absolute_url()获取URLs值是危险的。然而,物理路径作为对象的标识是有用的(直到那些对象被用户删除),同时在构造目录查询时被高频的使用。转换物理元组的一个通常构造是‘/’.join(context.getPhysicalPath()) 。特别地,当搜索一个物理路径的路径索引时,我们所传递给目录的就是上述值。[/align]
[align=left] 因此,如果有一个对象taylor在一个Plone站点(guitarsrus)的文件夹(acoustic)中找到,那么它的绝对路径就是http://localhost:8080/ guitarsrus/ acoustic/taylor。正如在第17章(安装一个Production服务器)中我们将要学习到,这台Production服务器也可以明确地是一台虚拟主机。例如假设URL值http://example.org/acoustic/taylor。在这两种情景中,物理路径将会是/guitarsrus/acoustic/taylor/,因此taylor.getPhysicalPath()将会返回一个元组(‘’,‘guitarsrus’,‘acoustic’,‘taylor’)。[/align]
[align=left] 在ZODB对象图中,节点(或者能够充当容器的对象)被当作folderish对象,叶子被称为non-folderish对象。一个folderish对象有一个被设置为True的特殊属性isPrinicipiaFolderish;non-folderish对象将这个属性设置为False。[/align]
[align=left]笔记:为什么是isPrincipiaFolderish?很早很早之前,Zope被认为是Principia。[/align]
[align=left] Plone也引进了非结构化文件夹的概念。它其实仅仅是一个folderish对象,但在Plone的用户接口中不表现为一个文件夹。非结构化文件夹通过提供接口Products.CMFPlone.interfaces.INonStructualFolder来发布。有时你将会看到结构化文件夹被用来引用真正的文件夹。[/align]
[align=left] 在所有事物之前,为那个属性重新调用遍历检索。在Zope2中,folderish对象,尤其是那些继承于OFS.ObjectManager,以这种方式来实现__getattr__():任何获取一个属性的尝试,如果它没有在folderish对象本身中直接找到,那么这将会导致检索contained 对象发生。每一个containable 对象对于它所属的文件夹都有一个自己唯一的ID。这个值可以在这个对象的getId()方法中管理。[/align]
[align=left] 或许你已经猜想到,这在文件夹中有时候会导致contained对象、方法、属性间的命名冲突。在Plone中,那些会导致冲突的名字被处理成“保留”,同时任何添加的对象将会被重命名来产生一个没有二义性的id。在最新的代码中,更优先使用mapping semantics,即__getitem()__,而不是attribute semantics,对于model containment。[/align][font=宋体] 这里有一个实例,使用[/font]mapping semantics和getId()从一个对象所属的容器来检索这个对象。首先,在portal中,我们用ID guitars 创建一个文档,然后将这个ID作为那个文件夹的一个属性。方法invokeFactory(),它来自于Products.CMFCore.PortalFolder,可被用于任何标准文件夹, 并拿一个portal类型名字(在portal_types工具中找到)和一个ID作为参数,还有其它初始化的属性,以键值参数形式传递。
>>> name = self.folder.invokeFactory('Document', 'favorites', title=u"Favorite Guitars")
•••
>>> obj = portal[name]
>>> obj.getId()
'favorites'
为找到一个对象的父节点,我们可以使用aq_parent()函数:>>> from Acquisition import aq_parent >>> aq_parent(obj) is portal True >>> obj.aq_parent is portal True[align=left][font=Calibri]Acquisitionchains[/font][/align]或许你已经从上述例子中猜到,containment hierarchies 被连接至Zope acquisition。由第四章(基本定制)我们知道,获取值如何使对象上的属性来自于父节点——通常通过containment hierarchy——,在某种程度上,被称为acquisition chain。对于一个对象,拥有超过一个acquisition chain是允许的。例如,稍后的例子中我们将会看到,一个对象已被明确地‘打包’进去一个新的chain。因此,当我们想使用containmenthierarchies时,请求那个最内部(innermost)chain会更加安全。
>>> from Acquisition import aq_parent, aq_inner >>> containment_parent = aq_parent(aq_inner(obj))[align=left]有两种基类可以用来表明一个对象是继承了[font=Calibri]acquisition[/font]机制。大多数内容对象继承于Acquisition.Implicit,这就意味着对象的__getattr__()方法通过这一继承而自动得以实现:如果一个属性在对象中没有被发现,在最外层acquisition链中它的父节点将会被查询。这就是我们在第四章中看到的acquisition类型。提请注意,这个规则不仅适用于containment,而且也适用于代码中被定义的属性。当使用implicit acquisition(例如,在URL遍历中)时,如果请求的属性名称与这个对象的名字匹配,那么父文件夹中的该对象将被返回。[/align] 如果我们需要临时“关闭”implicitacquisition,我们可以使用aq_explicit属性,它由Acquisition.Implict提供。当判断引用的对象是否相等时,使用函数aq_base来忽略acquisitionwrapper并获取原始的生对象。 如果没有aq_base,我们将比较的将是acquisitionwrapper本身,而不是被它们打包的对象。
>>> from Acquisition import aq_base >>> favorites = folder['favorites'] >>> acquiring = folder.favorites >>> aq_base(acquiring) is aq_base(favorites) True >>> non_acquiring = getattr(folder.aq_explicit, 'favorites', None) >>> non_acquiring is None True[align=left][font=Calibri]acquisition[/font]的另一种类型是[font=Calibri]explicit acquisition[/font],由[font=Calibri]Acquisition.Explicit [/font]基类提供。在这里,属性的获取不像Acquisition.Implicit一样,来自父亲对象。但[font=Calibri]Explicit acquisition [/font]仍然维护一个[font=Calibri]acquisition [/font]链,这一点对于[font=Calibri]Zope[/font]的安全和[font=Calibri]containment[/font]至关重要。诸如absolute_url()和getPhysicalPath()方法,它们依赖[font=Calibri]acquisition[/font]来构造一个路径,才能够继续工作。[/align] 在遍历或者代码检查中,一个对象的acquisition chain 被动态构造。如果一个在acquisition-aware上对象的属性返回另外一个acquisition-aware对象,那么前一个对象是即时acquisition链,该链是返回对象的父节点。这个acquisition链能够使用如下函数检查:
>>> from Acquisition import aq_chain, aq_inner >>> aq_chain(portal.favorites) # doctest: +ELLIPSIS [<ATDocument at /plone/favorites>, <PloneSite at /plone>, <Application at >, <ZPublisher.BaseRequest.RequestContainer object at ...>] >>> aq_chain(folder.favorites) # doctest: +ELLIPSIS [<ATDocument at /plone/favorites used for /plone/folder>, <ATFolder at /plone/folder>, <PloneSite at /plone>, <Application at >, <ZPublisher. BaseRequest.RequestContainer object at ...>][align=left]注意到为favorites 的[font=Calibri]acquisition[/font]链是如何不同的取决于它是从[font=Calibri]portal[/font]或者[font=Calibri]folder[/font]获取。我们能明确的请求最内部链,通常该链是[font=Calibri]containment chain[/font]。[/align]
>>> aq_chain(aq_inner(portal.favorites)) # doctest: +ELLIPSIS [<ATDocument at /plone/favorites>, <PloneSite at /plone>, <Application at >, <ZPublisher.BaseRequest.RequestContainer object at ...>][align=left]另外,注意到在由[font=Calibri]URL[/font]遍历返回的[font=Calibri]acquisition[/font]链中,最终的父文件夹是一个特殊的[font=Calibri]request container[/font]对象。有时,你会看到这个:[/align]
[align=left][font=Calibri]request = context.REQUEST[/font][/align]在这里,这个REQUEST变量是从内容中获取。先前的构造随着时间会被否决,尽管,支持使用aq_get():
from Acquisition import aq_get request = aq_get(context, 'REQUEST')[align=left]这个达到同样的目的,但是明确的调用acquisition。它既适用于隐式又适用于非隐式的acquisition-aware对象。[/align]
[align=left]有时,我们需要直接控制acquisition链。当我们尝试使一个对象表现的像是从其他地方而不是从它本身所存在的地方(作为一个安全模式的一部分或是fool implicit acquisition)呈现时,这种需求(直接控制acquisition)就会发生。为了完成这个,我们可以使用任何acquisition-aware 对象都包含的__of__()方法。[/align]
[align=left] 在接下来的例子中,我们在内存中实例化一个临时内容对象,不把它连接至containment hierarchy,然后将它打包进portal的acquisition内容中:[/align][font=宋体]
>>> from Products.ATContentTypes.content.document import ATDocument
>>> temp_document = ATDocument('temp_document')
>>> aq_chain(temp_document)
[<ATDocument at temp_document>]
>>> aq_chain(temp_document.__of__(portal)) # doctest: +ELLIPSIS
[<ATDocument at /plone/temp_document>, <PloneSite at /plone>,
<Application at >, <ZPublisher.BaseRequest.RequestContainer object at...>]
[align=left]笔记:通过[font=Calibri]__of__()[/font]进行的手工[font=Calibri]acquisition[/font]打包是一个较新的概念,也是一个你基本上用不到的概念。[/align][align=left]路径遍历:[/align][align=left]在页面模板或行为(即有[font=Calibri]TALES[/font]表达式出现的地方),有时你会看到诸如[font=Calibri]context/guitars/fender[/font]的表达式。这就是使用与特定对象相关联的路径遍历,同时给那个为[font=Calibri]URL[/font]遍历而描述的表达式雇佣一个类似的[font=Calibri]heuristic[/font]。[font=Calibri]Acquisition [/font]在这里也被考虑进来。[/align]
[align=left][font=Calibri] [/font]从一个与内容对象相关联的[font=Calibri]Python[/font]代码中,能够调用路径遍历,使用如下句法:[/align]
>>> setRoles(portal, TEST_USER_NAME, ('Manager',))
>>> guitarsId = portal.invokeFactory('Folder', 'guitars')
>>> fenderId = portal[name].invokeFactory('Document', 'fender')
>>> portal.unrestrictedTraverse('guitars/fender')
<ATDocument at /plone/guitars/fender>
>>> fenderPath = \
... '/'.join(portal[guitarsId][fenderId].getPhysicalPath())
>>> fenderPath
'/plone/guitars/fender'
>>> portal.unrestrictedTraverse(fenderPath)
<ATDocument at /plone/guitars/fender>
[align=left][font=宋体][size=9pt]同时也有一个方法[/size][/font]restrictedTraverse(),它有相同的语义,但它为每一个遍历步骤运行安全检查,与skin layer[font=宋体][size=10.5pt]页面模板和脚本有着相同的方式,即页面模板和脚本屈服于‘不可信代码’的安全检查,而该概念在第六章(安全和工作流)中有解释。[/size][/font][/align][align=left]笔记:在页面模板中的路径表达式也能够遍历字典和链表中的元素。这条规则不适用于restrictedTraverse()/unrestrictedTraverse()方法。[/align][/font][/size]
[/font]