Sending and handling events

Events is undoubtedly one of the most useful things that Zope 3 brings to the Zope 2 world. Here's how b-org uses them.

In the previous section, you saw how an event handler was used to apply a placeful workflow policy to newly created projects. This pattern is quite powerful - instead of needing to subclass Project just to add something to at_post_create_script() or initializeArchetype(), say, you simply register an appropriate event handler. This pattern can of course apply to other situations, such as when objects are modified, deleted, added to a container, or on any other type of event that may occur in your system. Events are synchronous, so when code emits an event, it will block until all event handlers are finished.

Recall the event handler for adding projects. It can be found in events/project.py and has the following signature:

def addLocalProjectWorkflow(ob, event):
...

The first argument is the object the event was fired on, the second is an instance of the event itself. In fact, this two-part event dispatcher is a special case of events described with IObjectEvent and its sub-interfaces. Internally, Zope 3 catches all IObjectEvents and re-dispatches the event based on the object that is passed along the event instance. The registration for the event handler in events/configure.zcml looks like this:

<subscriber for="..interfaces.IProjectContent
zope.app.container.interfaces.IObjectAddedEvent"
handler=".project.addLocalProjectWorkflow" />

Note that there are two interfaces the subscriber is registered for - the object type and the event type. These must be separated by whitespace, though a newline like above is customary. This is the same syntax that is used to explicitly define multi-adapters (if you are not using the adapts() syntax in an adapter class) - in fact, the events machinery uses the adapter registry internally to map subscribers to events when they are fired.

A more general-case event can be found in events/employee.py, which takes care of assigning ownership of an Employee object to the user that is tied to that employee. The code is borrowed and adapted from PloneTool, but notice the signature which only includes the event:

def modifyEmployeeOwnership(event):
"""Let employees own their own objects.

Stolen from Plone and CMF core, but made less picky about where users are
found.
"""

The registration in events/onfigure.zcml is similar to the one above, but only uses one for interface:

 <subscriber for="..interfaces.IEmployeeModifiedEvent"
handler=".employee.modifyEmployeeOwnership" />

Sending custom events

You will notice that the IEmployeeModifiedEvent is a custom event. In Plone 3.0 (or rather, Archetypes 1.5) this won't be necessary, because Archetypes will take care of sending an event derived from IObjectModifiedEvent, which in turn derives from IObjectEvent and thus is subject to the same registration as the IObjectAddedEvent that includes the object type and the event type. For now, though, we need to send the event ourselves.

The event is described by an interface in interfaces/employee.py:

from zope.interface import Interface, Attribute

...

class IEmployeeModifiedEvent(Interface):
"""An event fired when an employee object is saved.
"""

context = Attribute("The content object that was saved.")
The implementation is trivial, and can be found in content/employee.py:
from zope.interface import implements

...

from Products.borg.interfaces import IEmployeeModifiedEvent

...

class EmployeeModifiedEvent(object):
"""Event to notify that employees have been saved.
"""
implements(IEmployeeModifiedEvent)

def __init__(self, context):
self.context = context
It is of course the event class that we instantiate and send, whilst we register the event handler for the event interface. This means that we could provide alternative implementations for the same event interface, if need be. It also means that event handlers subscribed for a parent interface will be invoked for events that provide a sub-interface.

Sending the event is very simple. In the definition of Employee in content/employee.py, we have:

from zope.event import notify

...

class Employee(ExtensibleSchemaSupport, BaseContent):

...
 
security.declarePrivate(permissions.View, 'at_post_create_script')
def at_post_create_script(self):
"""Notify that the employee has been saved.
"""
notify(EmployeeModifiedEvent(self))

security.declarePrivate(permissions.View, 'at_post_edit_script')
def at_post_edit_script(self):
"""Notify that the employee has been saved.
"""
notify(EmployeeModifiedEvent(self))

We construct an event instance and parameterise it with the right object (i.e. self) before sending it with notify(), all on one line.

 

IObjectModifiedEvent versus at_post_edit_script in Plone 3

Posted by Josef Dabernig at Apr 12, 2008 04:45 PM
i noticed, that the IObjectModifiedEvent is called multiple times when submitting an edit form, while the at_post_edit_script (deprecated) is called once, as expected.

is this a bug?

thanks, josef

Not quite

Posted by Martin Aspeli at Apr 12, 2008 06:38 PM
It's not entirely a bug, more like a weakness in Archetypes.

For AT content you probably want to listen to IObjectEditedEvent from Products.Archetypes.interfaces. This should be called only once per save.

IObjectInitializedEvent

Posted by Duke at Jun 04, 2008 03:27 PM
If there's some subscriber you want executed upon creation of a new object, IObjectInitializedEvent (from Archetypes) would be the correct event - it's called once upon saving the new AT content object, whereas IObjectEditedEvent only occurs upon editing the object after its creation.

The problem with Archetypes is that it creates a temporary object then brings you to the edit form. Once you click save, the temporary object is then moved to the proper folder and saved with the data you entered. I never understood why Archetypes couldn't just generate a form based on the schema, then create an object from the data submitted via the form.

Anyhow, IObjectModifiedEvent is a Zope event and Archetypes makes several modifications throughout the initial creation of an AT object, which is why it's called multiple times.