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() _______________________________________________ CMF-checkins mailing list [email protected] http://mail.zope.org/mailman/listinfo/cmf-checkins
