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

Martin Aspeli optilude at gmx.net
Wed Dec 27 09:28:52 EST 2006


Jens Vagelpohl wrote:

> You need to provide full patches and find someone who has the time - I'm 
> afraid I don't. The best solution would be for you to get in touch with 
> Jim and get a contributor agreement going. That's not rocket science.

This turned out to be easier than I feared. I've put a patch in the 
collector (http://www.zope.org/Collectors/CMF/461); also pasting below 
to make it easier to inspect.

There is a (fairly comprehensive) test, and all the other tests are passing.

Note - I've used Zope 2.10 import locations. DEPENDENCIES.txt suggests 
that should be fine.

What are the chances of getting this in CMF svn trunk and into CMF 2.1? 
I would *really* like to depend on this in Plone (the alternative would 
be monkey patches or stupid overrides in Plone's own WorkflowTool.py 
with proprietary events). Happy to provide what other help I can.

(and yes, I will get in touch with Jim re a contrib agreement, but I 
won't be able to sign anything for at least two weeks since I'm away).

> P.S.: I _hate hate hate_ doctests ;)

Why? They're useful sometimes, imho. Anyway, no doctests here. :)

Thanks,
Martin

The patch, to DCWorkflow trunk. Most of it is in tests :)

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,62 @@

          # 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' )
+
+        self.assertEquals(4, len(events))
+
+        initial = events[0]
+        self.failUnless(IBeforeTransitionEvent.providedBy(initial))
+        self.assertEquals(dummy, initial.object)
+        self.assertEquals('wf', initial.wf_name)
+        self.assertEquals(None, initial.action)
+        self.assertEquals('private', initial.state_before)
+        self.assertEquals('private', initial.state_after)
+
+        initial = events[1]
+        self.failUnless(IAfterTransitionEvent.providedBy(initial))
+        self.assertEquals(dummy, initial.object)
+        self.assertEquals('wf', initial.wf_name)
+        self.assertEquals(None, initial.action)
+        self.assertEquals('private', initial.state_before)
+        self.assertEquals('private', initial.state_after)
+
+        before = events[2]
+        self.failUnless(IBeforeTransitionEvent.providedBy(before))
+        self.assertEquals(dummy, before.object)
+        self.assertEquals('wf', before.wf_name)
+        self.assertEquals('publish', before.action)
+        self.assertEquals('private', before.state_before)
+        self.assertEquals('published', before.state_after)
+
+        after = events[3]
+        self.failUnless(IAfterTransitionEvent.providedBy(after))
+        self.assertEquals(dummy, after.object)
+        self.assertEquals('wf', after.wf_name)
+        self.assertEquals('publish', after.action)
+        self.assertEquals('private', after.state_before)
+        self.assertEquals('published', after.state_after)
+
+
      def test_checkTransitionGuard(self):

          wftool = self.site.portal_workflow
Index: interfaces.py
===================================================================
--- interfaces.py	(revision 71649)
+++ interfaces.py	(working copy)
@@ -16,9 +16,35 @@
  """

  from zope.interface import Interface
+from zope.component.interfaces import IObjectEvent
+from zope.schema import TextLine

-
  class IDCWorkflowDefinition(Interface):

      """Web-configurable workflow definition.
      """
+
+class ITransitionEvent(Interface):
+
+    """An event that's fired upon a workflow transition.
+    """
+
+    wf_name = TextLine(title=u"The name of the workflow providing the 
action")
+
+    action = TextLine(title=u"The name of the action taking place",
+                      description=u"Note - this may be None if there is 
no transition, "
+                                   "e.g. if this is the 'transition' to 
the initial state.")
+
+    state_before = TextLine(title=u"The state before the transition")
+    state_after = TextLine(title=u"The state after the transition")
+
+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:
@@ -469,6 +470,14 @@
                      mapping={'state_id': new_state})
              raise WorkflowException(msg)

+        # Figure out action name - may be None if this is an initial state
+        action_id = None
+        if tdef is not None:
+            action_id = tdef.getId()
+
+        # Fire "before" event
+        notify(BeforeTransitionEvent(ob, self.getId(), action_id, 
old_state, new_state))
+
          # Execute the "before" script.
          if tdef is not None and tdef.script_name:
              script = self.scripts[tdef.script_name]
@@ -532,6 +541,9 @@
                  ob, self, status, tdef, old_sdef, new_sdef, kwargs)
              script(sci)  # May throw an exception.

+        # Fire "after" event
+        notify(AfterTransitionEvent(ob, self.getId(), action_id, 
old_state, new_state))
+
          # 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