[CMF-checkins] SVN: CMF/trunk/C - added IConfigurableWorkflowTool
Yvo Schubbe
y.2006_ at wcm-solutions.de
Wed Nov 15 06:39:01 EST 2006
Log message for revision 71137:
- added IConfigurableWorkflowTool
- added support for 'remove' attribute in import XML (http://www.zope.org/Collectors/CMF/457)
- some related cleanup
Changed:
U CMF/trunk/CHANGES.txt
U CMF/trunk/CMFCore/WorkflowTool.py
U CMF/trunk/CMFCore/exportimport/tests/test_workflow.py
U CMF/trunk/CMFCore/exportimport/workflow.py
U CMF/trunk/CMFCore/interfaces/_tools.py
U CMF/trunk/CMFCore/tests/test_WorkflowTool.py
-=-
Modified: CMF/trunk/CHANGES.txt
===================================================================
--- CMF/trunk/CHANGES.txt 2006-11-15 09:26:18 UTC (rev 71136)
+++ CMF/trunk/CHANGES.txt 2006-11-15 11:38:59 UTC (rev 71137)
@@ -2,6 +2,12 @@
New Features
+ - WorkflowTool: Added the IConfigurableWorkflowTool interface.
+ This change includes the new 'getDefaultChain' and 'listChainOverrides'
+ methods and an improved 'setChainForPortalTypes' method. The import
+ handler now supports the 'remove' attribute for removing overrides.
+ (http://www.zope.org/Collectors/CMF/457)
+
- CMFCore.CachingPolicyManager: Implemented the old OFS.Cache.CacheManager
API. Now objects other than CMF content or CMF templates can have their
caching headers set by the caching policy manager with the same
Modified: CMF/trunk/CMFCore/WorkflowTool.py
===================================================================
--- CMF/trunk/CMFCore/WorkflowTool.py 2006-11-15 09:26:18 UTC (rev 71136)
+++ CMF/trunk/CMFCore/WorkflowTool.py 2006-11-15 11:38:59 UTC (rev 71137)
@@ -27,6 +27,7 @@
from zope.interface import implements
from ActionProviderBase import ActionProviderBase
+from interfaces import IConfigurableWorkflowTool
from interfaces import IWorkflowDefinition
from interfaces import IWorkflowTool
from interfaces.portal_workflow import portal_workflow as z2IWorkflowTool
@@ -49,7 +50,7 @@
""" Mediator tool, mapping workflow objects
"""
- implements(IWorkflowTool)
+ implements(IConfigurableWorkflowTool, IWorkflowTool)
__implements__ = (z2IWorkflowTool, ActionProviderBase.__implements__)
id = 'portal_workflow'
@@ -149,31 +150,8 @@
manage_tabs_message='Changed.')
#
- # portal_workflow implementation.
+ # 'IActionProvider' interface methods
#
- security.declarePrivate('getCatalogVariablesFor')
- def getCatalogVariablesFor(self, ob):
-
- """ Returns a mapping of the catalog variables that apply to ob.
-
- o Invoked by portal_catalog.
-
- o Allows workflows to add variables to the catalog based on
- workflow status, making it possible to implement queues.
- """
- wfs = self.getWorkflowsFor(ob)
- if wfs is None:
- return None
- # Iterate through the workflows backwards so that
- # earlier workflows can override later workflows.
- wfs.reverse()
- vars = {}
- for wf in wfs:
- v = wf.getCatalogVariablesFor(ob)
- if v is not None:
- vars.update(v)
- return vars
-
security.declarePrivate('listActions')
def listActions(self, info=None, object=None):
@@ -215,16 +193,29 @@
actions.extend(a)
return actions
+ #
+ # 'IWorkflowTool' interface methods
+ #
+ security.declarePrivate('getCatalogVariablesFor')
+ def getCatalogVariablesFor(self, ob):
+ """ Get a mapping of "workflow-relevant" attributes.
+ """
+ wfs = self.getWorkflowsFor(ob)
+ if wfs is None:
+ return None
+ # Iterate through the workflows backwards so that
+ # earlier workflows can override later workflows.
+ wfs.reverse()
+ vars = {}
+ for wf in wfs:
+ v = wf.getCatalogVariablesFor(ob)
+ if v is not None:
+ vars.update(v)
+ return vars
+
security.declarePublic('doActionFor')
def doActionFor(self, ob, action, wf_id=None, *args, **kw):
-
- """ Execute the given workflow action for the object.
-
- o Invoked by user interface code.
-
- o Allows the user to request a workflow action.
-
- o The workflow object must perform its own security checks.
+ """ Perform the given workflow action on 'ob'.
"""
wfs = self.getWorkflowsFor(ob)
if wfs is None:
@@ -251,14 +242,7 @@
security.declarePublic('getInfoFor')
def getInfoFor(self, ob, name, default=_marker, wf_id=None, *args, **kw):
-
- """ Return a given workflow-specific property for an object.
-
- o Invoked by user interface code.
-
- o Allows the user to request information provided by the workflow.
-
- o The workflow object must perform its own security checks.
+ """ Get the given bit of workflow information for the object.
"""
if wf_id is None:
wfs = self.getWorkflowsFor(ob)
@@ -295,9 +279,7 @@
security.declarePrivate('notifyCreated')
def notifyCreated(self, ob):
-
- """ Notify all applicable workflows that an object has been created
- and put in its new place.
+ """ Notify all applicable workflows that an object has been created.
"""
wfs = self.getWorkflowsFor(ob)
for wf in wfs:
@@ -306,14 +288,7 @@
security.declarePrivate('notifyBefore')
def notifyBefore(self, ob, action):
-
- """ Notifies all applicable workflows of an action before it
- happens, allowing veto by exception.
-
- o Unless an exception is thrown, either a notifySuccess() or
- notifyException() can be expected later on.
-
- o The action usually corresponds to a method name.
+ """ Notify all applicable workflows of an action before it happens.
"""
wfs = self.getWorkflowsFor(ob)
for wf in wfs:
@@ -321,7 +296,6 @@
security.declarePrivate('notifySuccess')
def notifySuccess(self, ob, action, result=None):
-
""" Notify all applicable workflows that an action has taken place.
"""
wfs = self.getWorkflowsFor(ob)
@@ -330,7 +304,6 @@
security.declarePrivate('notifyException')
def notifyException(self, ob, action, exc):
-
""" Notify all applicable workflows that an action failed.
"""
wfs = self.getWorkflowsFor(ob)
@@ -339,10 +312,7 @@
security.declarePrivate('getHistoryOf')
def getHistoryOf(self, wf_id, ob):
-
- """ Return the history of an object.
-
- o Invoked by workflow definitions.
+ """ Get the history of an object for a given workflow.
"""
if hasattr(aq_base(ob), 'workflow_history'):
wfh = ob.workflow_history
@@ -351,10 +321,7 @@
security.declarePrivate('getStatusOf')
def getStatusOf(self, wf_id, ob):
-
- """ Return the last entry of a workflow history.
-
- o Invoked by workflow definitions.
+ """ Get the last element of a workflow history for a given workflow.
"""
wfh = self.getHistoryOf(wf_id, ob)
if wfh:
@@ -363,10 +330,7 @@
security.declarePrivate('setStatusOf')
def setStatusOf(self, wf_id, ob, status):
-
- """ Append an entry to the workflow history.
-
- o Invoked by workflow definitions.
+ """ Append a record to the workflow history of a given workflow.
"""
wfh = None
has_history = 0
@@ -385,31 +349,36 @@
ob.workflow_history[wf_id] = tuple(wfh)
#
- # Administration methods
+ # 'IConfigurableWorkflowTool' interface methods
#
- security.declareProtected( ManagePortal, 'setDefaultChain')
+ security.declareProtected(ManagePortal, 'setDefaultChain')
def setDefaultChain(self, default_chain):
-
- """ Set the default chain for this tool
+ """ Set the default chain for this tool.
"""
default_chain = default_chain.replace(',', ' ')
ids = []
for wf_id in default_chain.split(' '):
if wf_id:
if not self.getWorkflowById(wf_id):
- raise ValueError, ( '"%s" is not a workflow ID.' % wf_id)
+ raise ValueError('"%s" is not a workflow ID.' % wf_id)
ids.append(wf_id)
self._default_chain = tuple(ids)
- security.declareProtected( ManagePortal, 'setChainForPortalTypes')
+ security.declareProtected(ManagePortal, 'setChainForPortalTypes')
def setChainForPortalTypes(self, pt_names, chain, verify=True):
- """ Set a chain for a specific portal type.
+ """ Set a chain for specific portal types.
"""
cbt = self._chains_by_type
if cbt is None:
self._chains_by_type = cbt = PersistentMapping()
+ if chain is None:
+ for type_id in pt_names:
+ if cbt.has_key(type_id):
+ del cbt[type_id]
+ return
+
if isinstance(chain, basestring):
chain = [ wf.strip() for wf in chain.split(',') if wf.strip() ]
@@ -420,9 +389,48 @@
continue
cbt[type_id] = tuple(chain)
- security.declareProtected( ManagePortal, 'updateRoleMappings')
- def updateRoleMappings(self, REQUEST=None):
+ security.declarePrivate('getDefaultChain')
+ def getDefaultChain(self):
+ """ Get the default chain for this tool.
+ """
+ return self._default_chain
+ security.declarePrivate('listChainOverrides')
+ def listChainOverrides(self):
+ """ List portal type specific chain overrides.
+ """
+ cbt = self._chains_by_type
+ return cbt and sorted(cbt.items()) or ()
+
+ security.declarePrivate('getChainFor')
+ def getChainFor(self, ob):
+ """ Get the chain that applies to the given object.
+ """
+ cbt = self._chains_by_type
+ if isinstance(ob, basestring):
+ pt = ob
+ elif hasattr(aq_base(ob), 'getPortalTypeName'):
+ pt = ob.getPortalTypeName()
+ else:
+ pt = None
+
+ if pt is None:
+ return ()
+
+ chain = None
+ if cbt is not None:
+ chain = cbt.get(pt, None)
+ # Note that if chain is not in cbt or has a value of
+ # None, we use a default chain.
+ if chain is None:
+ return self.getDefaultChain()
+ return chain
+
+ #
+ # Other methods
+ #
+ security.declareProtected(ManagePortal, 'updateRoleMappings')
+ def updateRoleMappings(self, REQUEST=None):
""" Allow workflows to update the role-permission mappings.
"""
wfs = {}
@@ -451,8 +459,7 @@
security.declarePrivate('getDefaultChainFor')
def getDefaultChainFor(self, ob):
-
- """ Return the default chain, if applicable, for ob.
+ """ Get the default chain, if applicable, for ob.
"""
types_tool = getToolByName( self, 'portal_types', None )
if ( types_tool is not None
@@ -461,35 +468,6 @@
return ()
- security.declarePrivate('getChainFor')
- def getChainFor(self, ob):
-
- """ Returns the chain that applies to the given object.
- If we get a string as the ob parameter, use it as
- the portal_type.
- """
- cbt = self._chains_by_type
- if isinstance(ob, basestring):
- pt = ob
- elif hasattr(aq_base(ob), 'getPortalTypeName'):
- pt = ob.getPortalTypeName()
- else:
- pt = None
-
- if pt is None:
- return ()
-
- chain = None
- if cbt is not None:
- chain = cbt.get(pt, None)
- # Note that if chain is not in cbt or has a value of
- # None, we use a default chain.
- if chain is None:
- chain = self.getDefaultChainFor(ob)
- if chain is None:
- return ()
- return chain
-
security.declarePrivate('getWorkflowIds')
def getWorkflowIds(self):
Modified: CMF/trunk/CMFCore/exportimport/tests/test_workflow.py
===================================================================
--- CMF/trunk/CMFCore/exportimport/tests/test_workflow.py 2006-11-15 09:26:18 UTC (rev 71136)
+++ CMF/trunk/CMFCore/exportimport/tests/test_workflow.py 2006-11-15 11:38:59 UTC (rev 71137)
@@ -32,7 +32,7 @@
from Products.GenericSetup.utils import BodyAdapterBase
from Products.CMFCore.interfaces import IWorkflowDefinition
-from Products.CMFCore.interfaces import IWorkflowTool
+from Products.CMFCore.interfaces import IConfigurableWorkflowTool
_DUMMY_ZCML = """\
<configure
@@ -109,10 +109,19 @@
</object>
"""
+_FRAGMENT_IMPORT = """\
+<?xml version="1.0"?>
+<object name="portal_workflow">
+ <bindings>
+ <type type_id="sometype" remove=""/>
+ </bindings>
+</object>
+"""
+
class DummyWorkflowTool(Folder):
- implements(IWorkflowTool)
+ implements(IConfigurableWorkflowTool)
meta_type = 'Dummy Workflow Tool'
@@ -127,17 +136,26 @@
def getWorkflowById(self, workflow_id):
return self._getOb(workflow_id)
+ def getDefaultChain(self):
+ return self._default_chain
+
def setDefaultChain(self, chain):
chain = chain.replace(',', ' ')
self._default_chain = tuple(chain.split())
+ def listChainOverrides(self):
+ return sorted(self._chains_by_type.items())
+
def setChainForPortalTypes(self, pt_names, chain, verify=True):
+ if chain is None:
+ for pt_name in pt_names:
+ if pt_name in self._chains_by_type:
+ del self._chains_by_type[pt_name]
+ return
+
chain = chain.replace(',', ' ')
chain = tuple(chain.split())
- if self._chains_by_type is None:
- self._chains_by_type = {}
-
for pt_name in pt_names:
self._chains_by_type[pt_name] = chain
@@ -245,6 +263,7 @@
_BINDINGS_TOOL_EXPORT = _BINDINGS_TOOL_EXPORT
_EMPTY_TOOL_EXPORT = _EMPTY_TOOL_EXPORT
+ _FRAGMENT_IMPORT = _FRAGMENT_IMPORT
def test_empty_default_purge(self):
from Products.CMFCore.exportimport.workflow import importWorkflowTool
@@ -359,7 +378,34 @@
self.assertEqual(wf_tool._chains_by_type['anothertype'],
(WF_ID_NON % 3,))
+ def test_fragment_skip_purge(self):
+ from Products.CMFCore.exportimport.workflow import importWorkflowTool
+ WF_ID_NON = 'non_dcworkflow_%s'
+ WF_TITLE_NON = 'Non-DCWorkflow #%s'
+
+ site = self._initSite()
+ wf_tool = site.portal_workflow
+
+ for i in range(4):
+ nondcworkflow = DummyWorkflow(WF_TITLE_NON % i)
+ nondcworkflow.title = WF_TITLE_NON % i
+ wf_tool._setObject(WF_ID_NON % i, nondcworkflow)
+
+ wf_tool._default_chain = (WF_ID_NON % 1,)
+ wf_tool._chains_by_type['sometype'] = (WF_ID_NON % 2,)
+ self.assertEqual(len(wf_tool.objectIds()), 4)
+
+ context = DummyImportContext(site, False)
+ context._files['workflows.xml'] = self._FRAGMENT_IMPORT
+ importWorkflowTool(context)
+
+ self.assertEqual(len(wf_tool.objectIds()), 4)
+ self.assertEqual(len(wf_tool._default_chain), 1)
+ self.assertEqual(wf_tool._default_chain[0], WF_ID_NON % 1)
+ self.assertEqual(len(wf_tool._chains_by_type), 0)
+
+
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(WorkflowToolXMLAdapterTests),
Modified: CMF/trunk/CMFCore/exportimport/workflow.py
===================================================================
--- CMF/trunk/CMFCore/exportimport/workflow.py 2006-11-15 09:26:18 UTC (rev 71136)
+++ CMF/trunk/CMFCore/exportimport/workflow.py 2006-11-15 11:38:59 UTC (rev 71137)
@@ -24,7 +24,7 @@
from Products.GenericSetup.utils import PropertyManagerHelpers
from Products.GenericSetup.utils import XMLAdapterBase
-from Products.CMFCore.interfaces import IWorkflowTool
+from Products.CMFCore.interfaces import IConfigurableWorkflowTool
from Products.CMFCore.utils import getToolByName
@@ -34,7 +34,7 @@
"""XML im- and exporter for WorkflowTool.
"""
- adapts(IWorkflowTool, ISetupEnviron)
+ adapts(IConfigurableWorkflowTool, ISetupEnviron)
_LOGGER_ID = 'workflow'
@@ -69,31 +69,28 @@
fragment = self._doc.createDocumentFragment()
node = self._doc.createElement('bindings')
child = self._doc.createElement('default')
- chain = self.context._default_chain
+ chain = self.context.getDefaultChain()
for workflow_id in chain:
sub = self._doc.createElement('bound-workflow')
sub.setAttribute('workflow_id', workflow_id)
child.appendChild(sub)
node.appendChild(child)
- cbt = self.context._chains_by_type
- if cbt:
- overrides = cbt.items()
- overrides.sort()
- for type_id, chain in overrides:
- child = self._doc.createElement('type')
- child.setAttribute('type_id', type_id)
- for workflow_id in chain:
- sub = self._doc.createElement('bound-workflow')
- sub.setAttribute('workflow_id', workflow_id)
- child.appendChild(sub)
- node.appendChild(child)
+ for type_id, chain in self.context.listChainOverrides():
+ child = self._doc.createElement('type')
+ child.setAttribute('type_id', type_id)
+ for workflow_id in chain:
+ sub = self._doc.createElement('bound-workflow')
+ sub.setAttribute('workflow_id', workflow_id)
+ child.appendChild(sub)
+ node.appendChild(child)
fragment.appendChild(node)
return fragment
def _purgeChains(self):
self.context.setDefaultChain('')
- if self.context._chains_by_type is not None:
- self.context._chains_by_type.clear()
+ for type_id, chain in self.context.listChainOverrides():
+ self.context.setChainForPortalTypes((type_id,), None,
+ verify=False)
def _initChains(self, node):
for child in node.childNodes:
@@ -104,8 +101,12 @@
self.context.setDefaultChain(self._getChain(sub))
if sub.nodeName == 'type':
type_id = str(sub.getAttribute('type_id'))
- self.context.setChainForPortalTypes((type_id,),
- self._getChain(sub), verify=False)
+ if sub.hasAttribute('remove'):
+ chain = None
+ else:
+ chain = self._getChain(sub)
+ self.context.setChainForPortalTypes((type_id,), chain,
+ verify=False)
def _getChain(self, node):
workflow_ids = []
Modified: CMF/trunk/CMFCore/interfaces/_tools.py
===================================================================
--- CMF/trunk/CMFCore/interfaces/_tools.py 2006-11-15 09:26:18 UTC (rev 71136)
+++ CMF/trunk/CMFCore/interfaces/_tools.py 2006-11-15 11:38:59 UTC (rev 71137)
@@ -1545,7 +1545,7 @@
)
def getCatalogVariablesFor(ob):
- """ Return a mapping of "workflow-relevant" attributes.
+ """ Get a mapping of "workflow-relevant" attributes.
o Invoked by 'portal_catalog' when indexing content.
@@ -1575,7 +1575,7 @@
"""
def getInfoFor(ob, name, default=_marker, wf_id=None):
- """ Return the given bit of workflow information for the object.
+ """ Get the given bit of workflow information for the object.
o 'ob' is the target object.
@@ -1644,7 +1644,7 @@
"""
def getHistoryOf(wf_id, ob):
- """ Returns the history of an object for a given workflow.
+ """ Get the history of an object for a given workflow.
o 'wf_id' is the id of the selected workflow.
@@ -1656,7 +1656,7 @@
"""
def getStatusOf(wf_id, ob):
- """ Return the last element of a workflow history for a given workflow.
+ """ Get the last element of a workflow history for a given workflow.
o 'wf_id' is the id of the selected workflow.
@@ -1682,6 +1682,47 @@
"""
+class IConfigurableWorkflowTool(IWorkflowTool):
+
+ """ Manage workflow tool settings.
+ """
+
+ def setDefaultChain(default_chain):
+ """ Set the default chain for this tool.
+
+ o Permission: Manage portal
+ """
+
+ def setChainForPortalTypes(pt_names, chain, verify=True):
+ """ Set a chain for specific portal types.
+
+ o If chain is None, set the chain for the portal types to be the
+ default chain.
+
+ o Permission: Manage portal
+ """
+
+ def getDefaultChain():
+ """ Get the default chain for this tool.
+
+ o Permission: Private (Python only)
+ """
+
+ def listChainOverrides():
+ """ List portal type specific chain overrides.
+
+ o Permission: Private (Python only)
+ """
+
+ def getChainFor(ob):
+ """ Get the chain that applies to the given object.
+
+ o If 'ob' is a string, it is used as portal type name.
+
+ o Permission: Private (Python only)
+ """
+
+
class IWorkflowDefinition(Interface):
"""Plugin interface for workflow definitions managed by IWorkflowTool.
Modified: CMF/trunk/CMFCore/tests/test_WorkflowTool.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_WorkflowTool.py 2006-11-15 09:26:18 UTC (rev 71136)
+++ CMF/trunk/CMFCore/tests/test_WorkflowTool.py 2006-11-15 11:38:59 UTC (rev 71137)
@@ -173,10 +173,12 @@
def test_z3interfaces(self):
from zope.interface.verify import verifyClass
from Products.CMFCore.interfaces import IActionProvider
+ from Products.CMFCore.interfaces import IConfigurableWorkflowTool
from Products.CMFCore.interfaces import IWorkflowTool
from Products.CMFCore.WorkflowTool import WorkflowTool
verifyClass(IActionProvider, WorkflowTool)
+ verifyClass(IConfigurableWorkflowTool, WorkflowTool)
verifyClass(IWorkflowTool, WorkflowTool)
def test_empty( self ):
@@ -215,6 +217,8 @@
tool = self._makeWithTypesAndChain()
self.assertEquals( len( tool.getDefaultChainFor( None ) ), 0 )
+ self.assertEquals( len( tool.getDefaultChain() ), 1 )
+ self.assertEquals( len( tool.listChainOverrides() ), 1 )
self.assertEquals( len( tool.getChainFor( None ) ), 0 )
self.assertEquals( len( tool.getCatalogVariablesFor( None ) ), 0 )
@@ -223,6 +227,8 @@
tool = self._makeWithTypesAndChain()
dummy = DummyNotReallyContent( 'doh' )
self.assertEquals( len( tool.getDefaultChainFor( dummy ) ), 0 )
+ self.assertEquals( len( tool.getDefaultChain() ), 1 )
+ self.assertEquals( len( tool.listChainOverrides() ), 1 )
self.assertEquals( len( tool.getChainFor( dummy ) ), 0 )
self.assertEquals( len( tool.getCatalogVariablesFor( dummy ) ), 0 )
@@ -231,10 +237,11 @@
tool = self._makeWithTypes()
dummy = DummyContent( 'dummy' )
self.assertEquals( len( tool.getDefaultChainFor( dummy ) ), 1 )
+ self.assertEquals( len( tool.getDefaultChain() ), 1 )
+ self.assertEquals( len( tool.listChainOverrides() ), 0 )
self.assertEquals( len( tool.getChainFor( dummy ) ), 1 )
self.assertEquals( len( tool.getCatalogVariablesFor( dummy ) ), 0 )
- self.assertEquals( tool.getDefaultChainFor( dummy )
- , tool.getChainFor( dummy ) )
+ self.assertEquals( tool.getDefaultChain(), tool.getChainFor( dummy ) )
def test_content_own_chain( self ):
@@ -243,6 +250,8 @@
dummy = DummyContent( 'dummy' )
self.assertEquals( len( tool.getDefaultChainFor( dummy ) ), 1 )
+ self.assertEquals( len( tool.getDefaultChain() ), 1 )
+ self.assertEquals( len( tool.listChainOverrides() ), 1 )
chain = tool.getChainFor( dummy )
self.assertEquals( len( chain ), 2 )
self.failUnless( 'a' in chain )
@@ -256,6 +265,7 @@
def test_setChainForPortalTypes(self):
tool = self._makeWithTypes()
+ tool.setDefaultChain('b, a')
dummy = DummyContent('dummy')
tool.setChainForPortalTypes( ('Dummy Content',), ('a', 'b') )
@@ -268,6 +278,9 @@
tool.setChainForPortalTypes( ('Dummy Content',), '' )
self.assertEquals( tool.getChainFor(dummy), () )
+ tool.setChainForPortalTypes( ('Dummy Content',), None )
+ self.assertEquals( tool.getChainFor(dummy), ('b', 'a') )
+
def test_getCatalogVariablesFor( self ):
tool = self._makeWithTypesAndChain()
More information about the CMF-checkins
mailing list