[Zope-CMF] Re: Zope 3 events from workflow

Martin Aspeli optilude at gmx.net
Wed Dec 27 11:22:14 EST 2006


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.



More information about the Zope-CMF mailing list