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 ) ) [EMAIL PROTECTED](IActionWillBeInvokedEvent) +def notifyBeforeHandler(evt): + evt.workflow.notified( 'before-evt' ).append( (evt.object, evt.action) ) + [EMAIL PROTECTED](IActionSucceededEvent) +def notifySuccessHandler(evt): + evt.workflow.notified( 'success-evt' ).append( (evt.object, evt.action, + evt.result ) ) [EMAIL PROTECTED](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 _______________________________________________ CMF-checkins mailing list [email protected] http://mail.zope.org/mailman/listinfo/cmf-checkins
