Event handlers¶
Adding custom event handlers for your type
So far, we have mainly been concerned with content types’ schemata and forms created from these. However, we often want to add more dynamic functionality, reacting when something happens to objects of our type. In Zope, that usually means writing event subscribers.
Zope’s event model is synchronous. When an event is
broadcast (via the
notify()
function from the
zope.event
package), for example from the
save
action of an add form, all registered event handlers will be
called. There is no guarantee of which order the event
handlers will be called in, however.
Each event is described by an interface, and will typically
carry some information about the event. Some events are
known as object events, and provide
zope.component.interfaces.IObjectEvent
. These have an
object
attribute giving access to the (content) object that the
event relates to. Object events allow event handlers to be
registered for a specific type of object as well as a
specific type of event.
Some of the most commonly used event types in Plone are shown below. They are all object events.
-
zope.lifecycleevent.interfaces.IObjectCreatedEvent
-
fired by the standard add form just after an object has
been created, but before it has been added on the
container. Note that it is often easier to write a handler
for
IObjectAddedEvent
(see below), because at this point the object has a proper acquisition context. -
zope.lifecycleevent.interfaces.IObjectModifiedEvent
- fired by the standard edit form when an object has been modified.
-
zope.lifecycleevent.interfaces.IObjectAddedEvent
-
fired when an object has been added to its container. The
container is available as the
newParent
attribute, and the name the new item holds in the container is available asnewName
. -
zope.lifecycleevent.interfaces.IObjectRemovedEvent
-
fired when an object has been removed from its container.
The container is available as the
oldParent
attribute, and the name the item held in the container is available asoldName
. -
zope.lifecycleevent.interfaces.IObjectMovedEvent
-
fired when an object is added to, removed from, renamed
in, or moved between containers. This event is a
super-type of
IObjectAddedEvent
andIObjectRemovedEvent
, shown above, so an event handler registered for this interface will be invoked for the ‘added’ and ‘removed’ cases as well. When an object is moved or renamed, all ofoldParent
,newParent
,oldName
andnewName
will be set. -
Products.CMFCore.interfaces.IActionSucceededEvent
-
fired when a workflow event has completed. The
workflow
attribute holds the workflow instance involved, and theaction
attribute holds the action (transition) invoked.
Event handlers can be registered using ZCML with the
<subscriber
/>
directive, but when working with Dexterity types, we’ll more
commonly use the
grok.subscriber()
in Python code.
As an example, let’s add an event handler to the
Presenter
type that tries to find users with matching names matching
the presenter id, and send these users an email.
First, we require a few additional imports at the top of
presenter.py
:
from zope.lifecycleevent.interfaces import IObjectAddedEvent
from Products.CMFCore.utils import getToolByName
Then, we’ll add the following event subscriber after the schema definition:
@grok.subscribe(IPresenter, IObjectAddedEvent)
def notifyUser(presenter, event):
acl_users = getToolByName(presenter, 'acl_users')
mail_host = getToolByName(presenter, 'MailHost')
portal_url = getToolByName(presenter, 'portal_url')
portal = portal_url.getPortalObject()
sender = portal.getProperty('email_from_address')
if not sender:
return
subject = "Is this you?"
message = "A presenter called %s was added here %s" % (presenter.title, presenter.absolute_url(),)
matching_users = acl_users.searchUsers(fullname=presenter.title)
for user_info in matching_users:
email = user_info.get('email', None)
if email is not None:
mail_host.secureSend(message, email, sender, subject)
There are many ways to improve this rather simplistic event
handler, but it illustrates how events can be used. The
first argument to
grok.subscribe()
is an interface describing the object type. For non-object
events, this is omitted. The second argument is the event
type. The arguments to the function reflects these two, so
the first argument is the
IPresenter
instance and the second is an
IObjectAddedEvent
instance.