[z3-checkins] r18979 - in z3/Five/trunk: . doc tests
efge at codespeak.net
efge at codespeak.net
Wed Oct 26 02:08:10 CEST 2005
Author: efge
Date: Wed Oct 26 02:08:09 2005
New Revision: 18979
Added:
z3/Five/trunk/event.py
- copied unchanged from r18978, z3/Five/branch/efge-object-event/event.py
z3/Five/trunk/event.zcml
- copied unchanged from r18978, z3/Five/branch/efge-object-event/event.zcml
Modified:
z3/Five/trunk/doc/directives.txt
z3/Five/trunk/doc/features.txt
z3/Five/trunk/eventconfigure.py
z3/Five/trunk/fivedirectives.py
z3/Five/trunk/meta.zcml
z3/Five/trunk/tests/event.txt
z3/Five/trunk/tests/test_event.py
Log:
Merged 18937:18978 from efge-object-event branch:
The standard Zope 2 containers can now send events.
<five:sendEvents class=.../> is removed, and replaced by
<five:containerEvents/> and <five:deprecatedManageAddDelete class=.../>
manage_afterAdd, manage_beforeDelete and manage_afterClone are deprecated.
tests/event.txt gives more details about the exact events sent.
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.
In the transitional phase (<five:containerEvents transitional="true"/>),
you use <five:containerEventAware class=.../> to specify which classes
are "modern" with respect to events, instead of specifying which ones
are "old" using five:deprecatedManageAddDelete like described above.
Modified: z3/Five/trunk/doc/directives.txt
==============================================================================
--- z3/Five/trunk/doc/directives.txt (original)
+++ z3/Five/trunk/doc/directives.txt Wed Oct 26 02:08:09 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/trunk/doc/features.txt
==============================================================================
--- z3/Five/trunk/doc/features.txt (original)
+++ z3/Five/trunk/doc/features.txt Wed Oct 26 02:08:09 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.
Modified: z3/Five/trunk/eventconfigure.py
==============================================================================
--- z3/Five/trunk/eventconfigure.py (original)
+++ z3/Five/trunk/eventconfigure.py Wed Oct 26 02:08:09 2005
@@ -17,107 +17,42 @@
$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, info):
+ doMonkies(transitional, info)
+
+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=False):
+ # Remember context info to be able to provide information
+ # when resolving conflicts about this directive.
+ info = getattr(_context, 'info', '')
_context.action(
- discriminator = ('five:sendEvents', class_),
- callable = classSendEvents,
- args=(class_,)
+ discriminator=None, # conflict resolution is done "by hand"
+ callable=setContainerEvents,
+ args=(transitional, info),
)
-# 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/trunk/fivedirectives.py
==============================================================================
--- z3/Five/trunk/fivedirectives.py (original)
+++ z3/Five/trunk/fivedirectives.py Wed Oct 26 02:08:09 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,31 @@
required=True
)
+class IContainerEventsDirective(Interface):
+ """Global switch to enable container events
+ """
+ transitional = Bool(
+ title=u"Transitional",
+ required=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/trunk/meta.zcml
==============================================================================
--- z3/Five/trunk/meta.zcml (original)
+++ z3/Five/trunk/meta.zcml Wed Oct 26 02:08:09 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/trunk/tests/event.txt
==============================================================================
--- z3/Five/trunk/tests/event.txt (original)
+++ z3/Five/trunk/tests/event.txt Wed Oct 26 02:08:09 2005
@@ -1,295 +1,540 @@
-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
+These events replace the Zope 2 manage_afterAdd, manage_beforeDelete and
+manage_afterClone methods.
-Finally add a manager user login, give it the right permissions and
-log in using it:
+Phases
+======
- >>> uf = self.folder.acl_users
- >>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
- >>> self.setPermissions(standard_permissions + ['Copy or Move'], 'Manager')
- >>> self.login('manager')
+There are 4 steps to the migration process of having Zope 2 use Zope 3
+container events.
- >>> from zope.app.event.tests.placelesssetup import getEvents, clearEvents
+Phase 1
+-------
+Original Zope 2 status, where no events are sent, and manage_afterAdd &
+co are called on the children.
-Sending events
---------------
+Phase 2
+-------
-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, ...
+<five:containerEvents transitional="true"/>
- >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
- >>> manage_addSimpleContent(folder, 'foo', 'Foo')
+Some content classes have been migrated to using Zope 3 events.
-and no event will have been triggered:
+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.
- >>> len(getEvents())
- 0
+Containers will still call manage_afterAdd & co (with a deprecation
+warning) for content classes not specified in a::
-Clean up:
+ <five:containerEventAware class="some.content.class"/>
- >>> folder.manage_delObjects(['foo'])
+directive.
-Now make the class send events:
+Phase 3
+-------
- >>> from Products.Five.eventconfigure import classSendEvents
- >>> from Products.Five.tests.testing.simplecontent import SimpleContent
- >>> classSendEvents(SimpleContent)
+<five:containerEvents/>
+Most content classes have been migrated to using Zope 3 events.
-Added event
-------------
+All standard Zope containers will only call manage_afterAdd & co on
+classes specified in a::
-Let's add an object to a folder:
+ <five:deprecatedManageAddDelete class="some.content.class"/>
- >>> manage_addSimpleContent(folder, 'foo', 'Foo')
+directive. A deprecation warning is sent for these.
-One object event should have been sent with the event's object being
-our foo object:
+<five:containerEventAware/> is deprecated, as it is now the default
+for other classes.
- >>> events = getEvents()
- >>> len(events)
- 1
- >>> foo = folder.foo
- >>> events[0].object == foo
- True
+Phase 4
+-------
-That object event should have been an object added event:
+All content classes use Zope 3 events. <five:containerEvents/> is
+deprecated as it is now the default. <five:deprecatedManageAddDelete/>
+is forbidden.
- >>> 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:
+Testing
+=======
- >>> foo.afterAdd_called
- True
+Let's see what happens for these phases. There is a bit of setup for the
+tests.
-Now clean up:
-
- >>> clearEvents()
+ >>> from zope.app.tests.placelesssetup import setUp, tearDown
+ >>> setUp()
+Because we'll test copy/paste, we need to work inside a database.
-Moved event (I) -- Renaming
---------------------------
+ >>> 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
+ >>> from Products.Five.tests.test_event import MyOrderedFolder
+
+ >>> 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 Products.Five.event import IFiveObjectClonedEvent
+ >>> 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 printObjectEventExceptSome(object, event):
+ ... if (IObjectMovedEvent.providedBy(event) or
+ ... IObjectWillBeMovedEvent.providedBy(event) or
+ ... IFiveObjectClonedEvent.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([IObjectManager, IFiveObjectClonedEvent], printObjectEvent)
+ >>> ztapi.handle([ISimpleItem, IFiveObjectClonedEvent], printObjectEvent)
+ >>> ztapi.handle([None, IObjectEvent], printObjectEventExceptSome)
+
+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 manage_beforeDelete tintin tintin folder
+ old manage_afterAdd tintin tintin folder
+ [{'new_id': 'tintin', 'id': 'tintin'}]
+
+And we can copy them.
+
+ >>> cp = folder.manage_copyObjects('tintin')
+ >>> folder.manage_pasteObjects(cp)
+ old manage_afterAdd copy_of_tintin copy_of_tintin folder
+ old manage_afterClone copy_of_tintin copy_of_tintin
+ [{'new_id': 'copy_of_tintin', 'id': 'tintin'}]
+
+We can rename objects:
+
+ >>> folder.manage_renameObject('copy_of_tintin', 'haddock')
+ old manage_beforeDelete copy_of_tintin copy_of_tintin folder
+ old manage_afterAdd haddock haddock folder
+
+We can also call manage_clone by hand:
+
+ >>> res = folder.manage_clone(folder.tintin, 'tournesol')
+ old manage_afterAdd tournesol tournesol folder
+ old manage_afterClone tournesol tournesol
+ >>> res.getId()
+ 'tournesol'
+
+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. Note that manage_beforeDelete is called
+bottom-up.
+
+ >>> folder.manage_renameObject('subfolder', 'bob')
+ old manage_beforeDelete riri subfolder folder
+ old manage_beforeDelete subfolder subfolder folder
+ old manage_afterAdd bob bob folder
+ old manage_afterAdd riri bob folder
+
+Cloning a tree of objects:
+
+ >>> res = folder.manage_clone(folder.bob, 'loulou')
+ old manage_afterAdd loulou loulou folder
+ old manage_afterAdd riri loulou folder
+ old manage_afterClone loulou loulou
+ old manage_afterClone riri loulou
+ >>> res.getId()
+ 'loulou'
+
+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)
+ ObjectWillBeMovedEvent blueberry
+ old manage_beforeDelete blueberry blueberry folder
+ ObjectMovedEvent blueberry
+ old manage_afterAdd blueberry blueberry folder
+ [{'new_id': 'blueberry', 'id': 'blueberry'}]
+
+Old behavior with events for a copy:
+
+ >>> cp = folder.manage_copyObjects('blueberry')
+ >>> folder.manage_pasteObjects(cp)
+ ObjectCopiedEvent copy_of_blueberry
+ ObjectWillBeAddedEvent copy_of_blueberry
+ ObjectAddedEvent copy_of_blueberry
+ old manage_afterAdd copy_of_blueberry copy_of_blueberry folder
+ FiveObjectClonedEvent copy_of_blueberry
+ old manage_afterClone copy_of_blueberry copy_of_blueberry
+ [{'new_id': 'copy_of_blueberry', 'id': 'blueberry'}]
+
+Old behavior with events for a renaming:
+
+ >>> folder.manage_renameObject('copy_of_blueberry', 'myrtille')
+ ObjectWillBeMovedEvent copy_of_blueberry
+ old manage_beforeDelete copy_of_blueberry copy_of_blueberry folder
+ ObjectMovedEvent myrtille
+ old manage_afterAdd myrtille myrtille folder
+
+Old behavior with events for a clone:
+
+ >>> res = folder.manage_clone(folder.blueberry, 'strawberry')
+ ObjectCopiedEvent strawberry
+ ObjectWillBeAddedEvent strawberry
+ ObjectAddedEvent strawberry
+ old manage_afterAdd strawberry strawberry folder
+ FiveObjectClonedEvent strawberry
+ old manage_afterClone strawberry strawberry
+ >>> res.getId()
+ 'strawberry'
+
+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. Note that manage_beforeDelete is called
+bottom-up.
+
+ >>> folder.manage_renameObject('subfolder', 'pluto')
+ 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
+
+Cloning a tree of objects:
+
+ >>> res = folder.manage_clone(folder.pluto, 'mickey')
+ ObjectCopiedEvent mickey
+ ObjectWillBeAddedEvent mickey
+ ObjectWillBeAddedEvent donald
+ ObjectAddedEvent mickey
+ old manage_afterAdd mickey mickey folder
+ ObjectAddedEvent donald
+ old manage_afterAdd donald mickey folder
+ FiveObjectClonedEvent mickey
+ old manage_afterClone mickey mickey
+ FiveObjectClonedEvent donald
+ old manage_afterClone donald mickey
+ >>> res.getId()
+ 'mickey'
+
+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'}]
+
+And copy:
+
+ >>> cp = folder.manage_copyObjects('dilbert')
+ >>> folder.manage_pasteObjects(cp)
+ ObjectCopiedEvent copy_of_dilbert
+ ObjectWillBeAddedEvent copy_of_dilbert
+ ObjectAddedEvent copy_of_dilbert
+ FiveObjectClonedEvent copy_of_dilbert
+ [{'new_id': 'copy_of_dilbert', 'id': 'dilbert'}]
+
+Then rename:
+
+ >>> folder.manage_renameObject('copy_of_dilbert', 'wally')
+ ObjectWillBeMovedEvent copy_of_dilbert
+ ObjectMovedEvent wally
+
+Or copy using manage_clone:
+
+ >>> res = folder.manage_clone(folder.dilbert, 'phb')
+ ObjectCopiedEvent phb
+ ObjectWillBeAddedEvent phb
+ ObjectAddedEvent phb
+ FiveObjectClonedEvent phb
+ >>> res.getId()
+ 'phb'
+
+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
+
+Cloning a tree of objects:
+
+ >>> res = folder.manage_clone(folder.firefly, 'serenity')
+ ObjectCopiedEvent serenity
+ ObjectWillBeAddedEvent serenity
+ ObjectWillBeAddedEvent mel
+ ObjectAddedEvent serenity
+ ObjectAddedEvent mel
+ FiveObjectClonedEvent serenity
+ FiveObjectClonedEvent mel
+ >>> res.getId()
+ 'serenity'
+
+OrderedFolder has the same renaming behavior than before:
+
+ >>> ofolder = MyOrderedFolder('ofolder')
+ >>> app._setObject('ofolder', ofolder)
+ ObjectWillBeAddedEvent ofolder
+ ObjectAddedEvent ofolder
+ old manage_afterAdd ofolder ofolder
+ 'ofolder'
+ >>> ob1 = MyContent('ob1')
+ >>> ofolder._setObject('ob1', ob1)
+ ObjectWillBeAddedEvent ob1
+ ObjectAddedEvent ob1
+ 'ob1'
+ >>> ob2 = MyContent('ob2')
+ >>> ofolder._setObject('ob2', ob2)
+ ObjectWillBeAddedEvent ob2
+ ObjectAddedEvent ob2
+ 'ob2'
+ >>> ofolder.manage_renameObject('ob1', 'ob4')
+ ObjectWillBeMovedEvent ob1
+ ObjectMovedEvent ob4
+ >>> ofolder.objectIds()
+ ['ob4', 'ob2']
+
+
+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
-Somehow we need to at least commit a subtransaction to make renaming
-succeed:
+Now cleanup:
>>> 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
-
-Now clean up:
-
- >>> folder.manage_delObjects(['bar'])
- >>> clearEvents()
-
-We don't delete the stub object just yet because it's being used in
-the next part of the test.
-
-
-Moved event (II) -- Cut and paste
----------------------------------
-
-Let's move from one folder to another:
-
- >>> manage_addNoVerifyPasteFolder(folder, 'folder1', 'Folder1')
- >>> folder1 = folder.folder1
- >>> manage_addNoVerifyPasteFolder(folder, 'folder2', 'Folder2')
- >>> folder2 = folder.folder2
- >>> manage_addSimpleContent(folder1, 'foo', 'Foo')
- >>> foo = folder1.foo
-
-We need to trigger a subtransaction before cut/paste can work:
-
- >>> 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...
-
- >>> events = getEvents()
- >>> len(events)
- 3
- >>> len(getEvents(IObjectAddedEvent))
- 1
-
-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
-
-and an added event:
-
- >>> event = events[2]
- >>> IObjectAddedEvent.providedBy(event)
- True
- >>> event.object == foo
- True
- >>> event.newName
- 'foo'
- >>> event.newParent == folder1
- True
-
-Now clean up:
-
- >>> folder.manage_delObjects(['folder1'])
- >>> folder.manage_delObjects(['foo'])
- >>> clearEvents()
-
-
-Removed event
--------------
-
- >>> manage_addSimpleContent(folder, 'foo', 'Foo')
- >>> foo = folder.foo
- >>> foo.beforeDelete_called
- 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
- True
-
- >>> clearEvents()
-
-
-Clean up
---------
-
-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.)
-
+ >>> transaction.abort()
+ >>> undoMonkies()
>>> tearDown()
Modified: z3/Five/trunk/tests/test_event.py
==============================================================================
--- z3/Five/trunk/tests/test_event.py (original)
+++ z3/Five/trunk/tests/test_event.py Wed Oct 26 02:08:09 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.SimpleItem import SimpleItem
+from OFS.Folder import Folder
+from OFS.OrderedFolder import OrderedFolder
+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 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 MyOrderedFolder(NotifyBase, OrderedFolder):
+ 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