[CMF-checkins] SVN: CMF/trunk/ Merged patches from Martin Aspeli to enable generating events before and after

Tres Seaver tseaver at palladion.com
Tue Jan 2 09:51:02 EST 2007


Log message for revision 71686:
  Merged patches from Martin Aspeli to enable generating events before and after
  DCWorkflow transitions, and in the 'notify' methods of the workflow tool.
  
  o See http://www.zope.org/Collectors/CMF/461
  

Changed:
  U   CMF/trunk/CHANGES.txt
  U   CMF/trunk/CMFCore/WorkflowCore.py
  U   CMF/trunk/CMFCore/WorkflowTool.py
  U   CMF/trunk/CMFCore/interfaces/__init__.py
  A   CMF/trunk/CMFCore/interfaces/_events.py
  U   CMF/trunk/CMFCore/tests/test_WorkflowTool.py
  U   CMF/trunk/DCWorkflow/DCWorkflow.py
  A   CMF/trunk/DCWorkflow/events.py
  U   CMF/trunk/DCWorkflow/interfaces.py
  U   CMF/trunk/DCWorkflow/tests/test_DCWorkflow.py

-=-
Modified: CMF/trunk/CHANGES.txt
===================================================================
--- CMF/trunk/CHANGES.txt	2007-01-02 14:19:42 UTC (rev 71685)
+++ CMF/trunk/CHANGES.txt	2007-01-02 14:51:01 UTC (rev 71686)
@@ -1,5 +1,11 @@
 CMF 2.1.0-beta (unreleased)
 
