SVN: CMF/trunk/C - added IConfigurableWorkflowTool

Yvo Schubbe <[email protected]>
Newsgroups gmane.comp.web.zope.cmf.cvs
Message-ID <20061115113901.D1A5D20372D__4817.82526844672$1163590750$gmane$org@mail.zope.org>
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()
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.