Events
Zope 3 introduces a clean way to define event. Zope 2 didn't give a good way to play with events (implementing a specific method name in a class isn't the best way to handle events). Just let's forget Zope 2 and dive directly into Zope 3.
First let's get clearer what we mean by event. A definition first:
An event is something that takes place; an occurrence and arbitrary point in time.
In computer science, an event indicates something has happened. Zope 3 definition of event isn't far from the definition you might know if you have programmed a GUI. A user clicks on a button, an event is sent to the program, the program catches the event and does what it has to.
Zope 3 wants to keep events registration and event processing outside the base application. Which (as for adapters and views) helps a lot to define your application without having to directly define inside your base class how to play with events (as Zope 2 did with the manageAfterAdd method...).
So what kind of things could we handle in a content management system ?
- an object has been created (zope.app.container.interfaces.IObjectAddedEvent)
- an object has been modified (zope.app.event.interfaces.IObjectModifiedEvent)
- an object has been removed (zope.app.container.interfaces.IObjectRemovedEvent)
- an object has been copied (zope.app.event.interfaces.IObjectCopiedEvent)
- an object has been moved (zope.app.container.interfaces.IObjectMovedEvent)
- an object container has been modified (zope.app.container.interfaces.IContainerModifiedEvent)
- an object's metadata has been modified (zope.app.event.interfaces.IObjectAnnotationsModifiedEvent)
- ... (others but really specific events (mails, traversing, zope startup)
We have to register an event on a certain type of object, just like we registered an adapter for a certain type of object. In Zope 3, an object which registers for an event is called an "event subscriber", most of the time a subscriber will have a handler which is a callback function that will do some stuff on the object.
All these events are of course defined by their interface (as you see above) and these interfaces we will be used to register the event and create our subscriber.
Let's take a simple event, once a document is created we want to get an email which says that says so. We don't care here about telling the container folder that our document has been created.
First thing we need to do is to define the code of the object event subscriber. You see here that an event subscriber handler takes two parameter,
first is the object for which the event has been sent, second is the event (we might try to look from where the event has been sent):
>>> from Products.CMFCore.utils import getToolByName >>> def sendMailUpponDocumentCreation(document, event): ... mailhost = getToolByName(document, 'MailHost') ... msg = "A Document %s has been created in %s " % (document.getId(), document.absolute_url()) ... mto = 'somebody@somewhere.org' ... mfrom = 'plone@plone.org' ... msubject = 'Document Created' ... mailhost.send(msg, mto, mfrom, msubject=subject)
And that's all for the event subscriber. Now we need to put some glue around the Document content type (using its interface as always - this allows us to use marker interfaces there too!) and the created event will use our new function. Five 1.2 and Five 1.3 describe event in a different way, let's say we use Five 1.2:
<configure
xmlns="http://namespaces.zope.org/zope"
>
<subscriber
for="Products.ATContentTypes.interface.IATDocument
zope.app.container.interfaces.IObjectAddedEvent"
factory="Products.ATContentTypes.content.document.sendMailUpponDocumentCreation"
/>
</configure>
(in Five 1.3 just replace the factory attribute by the handler attribute - that's all).
So as you see an event subscriber is defined by the subscriber directive
(this directive is in the zope namespaces). Watch out for the two interfaces in
the for attribute. The first one has to be the interface which describe the
object. The second one describe the event (and is one of the interface we gave
in the event list above). Factory describe the factory (often a function)
that we use for the event for the object we want.
Again the directive is clear:
for="A
B"
factory="C"
which means
for all the object that provide the interface A
I want to do C when event B occur
Note that we can have more than one subscriber for a certain content interface. This will allow for example to email once I create my document but also send a message to the container that contains the new document.
More about Zope 2 backward compatibility
As we said earlier in Zope 2 style, we were using methods in the class like manage_afterAdd, manage_beforeDelete, manage_afterClone ... to manage the event. In this method we needed to explicitly call the event on the class(es) we inherited from to propagate the event in the class we inherit from (being careful to propagate the event to the parent class only if the parent class can handle the event...).
As we can now use Zope 3 events, manage_before manage_after are deprecated! Let's see how we can migrate them.
Let's imagine we are in ATDocument, we have a manage_afterAdd method in this old Zope 2 style:
class ATDocument(ATCTContent, HistoryAwareMixin):
...
implements(ATCTContent, IATDocument, HistoryAwareMixin)
...
security.declarePrivate('manage_afterAdd')
def manage_afterAdd(self, item, container):
"""Fix text when created througt webdav
Guess the right mimetype from the id/data
"""
ATCTContent.manage_afterAdd(self, item, container)
field = self.getField('text')
# hook for mxTidy / isTidyHtmlWithCleanup validator
tidyOutput = self.getTidyOutput(field)
if tidyOutput:
if hasattr(self, '_v_renamed'):
mimetype = field.getContentType(self)
del self._v_renamed
else:
mimetype = self.guessMimetypeOfText()
if mimetype:
field.set(self, tidyOutput, mimetype=mimetype) # set is ok
elif tidyOutput:
field.set(self, tidyOutput) # set is ok
...
Note that ATDocument implements the IATDocument interface. So we will use it in the ZCML later.
The first thing to do is to create the factory function. Let's call it afterDocumentCreation. We replace self by document:
def afterDocumentCreation(document, event):
"""Fix text when created through webdav
Guess the right mimetype from the id/data
"""
field = document.getField('text')
# hook for mxTidy / isTidyHtmlWithCleanup validator
tidyOutput = document.getTidyOutput(field)
if tidyOutput:
if hasattr(document, '_v_renamed'):
mimetype = field.getContentType(document)
del document._v_renamed
else:
mimetype = document.guessMimetypeOfText()
if mimetype:
field.set(document, tidyOutput, mimetype=mimetype) # set is ok
elif tidyOutput:
field.set(document, tidyOutput) # set is ok
Note that we can't propagate the event to ATCTContent from this function as we aren't in the class anymore... This is a problem because we know that ATContent needs to get information about the event and isn't Zope 3 event aware. Two solution there, make it aware or call the event in the Zope 3 style and in the Zope 2 style.
Let's implement the second solution. So we will have to leave the call to manage_afterAdd in our class with one line, the call to ATContent.manage_afterAdd. So manage_afterAdd becomes:
class ATDocument(ATCTContent, HistoryAwareMixin):
...
implements(ATCTContent, IATDocument, HistoryAwareMixin)
...
security.declarePrivate('manage_afterAdd')
def manage_afterAdd(self, item, container):
"""Fix text when created through webdav
Guess the right mimetype from the id/data
"""
ATCTContent.manage_afterAdd(self, item, container)
...
Now we need to configure ATDocument to be at the same time Zope 3 and Zope 2 event aware. Five gives us the possibility to do that with one directive: deprecatedManageAddDelete:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<subscriber
for="Products.ATContentTypes.interface.IATDocument
zope.app.container.interfaces.IObjectAddedEvent"
factory="Products.ATContentTypes.content.document.afterDocumentCreation"
/>
<five:deprecatedManageAddDelete
class="Products.ATContentTypes.content.document.ATDocument"
/>
</configure>
And that's all. The first directive is like the above example, we link the event to the objects which provide the IATDocument interface and specify the factory we have just implemented by taking manage_afterAdd code. The second directive says that we know that ATDocument uses a zope 2 event method in its class (manage_afterAdd) but we still need to call it.