+  New Features
+
+    - Merged patches from Martin Aspeli to enable generating events before
+      and after DCWorkflow transitions, and in the 'notify' methods of the
+      workflow tool (http://www.zope.org/Collectors/CMF/461).
+
   Bug Fixes
 
     - CMFDefault.File and CMFDefault.Image: Restored ZMI Cache tab which was

Modified: CMF/trunk/CMFCore/WorkflowCore.py
===================================================================
--- CMF/trunk/CMFCore/WorkflowCore.py	2007-01-02 14:19:42 UTC (rev 71685)
+++ CMF/trunk/CMFCore/WorkflowCore.py	2007-01-02 14:51:01 UTC (rev 71686)
@@ -15,12 +15,20 @@
 $Id$
 """
 
+from zope.interface import implements
+from zope.component.interfaces import ObjectEvent
+
+from Products.CMFCore.interfaces import IWorkflowActionEvent
+from Products.CMFCore.interfaces import IActionWillBeInvokedEvent
+from Products.CMFCore.interfaces import IActionRaisedExceptionEvent
+from Products.CMFCore.interfaces import IActionSucceededEvent
+
 class WorkflowException( Exception ):
 
     """ Exception while invoking workflow.
     """
+    
 
-
 class ObjectDeleted( Exception ):
 
     """ Raise to tell the workflow tool that the object has been deleted.
@@ -49,3 +57,32 @@
 
     def getNewObject(self):
         return self._ob
+
+# Events
+
+class WorkflowActionEvent(ObjectEvent):
+    implements(IWorkflowActionEvent)
+    
+    def __init__(self, object, workflow, action):
+        ObjectEvent.__init__(self, object)
+        self.workflow = workflow
+        self.action = action
+    
+class ActionWillBeInvokedEvent(WorkflowActionEvent):
+    implements(IActionWillBeInvokedEvent)
+
+            
+class ActionRaisedExceptionEvent(WorkflowActionEvent):
+    implements(IActionRaisedExceptionEvent)
+    
+    def __init__(self, object, workflow, action, exc):
+        WorkflowActionEvent.__init__(self, object, workflow, action)
+        self.exc = exc
+    
+class ActionSucceededEvent(WorkflowActionEvent):
+    implements(IActionSucceededEvent)
+    
+    def __init__(self, object, workflow, action, result):
+        WorkflowActionEvent.__init__(self, object, workflow, action)
+        self.result = result
+

Modified: CMF/trunk/CMFCore/WorkflowTool.py
===================================================================
--- CMF/trunk/CMFCore/WorkflowTool.py	2007-01-02 14:19:42 UTC (rev 71685)
+++ CMF/trunk/CMFCore/WorkflowTool.py	2007-01-02 14:51:01 UTC (rev 71686)
@@ -25,6 +25,7 @@
 from OFS.Folder import Folder
 from OFS.ObjectManager import IFAwareObjectManager
 from zope.interface import implements
+from zope.event import notify
 
 from ActionProviderBase import ActionProviderBase
 from interfaces import IConfigurableWorkflowTool
@@ -39,8 +40,10 @@
 from WorkflowCore import ObjectDeleted
 from WorkflowCore import ObjectMoved
 from WorkflowCore import WorkflowException
+from WorkflowCore import ActionWillBeInvokedEvent
+from WorkflowCore import ActionRaisedExceptionEvent
+from WorkflowCore import ActionSucceededEvent
 
-
 _marker = []  # Create a new marker object.
 
 
@@ -293,6 +296,7 @@
         wfs = self.getWorkflowsFor(ob)
         for wf in wfs:
             wf.notifyBefore(ob, action)
+            notify(ActionWillBeInvokedEvent(ob, wf, action))
 
     security.declarePrivate('notifySuccess')
     def notifySuccess(self, ob, action, result=None):
@@ -301,6 +305,7 @@
         wfs = self.getWorkflowsFor(ob)
         for wf in wfs:
             wf.notifySuccess(ob, action, result)
+            notify(ActionSucceededEvent(ob, wf, action, result))
 
     security.declarePrivate('notifyException')
     def notifyException(self, ob, action, exc):
@@ -309,6 +314,7 @@
         wfs = self.getWorkflowsFor(ob)
         for wf in wfs:
             wf.notifyException(ob, action, exc)
+            notify(ActionRaisedExceptionEvent(ob, wf, action, exc))
 
     security.declarePrivate('getHistoryOf')
     def getHistoryOf(self, wf_id, ob):
@@ -516,6 +522,7 @@
         reindex = 1
         for w in wfs:
             w.notifyBefore(ob, action)
+            notify(ActionWillBeInvokedEvent(ob, w, action))
         try:
             res = func(*args, **kw)
         except ObjectDeleted, ex:
@@ -529,11 +536,13 @@
             try:
                 for w in wfs:
                     w.notifyException(ob, action, exc)
+                    notify(ActionRaisedExceptionEvent(ob, w, action, exc))
                 raise exc[0], exc[1], exc[2]
             finally:
                 exc = None
         for w in wfs:
             w.notifySuccess(ob, action, res)
+            notify(ActionSucceededEvent(ob, w, action, res))
         if reindex:
             self._reindexWorkflowVariables(ob)
         return res

Modified: CMF/trunk/CMFCore/interfaces/__init__.py
===================================================================
--- CMF/trunk/CMFCore/interfaces/__init__.py	2007-01-02 14:19:42 UTC (rev 71685)
+++ CMF/trunk/CMFCore/interfaces/__init__.py	2007-01-02 14:51:01 UTC (rev 71686)
@@ -17,6 +17,7 @@
 
 from _content import *
 from _tools import *
+from _events import *
 
 import CachingPolicyManager
 import Contentish

Added: CMF/trunk/CMFCore/interfaces/_events.py
===================================================================
--- CMF/trunk/CMFCore/interfaces/_events.py	2007-01-02 14:19:42 UTC (rev 71685)
+++ CMF/trunk/CMFCore/interfaces/_events.py	2007-01-02 14:51:01 UTC (rev 71686)
@@ -0,0 +1,46 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+""" CMFCore event interfaces.
+
+$Id$
+"""
+from zope.interface import Attribute
+from zope.component.interfaces import IObjectEvent
+
+
+class IWorkflowActionEvent(IObjectEvent):
+    
+    """Base interface for events around workflow action invocation
+    """
+    
+    workflow = Attribute("The workflow definition object")
+    action = Attribute("The name of the action being invoked")
+    
+class IActionWillBeInvokedEvent(IWorkflowActionEvent):
+    
+    """Event fired immediately before a workflow action is invoked
+    """
+    
+class IActionRaisedExceptionEvent(IWorkflowActionEvent):
+    
+    """Event fired when a workflow action raised an exception
+    """
+    
+    exc = Attribute("The exception info for the exception raised")
+    
+class IActionSucceededEvent(IWorkflowActionEvent):
+    
+    """Event fired when a workflow action succeeded
+    """
+    
+    result = Attribute("The result of the workflow action")


Property changes on: CMF/trunk/CMFCore/interfaces/_events.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: CMF/trunk/CMFCore/tests/test_WorkflowTool.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_WorkflowTool.py	2007-01-02 14:19:42 UTC (rev 71685)
+++ CMF/trunk/CMFCore/tests/test_WorkflowTool.py	2007-01-02 14:51:01 UTC (rev 71686)
@@ -20,6 +20,10 @@
 
 from OFS.SimpleItem import SimpleItem
 
+from zope.component import adapter, provideHandler
+from Products.CMFCore.interfaces import IActionWillBeInvokedEvent
+from Products.CMFCore.interfaces import IActionRaisedExceptionEvent
+from Products.CMFCore.interfaces import IActionSucceededEvent
 
 class Dummy( SimpleItem ):
 
@@ -98,7 +102,20 @@
     def notifyException( self, ob, action, exc ):
         self.notified( 'exception' ).append( ( ob, action, exc ) )
 
+ at adapter(IActionWillBeInvokedEvent)
+def notifyBeforeHandler(evt):
+    evt.workflow.notified( 'before-evt' ).append( (evt.object, evt.action) )
+    
+ at adapter(IActionSucceededEvent)
+def notifySuccessHandler(evt):
+    evt.workflow.notified( 'success-evt' ).append( (evt.object, evt.action,
+                                                    evt.result ) )
 
+ at adapter(IActionRaisedExceptionEvent)
+def notifyExceptionHandler(evt):
+    evt.workflow.notified( 'exception-evt' ).append( (evt.object, evt.action,
+                                                      evt.exc) )
+
 class DummyContent( Dummy ):
 
     meta_type = 'Dummy'
@@ -328,6 +345,8 @@
 
     def test_notifyBefore( self ):
 
+        provideHandler(notifyBeforeHandler)
+
         tool = self._makeWithTypesAndChain()
 
         ob = DummyContent( 'dummy' )
@@ -337,9 +356,15 @@
             notified = wf.notified( 'before' )
             self.assertEqual( len( notified ), 1 )
             self.assertEqual( notified[0], ( ob, 'action' ) )
+            
+            notified = wf.notified( 'before-evt' )
+            self.assertEqual( len( notified ), 1 )
+            self.assertEqual( notified[0], ( ob, 'action' ) )
 
     def test_notifySuccess( self ):
 
+        provideHandler(notifySuccessHandler)
+
         tool = self._makeWithTypesAndChain()
 
         ob = DummyContent( 'dummy' )
@@ -349,9 +374,15 @@
             notified = wf.notified( 'success' )
             self.assertEqual( len( notified ), 1 )
             self.assertEqual( notified[0], ( ob, 'action', None ) )
+            
+            notified = wf.notified( 'success-evt' )
+            self.assertEqual( len( notified ), 1 )
+            self.assertEqual( notified[0], ( ob, 'action', None ) )
 
     def test_notifyException( self ):
 
+        provideHandler(notifyExceptionHandler)
+
         tool = self._makeWithTypesAndChain()
 
         ob = DummyContent( 'dummy' )
@@ -361,6 +392,10 @@
             notified = wf.notified( 'exception' )
             self.assertEqual( len( notified ), 1 )
             self.assertEqual( notified[0], ( ob, 'action', 'exception' ) )
+            
+            notified = wf.notified( 'exception-evt' )
+            self.assertEqual( len( notified ), 1 )
+            self.assertEqual( notified[0], ( ob, 'action', 'exception' ) )
 
     def xxx_test_updateRoleMappings( self ):
         """

Modified: CMF/trunk/DCWorkflow/DCWorkflow.py
===================================================================
--- CMF/trunk/DCWorkflow/DCWorkflow.py	2007-01-02 14:19:42 UTC (rev 71685)
+++ CMF/trunk/DCWorkflow/DCWorkflow.py	2007-01-02 14:51:01 UTC (rev 71686)
@@ -26,6 +26,7 @@
 from OFS.Folder import Folder
 from OFS.ObjectManager import bad_id
 from zope.interface import implements
+from zope.event import notify
 
 # CMFCore
 from Products.CMFCore.interfaces import IWorkflowDefinition
@@ -47,8 +48,8 @@
 from Transitions import TRIGGER_USER_ACTION
 from Expression import StateChangeInfo
 from Expression import createExprContext
+from events import BeforeTransitionEvent, AfterTransitionEvent
 
-
 def checkId(id):
     res = bad_id(id)
     if res != -1 and res is not None:
@@ -468,6 +469,9 @@
             msg = _(u'Destination state undefined: ${state_id}',
                     mapping={'state_id': new_state})
             raise WorkflowException(msg)
+        
+        # Fire "before" event
+        notify(BeforeTransitionEvent(ob, self, old_sdef, new_sdef, tdef, former_status, kwargs))
 
         # Execute the "before" script.
         if tdef is not None and tdef.script_name:
@@ -532,6 +536,9 @@
                 ob, self, status, tdef, old_sdef, new_sdef, kwargs)
             script(sci)  # May throw an exception.
 
+        # Fire "after" event
+        notify(AfterTransitionEvent(ob, self, old_sdef, new_sdef, tdef, former_status, kwargs))
+
         # Return the new state object.
         if moved_exc is not None:
             # Propagate the notification that the object has moved.

Added: CMF/trunk/DCWorkflow/events.py
===================================================================
--- CMF/trunk/DCWorkflow/events.py	2007-01-02 14:19:42 UTC (rev 71685)
+++ CMF/trunk/DCWorkflow/events.py	2007-01-02 14:51:01 UTC (rev 71686)
@@ -0,0 +1,22 @@
+from zope.interface import implements
+from zope.component.interfaces import ObjectEvent
+
+from interfaces import ITransitionEvent, IBeforeTransitionEvent, IAfterTransitionEvent
+
+class TransitionEvent(ObjectEvent):
+    implements(ITransitionEvent)
+    
+    def __init__(self, obj, workflow, old_state, new_state, transition, status, kwargs):
+        ObjectEvent.__init__(self, obj)
+        self.workflow = workflow
+        self.old_state = old_state
+        self.new_state = new_state
+        self.transition = transition
+        self.status = status
+        self.kwargs = kwargs
+        
+class BeforeTransitionEvent(TransitionEvent):
+    implements(IBeforeTransitionEvent)
+    
+class AfterTransitionEvent(TransitionEvent):
+    implements(IAfterTransitionEvent)
\ No newline at end of file


Property changes on: CMF/trunk/DCWorkflow/events.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: CMF/trunk/DCWorkflow/interfaces.py
===================================================================
--- CMF/trunk/DCWorkflow/interfaces.py	2007-01-02 14:19:42 UTC (rev 71685)
+++ CMF/trunk/DCWorkflow/interfaces.py	2007-01-02 14:51:01 UTC (rev 71686)
@@ -15,10 +15,34 @@
 $Id$
 """
 
-from zope.interface import Interface
+from zope.interface import Interface, Attribute
+from zope.component.interfaces import IObjectEvent
 
-
 class IDCWorkflowDefinition(Interface):
 
     """Web-configurable workflow definition.
     """
+
+class ITransitionEvent(Interface):
+    
+    """An event that's fired upon a workflow transition.
+    """
+    
+    workflow = Attribute(u"The workflow definition triggering the transition")
+    old_state = Attribute(u"The state definition of the workflow state before the transition")
+    new_state = Attribute(u"The state definition of the workflow state before after transition")
+    transition = Attribute(u"The transition definition taking place. "
+                            "May be None if this is the 'transition' to the initial state.")                                   
+    status = Attribute(u"The history/status dict of the object before the transition.")
+    kwargs = Attribute(u"Any keyword arguments passed to doActionFor() when the transition was invoked")
+    
+class IBeforeTransitionEvent(ITransitionEvent):
+    
+    """An event fired before a workflow transition.
+    """
+    
+class IAfterTransitionEvent(ITransitionEvent):
+    
+    """An event that's fired after a workflow transition.
+    """
+    
\ No newline at end of file

Modified: CMF/trunk/DCWorkflow/tests/test_DCWorkflow.py
===================================================================
--- CMF/trunk/DCWorkflow/tests/test_DCWorkflow.py	2007-01-02 14:19:42 UTC (rev 71685)
+++ CMF/trunk/DCWorkflow/tests/test_DCWorkflow.py	2007-01-02 14:51:01 UTC (rev 71686)
@@ -23,6 +23,10 @@
 from Products.CMFCore.tests.base.dummy import DummyTool
 from Products.CMFCore.WorkflowTool import WorkflowTool
 
+from zope.interface import Interface
+from zope.component import provideHandler, adapter
+from Products.DCWorkflow.interfaces import IBeforeTransitionEvent
+from Products.DCWorkflow.interfaces import IAfterTransitionEvent
 
 class DCWorkflowDefinitionTests(unittest.TestCase):
 
@@ -91,6 +95,65 @@
 
         # XXX more
 
+    def test_events(self):
+        
+        events = []
+        
+        @adapter(IBeforeTransitionEvent)
+        def _handleBefore(event):
+            events.append(event)
+        provideHandler(_handleBefore)
+    
+        @adapter(IAfterTransitionEvent)
+        def _handleAfter(event):
+            events.append(event)
+        provideHandler(_handleAfter)
+        
+        wftool = self.site.portal_workflow
+        wf = self._getDummyWorkflow()
+
+        dummy = self.site._setObject( 'dummy', DummyContent() )
+        wftool.notifyCreated(dummy)
+        wf.doActionFor(dummy, 'publish', comment='foo', test='bar')
+        
+        self.assertEquals(4, len(events))
+        
+        evt = events[0]
+        self.failUnless(IBeforeTransitionEvent.providedBy(evt))
+        self.assertEquals(dummy, evt.object)
+        self.assertEquals('private', evt.old_state.id)
+        self.assertEquals('private', evt.new_state.id)
+        self.assertEquals(None, evt.transition)
+        self.assertEquals({}, evt.status)
+        self.assertEquals(None, evt.kwargs)
+        
+        evt = events[1]
+        self.failUnless(IAfterTransitionEvent.providedBy(evt))
+        self.assertEquals(dummy, evt.object)
+        self.assertEquals('private', evt.old_state.id)
+        self.assertEquals('private', evt.new_state.id)
+        self.assertEquals(None, evt.transition)
+        self.assertEquals({}, evt.status)
+        self.assertEquals(None, evt.kwargs)
+        
+        evt = events[2]
+        self.failUnless(IBeforeTransitionEvent.providedBy(evt))
+        self.assertEquals(dummy, evt.object)
+        self.assertEquals('private', evt.old_state.id)
+        self.assertEquals('published', evt.new_state.id)
+        self.assertEquals('publish', evt.transition.id)
+        self.assertEquals({'state': 'private', 'comments': ''}, evt.status)
+        self.assertEquals({'test' : 'bar', 'comment' : 'foo'}, evt.kwargs)
+        
+        evt = events[3]
+        self.failUnless(IAfterTransitionEvent.providedBy(evt))
+        self.assertEquals(dummy, evt.object)
+        self.assertEquals('private', evt.old_state.id)
+        self.assertEquals('published', evt.new_state.id)
+        self.assertEquals('publish', evt.transition.id)
+        self.assertEquals({'state': 'private', 'comments': ''}, evt.status)
+        self.assertEquals({'test' : 'bar', 'comment' : 'foo'}, evt.kwargs)
+
     def test_checkTransitionGuard(self):
 
         wftool = self.site.portal_workflow



More information about the CMF-checkins mailing list