Events in Zope 2 ================ Zope 2 supports zope.lifecycleevent style events including container events. With container events, you finally have the ability to react to things happening to objects without have to subclass ``manage_afterAdd``, ``manage_beforeDelete`` or ``manage_afterClone``. Instead, you just have to register a subscriber for the appropriate event, for instance IObjectAddedEvent, and make it do the work. Indeed, the old methods like ``manage_afterAdd`` are now discouraged, you shouldn't use them anymore. Let's see how to migrate your products. Old product ----------- Suppose that in an old product you have code that needs to register through a central tool whenever a document is created. Or it could be indexing itself. Or it could initialize an attribute according to its current path. Code like:: class CoolDocument(...): ... def manage_afterAdd(self, item, container): self.mangled_path = mangle('/'.join(self.getPhysicalPath())) getToolByName(self, 'portal_cool').registerCool(self) super(CoolDocument, self).manage_afterAdd(item, container) def manage_afterClone(self, item): self.mangled_path = mangle('/'.join(self.getPhysicalPath())) getToolByName(self, 'portal_cool').registerCool(self) super(CoolDocument, self).manage_afterClone(item) def manage_beforeDelete(self, item, container): super(CoolDocument, self).manage_beforeDelete(item, container) getToolByName(self, 'portal_cool').unregisterCool(self) This had been the best practice in old Zope 2 versions. Note the use of ``super()`` to call the base class, which is often omitted because people "know" that SimpleItem for instance doesn't do anything in these methods. If you run this code today, you will get deprecation warnings, telling you that:: Calling Products.CoolProduct.CoolDocument.CoolDocument.manage_afterAdd is discouraged. You should use event subscribers instead. Using five:deprecatedManageAddDelete ------------------------------------ The simplest thing you can do to deal with the deprecation warnings, and have correct behavior, is to add in your products a ``configure.zcml`` file containing:: This tells Zope that you acknowledge that your class contains deprecated methods, and ask it to still call them in the proper manner. So Zope will be sending events when an object is added, for instance, and in addition call your old ``manage_afterAdd`` method. One subtlety here is that you may have to modify your methods to just do their work, and not call their super class. This is necessary because proper events are already dispatched to all relevant classes, and the work of the super class will be done trough events, you must not redo it by hand. If you call the super class, you will get a warning, saying for instance:: CoolDocument.manage_afterAdd is discouraged. You should use an IObjectAddedEvent subscriber instead. The fact that you must "just do your work" is especially important for the rare cases where people subclass the ``manage_afterAdd`` of object managers like folders, and decided to reimplement recursion into the children themselves. If you do that, then there will be two recursions going on in parallel, the one done by events, and the one done by your code. This would be bad. Using subscribers ----------------- In the long run, you will want to use proper subscribers. First, you'll have to write a subscriber that "does the work", for instance:: def addedCoolDocument(ob, event): """A Cool Document was added to a container.""" self.mangled_path = mangle('/'.join(self.getPhysicalPath())) Note that we're not calling the ``portal_cool`` tool anymore, because presumably this tool will also be modified to do its work through events, and will have a similar subscriber doing the necessary ``registerCool``. Note also that here we don't care about the event, but in more complex cases we would. Now we have to register our subscriber for our object. To do that, we need to "mark" our object through an interface. We can define in our product's ``interfaces.py``:: from zope.interface import Interface, Attribute class ICoolDocument(Interface): """Cool Document.""" mangled_path = Attribute("Our mangled path.") ... Then the class CoolDocument is marked with this interface:: from zope.interface import implements from Products.CoolProduct.interfaces import ICoolDocument class CoolDocument(...): implements(ICoolDocument) ... Finally we must link the event and the interface to the subscriber using zcml, so in ``configure.zcml`` we'll add:: ... ... And that's it, everything is plugged. Note that IObjectAddedEvent takes care of both ``manage_afterAdd`` and ``manage_afterClone``, as it's sent whenever a new object is placed into a container. However this won't take care of moves and renamings, we'll see below how to do that. Event dispatching ----------------- When an IObjectEvent (from which all the events we're talking here derive) is initially sent, it concerns one object. For instance, a specific object is removed. The ``event.object`` attribute is this object. To be able to know about removals, we could just subscribe to the appropriate event using a standard event subscriber. In that case, we'd have to filter "by hand" to check if the object removed is of the type we're interested in, which would be a chore. In addition, any subobjects of the removed object wouldn't know what happens to them, and for instance they wouldn't have any way of doing some cleanup before they disappear. To solve these two problems, Zope has an additional mechanism by which any IObjectEvent is redispatched using multi-adapters of the form ``(ob, event)``, so that a subscriber can be specific about the type of object it's interested in. Furthermore, this is done recursively for all sublocations ``ob`` of the initial object. The ``event`` won't change though, and ``event.object`` will still be the original object for which the event was initially sent (this corresponds to ``self`` and ``item`` in the ``manage_afterAdd`` method -- ``self`` is ``ob``, and ``item`` is ``event.object``). Understanding the hierarchy of events is important to see how to subscribe to them. * IObjectEvent is the most general. Any event focused on an object derives from this. * IObjectMovedEvent is sent when an object changes location or is renamed. It is quite general, as it also encompasses the case where there's no old location (addition) or no new location (removal). * IObjectAddedEvent and IObjectRemovedEvent both derive from IObjectMovedEvent. * IObjectCopiedEvent is sent just after an object copy is made, but this doesn't mean the object has been put into its new container yet, so it doesn't have a location. There are only a few basic use cases about what one wants to do with respect to events. The first use case is the one where the object has to be aware of its path, like in the CoolDocument example above. In Zope 2 an object has a new path through creation, copy or move (rename is a kind of move). The events sent during these three operations are varied: creation sends IObjectAddedEvent, copy sends IObjectCopiedEvent then IObjectAddedEvent, and move sends IObjectMovedEvent. So to react to new paths, we have to subscribe to IObjectMovedEvent, but this will also get us any IObjectRemovedEvent, which we'll have to filter out by hand (this is unfortunate, and due to the way the Zope interface hierarchy is organized). So to fix the CoolDocument configuration we have to add:: def movedCoolDocument(ob, event): """A Cool Document was moved.""" if not IObjectRemovedEvent.providedBy(event): addedCoolDocument(ob, event) And replace the subscriber with:: ... ... The second use case is when the object has to do some cleanup when it is removed from its parent. This used to be in ``manage_beforeDelete``, now we can do the work in a ``removedCoolDocument`` method and just subscribe to IObjectRemovedEvent. But wait, this won't take into account moves... So in the same vein as above, we would have to write:: def movedCoolDocument(ob, event): """A Cool Document was moved.""" if not IObjectRemovedEvent.providedBy(event): addedCoolDocument(ob, event) if not IObjectAddedEvent.providedBy(event): removedCoolDocument(ob, event) The third use case is when your object has to stay registered with some tool, for instance indexed in a catalog, or as above registered with ``portal_cool``. Here we have to know the old object's path to unregister it, so we have to be called *before* it is removed. We'll use ``IObjectWillBe...`` events, that are sent before the actual operations take place:: from OFS.interfaces import IObjectWillBeAddedEvent def beforeMoveCoolDocument(ob, event): """A Cool Document will be moved.""" if not IObjectWillBeAddedEvent.providedBy(event): getToolByName(ob, 'portal_cool').unregisterCool(ob) def movedCoolDocument(ob, event): """A Cool Document was moved.""" if not IObjectRemovedEvent.providedBy(event): getToolByName(ob, 'portal_cool').registerCool(ob) ... And use an additional subscriber:: ... ... This has to be done if the tool cannot react by itself to objects being added and removed, which obviously would be better as it's ultimately the tool's responsibility and not the object's. Note that if having tests like:: if not IObjectWillBeAddedEvent.providedBy(event): if not IObjectRemovedEvent.providedBy(event): seems cumbersome (and backwards), it is also possible to check what kind of event you're dealing with using:: if event.oldParent is not None: if event.newParent is not None: (However be careful, the ``oldParent`` and ``newParent`` are the old and new parents *of the original object* for which the event was sent, not of the one to which the event was redispatched using the multi-subscribers we have registered.) The ``IObjectWillBe...`` events are specific to Zope 2 (and imported from ``OFS.interfaces``). zope.lifecycleevent doesn't really need them, as object identity is often enough.