[z3-checkins] r18938 - in z3/Five/branch/efge-object-event: . doc
tests
efge at codespeak.net
efge at codespeak.net
Tue Oct 25 16:26:56 CEST 2005
Author: efge
Date: Tue Oct 25 16:26:56 2005
New Revision: 18938
Added:
z3/Five/branch/efge-object-event/event.py (contents, props changed)
z3/Five/branch/efge-object-event/event.zcml (contents, props changed)
Modified:
z3/Five/branch/efge-object-event/doc/directives.txt
z3/Five/branch/efge-object-event/doc/features.txt
z3/Five/branch/efge-object-event/eventconfigure.py
z3/Five/branch/efge-object-event/fivedirectives.py
z3/Five/branch/efge-object-event/meta.zcml
z3/Five/branch/efge-object-event/tests/event.txt
z3/Five/branch/efge-object-event/tests/test_event.py
Log:
The standard Zope 2 containers now send events. <five:sendEvents> is
removed, and replaced by more <five:containerEvents> and
<five:deprecatedManageAddDelete>
This is a work in progress, some XXX remain, notably:
- add event listeners for _notifyOfCopyTo and _postCopy
- add a subscriber for manage_afterClone emulation
- add subscribers for executable ownership and Owner role fixups
More doc will also be included.
Finally, this includes a "transitional" phase, which will only be used
during development but will be removed when things have been suitably
tested with Zope and CMF, and deprecated classes have been converted or
identified.
Modified: z3/Five/branch/efge-object-event/doc/directives.txt
==============================================================================
--- z3/Five/branch/efge-object-event/doc/directives.txt (original)
+++ z3/Five/branch/efge-object-event/doc/directives.txt Tue Oct 25 16:26:56 2005
@@ -164,12 +164,19 @@
Retrieve size information for a Zope 2 content class via a Zope 3
style ``ISized`` adapter.
-sendEvents
-----------
+containerEvents
+---------------
-Lets a Zope 2 content class send out Zope 3 object events that
-correspond to the Zope 2 methods ``manage_afterAdd`` and
-``manage_beforeDelete``.
+Make events be sent for Zope 2 container objects, instead of calling old
+methods like ``manage_afterAdd``. These old methods will still be called
+for classes specified in a ``deprecatedManageAddDelete`` directive.
+
+deprecatedManageAddDelete
+-------------------------
+
+Specify a class that needs its old deprecated methods like
+``manage_afterAdd``, ``manage_beforeDelete`` and ``manage_afterClone``
+to be called. Modern classes should use event subscribers instead.
pagesFromDirectory
------------------
Modified: z3/Five/branch/efge-object-event/doc/features.txt
==============================================================================
--- z3/Five/branch/efge-object-event/doc/features.txt (original)
+++ z3/Five/branch/efge-object-event/doc/features.txt Tue Oct 25 16:26:56 2005
@@ -103,3 +103,10 @@
from Products.Five.localsite import enableLocalSiteHook
enableLocalSiteHook(obj)
+
+Object events
+=============
+
+Five supports sending Zope 3 object events when objects are added,
+moved, renamed, copied and deleted. The use of ``manage_afterAdd`` & co
+methods is deprecated.
Added: z3/Five/branch/efge-object-event/event.py
==============================================================================
--- (empty file)
+++ z3/Five/branch/efge-object-event/event.py Tue Oct 25 16:26:56 2005
@@ -0,0 +1,751 @@
+##############################################################################
+#
+# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Five event definitions.
+
+May eventually be folded back into Zope 3 proper.
+
+$Id$
+"""
+
+import warnings
+
+from zope.event import notify
+from zope.interface import implements
+from zope.interface import Attribute
+
+from zope.app.event.interfaces import IObjectEvent
+from zope.app.container.interfaces import IObjectAddedEvent
+from zope.app.container.interfaces import IObjectRemovedEvent
+
+from zope.app.event.objectevent import ObjectEvent
+from zope.app.container.contained import ObjectMovedEvent
+from zope.app.container.contained import ObjectAddedEvent
+from zope.app.container.contained import ObjectRemovedEvent
+from zope.app.event.objectevent import ObjectCopiedEvent
+
+from Products.Five.fiveconfigure import isFiveMethod
+
+
+class IObjectWillBeMovedEvent(IObjectEvent):
+ """An object will be moved."""
+ oldParent = Attribute("The old location parent for the object.")
+ oldName = Attribute("The old location name for the object.")
+ newParent = Attribute("The new location parent for the object.")
+ newName = Attribute("The new location name for the object.")
+
+class IObjectWillBeAddedEvent(IObjectWillBeMovedEvent):
+ """An object will be added to a container."""
+
+class IObjectWillBeRemovedEvent(IObjectWillBeMovedEvent):
+ """An object will be removed from a container"""
+
+
+class ObjectWillBeMovedEvent(ObjectEvent):
+ """An object will be moved"""
+ implements(IObjectWillBeMovedEvent)
+
+ def __init__(self, object, oldParent, oldName, newParent, newName):
+ ObjectEvent.__init__(self, object)
+ self.oldParent = oldParent
+ self.oldName = oldName
+ self.newParent = newParent
+ self.newName = newName
+
+class ObjectWillBeAddedEvent(ObjectWillBeMovedEvent):
+ """An object will be added to a container"""
+ implements(IObjectWillBeAddedEvent)
+
+ def __init__(self, object, newParent=None, newName=None):
+ #if newParent is None:
+ # newParent = object.__parent__
+ #if newName is None:
+ # newName = object.__name__
+ ObjectWillBeMovedEvent.__init__(self, object, None, None,
+ newParent, newName)
+
+class ObjectWillBeRemovedEvent(ObjectWillBeMovedEvent):
+ """An object will be removed from a container"""
+ implements(IObjectWillBeRemovedEvent)
+
+ def __init__(self, object, oldParent=None, oldName=None):
+ #if oldParent is None:
+ # oldParent = object.__parent__
+ #if oldName is None:
+ # oldName = object.__name__
+ ObjectWillBeMovedEvent.__init__(self, object, oldParent, oldName,
+ None, None)
+
+
+##################################################
+
+import sys
+from cgi import escape
+from zLOG import LOG, ERROR
+from Acquisition import aq_base, aq_parent, aq_inner
+from App.config import getConfiguration
+from App.Dialogs import MessageDialog
+from AccessControl import getSecurityManager
+from ZODB.POSException import ConflictError
+from OFS.ObjectManager import BeforeDeleteException
+from OFS import Moniker
+from OFS.CopySupport import CopyError # Yuck, a string exception
+from OFS.CopySupport import eNoData, eNotFound, eInvalid, _cb_decode
+from OFS.CopySupport import cookie_path, sanity_check
+from webdav.Lockable import ResourceLockedError
+FIVE_ORIGINAL_PREFIX = '__five_original_'
+
+
+containerEventsTransitional = None
+containerEventAwareClasses = []
+deprecatedManageAddDeleteClasses = []
+
+
+def callOldMethods(ob):
+ """Check if we need to call the old methods.
+ """
+ if containerEventsTransitional:
+ for class_ in containerEventAwareClasses:
+ if isinstance(ob, class_):
+ return False
+ return True
+ else:
+ for class_ in deprecatedManageAddDeleteClasses:
+ if isinstance(ob, class_):
+ return True
+ return False
+
+##################################################
+# Adapters and subscribers
+
+from OFS.interfaces import IObjectManager
+from zope.app.location.interfaces import ISublocations
+
+class ObjectManagerSublocations(object):
+ """Get the sublocations for an ObjectManager.
+ """
+ #__used_for__ = IObjectManager
+ #implements(ISublocations)
+
+ def __init__(self, container):
+ self.container = container
+
+ def sublocations(self):
+ for ob in self.container.objectValues():
+ yield ob
+
+
+def callManageAfterAdd(ob, event):
+ """Compatibility subscriber for manage_afterAdd.
+ """
+ if not callOldMethods(ob):
+ return
+ container = event.newParent
+ if container is None:
+ return
+ item = event.object
+ if not isFiveMethod(ob.manage_afterAdd):
+ warnings.warn(
+ "Calling %s.manage_afterAdd is deprecated when using Five, "
+ "use an IObjectAddedEvent subscriber instead."
+ % ob.__class__.__name__,
+ DeprecationWarning)
+ ob.manage_afterAdd(item, container)
+
+
+def callManageBeforeDelete(ob, event):
+ """Compatibility subscriber for manage_beforeDelete.
+ """
+ if not callOldMethods(ob):
+ return
+ container = event.oldParent
+ if container is None:
+ return
+ item = event.object
+ if not isFiveMethod(ob.manage_beforeDelete):
+ warnings.warn(
+ "Calling %s.manage_beforeDelete is deprecated "
+ "when using Five, "
+ "use an IObjectWillBeRemovedEvent subscriber instead."
+ % ob.__class__.__name__,
+ DeprecationWarning)
+ try:
+ ob.manage_beforeDelete(item, container)
+ except BeforeDeleteException:
+ raise
+ except ConflictError:
+ raise
+ except:
+ LOG('Zope', ERROR, '_delObject() threw', error=sys.exc_info())
+ # In debug mode when non-Manager, let exceptions propagate.
+ if getConfiguration().debug_mode:
+ if not getSecurityManager().getUser().has_role('Manager'):
+ raise
+
+def callManageAfterClone(ob, event):
+ """Compatibility subscriber for manage_afterClone.
+ """
+ if not callOldMethods(ob):
+ return
+ container = event.newParent
+ if container is None:
+ return
+ item = event.object
+ if not isFiveMethod(ob.manage_afterAdd):
+ warnings.warn(
+ "Calling %s.manage_afterAdd is deprecated when using Five, "
+ "use an IObjectAddedEvent subscriber instead."
+ % ob.__class__.__name__,
+ DeprecationWarning)
+ ob.manage_afterAdd(item, container)
+
+
+
+
+##################################################
+# Monkey patches
+
+# From ObjectManager
+def manage_afterAdd(self, item, container):
+ # Don't do recursion anymore, a subscriber does that.
+ # A warning is sent by the subscriber
+ pass
+
+# From ObjectManager
+def manage_beforeDelete(self, item, container):
+ # Don't do recursion anymore, a subscriber does that.
+ # A warning is sent by the subscriber
+ pass
+
+# From ObjectManager
+def manage_afterClone(self, item):
+ # Don't do recursion anymore, a subscriber does that.
+ # A warning is sent by the subscriber
+ pass
+
+# From CopyContainer
+def _notifyOfCopyTo(self, container, op=0):
+ # A warning is sent by the caller
+ pass
+
+# From CopyContainer
+def _postCopy(self, container, op=0):
+ # A warning is sent by the caller
+ pass
+
+
+# XXX other recursive dispatching:
+# changeOwnership (called by manage_takeOwnership)
+# manage_fixupOwnershipAfterAdd
+# _deleteOwnershipAfterAdd (called by manage_fixupOwnershipAfterAdd)
+
+
+# From ObjectManager
+def _setObject(self, id, object, roles=None, user=None, set_owner=1,
+ suppress_events=False):
+ """Set an object into this container.
+
+ Also sends IObjectAddedEvent.
+ """
+ v = self._checkId(id)
+ if v is not None:
+ id = v
+ t = getattr(object, 'meta_type', None)
+
+ # If an object by the given id already exists, remove it.
+ for object_info in self._objects:
+ if object_info['id'] == id:
+ self._delObject(id)
+ break
+
+ if not suppress_events:
+ notify(ObjectWillBeAddedEvent(object, self, id))
+
+ self._objects = self._objects + ({'id': id, 'meta_type': t},)
+ self._setOb(id, object)
+ object = self._getOb(id)
+
+ if not suppress_events:
+ notify(ObjectAddedEvent(object, self, id))
+
+ # XXX eventify manage_fixupOwnershipAfterAdd and local role Owner
+ if 0:
+ if set_owner:
+ object.manage_fixupOwnershipAfterAdd()
+
+ # Try to give user the local role "Owner", but only if
+ # no local roles have been set on the object yet.
+ if hasattr(object, '__ac_local_roles__'):
+ if object.__ac_local_roles__ is None:
+ user = getSecurityManager().getUser()
+ if user is not None:
+ userid = user.getId()
+ if userid is not None:
+ object.manage_setLocalRoles(userid, ['Owner'])
+
+ # manage_afterAdd was here
+
+ return id
+
+
+# From BTreeFolder2
+def BT_setObject(self, id, object, roles=None, user=None, set_owner=1,
+ suppress_events=False):
+ v = self._checkId(id)
+ if v is not None:
+ id = v
+
+ # If an object by the given id already exists, remove it.
+ if self.has_key(id):
+ self._delObject(id)
+
+ if not suppress_events:
+ notify(ObjectWillBeAddedEvent(object, self, id))
+
+ self._setOb(id, object)
+ object = self._getOb(id)
+
+ if not suppress_events:
+ notify(ObjectAddedEvent(object, self, id))
+
+ # XXX eventify manage_fixupOwnershipAfterAdd and local role Owner
+ if 0:
+ if set_owner:
+ object.manage_fixupOwnershipAfterAdd()
+
+ # Try to give user the local role "Owner", but only if
+ # no local roles have been set on the object yet.
+ if hasattr(object, '__ac_local_roles__'):
+ if object.__ac_local_roles__ is None:
+ user = getSecurityManager().getUser()
+ if user is not None:
+ userid = user.getId()
+ if userid is not None:
+ object.manage_setLocalRoles(userid, ['Owner'])
+
+ # manage_afterAdd was here
+
+ return id
+
+
+# From ObjectManager
+def _delObject(self, id, dp=1, suppress_events=False):
+ """Delete an object from this container.
+
+ Also sends IObjectRemovedEvent.
+ """
+ ob = self._getOb(id)
+
+ # manage_beforeDelete was here
+
+ if not suppress_events:
+ notify(ObjectWillBeRemovedEvent(ob, self, id))
+
+ self._objects = tuple([i for i in self._objects
+ if i['id'] != id])
+ self._delOb(id)
+
+ # Indicate to the object that it has been deleted. This is
+ # necessary for object DB mount points. Note that we have to
+ # tolerate failure here because the object being deleted could
+ # be a Broken object, and it is not possible to set attributes
+ # on Broken objects.
+ try:
+ ob._v__object_deleted__ = 1
+ except:
+ pass
+
+ if not suppress_events:
+ notify(ObjectRemovedEvent(ob, self, id))
+
+
+# From BTreeFolder2
+def BT_delObject(self, id, dp=1, suppress_events=False):
+ ob = self._getOb(id)
+
+ # manage_beforeDelete was here
+
+ if not suppress_events:
+ notify(ObjectWillBeRemovedEvent(ob, self, id))
+
+ self._delOb(id)
+
+ if not suppress_events:
+ notify(ObjectRemovedEvent(ob, self, id))
+
+# From CopyContainer
+def manage_renameObject(self, id, new_id, REQUEST=None):
+ """Rename a particular sub-object.
+ """
+ try:
+ self._checkId(new_id)
+ except:
+ raise CopyError, MessageDialog(
+ title='Invalid Id',
+ message=sys.exc_info()[1],
+ action ='manage_main')
+
+ ob = self._getOb(id)
+
+ if ob.wl_isLocked():
+ raise ResourceLockedError, ('Object "%s" is locked via WebDAV'
+ % ob.getId())
+ if not ob.cb_isMoveable():
+ raise CopyError, eNotSupported % escape(id)
+ self._verifyObjectPaste(ob)
+
+ if callOldMethods(ob):
+ # BBB
+ if not isFiveMethod(ob._notifyOfCopyTo):
+ warnings.warn(
+ "Calling %s._notifyOfCopyTo is deprecated when using Five, "
+ "use an IObjectWillBeMovedEvent subscriber "
+ "instead." % ob.__class__.__name__,
+ DeprecationWarning, stacklevel=2)
+ try:
+ ob._notifyOfCopyTo(self, op=1)
+ except:
+ raise CopyError, MessageDialog(
+ title='Rename Error',
+ message=sys.exc_info()[1],
+ action ='manage_main')
+
+ notify(ObjectWillBeMovedEvent(ob, self, id, self, new_id))
+
+ self._delObject(id, suppress_events=True)
+ ob = aq_base(ob)
+ ob._setId(new_id)
+
+ # Note - because a rename always keeps the same context, we
+ # can just leave the ownership info unchanged.
+ self._setObject(new_id, ob, set_owner=0, suppress_events=True)
+ ob = self._getOb(new_id)
+
+ notify(ObjectMovedEvent(ob, self, id, self, new_id))
+
+ if callOldMethods(ob):
+ # BBB
+ if not isFiveMethod(ob._postCopy):
+ warnings.warn(
+ "Calling %s._postCopy(op=1) is deprecated when using Five, "
+ "use an IObjectMovedEvent subscriber instead."
+ % ob.__class__.__name__,
+ DeprecationWarning, stacklevel=2)
+ ob._postCopy(self, op=1)
+
+ if REQUEST is not None:
+ return self.manage_main(self, REQUEST, update_menu=1)
+ return None
+
+
+# From CopyContainer
+def manage_pasteObjects(self, cb_copy_data=None, REQUEST=None):
+ """Paste previously copied objects into the current object.
+
+ If calling manage_pasteObjects from python code, pass the result of a
+ previous call to manage_cutObjects or manage_copyObjects as the first
+ argument.
+
+ Also sends IObjectCopiedEvent or IObjectMovedEvent.
+ """
+ if cb_copy_data is not None:
+ cp = cb_copy_data
+ elif REQUEST is not None and REQUEST.has_key('__cp'):
+ cp = REQUEST['__cp']
+ else:
+ cp = None
+ if cp is None:
+ raise CopyError, eNoData
+
+ try:
+ op, mdatas = _cb_decode(cp)
+ except:
+ raise CopyError, eInvalid
+
+ oblist = []
+ app = self.getPhysicalRoot()
+ for mdata in mdatas:
+ m = Moniker.loadMoniker(mdata)
+ try:
+ ob = m.bind(app)
+ except ConflictError:
+ raise
+ except:
+ raise CopyError, eNotFound
+ self._verifyObjectPaste(ob, validate_src=op+1)
+ oblist.append(ob)
+
+ result = []
+ if op == 0:
+ # Copy operation
+ for ob in oblist:
+ orig_id = ob.getId()
+ if not ob.cb_isCopyable():
+ raise CopyError, eNotSupported % escape(orig_id)
+
+ try:
+ if callOldMethods(ob):
+ # BBB
+ if not isFiveMethod(ob._notifyOfCopyTo):
+ warnings.warn(
+ "Calling %s._notifyOfCopyTo is deprecated "
+ "when using Five, use an IObjectCopiedEvent or "
+ "IObjectWillBeAddedEvent subscriber instead."
+ % ob.__class__.__name__,
+ DeprecationWarning, stacklevel=2)
+ ob._notifyOfCopyTo(self, op=0)
+ except ConflictError:
+ raise
+ except:
+ raise CopyError, MessageDialog(
+ title="Copy Error",
+ message=sys.exc_info()[1],
+ action='manage_main')
+
+ id = self._get_id(orig_id)
+ result.append({'id': orig_id, 'new_id': id})
+
+ ob = ob._getCopy(self)
+ ob._setId(id)
+ notify(ObjectCopiedEvent(ob))
+
+ self._setObject(id, ob)
+ ob = self._getOb(id)
+ ob.wl_clearLocks()
+
+ if callOldMethods(ob):
+ # BBB
+ if not isFiveMethod(ob._postCopy):
+ warnings.warn(
+ "Calling %s._postCopy(op=0) is deprecated "
+ "when using Five, "
+ "use an IObjectCopiedEvent subscriber instead."
+ % ob.__class__.__name__,
+ DeprecationWarning, stacklevel=2)
+ ob._postCopy(self, op=0)
+ if not isFiveMethod(ob.manage_afterClone):
+ warnings.warn(
+ "Calling %s.manage_afterClone is deprecated "
+ "when using Five, "
+ "use an IObjectCopiedEvent subscriber instead."
+ % self.__class__.__name__,
+ DeprecationWarning, stacklevel=2)
+ ob.manage_afterClone(ob)
+
+ if REQUEST is not None:
+ return self.manage_main(self, REQUEST, update_menu=1,
+ cb_dataValid=1)
+
+ elif op == 1:
+ # Move operation
+ for ob in oblist:
+ orig_id = ob.getId()
+ if not ob.cb_isMoveable():
+ raise CopyError, eNotSupported % escape(orig_id)
+
+ try:
+ if callOldMethods(ob):
+ # BBB
+ if not isFiveMethod(ob._notifyOfCopyTo):
+ warnings.warn(
+ "Calling %s._notifyOfCopyTo is deprecated "
+ "when using Five, "
+ "use an IObjectWillBeMovedEvent subscriber "
+ "instead." % ob.__class__.__name__,
+ DeprecationWarning, stacklevel=2)
+ ob._notifyOfCopyTo(self, op=1)
+ except ConflictError:
+ raise
+ except:
+ raise CopyError, MessageDialog(
+ title="Move Error",
+ message=sys.exc_info()[1],
+ action='manage_main')
+ if not sanity_check(self, ob):
+ raise CopyError, "This object cannot be pasted into itself"
+
+ orig_container = aq_parent(aq_inner(ob))
+ if aq_base(orig_container) is aq_base(self):
+ id = orig_id
+ else:
+ id = self._get_id(orig_id)
+ result.append({'id': orig_id, 'new_id': id})
+
+ notify(ObjectWillBeMovedEvent(ob, orig_container, orig_id,
+ self, id))
+
+ # try to make ownership explicit so that it gets carried
+ # along to the new location if needed.
+ ob.manage_changeOwnershipType(explicit=1) # XXX event?
+
+ orig_container._delObject(orig_id, suppress_events=True)
+ ob = aq_base(ob)
+ ob._setId(id)
+
+ self._setObject(id, ob, set_owner=0, suppress_events=True)
+ ob = self._getOb(id)
+
+ notify(ObjectMovedEvent(ob, orig_container, orig_id,
+ self, id))
+
+ if callOldMethods(ob):
+ # BBB
+ if not isFiveMethod(ob._postCopy):
+ warnings.warn(
+ "Calling %s._postCopy(op=1) is deprecated "
+ "when using Five, "
+ "use an IObjectMovedEvent subscriber instead."
+ % ob.__class__.__name__,
+ DeprecationWarning, stacklevel=2)
+ ob._postCopy(self, op=1)
+
+ # try to make ownership implicit if possible
+ ob.manage_changeOwnershipType(explicit=0) # XXX event?
+
+ if REQUEST is not None:
+ REQUEST['RESPONSE'].setCookie('__cp', 'deleted',
+ path='%s' % cookie_path(REQUEST),
+ expires='Wed, 31-Dec-97 23:59:59 GMT')
+ REQUEST['__cp'] = None
+ return self.manage_main(self, REQUEST, update_menu=1,
+ cb_dataValid=0)
+
+ return result
+
+# From CopyContainer
+def manage_clone(self, ob, id, REQUEST=None):
+ """Clone an object, creating a new object with the given id.
+ """
+ if not ob.cb_isCopyable():
+ raise CopyError, eNotSupported % escape(ob.getId())
+ try:
+ self._checkId(id)
+ except:
+ raise CopyError, MessageDialog(
+ title='Invalid Id',
+ message=sys.exc_info()[1],
+ action ='manage_main')
+
+ self._verifyObjectPaste(ob)
+
+ if callOldMethods(ob):
+ # BBB
+ if not isFiveMethod(ob._notifyOfCopyTo):
+ warnings.warn(
+ "Calling %s._notifyOfCopyTo is deprecated when using Five, "
+ "use an IObjectCopiedEvent or IObjectWillBeAddedEvent "
+ "subscriber instead." % ob.__class__.__name__,
+ DeprecationWarning, stacklevel=2)
+ try:
+ ob._notifyOfCopyTo(self, op=0)
+ except:
+ raise CopyError, MessageDialog(
+ title='Clone Error',
+ message=sys.exc_info()[1],
+ action ='manage_main')
+
+ ob = ob._getCopy(self)
+ ob._setId(id)
+ notify(ObjectCopiedEvent(ob))
+
+ self._setObject(id, ob)
+ ob = self._getOb(id)
+
+ if callOldMethods(ob):
+ # BBB
+ if not isFiveMethod(ob._postCopy):
+ warnings.warn(
+ "Calling %s._postCopy(op=0) is deprecated when using Five, "
+ "use an IObjectCopiedEvent subscriber instead."
+ % ob.__class__.__name__,
+ DeprecationWarning, stacklevel=2)
+ ob._postCopy(self, op=0)
+ if not isFiveMethod(ob.manage_afterClone):
+ warnings.warn(
+ "Calling %s.manage_afterClone is deprecated when using Five, "
+ "use an IObjectCopiedEvent subscriber instead."
+ % self.__class__.__name__,
+ DeprecationWarning, stacklevel=2)
+ ob.manage_afterClone(ob)
+
+ return ob
+
+##################################################
+# Structured monkey-patching
+
+import Products.Five
+from Products.Five import zcml
+from Products.Five.fiveconfigure import killMonkey
+from zope.testing.cleanup import addCleanUp
+
+_monkied = []
+
+from OFS.ObjectManager import ObjectManager
+from OFS.CopySupport import CopyContainer
+from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2Base
+
+def doMonkies(transitional):
+ """Monkey patch various methods to provide container events.
+ """
+ global containerEventsTransitional
+ containerEventsTransitional = transitional
+
+ patchMethod(ObjectManager, '_setObject',
+ _setObject)
+ patchMethod(ObjectManager, '_delObject',
+ _delObject)
+ patchMethod(ObjectManager, 'manage_afterAdd',
+ manage_afterAdd)
+ patchMethod(ObjectManager, 'manage_beforeDelete',
+ manage_beforeDelete)
+ patchMethod(ObjectManager, 'manage_afterClone',
+ manage_afterClone)
+
+ patchMethod(BTreeFolder2Base, '_setObject',
+ BT_setObject)
+ patchMethod(BTreeFolder2Base, '_delObject',
+ BT_delObject)
+
+ patchMethod(CopyContainer, '_notifyOfCopyTo',
+ _notifyOfCopyTo)
+ patchMethod(CopyContainer, '_postCopy',
+ _postCopy)
+ patchMethod(CopyContainer, 'manage_renameObject',
+ manage_renameObject)
+ # XXX also fix OrderSupport._old_manage_renameObject
+ patchMethod(CopyContainer, 'manage_pasteObjects',
+ manage_pasteObjects)
+ patchMethod(CopyContainer, 'manage_clone',
+ manage_clone)
+
+ zcml.load_config('event.zcml', Products.Five)
+
+ addCleanUp(undoMonkies)
+
+def patchMethod(class_, name, new_method):
+ method = getattr(class_, name, None)
+ if isFiveMethod(method):
+ return
+ setattr(class_, '__five_original_' + name, method)
+ setattr(class_, name, new_method)
+ new_method.__five_method__ = True
+ _monkied.append((class_, name))
+
+def undoMonkies():
+ """Undo monkey patches.
+ """
+ for class_, name in _monkied:
+ killMonkey(class_, name, FIVE_ORIGINAL_PREFIX + name)
+ containerEventAwareClasses[:] = []
+ deprecatedManageAddDeleteClasses[:] = []
Added: z3/Five/branch/efge-object-event/event.zcml
==============================================================================
--- (empty file)
+++ z3/Five/branch/efge-object-event/event.zcml Tue Oct 25 16:26:56 2005
@@ -0,0 +1,64 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+ <adapter
+ for="OFS.interfaces.IObjectManager"
+ provides="zope.app.location.interfaces.ISublocations"
+ factory=".event.ObjectManagerSublocations"
+ />
+
+ <!-- Ordering of the following subscribers is important -->
+
+ <!-- dispatch IObjectWillBeMovedEvent to sublocation before
+ compatibility calls, to have "bottom-up" semantics -->
+
+ <subscriber
+ for="OFS.interfaces.IObjectManager
+ .event.IObjectWillBeMovedEvent"
+ factory="zope.app.container.contained.dispatchToSublocations">
+ Handler dispatches moved events to sublocations of any ObjectManager.
+ </subscriber>
+
+ <!-- manage_beforeDelete compatibility -->
+
+ <subscriber
+ for="OFS.interfaces.ISimpleItem
+ .event.IObjectWillBeMovedEvent"
+ factory=".event.callManageBeforeDelete"
+ />
+
+ <subscriber
+ for="OFS.interfaces.IObjectManager
+ .event.IObjectWillBeMovedEvent"
+ factory=".event.callManageBeforeDelete"
+ />
+
+ <!-- manage_afterAdd compatibility -->
+
+ <subscriber
+ for="OFS.interfaces.ISimpleItem
+ zope.app.container.interfaces.IObjectMovedEvent"
+ factory=".event.callManageAfterAdd"
+ />
+
+ <subscriber
+ for="OFS.interfaces.IObjectManager
+ zope.app.container.interfaces.IObjectMovedEvent"
+ factory=".event.callManageAfterAdd"
+ />
+
+ <!-- dispatch IObjectMovedEvent to sublocations after
+ compatibility calls, to have "top-down" semantics -->
+
+ <subscriber
+ for="OFS.interfaces.IObjectManager
+ zope.app.container.interfaces.IObjectMovedEvent"
+ factory="zope.app.container.contained.dispatchToSublocations">
+ Handler dispatches moved events to sublocations of any ObjectManager.
+ </subscriber>
+
+ <!-- XXX dispatcher for manage_afterClone -->
+
+ <!-- XXX subscriber (not multi) for _notifOfCopyTo and _postCopy -->
+
+
+</configure>
Modified: z3/Five/branch/efge-object-event/eventconfigure.py
==============================================================================
--- z3/Five/branch/efge-object-event/eventconfigure.py (original)
+++ z3/Five/branch/efge-object-event/eventconfigure.py Tue Oct 25 16:26:56 2005
@@ -17,107 +17,39 @@
$Id$
"""
-from Products.Five.fiveconfigure import isFiveMethod
-from zope.event import notify
-from zope.interface import implements
-from zope.app.container.interfaces import IObjectAddedEvent,\
- IObjectRemovedEvent
-from zope.app.container.contained import ObjectMovedEvent
-from zope.app.event.objectevent import ObjectCopiedEvent
-
-# holds classes that were monkeyed with; for clean up
-_monkied = []
-
-# ObjectAddedEvent and ObjectRemovedEvent are different in Zope 2
-class ObjectAddedEvent(ObjectMovedEvent):
- implements(IObjectAddedEvent)
-
- def __init__(self, object, newParent=None, newName=None):
- if newParent is None:
- newParent = object.aq_inner.aq_parent
- if newName is None:
- newName = object.id
- ObjectMovedEvent.__init__(self, object, None, None, newParent, newName)
-
-class ObjectRemovedEvent(ObjectMovedEvent):
- implements(IObjectRemovedEvent)
-
- def __init__(self, object, oldParent=None, oldName=None):
- if oldParent is None:
- oldParent = object.aq_inner.aq_parent
- if oldName is None:
- oldName = object.id
- ObjectMovedEvent.__init__(self, object, oldParent, oldName, None, None)
-
-def manage_afterAdd(self, item, container):
- original_location_path = getattr(self, '__five_location_path__', None)
- self.__five_location_path__ = self.getPhysicalPath()
- # if there still is an object in the original location, we're copied
- # we cannot rely on manage_afterClone, as this gets triggered only
- # *after* a manage_afterAdd. This logic might fail in the case where
- # something *is* somehow left in the original location that can
- # be traversed to.
- is_copied = original_location_path and (self.unrestrictedTraverse(
- original_location_path, None) is not None)
- if is_copied:
- notify(ObjectCopiedEvent(self))
- if original_location_path is None or is_copied:
- notify(ObjectAddedEvent(self))
- else:
- original_location = self.unrestrictedTraverse(
- original_location_path[:-1])
- notify(ObjectMovedEvent(self,
- original_location, original_location_path[-1],
- container, self.id))
- # call original
- method = getattr(self, '__five_original_manage_afterAdd', None)
- if method is not None:
- self.__five_original_manage_afterAdd(item, container)
-
-manage_afterAdd.__five_method__ = True
-
-def manage_beforeDelete(self, item, container):
- notify(ObjectRemovedEvent(self))
- # call original
- method = getattr(self, '__five_original_manage_beforeDelete', None)
- if method is not None:
- self.__five_original_manage_beforeDelete(item, container)
-
-manage_beforeDelete.__five_method__ = True
-
-def classSendEvents(class_):
- """Make instances of the class send Object*Event."""
- # tuck away original methods if necessary
- for name in ['manage_afterAdd', 'manage_beforeDelete']:
- method = getattr(class_, name, None)
- if not isFiveMethod(method):
- # if we haven't alread overridden this, tuck away originals
- setattr(class_, '__five_original_' + name, method)
-
- class_.manage_afterAdd = manage_afterAdd
- class_.manage_beforeDelete = manage_beforeDelete
- # remember class for clean up
- _monkied.append(class_)
-
-def sendEvents(_context, class_):
+
+from event import doMonkies
+from event import containerEventAwareClasses
+from event import deprecatedManageAddDeleteClasses
+
+def setContainerEvents(transitional):
+ doMonkies(transitional)
+
+def setContainerEventAware(class_):
+ """Instances of the class will receive object events."""
+ containerEventAwareClasses.append(class_)
+
+def setDeprecatedManageAddDelete(class_):
+ """Instances of the class will still see their old methods called."""
+ deprecatedManageAddDeleteClasses.append(class_)
+
+def containerEvents(_context, transitional):
_context.action(
- discriminator = ('five:sendEvents', class_),
- callable = classSendEvents,
- args=(class_,)
+ discriminator=('five:containerEvents',),
+ callable=setContainerEvents,
+ args=(transitional,),
)
-# clean up code
-from Products.Five.fiveconfigure import killMonkey
-from zope.testing.cleanup import addCleanUp
-
-def unsendEvents(class_):
- """Restore class's initial state with respect to sending events"""
- for name in ['manage_afterAdd', 'manage_beforeDelete']:
- killMonkey(class_, name, '__five_original_'+name)
-
-def cleanUp():
- for class_ in _monkied:
- unsendEvents(class_)
+def containerEventAware(_context, class_):
+ _context.action(
+ discriminator=('five:containerEventAware', class_),
+ callable=setContainerEventAware,
+ args=(class_,),
+ )
-addCleanUp(cleanUp)
-del addCleanUp
+def deprecatedManageAddDelete(_context, class_):
+ _context.action(
+ discriminator=('five:deprecatedManageAddDelete', class_),
+ callable=setDeprecatedManageAddDelete,
+ args=(class_,),
+ )
Modified: z3/Five/branch/efge-object-event/fivedirectives.py
==============================================================================
--- z3/Five/branch/efge-object-event/fivedirectives.py (original)
+++ z3/Five/branch/efge-object-event/fivedirectives.py Tue Oct 25 16:26:56 2005
@@ -18,6 +18,7 @@
from zope.interface import Interface
from zope.app.publisher.browser.metadirectives import IBasicResourceInformation
from zope.configuration.fields import GlobalObject, Tokens, PythonIdentifier
+from zope.configuration.fields import Bool
from zope.schema import TextLine
class IImplementsDirective(Interface):
@@ -56,7 +57,7 @@
required=True
)
-class ISendEventsDirective(Interface):
+class ISizableDirective(Interface):
"""Make instances of class send events.
"""
@@ -65,6 +66,32 @@
required=True
)
+class IContainerEventsDirective(Interface):
+ """Global switch to enable container events
+ """
+ transitional = Bool(
+ title=u"Transitional",
+ required=False,
+ default=False,
+ )
+
+class IContainerEventAwareDirective(Interface):
+ """Send events for these contained content classes (transitional).
+ """
+ class_ = GlobalObject(
+ title=u"Class",
+ required=True,
+ )
+
+class IDeprecatedManageAddDeleteDirective(Interface):
+ """Call manage_afterAdd & co for these contained content classes.
+ """
+ class_ = GlobalObject(
+ title=u"Class",
+ required=True,
+ )
+
+
class IBridgeDirective(Interface):
"""Bridge from a Zope 2 interface to an equivalent Zope3 interface.
"""
Modified: z3/Five/branch/efge-object-event/meta.zcml
==============================================================================
--- z3/Five/branch/efge-object-event/meta.zcml (original)
+++ z3/Five/branch/efge-object-event/meta.zcml Tue Oct 25 16:26:56 2005
@@ -128,14 +128,26 @@
/>
<meta:directive
- name="sendEvents"
- schema=".fivedirectives.ISendEventsDirective"
- handler=".eventconfigure.sendEvents"
+ name="containerEvents"
+ schema=".fivedirectives.IContainerEventsDirective"
+ handler=".eventconfigure.containerEvents"
+ />
+
+ <meta:directive
+ name="containerEventAware"
+ schema=".fivedirectives.IContainerEventAwareDirective"
+ handler=".eventconfigure.containerEventAware"
+ />
+
+ <meta:directive
+ name="deprecatedManageAddDelete"
+ schema=".fivedirectives.IDeprecatedManageAddDeleteDirective"
+ handler=".eventconfigure.deprecatedManageAddDelete"
/>
<meta:directive
name="sizable"
- schema=".fivedirectives.ISendEventsDirective"
+ schema=".fivedirectives.ISizableDirective"
handler=".sizeconfigure.sizable"
/>
Modified: z3/Five/branch/efge-object-event/tests/event.txt
==============================================================================
--- z3/Five/branch/efge-object-event/tests/event.txt (original)
+++ z3/Five/branch/efge-object-event/tests/event.txt Tue Oct 25 16:26:56 2005
@@ -1,295 +1,579 @@
-Test events
-===========
+================
+Container events
+================
-Before we can start, we need to set up an event subscriber that allows
-us to inspect events that will be thrown during the test:
+Zope 3 container events are used to inform subscribers that an object is
+about to be added/removed from a container, and also after it has been
+done. This is used for bookkeeping and cleaning up in subobjects.
- >>> from zope.app.tests.placelesssetup import setUp, tearDown
- >>> setUp()
-
-Add a folder that doesn't verify objects on paste. We use it as a
-test sandbox:
-
- >>> from Products.Five.tests.testing import manage_addNoVerifyPasteFolder
- >>> manage_addNoVerifyPasteFolder(self.folder, 'npvf')
- >>> folder = self.folder.npvf
-
-Finally add a manager user login, give it the right permissions and
-log in using it:
-
- >>> uf = self.folder.acl_users
- >>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
- >>> self.setPermissions(standard_permissions + ['Copy or Move'], 'Manager')
- >>> self.login('manager')
-
- >>> from zope.app.event.tests.placelesssetup import getEvents, clearEvents
-
-
-Sending events
---------------
-
-Zope 2 classes need to be modified so that they send Zope 3 style
-events. Our stub class here is such a case. We can add it to a
-folder, for example, ...
-
- >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
- >>> manage_addSimpleContent(folder, 'foo', 'Foo')
+These events replace the Zope 2 manage_afterAdd, manage_beforeDelete and
+manage_afterClone methods.
-and no event will have been triggered:
+Phases
+======
- >>> len(getEvents())
- 0
+There are 4 steps to the migration process of having Zope 2 use Zope 3
+container events.
-Clean up:
+Phase 1
+-------
- >>> folder.manage_delObjects(['foo'])
+Original Zope 2 status, where no events are sent, and manage_afterAdd &
+co are called on the children.
-Now make the class send events:
+Phase 2
+-------
- >>> from Products.Five.eventconfigure import classSendEvents
- >>> from Products.Five.tests.testing.simplecontent import SimpleContent
- >>> classSendEvents(SimpleContent)
+<five:containerEvents transitional="true"/>
+Some content classes have been migrated to using Zope 3 events.
-Added event
-------------
+All standard Zope containers send Zope 3 events. Their manage_afterAdd &
+co methods do not do recursion anymore, and send a deprecation warning
+if called. The dispatching to subobjects is done through the standard
+Zope 3 dispatchToSublocations subscriber.
-Let's add an object to a folder:
+Containers will still call manage_afterAdd & co (with a deprecation
+warning) for content classes not specified in a::
- >>> manage_addSimpleContent(folder, 'foo', 'Foo')
-
-One object event should have been sent with the event's object being
-our foo object:
-
- >>> events = getEvents()
- >>> len(events)
- 1
- >>> foo = folder.foo
- >>> events[0].object == foo
- True
-
-That object event should have been an object added event:
-
- >>> from zope.app.container.interfaces import IObjectAddedEvent
- >>> events = getEvents(IObjectAddedEvent)
- >>> len(events)
- 1
- >>> events[0].object == foo
- True
- >>> events[0].newParent == foo.aq_parent
- True
-
-Check that the object's original manage_afterAdd method was also called:
-
- >>> foo.afterAdd_called
- True
+ <five:containerEventAware class="some.content.class"/>
-Now clean up:
+directive.
- >>> clearEvents()
-
-
-Moved event (I) -- Renaming
---------------------------
-
-Somehow we need to at least commit a subtransaction to make renaming
-succeed:
-
- >>> import transaction
- >>> transaction.commit(1)
-
-Let's rename the object we created before:
-
- >>> folder.manage_renameObject('foo', 'bar')
-
-We should get two events...
-
- >>> events = getEvents()
- >>> len(events)
- 2
-
-the removed event...
-
- >>> event = events[0]
- >>> from zope.app.container.interfaces import IObjectRemovedEvent
- >>> IObjectRemovedEvent.providedBy(event)
- True
- >>> event.object == foo
- True
- >>> event.oldName, event.newName
- ('foo', None)
- >>> event.oldParent == folder
- True
- >>> event.newParent is None
- True
-
-and the moved event:
-
- >>> event = events[1]
- >>> event.object == foo
- True
- >>> event.oldName, event.newName
- ('foo', 'bar')
- >>> event.oldParent == folder
- True
- >>> event.newParent == folder
- True
+Phase 3
+-------
-Now clean up:
+<five:containerEvents/>
- >>> folder.manage_delObjects(['bar'])
- >>> clearEvents()
+Most content classes have been migrated to using Zope 3 events.
-We don't delete the stub object just yet because it's being used in
-the next part of the test.
+All standard Zope containers will only call manage_afterAdd & co on
+classes specified in a::
+ <five:deprecatedManageAddDelete class="some.content.class"/>
-Moved event (II) -- Cut and paste
----------------------------------
+directive. A deprecation warning is sent for these.
-Let's move from one folder to another:
+<five:containerEventAware/> is deprecated, as it is now the default
+for other classes.
- >>> manage_addNoVerifyPasteFolder(folder, 'folder1', 'Folder1')
- >>> folder1 = folder.folder1
- >>> manage_addNoVerifyPasteFolder(folder, 'folder2', 'Folder2')
- >>> folder2 = folder.folder2
- >>> manage_addSimpleContent(folder1, 'foo', 'Foo')
- >>> foo = folder1.foo
+Phase 4
+-------
-We need to trigger a subtransaction before cut/paste can work:
+All content classes use Zope 3 events. <five:containerEvents/> is
+deprecated as it is now the default. <five:deprecatedManageAddDelete/>
+is forbidden.
- >>> transaction.commit(1)
- >>> cb = folder1.manage_cutObjects(['foo'])
- >>> info = folder2.manage_pasteObjects(cb)
-Apart from the added event we triggerred when we added the stub object
-to the folder, we expect two events...
+Testing
+=======
- >>> events = getEvents()
- >>> len(events)
- 3
- >>> len(getEvents(IObjectAddedEvent))
- 1
+Let's see what happens for these phases. There is a bit of setup for the
+tests.
-a removed event...
-
- >>> event = events[1]
- >>> from zope.app.container.interfaces import IObjectRemovedEvent
- >>> IObjectRemovedEvent.providedBy(event)
- True
- >>> event.oldParent == folder1
- True
- >>> event.newParent is None
- True
-
-and a moved event:
-
- >>> event = events[2]
- >>> event.object == foo
- True
- >>> event.oldName, event.newName
- ('foo', 'foo')
- >>> event.oldParent == folder1
- True
- >>> event.newParent == folder2
- True
-
-Now clean up:
-
- >>> folder.manage_delObjects(['folder1'])
- >>> folder.manage_delObjects(['folder2'])
- >>> clearEvents()
-
-
-Copied event
-------------
-
- >>> manage_addSimpleContent(folder, 'foo', 'Foo')
- >>> manage_addNoVerifyPasteFolder(folder, 'folder1')
- >>> folder1 = folder.folder1
-
-We need to trigger subtransaction before copy/paste can work
-
- >>> transaction.commit(1)
- >>> cb = folder.manage_copyObjects(['foo'])
- >>> info = folder1.manage_pasteObjects(cb)
- >>> foo = folder1.foo
-
-Apart from the added event we triggerred when we added the stub object
-to the folder, we expect two events...
-
- >>> events = getEvents()
- >>> len(events)
- 3
-
-a copied event...
-
- >>> event = events[1]
- >>> from zope.app.event.interfaces import IObjectCopiedEvent
- >>> IObjectCopiedEvent.providedBy(event)
- True
- >>> events[1].object == foo
- True
+ >>> from zope.app.tests.placelesssetup import setUp, tearDown
+ >>> setUp()
-and an added event:
+Because we'll test copy/paste, we need to work inside a database.
- >>> event = events[2]
- >>> IObjectAddedEvent.providedBy(event)
- True
- >>> event.object == foo
- True
- >>> event.newName
- 'foo'
- >>> event.newParent == folder1
+ >>> import ZODB.tests.util
+ >>> db = ZODB.tests.util.DB()
+ >>> connection = db.open()
+ >>> root = connection.root()
+
+We'll use two simple classes (defined in python code for picklability)
+and simple folders for our tests.
+
+ >>> from Products.Five.tests.test_event import MyApp, MyContent
+ >>> from Products.Five.tests.test_event import MyFolder, MyBTreeFolder
+
+ >>> app = MyApp('')
+ >>> root['app'] = app
+ >>> folder = MyFolder('folder')
+ >>> app._setObject('folder', folder)
+ old manage_afterAdd folder folder
+ 'folder'
+ >>> folder = app.folder
+ >>> btfolder = MyBTreeFolder('btfolder')
+ >>> app._setObject('btfolder', btfolder)
+ old manage_afterAdd btfolder btfolder
+ 'btfolder'
+
+To observe what object events are dispatched, we'll have some
+subscribers print them. We'll actually do that for a specific interface,
+not for (None, IObjectEvent), and register our subscribers before the
+framework's ones, so ours will be called first. This has the effect that
+printed events will be in their "natural" order.
+
+ >>> from zope.app.event.interfaces import IObjectEvent
+ >>> from zope.app.container.interfaces import IObjectMovedEvent
+ >>> from Products.Five.event import IObjectWillBeMovedEvent
+ >>> from OFS.interfaces import IObjectManager
+ >>> from OFS.interfaces import ISimpleItem
+ >>> from zope.app.tests import ztapi
+ >>> def printObjectEvent(object, event):
+ ... print event.__class__.__name__, object.getId()
+ >>> def printObjectEventExceptMoved(object, event):
+ ... if (IObjectMovedEvent.providedBy(event) or
+ ... IObjectWillBeMovedEvent.providedBy(event)):
+ ... return
+ ... print event.__class__.__name__, object.getId()
+ >>> ztapi.handle([IObjectManager, IObjectMovedEvent], printObjectEvent)
+ >>> ztapi.handle([ISimpleItem, IObjectMovedEvent], printObjectEvent)
+ >>> ztapi.handle([IObjectManager, IObjectWillBeMovedEvent], printObjectEvent)
+ >>> ztapi.handle([ISimpleItem, IObjectWillBeMovedEvent], printObjectEvent)
+ >>> ztapi.handle([None, IObjectEvent], printObjectEventExceptMoved)
+
+Phase 1
+-------
+
+Let's test standard folder operations. The methods manage_afterAdd and
+manage_beforeDelete, and several others, are called.
+
+ >>> ob = MyContent('milou')
+ >>> folder._setObject('milou', ob)
+ old manage_afterAdd milou milou folder
+ 'milou'
+ >>> folder.manage_delObjects('milou')
+ old manage_beforeDelete milou milou folder
+
+We can also move objects.
+
+ >>> ob = MyContent('tintin')
+ >>> folder._setObject('tintin', ob)
+ old manage_afterAdd tintin tintin folder
+ 'tintin'
+ >>> cp = folder.manage_cutObjects('tintin')
+ >>> folder.manage_pasteObjects(cp)
+ old _notifyOfCopyTo tintin folder 1
+ old manage_beforeDelete tintin tintin folder
+ old manage_afterAdd tintin tintin folder
+ old _postCopy tintin folder 1
+ [{'new_id': 'tintin', 'id': 'tintin'}]
+ >>> 'tintin' in folder.objectIds()
+ True
+
+And we can copy them.
+
+ >>> cp = folder.manage_copyObjects('tintin')
+ >>> folder.manage_pasteObjects(cp)
+ old _notifyOfCopyTo tintin folder 0
+ old manage_afterAdd copy_of_tintin copy_of_tintin folder
+ old _postCopy copy_of_tintin folder 0
+ old manage_afterClone copy_of_tintin copy_of_tintin
+ [{'new_id': 'copy_of_tintin', 'id': 'tintin'}]
+ >>> 'tintin' in folder.objectIds()
+ True
+ >>> 'copy_of_tintin' in folder.objectIds()
+ True
+
+We can rename objects:
+
+ >>> folder.manage_renameObject('copy_of_tintin', 'haddock')
+ old _notifyOfCopyTo copy_of_tintin folder 1
+ old manage_beforeDelete copy_of_tintin copy_of_tintin folder
+ old manage_afterAdd haddock haddock folder
+ old _postCopy haddock folder 1
+ >>> 'copy_of_tintin' in folder.objectIds()
+ False
+ >>> 'haddock' in folder.objectIds()
True
-Now clean up:
-
- >>> folder.manage_delObjects(['folder1'])
- >>> folder.manage_delObjects(['foo'])
- >>> clearEvents()
-
-
-Removed event
--------------
+We can also call manage_clone by hand:
- >>> manage_addSimpleContent(folder, 'foo', 'Foo')
- >>> foo = folder.foo
- >>> foo.beforeDelete_called
+ >>> res = folder.manage_clone(folder.tintin, 'tournesol')
+ old _notifyOfCopyTo tintin folder 0
+ old manage_afterAdd tournesol tournesol folder
+ old _postCopy tournesol folder 0
+ old manage_afterClone tournesol tournesol
+ >>> res.getId()
+ 'tournesol'
+ >>> 'tournesol' in folder.objectIds()
+ True
+
+Let's also test with a BTreeFolder:
+
+ >>> ob = MyContent('castafiore')
+ >>> btfolder._setObject('castafiore', ob)
+ old manage_afterAdd castafiore castafiore btfolder
+ 'castafiore'
+ >>> btfolder.manage_delObjects('castafiore')
+ old manage_beforeDelete castafiore castafiore btfolder
+
+When a tree of objects is affected, the methods are called for all
+levels.
+
+ >>> subfolder = MyFolder('subfolder')
+ >>> folder._setObject('subfolder', subfolder)
+ old manage_afterAdd subfolder subfolder folder
+ 'subfolder'
+ >>> subfolder = folder.subfolder
+ >>> ob = MyContent('riri')
+ >>> subfolder._setObject('riri', ob)
+ old manage_afterAdd riri riri subfolder
+ 'riri'
+
+Renaming a tree of objects:
+
+ >>> folder.manage_renameObject('subfolder', 'bob')
+ old _notifyOfCopyTo subfolder folder 1
+ old manage_beforeDelete riri subfolder folder
+ old manage_beforeDelete subfolder subfolder folder
+ old manage_afterAdd bob bob folder
+ old manage_afterAdd riri bob folder
+ old _postCopy bob folder 1
+ >>> 'bob' in folder.objectIds()
+ True
+
+Cloning a tree of objects:
+
+ >>> res = folder.manage_clone(folder.bob, 'loulou')
+ old _notifyOfCopyTo bob folder 0
+ old manage_afterAdd loulou loulou folder
+ old manage_afterAdd riri loulou folder
+ old _postCopy loulou folder 0
+ old manage_afterClone loulou loulou
+ old manage_afterClone riri loulou
+ >>> res.getId()
+ 'loulou'
+ >>> 'bob' in folder.objectIds()
+ True
+ >>> 'loulou' in folder.objectIds()
+ True
+
+Phase 2
+-------
+
+In this phase, transitional events are in effect.
+
+We need some of the Five setup, to get proper five:interfaces declared
+for Zope 2 classes.
+
+ >>> from Products.Five import zcml
+ >>> import Products.Five
+ >>> zcml.load_config('meta.zcml', Products.Five)
+ >>> zcml.load_config('interfaces.zcml', Products.Five)
+
+Phase 2 is when we're using <five:containerEvents transitional="true"/>
+
+ >>> from Products.Five.event import doMonkies, undoMonkies
+ >>> doMonkies(transitional=True)
+
+When we add an instance of an old class for which we haven't specified
+anything, some events are sent but the old manage_afterAdd method is
+also called.
+
+ >>> ob = MyContent('lassie')
+ >>> folder._setObject('lassie', ob)
+ ObjectWillBeAddedEvent lassie
+ ObjectAddedEvent lassie
+ old manage_afterAdd lassie lassie folder
+ 'lassie'
+
+And when we delete the object, manage_beforeDelete is also called and
+events are sent.
+
+ >>> folder.manage_delObjects('lassie')
+ ObjectWillBeRemovedEvent lassie
+ old manage_beforeDelete lassie lassie folder
+ ObjectRemovedEvent lassie
+
+The old behavior happens for a move or a copy, with events too.
+For a move:
+
+ >>> ob = MyContent('blueberry')
+ >>> folder._setObject('blueberry', ob)
+ ObjectWillBeAddedEvent blueberry
+ ObjectAddedEvent blueberry
+ old manage_afterAdd blueberry blueberry folder
+ 'blueberry'
+ >>> cp = folder.manage_cutObjects('blueberry')
+ >>> folder.manage_pasteObjects(cp)
+ old _notifyOfCopyTo blueberry folder 1
+ ObjectWillBeMovedEvent blueberry
+ old manage_beforeDelete blueberry blueberry folder
+ ObjectMovedEvent blueberry
+ old manage_afterAdd blueberry blueberry folder
+ old _postCopy blueberry folder 1
+ [{'new_id': 'blueberry', 'id': 'blueberry'}]
+ >>> 'blueberry' in folder.objectIds()
+ True
+
+Old behavior with events for a copy:
+
+ >>> cp = folder.manage_copyObjects('blueberry')
+ >>> folder.manage_pasteObjects(cp)
+ old _notifyOfCopyTo blueberry folder 0
+ ObjectCopiedEvent copy_of_blueberry
+ ObjectWillBeAddedEvent copy_of_blueberry
+ ObjectAddedEvent copy_of_blueberry
+ old manage_afterAdd copy_of_blueberry copy_of_blueberry folder
+ old _postCopy copy_of_blueberry folder 0
+ old manage_afterClone copy_of_blueberry copy_of_blueberry
+ [{'new_id': 'copy_of_blueberry', 'id': 'blueberry'}]
+ >>> 'blueberry' in folder.objectIds()
+ True
+ >>> 'copy_of_blueberry' in folder.objectIds()
+ True
+
+Old behavior with events for a renaming:
+
+ >>> folder.manage_renameObject('copy_of_blueberry', 'myrtille')
+ old _notifyOfCopyTo copy_of_blueberry folder 1
+ ObjectWillBeMovedEvent copy_of_blueberry
+ old manage_beforeDelete copy_of_blueberry copy_of_blueberry folder
+ ObjectMovedEvent myrtille
+ old manage_afterAdd myrtille myrtille folder
+ old _postCopy myrtille folder 1
+ >>> 'copy_of_blueberry' in folder.objectIds()
False
- >>> folder.manage_delObjects(['foo'])
-
- >>> events = getEvents()
- >>> len(events)
- 2
-
- >>> events[1].object.id
- 'foo'
-
-Check that the object's original manage_beforeDelete method was also called:
-
- >>> foo.beforeDelete_called
+ >>> 'myrtille' in folder.objectIds()
True
- >>> clearEvents()
-
+Old behavior with events for a clone:
-Clean up
---------
+ >>> res = folder.manage_clone(folder.blueberry, 'strawberry')
+ old _notifyOfCopyTo blueberry folder 0
+ ObjectCopiedEvent strawberry
+ ObjectWillBeAddedEvent strawberry
+ ObjectAddedEvent strawberry
+ old manage_afterAdd strawberry strawberry folder
+ old _postCopy strawberry folder 0
+ old manage_afterClone strawberry strawberry
+ >>> res.getId()
+ 'strawberry'
+ >>> 'strawberry' in folder.objectIds()
+ True
+
+Events are also sent when we work with a BTreeFolder:
+
+ >>> ob = MyContent('luckyluke')
+ >>> btfolder._setObject('luckyluke', ob)
+ ObjectWillBeAddedEvent luckyluke
+ ObjectAddedEvent luckyluke
+ old manage_afterAdd luckyluke luckyluke btfolder
+ 'luckyluke'
+
+ >>> btfolder.manage_delObjects('luckyluke')
+ ObjectWillBeRemovedEvent luckyluke
+ old manage_beforeDelete luckyluke luckyluke btfolder
+ ObjectRemovedEvent luckyluke
+
+Here is what happens for a tree of objects. Let's create a simple one.
+
+ >>> subfolder = MyFolder('subfolder')
+ >>> folder._setObject('subfolder', subfolder)
+ ObjectWillBeAddedEvent subfolder
+ ObjectAddedEvent subfolder
+ old manage_afterAdd subfolder subfolder folder
+ 'subfolder'
+ >>> subfolder = folder.subfolder
+ >>> ob = MyContent('donald')
+ >>> subfolder._setObject('donald', ob)
+ ObjectWillBeAddedEvent donald
+ ObjectAddedEvent donald
+ old manage_afterAdd donald donald subfolder
+ 'donald'
+
+Renaming a tree of objects.
+
+ >>> folder.manage_renameObject('subfolder', 'pluto')
+ old _notifyOfCopyTo subfolder folder 1
+ ObjectWillBeMovedEvent subfolder
+ ObjectWillBeMovedEvent donald
+ old manage_beforeDelete donald subfolder folder
+ old manage_beforeDelete subfolder subfolder folder
+ ObjectMovedEvent pluto
+ old manage_afterAdd pluto pluto folder
+ ObjectMovedEvent donald
+ old manage_afterAdd donald pluto folder
+ old _postCopy pluto folder 1
+ >>> 'pluto' in folder.objectIds()
+ True
+
+Cloning a tree of objects:
+
+ >>> res = folder.manage_clone(folder.pluto, 'mickey')
+ old _notifyOfCopyTo pluto folder 0
+ ObjectCopiedEvent mickey
+ ObjectWillBeAddedEvent mickey
+ ObjectWillBeAddedEvent donald
+ ObjectAddedEvent mickey
+ old manage_afterAdd mickey mickey folder
+ ObjectAddedEvent donald
+ old manage_afterAdd donald mickey folder
+ old _postCopy mickey folder 0
+ old manage_afterClone mickey mickey
+ >>> res.getId()
+ 'mickey'
+ >>> 'pluto' in folder.objectIds()
+ True
+ >>> 'mickey' in folder.objectIds()
+ True
+
+XXX there should be more calls to manage_afterClone above
+
+If however we specify using ZCML that our classes can react to events,
+the framework won't call manage_afterAdd and manage_beforeDelete
+anymore.
+
+ >>> from Products.Five.eventconfigure import setContainerEventAware
+ >>> setContainerEventAware(MyContent)
+ >>> setContainerEventAware(MyFolder)
+
+ >>> ob = MyContent('dogbert')
+ >>> folder._setObject('dogbert', ob)
+ ObjectWillBeAddedEvent dogbert
+ ObjectAddedEvent dogbert
+ 'dogbert'
+ >>> folder.manage_delObjects('dogbert')
+ ObjectWillBeRemovedEvent dogbert
+ ObjectRemovedEvent dogbert
+
+Now move:
+
+ >>> ob = MyContent('dilbert')
+ >>> folder._setObject('dilbert', ob)
+ ObjectWillBeAddedEvent dilbert
+ ObjectAddedEvent dilbert
+ 'dilbert'
+ >>> cp = folder.manage_cutObjects('dilbert')
+ >>> folder.manage_pasteObjects(cp)
+ ObjectWillBeMovedEvent dilbert
+ ObjectMovedEvent dilbert
+ [{'new_id': 'dilbert', 'id': 'dilbert'}]
+ >>> 'dilbert' in folder.objectIds()
+ True
+
+And copy:
+
+ >>> cp = folder.manage_copyObjects('dilbert')
+ >>> folder.manage_pasteObjects(cp)
+ ObjectCopiedEvent copy_of_dilbert
+ ObjectWillBeAddedEvent copy_of_dilbert
+ ObjectAddedEvent copy_of_dilbert
+ [{'new_id': 'copy_of_dilbert', 'id': 'dilbert'}]
+ >>> 'dilbert' in folder.objectIds()
+ True
+ >>> 'copy_of_dilbert' in folder.objectIds()
+ True
+
+Then rename:
+
+ >>> folder.manage_renameObject('copy_of_dilbert', 'wally')
+ ObjectWillBeMovedEvent copy_of_dilbert
+ ObjectMovedEvent wally
+ >>> 'wally' in folder.objectIds()
+ True
+
+Or copy using manage_clone:
+
+ >>> res = folder.manage_clone(folder.dilbert, 'phb')
+ ObjectCopiedEvent phb
+ ObjectWillBeAddedEvent phb
+ ObjectAddedEvent phb
+ >>> res.getId()
+ 'phb'
+ >>> 'dilbert' in folder.objectIds()
+ True
+ >>> 'phb' in folder.objectIds()
+ True
+
+Also on a BTreeFolder:
+
+ >>> ob = MyContent('alice')
+ >>> btfolder._setObject('alice', ob)
+ ObjectWillBeAddedEvent alice
+ ObjectAddedEvent alice
+ 'alice'
+ >>> btfolder.manage_renameObject('alice', 'rabbit')
+ ObjectWillBeMovedEvent alice
+ ObjectMovedEvent rabbit
+ >>> btfolder.manage_delObjects('rabbit')
+ ObjectWillBeRemovedEvent rabbit
+ ObjectRemovedEvent rabbit
+
+Now for a tree of objects. Let's create a simple one.
+
+ >>> subfolder = MyFolder('subfolder')
+ >>> folder._setObject('subfolder', subfolder)
+ ObjectWillBeAddedEvent subfolder
+ ObjectAddedEvent subfolder
+ 'subfolder'
+ >>> subfolder = folder.subfolder
+ >>> ob = MyContent('mel')
+ >>> subfolder._setObject('mel', ob)
+ ObjectWillBeAddedEvent mel
+ ObjectAddedEvent mel
+ 'mel'
+
+Renaming a tree of objects.
+
+ >>> folder.manage_renameObject('subfolder', 'firefly')
+ ObjectWillBeMovedEvent subfolder
+ ObjectWillBeMovedEvent mel
+ ObjectMovedEvent firefly
+ ObjectMovedEvent mel
+ >>> 'firefly' in folder.objectIds()
+ True
+
+Cloning a tree of objects:
+
+ >>> res = folder.manage_clone(folder.firefly, 'serenity')
+ ObjectCopiedEvent serenity
+ ObjectWillBeAddedEvent serenity
+ ObjectWillBeAddedEvent mel
+ ObjectAddedEvent serenity
+ ObjectAddedEvent mel
+ >>> res.getId()
+ 'serenity'
+ >>> 'firefly' in folder.objectIds()
+ True
+ >>> 'serenity' in folder.objectIds()
+ True
+
+Now cleanup all the monkey patches:
+
+ >>> undoMonkies()
+
+Phase 3
+-------
+
+This is the target phase.
+
+ >>> doMonkies(transitional=False)
+
+By default, events are sent and manage_afterAdd is not called.
+
+ >>> ob = MyContent('droopy')
+ >>> folder._setObject('droopy', ob)
+ ObjectWillBeAddedEvent droopy
+ ObjectAddedEvent droopy
+ 'droopy'
+ >>> folder.manage_delObjects('droopy')
+ ObjectWillBeRemovedEvent droopy
+ ObjectRemovedEvent droopy
+
+Old methods are called only for classes where we have specified that
+they are old.
+
+ >>> from Products.Five.eventconfigure import setDeprecatedManageAddDelete
+ >>> setDeprecatedManageAddDelete(MyContent)
+
+ >>> ob = MyContent('rantanplan')
+ >>> folder._setObject('rantanplan', ob)
+ ObjectWillBeAddedEvent rantanplan
+ ObjectAddedEvent rantanplan
+ old manage_afterAdd rantanplan rantanplan folder
+ 'rantanplan'
+ >>> folder.manage_delObjects('rantanplan')
+ ObjectWillBeRemovedEvent rantanplan
+ old manage_beforeDelete rantanplan rantanplan folder
+ ObjectRemovedEvent rantanplan
-Finally, we need to put our stub class back the way it was before we
-monkeyed with it:
-
- >>> from Products.Five.eventconfigure import cleanUp
- >>> cleanUp()
-
-Now adding an object won't trigger an event anymore:
-
- >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
- >>> manage_addSimpleContent(folder, 'foo', 'Foo')
- >>> len(getEvents())
- 0
-
-Finally, we need to tear down everything else (services, etc.)
+Now cleanup:
+ >>> import transaction
+ >>> transaction.abort()
+ >>> undoMonkies()
>>> tearDown()
Modified: z3/Five/branch/efge-object-event/tests/test_event.py
==============================================================================
--- z3/Five/branch/efge-object-event/tests/test_event.py (original)
+++ z3/Five/branch/efge-object-event/tests/test_event.py Tue Oct 25 16:26:56 2005
@@ -19,6 +19,53 @@
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
+
+# These classes aren't defined in the doctest because otherwise
+# they wouldn't be picklable, and we need that to test copy/paste.
+
+from OFS.Folder import Folder
+from OFS.SimpleItem import SimpleItem
+from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2
+
+class NotifyBase(object):
+ def _verifyObjectPaste(self, object, validate_src=1):
+ pass
+ def cb_isMoveable(self):
+ return True
+ def cb_isCopyable(self):
+ return True
+ def _notifyOfCopyTo(self, container, op):
+ print 'old _notifyOfCopyTo', self.getId(), container.getId(), op
+ def _postCopy(self, container, op):
+ print 'old _postCopy', self.getId(), container.getId(), op
+ def manage_afterAdd(self, item, container):
+ print 'old manage_afterAdd %s %s %s' % (self.getId(), item.getId(),
+ container.getId())
+ super(NotifyBase, self).manage_afterAdd(item, container)
+ def manage_beforeDelete(self, item, container):
+ super(NotifyBase, self).manage_beforeDelete(item, container)
+ print 'old manage_beforeDelete %s %s %s' % (self.getId(), item.getId(),
+ container.getId())
+ def manage_afterClone(self, item):
+ print 'old manage_afterClone %s %s' % (self.getId(), item.getId())
+ super(NotifyBase, self).manage_afterClone(item)
+
+class MyApp(Folder):
+ def getPhysicalRoot(self):
+ return self
+
+class MyFolder(NotifyBase, Folder):
+ pass
+
+class MyBTreeFolder(NotifyBase, BTreeFolder2):
+ def _verifyObjectPaste(self, object, validate_src=1):
+ pass
+
+class MyContent(NotifyBase, SimpleItem):
+ def __init__(self, id):
+ self._setId(id)
+
+
def test_suite():
from Testing.ZopeTestCase import ZopeDocFileSuite
return ZopeDocFileSuite('event.txt', package="Products.Five.tests")
More information about the z3-checkins
mailing list