Plone的事件体系

Plone的事件体系
Zope3带给Zope2最好的东西是Event 系统。 这使得可以容易地从任何地方发出事件或者注册事件的预定者。Event是同步的——触发代码将被锁定直到所有event handlers完成;并且event handlers不可排序,不能保证event handlers 被调用的顺序。
定义event的新类型非常简单,只需要一个标示event的接口和一个实际的类实现。
>>> from zope.interface import Interface, Attribute
    >>> class INewGigEvent(Interface):
    ...       """An event signaling that there's a new gig in town
    ...       """
    ...
    ...       band = Attribute("Name of the band")
    >>> from zope.interface import implements
    >>> class NewGigEvent(object):
    ...       implements(INewGigEvent)
    ...
    ...       def __init__(self, band):
    ...            self.band = band
上述代码就是一个完整的event类型,然后我们可以为这个事件类型定义一个预定者,这应当是一个简单的可调用对象,当被调用时,将把event对象作为参数传进来。
    >>> from zope.component import adapter
    >>> @adapter(INewGigEvent)
    ... def invite_friends(new_gig):
    ...       print "Hey guys, let's go see", new_gig.band
这个@adapter标识符用于标明被操纵的event类型。
Event预定者被用ZCML注册:

 <subscriber handler=".events.invite_friends" />
如果我们不用@adapter申明,我们也可在zcml中显式地指定事件的类型,如下:
    <subscriber
          for=".interfaces.INewGigEvent"
          handler=".events.invite_friends"
          />
一旦触发该事件,所有预定该事件的预定者将被调用而执行代码,象下面样例一样简单:
     >>> from zope.event import notify
     >>> notify(NewGigEvent("The Gypsy Sun and Rainbow Band"))
     Hey guys, let's go see The Gypsy Sun and Rainbow Band
这里不需要显式注册事件的类型,因为事件预定者是基于该事件对象提供的接口而找到的。这也意味着如果一个事件对象提供的接口继承自一个基本接口,并且这个基接口也有更通常的预定者,那么这些预定者也将被调用。
对象事件
Zope 和 Plone 触发一些通常的事件,我们称之为对象事件,如当一个内容对象在容器中被添加、删除或者移动,或者当被首次创建、修改、拷贝时,就触发对象事件。这些事件都生成于zope.component.interfaces.IObjectEvent.
我们为对象事件注册预定者时,和其他类型的事件一样,但通常对于对象事件,我们在触发它时,往往针对特定类型的对象。也就是说我们在注册对象事件预定者时,往往针对对象类型和事件类型两个接口:
     >>> class IBand(Interface):
     ...       """A band
     ...       """
     ...
     ...       name = Attribute("The name of the band")
     >>> class Band(object):
     ...       implements(IBand)
     ...
     ...       def __init__(self, name):
     ...           self.name = name
    >>> from zope.lifecycleevent.interfaces import IObjectModifiedEvent
    >>> @adapter(IBand, IObjectModifiedEvent)
    ... def band_changed(band, event):
    ...       assert band == event.object # At least normally, see below
    ...       print "Changes to the lineup in", band.name
ZCML配置:

  <subscriber handler=".events.band_changed" />
或者,如果没有使用@adapter标识,注册如下,两个接口间用空格分开:
    <subscriber
         for=".interfaces.IBand
                 zope.lifecycleevent.interfaces.IObjectModifiedEvent"
         handler=".events.band_changed"
         />
对象事件触发和普通事件触发没有什么不同,但我们必须确保正确构造该对象事件的实例,以便event.object索引到正确的内容对象:
   >>> from zope.lifecycleevent import ObjectModifiedEvent
    >>> beatles = Band("The Beatles")
    >>> notify(ObjectModifiedEvent(beatles))
Changes to the lineup in The Beatles
因为我们现在依赖两个接口,这个预定者被传输进来两个对象:内容对象和事件对象。在大多数情况下,象上面样例代码中的断言一样,内容对象被传输进来作为首个参数,它和event.object应该是同一个东西。
然而,容器对象的事件会被重新分发到该容器里面所有的内容。例如,如果一个文件夹被移动或删除,在这个文件夹里面的对象也将被通知触发相应的事件。在这种情况下,第二个参数event,其event.object将索引到这个原始的文件夹,而第一个参数将是该文件夹的子对象,并且该子对象是当前正在被处理。
发现在zope.app.container.interfaces接口的Container events被OFS.ObjectManager大量触发,这些Container events由于每一个都支持IObjectMovedEvent接口,实际处理时,需要一些技巧。事件过程涉及到oldParent, oldName,newParent和 newName这些属性所依靠的对象在哪里,它过去被调用什么,它现在的位置在哪里,它现在被调用什么等等依次问题。在一个IObjectAddedEvent中,oldParent 和 oldName 都为None,在IObjectRemovedEvent中方向是相反的,即有oldParent 和 oldName,而newParent和 newName都为None。这意味着如果你为IObjectMovedEvent预定了subscriber,该subscriber将在对象被renamed, moved, added, 或者 removed时都被调用。如果你想仅仅反应对象被实际移动才触发IObjectMovedEvent,应该在代码中显式地检查是否签署的四个变量是None。
给定通常的,以对象为中心Plone用户自然接口的环境下,object events是相对普遍的。
一些更普遍的
object events(对象事件)类型是:
  • 上述的在zope.app.container.interfaces接口的container events
  • life-cycle事件IObjectCreatedEvent 和 IObjectModifiedEvent
  • 来自zope.lifecycleevent.interfaces接口,当对象被首次创建并后序被修改时从视图代码触发的,在Products.Archetypes.interfaces接口包括的 Archetypes-specific events
  • 继承自IObjectModifiedEvent的IObjectInitializedEvent 和 IObjectEditedEvent。这些事件实际上是Archetypes objects被创建在ZODB中,但还没有实际的内容数据被写入时。
  • Workflow events like Products.CMFCore.interfaces
  • IActionSucceededEvent 和更低级别的 Products.DCWorkflow.IAfterTransitionEvent
通过从 zope.component.interfaces.IObjectEvent继承和实现,可以方便地创建自己的object events。如果想弄清楚对象事件怎样被分发,可以仔细查阅 zope.component.event模块和objectEventNotify()方法。


设置