Tres Seaver wrote:

Looks farily good overall.  I think one useful extension would be useful
to provide access to the same information currently exposed via
Products.DCWorkflow.Expression.StaTeChangeInfo (these are what the
guard expressions use):

  - The workflow object itself.

  - The transition object itself

  - The history of the object

  - Any keywords passed to the transition-trigger ('doActionFor')

Naming those attributes with the same names used in the expression
context might make porting some scripts / guard expressions to event
handlers simpler.

Okay - done this now. New patch in the collector and pasted below.

For full generality, we also need to allow the "before" handlers to veto
the transition, e.g. by raising WorkflowException.  Looking at the code,
it doesn't appear that the definition or the tool make any effort to
catch exceptions raised by transition scripts, so we should just emulate
how it handles them.

Didn't do anything with exceptions. I assume that if an event handler
raised an exception, it'd bubble up to wherever it'd need to go.

Comments?

Martin

Index: tests/test_DCWorkflow.py
===================================================================
--- tests/test_DCWorkflow.py    (revision 71649)
+++ tests/test_DCWorkflow.py    (working copy)
@@ -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
Index: interfaces.py
===================================================================
--- interfaces.py       (revision 71649)
+++ interfaces.py       (working copy)
@@ -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
Index: DCWorkflow.py
===================================================================
--- DCWorkflow.py       (revision 71649)
+++ DCWorkflow.py       (working copy)
@@ -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.

_______________________________________________
Zope-CMF maillist  -  Zope-CMF@lists.zope.org
http://mail.zope.org/mailman/listinfo/zope-cmf

See http://collector.zope.org/CMF for bug reports and feature requests

Reply via email to