[Checkins] SVN: Products.CMFCore/trunk/Products/CMFCore/ merging catalog adapterization into the HEAD

Miles Waller miles at jamkit.com
Mon Mar 23 06:18:08 EDT 2009


Log message for revision 98305:
  merging catalog adapterization into the HEAD

Changed:
  U   Products.CMFCore/trunk/Products/CMFCore/CHANGES.txt
  U   Products.CMFCore/trunk/Products/CMFCore/CatalogTool.py
  U   Products.CMFCore/trunk/Products/CMFCore/content.zcml
  U   Products.CMFCore/trunk/Products/CMFCore/interfaces/_tools.py
  U   Products.CMFCore/trunk/Products/CMFCore/tests/test_CatalogTool.py
  U   Products.CMFCore/trunk/Products/CMFCore/tests/test_PortalFolder.py

-=-
Modified: Products.CMFCore/trunk/Products/CMFCore/CHANGES.txt
===================================================================
--- Products.CMFCore/trunk/Products/CMFCore/CHANGES.txt	2009-03-23 10:07:51 UTC (rev 98304)
+++ Products.CMFCore/trunk/Products/CMFCore/CHANGES.txt	2009-03-23 10:18:07 UTC (rev 98305)
@@ -4,6 +4,11 @@
 2.2.0 (unreleased)
 ------------------
 
+- PortalCatalog: Changed to use a multi-adaptor to allow a pluggable
+  IndexableObjectWrapper class.  Objects that implement IIndexableObject
+  are not wrapped.  The change will assist in integrating with
+  other indexing strategies from third-party packages.
+
 - Events: Changed 'handleContentishEvent' behavior for IObjectCopiedEvent.
   'WorkflowTool.notifyCreated' no longer resets the workflow state, so the
   the event subscriber clears the workflow history instead.

Modified: Products.CMFCore/trunk/Products/CMFCore/CatalogTool.py
===================================================================
--- Products.CMFCore/trunk/Products/CMFCore/CatalogTool.py	2009-03-23 10:07:51 UTC (rev 98304)
+++ Products.CMFCore/trunk/Products/CMFCore/CatalogTool.py	2009-03-23 10:18:07 UTC (rev 98305)
@@ -25,6 +25,8 @@
 from Products.PluginIndexes.common import safe_callable
 from Products.ZCatalog.ZCatalog import ZCatalog
 from zope.interface import implements
+from zope.component import adapts
+from zope.component import queryMultiAdapter
 from zope.interface import providedBy
 from zope.interface.declarations import getObjectSpecification
 from zope.interface.declarations import ObjectSpecification
@@ -33,6 +35,8 @@
 from Products.CMFCore.ActionProviderBase import ActionProviderBase
 from Products.CMFCore.interfaces import ICatalogTool
 from Products.CMFCore.interfaces import IIndexableObjectWrapper
+from Products.CMFCore.interfaces import IIndexableObject
+from Products.CMFCore.interfaces import IContentish
 from Products.CMFCore.permissions import AccessInactivePortalContent
 from Products.CMFCore.permissions import ManagePortal
 from Products.CMFCore.permissions import View
@@ -43,9 +47,11 @@
 from Products.CMFCore.utils import getToolByName
 from Products.CMFCore.utils import UniqueObject
 
-
 class IndexableObjectSpecification(ObjectSpecificationDescriptor):
 
+    # This class makes the wrapper transparent, adapter lookup is
+    # carried out based on the interfaces of the wrapped object. 
+
     def __get__(self, inst, cls=None):
         if inst is None:
             return getObjectSpecification(cls)
@@ -57,11 +63,17 @@
 
 class IndexableObjectWrapper(object):
 
-    implements(IIndexableObjectWrapper)
+    implements(IIndexableObjectWrapper, IIndexableObject)
+    adapts(IContentish, ICatalogTool)
     __providedBy__ = IndexableObjectSpecification()
 
-    def __init__(self, vars, ob):
-        self.__vars = vars
+    def __init__(self, ob, catalog):
+        # look up the workflow variables for the object
+        wftool = getToolByName(catalog, 'portal_workflow', None)
+        if wftool is not None:
+            self.__vars = wftool.getCatalogVariablesFor(ob)
+        else:
+            self.__vars = {}
         self.__ob = ob
 
     def __str__(self):
@@ -246,12 +258,13 @@
         # information just before cataloging.
         # XXX: this method violates the rules for tools/utilities:
         # it depends on a non-utility tool
-        wftool = getToolByName(self, 'portal_workflow', None)
-        if wftool is not None:
-            vars = wftool.getCatalogVariablesFor(obj)
+        if IIndexableObject.providedBy(obj):
+            w = obj
         else:
-            vars = {}
-        w = IndexableObjectWrapper(vars, obj)
+            w = queryMultiAdapter( (obj, self), IIndexableObject )
+            if w is None:
+                # BBB
+                w = IndexableObjectWrapper(obj, self)
         ZCatalog.catalog_object(self, w, uid, idxs, update_metadata,
                                 pghandler)
 

Modified: Products.CMFCore/trunk/Products/CMFCore/content.zcml
===================================================================
--- Products.CMFCore/trunk/Products/CMFCore/content.zcml	2009-03-23 10:07:51 UTC (rev 98304)
+++ Products.CMFCore/trunk/Products/CMFCore/content.zcml	2009-03-23 10:18:07 UTC (rev 98305)
@@ -29,4 +29,11 @@
       name="cmf.folder.btree"
       />
 
+  <!-- Default wrapper for indexing IContentish objects -->
+  <adapter
+      for=".interfaces.ICatalogAware
+           .interfaces.ICatalogTool"
+      provides=".interfaces.IIndexableObject"
+      factory=".CatalogTool.IndexableObjectWrapper" />
+
 </configure>

Modified: Products.CMFCore/trunk/Products/CMFCore/interfaces/_tools.py
===================================================================
--- Products.CMFCore/trunk/Products/CMFCore/interfaces/_tools.py	2009-03-23 10:07:51 UTC (rev 98304)
+++ Products.CMFCore/trunk/Products/CMFCore/interfaces/_tools.py	2009-03-23 10:18:07 UTC (rev 98305)
@@ -406,7 +406,12 @@
           a user is not allowed to see.
         """
 
+class IIndexableObject(Interface):
 
+    """ Marker interface for objects that can be indexed in
+        the portal catalog
+    """
+
 #
 #   PUT factory handler interfaces
 #

Modified: Products.CMFCore/trunk/Products/CMFCore/tests/test_CatalogTool.py
===================================================================
--- Products.CMFCore/trunk/Products/CMFCore/tests/test_CatalogTool.py	2009-03-23 10:07:51 UTC (rev 98304)
+++ Products.CMFCore/trunk/Products/CMFCore/tests/test_CatalogTool.py	2009-03-23 10:18:07 UTC (rev 98305)
@@ -17,9 +17,41 @@
 
 import unittest
 
+from Acquisition import Implicit
+from zope.interface import implements
+from zope.component import getMultiAdapter
+from Products.CMFCore.interfaces import ICatalogTool
+from Products.CMFCore.tests.base.dummy import DummyContent
+from Products.CMFCore.interfaces import IIndexableObject
+from Products.CMFCore.interfaces import IContentish
+
 from Products.CMFCore.tests.base.testcase import SecurityTest
 
+class FakeFolder(Implicit):
+    id = 'portal'
 
+class FakeCatalog(Implicit):
+    implements(ICatalogTool)
+    id = 'portal_catalog'
+
+class FakeWorkflowTool(Implicit):
+    id = 'portal_workflow'
+
+    def __init__(self, vars):
+        self._vars = vars
+
+    def getCatalogVariablesFor(self, ob):
+        return self._vars
+
+class CatalogDummyContent(DummyContent):
+
+    """ Dummy content that already provides IIndexableObject
+        and therefore does not need a wrapper to be registered
+    """
+
+    implements(IIndexableObject)
+    allowedRolesAndUsers = ['Manager'] # default value
+
 class IndexableObjectWrapperTests(unittest.TestCase):
 
     def _getTargetClass(self):
@@ -27,11 +59,16 @@
 
         return IndexableObjectWrapper
 
-    def _makeOne(self, *args, **kw):
-        return self._getTargetClass()(*args, **kw)
+    def _makeOne(self, vars, obj):
+        self.root = FakeFolder()
+        self.root.portal_catalog = FakeCatalog()
+        self.root.portal_workflow = FakeWorkflowTool(vars)
+        catalog = self.root.portal_catalog
+        return self._getTargetClass()(obj, catalog)
 
     def _makeContent(self, *args, **kw):
         from Products.CMFCore.tests.base.dummy import DummyContent
+
         return DummyContent(*args, **kw)
 
     def test_interfaces(self):
@@ -69,13 +106,23 @@
     def test_provided(self):
         from Products.CMFCore.interfaces import IContentish
         from Products.CMFCore.interfaces import IIndexableObjectWrapper
+        from Products.CMFCore.interfaces import IIndexableObject
 
         obj = self._makeContent()
         w = self._makeOne({}, obj)
         self.failUnless(IContentish.providedBy(w))
         self.failUnless(IIndexableObjectWrapper.providedBy(w))
+        self.failUnless(IIndexableObject.providedBy(w))
 
+    def test_adapts(self):
+        from zope.component import adaptedBy
+        from Products.CMFCore.interfaces import IContentish
+        from Products.CMFCore.interfaces import ICatalogTool
 
+        w = self._getTargetClass()
+        adapts =  adaptedBy(w) 
+        self.assertEqual(adapts, (IContentish, ICatalogTool))
+
 class CatalogToolTests(SecurityTest):
 
     def _getTargetClass(self):
@@ -87,8 +134,7 @@
         return self._getTargetClass()(*args, **kw)
 
     def _makeContent(self, *args, **kw):
-        from Products.CMFCore.tests.base.dummy import DummyContent
-        return DummyContent(*args, **kw)
+        return CatalogDummyContent(*args, **kw)
 
     def test_interfaces(self):
         from zope.interface.verify import verifyClass
@@ -144,7 +190,7 @@
         catalog = self._makeOne()
         catalog.addIndex('allowedRolesAndUsers', 'KeywordIndex')
         dummy = self._makeContent(catalog=1)
-        dummy._View_Permission = ('Blob',)
+        dummy.allowedRolesAndUsers = ('Blob',)
         catalog.catalog_object(dummy, '/dummy')
 
         self.loginWithRoles('Blob')
@@ -156,7 +202,7 @@
         catalog = self._makeOne()
         catalog.addIndex('allowedRolesAndUsers', 'KeywordIndex')
         dummy = self._makeContent(catalog=1)
-        dummy._View_Permission = ('Blob',)
+        dummy.allowedRolesAndUsers = ('Blob',)
         catalog.catalog_object(dummy, '/dummy')
 
         self.loginWithRoles('Blob')
@@ -169,7 +215,7 @@
         catalog = self._makeOne()
         catalog.addIndex('allowedRolesAndUsers', 'KeywordIndex')
         dummy = self._makeContent(catalog=1)
-        dummy._View_Permission = ('Blob',)
+        dummy.allowedRoleAndUsers = ('Blob',)
         catalog.catalog_object(dummy, '/dummy')
 
         self.loginWithRoles('Waggle')
@@ -181,7 +227,7 @@
         catalog = self._makeOne()
         catalog.addIndex('allowedRolesAndUsers', 'KeywordIndex')
         dummy = self._makeContent(catalog=1)
-        dummy._View_Permission = ('Blob',)
+        dummy.allowedRolesAndUsers = ('Blob',)
         catalog.catalog_object(dummy, '/dummy')
 
         self.loginWithRoles('Waggle')
@@ -198,7 +244,7 @@
         catalog.addIndex('expires', 'DateIndex')
         now = DateTime()
         dummy = self._makeContent(catalog=1)
-        dummy._View_Permission = ('Blob',)
+        dummy.allowedRolesAndUsers = ('Blob',)
 
         self.loginWithRoles('Blob')
 
@@ -259,7 +305,7 @@
         catalog.addIndex('expires', 'DateIndex')
         now = DateTime()
         dummy = self._makeContent(catalog=1)
-        dummy._View_Permission = ('Blob',)
+        dummy.allowedRolesAndUsers = ('Blob',)
 
         self.loginWithRoles('Blob')
 
@@ -293,7 +339,7 @@
         catalog.addIndex('expires', 'DateIndex')
         now = DateTime()
         dummy = self._makeContent(catalog=1)
-        dummy._View_Permission = ('Blob',)
+        dummy.allowedRolesAndUsers = ('Blob',)
 
         self.loginWithRoles('Blob')
 
@@ -439,7 +485,41 @@
         self.failUnless('Blob' in arus)
         self.failUnless('user:%s' % user.getId() in arus)       
 
+    def test_wrapping1(self):
+        # DummyContent implements IIndexableObject
+        # so should be indexed
+        dummy = self._makeContent(catalog=1)
+        ctool = self._makeOne()
+        ctool.catalog_object(dummy, '/dummy')
+        self.assertEqual(1, len(ctool._catalog.searchResults()))
 
+    def test_wrapping2(self):
+        # DummyContent does not implement IIndexableObject
+        # no wrapper registered - should fall back to using
+        # wrapper class directly
+        dummy = DummyContent(catalog=1)
+        ctool = self._makeOne()
+        ctool.catalog_object(dummy, '/dummy')
+        self.assertEqual(1, len(ctool._catalog.searchResults()))
+
+    def test_wrapping3(self):
+        # DummyContent does not implement IIndexableObject
+        # wrapper registered - should look this up
+
+        def FakeWrapper(object, catalog):
+            return object
+
+        from zope.component import getSiteManager
+        self.sm = getSiteManager()
+        self.sm.registerAdapter( FakeWrapper
+                               , (IContentish, ICatalogTool)
+                               , IIndexableObject )
+
+        dummy =DummyContent(catalog=1)
+        ctool = self._makeOne()
+        ctool.catalog_object(dummy, '/dummy')
+        self.assertEqual(1, len(ctool._catalog.searchResults()))
+
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(IndexableObjectWrapperTests),

Modified: Products.CMFCore/trunk/Products/CMFCore/tests/test_PortalFolder.py
===================================================================
--- Products.CMFCore/trunk/Products/CMFCore/tests/test_PortalFolder.py	2009-03-23 10:07:51 UTC (rev 98304)
+++ Products.CMFCore/trunk/Products/CMFCore/tests/test_PortalFolder.py	2009-03-23 10:18:07 UTC (rev 98305)
@@ -31,7 +31,7 @@
 from zope.interface import implements
 from zope.interface.verify import verifyClass
 
-from Products.CMFCore.CatalogTool import CatalogTool
+from Products.CMFCore.interfaces import ICatalogTool
 from Products.CMFCore.exceptions import BadRequest
 from Products.CMFCore.interfaces import ITypesTool
 from Products.CMFCore.testing import ConformsToFolder
@@ -45,17 +45,44 @@
 from Products.CMFCore.tests.base.testcase import SecurityTest
 from Products.CMFCore.tests.base.tidata import FTIDATA_CMF15
 from Products.CMFCore.tests.base.tidata import FTIDATA_DUMMY
-from Products.CMFCore.tests.base.utils import has_path
 from Products.CMFCore.TypesTool import FactoryTypeInformation as FTI
 from Products.CMFCore.TypesTool import TypesTool
 from Products.CMFCore.WorkflowTool import WorkflowTool
+from types import TupleType
 
-
 def extra_meta_types():
     return [{'name': 'Dummy', 'action': 'manage_addFolder',
              'permission': 'View'}]
 
+class DummyCatalogTool:
+    implements(ICatalogTool)
 
+    def __init__(self):
+       self.paths = []
+       self.ids = []
+
+    def indexObject(self, object):
+       self.paths.append( '/'.join(object.getPhysicalPath()) )
+       self.ids.append( object.getId() )
+
+    def unindexObject(self, object):
+       self.paths.remove( '/'.join(object.getPhysicalPath()) )
+       self.ids.append( object.getId() )
+
+    def reindexObject(self, object):
+       pass
+
+    def __len__(self):
+       return len(self.paths)
+
+def has_path(catalog, path):
+    if type(path) is TupleType:
+       path = '/'.join(path)
+    return path in catalog.paths
+
+def has_id(catalog, id):
+    return id in catalog.ids
+
 class PortalFolderFactoryTests(SecurityTest):
 
     layer = TraversingEventZCMLLayer
@@ -211,7 +238,7 @@
         #
         test = self._makeOne('test')
         ttool = self.site._setObject( 'portal_types', TypesTool() )
-        ctool = self.site._setObject( 'portal_catalog', CatalogTool() )
+        ctool = self.site._setObject( 'portal_catalog', DummyCatalogTool() )
         self.assertEqual( len(ctool), 0 )
 
         test._setObject( 'foo', DummyContent( 'foo' , catalog=1 ) )
@@ -232,7 +259,7 @@
         # instantiation (Tracker issue 309)
         #
         ttool = self.site._setObject( 'portal_types', TypesTool() )
-        ctool = self.site._setObject( 'portal_catalog', CatalogTool() )
+        ctool = self.site._setObject( 'portal_catalog', DummyCatalogTool() )
         wftool = self.site._setObject( 'portal_workflow', WorkflowTool() )
         test = self._makeOne('test')
         wftool.notifyCreated(test)
@@ -248,7 +275,7 @@
 
         test = self._makeOne('test')
         ttool = self.site._setObject( 'portal_types', TypesTool() )
-        ctool = self.site._setObject( 'portal_catalog', CatalogTool() )
+        ctool = self.site._setObject( 'portal_catalog', DummyCatalogTool() )
         self.assertEqual( len(ctool), 0 )
 
         test._setObject( 'sub', PortalFolder( 'sub', '' ) )
@@ -436,29 +463,28 @@
         from Products.CMFCore.PortalFolder import PortalFolder
 
         ttool = self.site._setObject( 'portal_types', TypesTool() )
-        ctool = self.site._setObject( 'portal_catalog', CatalogTool() )
-        ctool.addIndex('getId', 'FieldIndex')
+        ctool = self.site._setObject( 'portal_catalog', DummyCatalogTool() )
         self.assertEqual( len(ctool), 0 )
 
         folder = self._makeOne('folder')
         folder._setObject( 'sub', PortalFolder( 'sub', '' ) )
         folder.sub._setObject( 'foo', DummyContent( 'foo', catalog=1 ) )
         self.assertEqual( len(ctool), 1 )
-        self.failUnless( 'foo' in ctool.uniqueValuesFor('getId') )
-        self.failUnless( has_path(ctool._catalog,
+        self.failUnless( has_id(ctool, 'foo') )
+        self.failUnless( has_path(ctool,
                                   '/bar/site/folder/sub/foo') )
 
         transaction.savepoint(optimistic=True)
         folder.manage_renameObject(id='sub', new_id='new_sub')
         self.assertEqual( len(ctool), 1 )
-        self.failUnless( 'foo' in ctool.uniqueValuesFor('getId') )
-        self.failUnless( has_path(ctool._catalog,
+        self.failUnless( has_id(ctool, 'foo') )
+        self.failUnless( has_path(ctool,
                                   '/bar/site/folder/new_sub/foo') )
 
         folder._setObject( 'bar', DummyContent( 'bar', catalog=1 ) )
         self.assertEqual( len(ctool), 2 )
-        self.failUnless( 'bar' in ctool.uniqueValuesFor('getId') )
-        self.failUnless( has_path(ctool._catalog, '/bar/site/folder/bar') )
+        self.failUnless( has_id(ctool, 'bar') )
+        self.failUnless( has_path(ctool, '/bar/site/folder/bar') )
 
         folder._setObject( 'sub2', PortalFolder( 'sub2', '' ) )
         sub2 = folder.sub2
@@ -471,17 +497,17 @@
         cookie = folder.manage_cutObjects(ids=['bar'])
         sub2.manage_pasteObjects(cookie)
 
-        self.failUnless( 'foo' in ctool.uniqueValuesFor('getId') )
-        self.failUnless( 'bar' in ctool.uniqueValuesFor('getId') )
+        self.failUnless( has_id( ctool, 'foo' ) )
+        self.failUnless( has_id( ctool, 'bar' ) )
         self.assertEqual( len(ctool), 2 )
-        self.failUnless( has_path(ctool._catalog,
+        self.failUnless( has_path(ctool,
                                   '/bar/site/folder/sub2/bar') )
 
     def test_contentPaste(self):
         #
         #   Does copy / paste work?
         #
-        ctool = self.site._setObject( 'portal_catalog', CatalogTool() )
+        ctool = self.site._setObject( 'portal_catalog', DummyCatalogTool() )
         ttool = self.site._setObject( 'portal_types', TypesTool() )
         fti = FTIDATA_DUMMY[0].copy()
         ttool._setObject( 'Dummy Content', FTI(**fti) )
@@ -497,9 +523,9 @@
         self.failIf( 'dummy' in sub2.contentIds() )
         self.failIf( 'dummy' in sub3.objectIds() )
         self.failIf( 'dummy' in sub3.contentIds() )
-        self.failUnless( has_path(ctool._catalog, '/bar/site/sub1/dummy') )
-        self.failIf( has_path(ctool._catalog, '/bar/site/sub2/dummy') )
-        self.failIf( has_path(ctool._catalog, '/bar/site/sub3/dummy') )
+        self.failUnless( has_path(ctool, '/bar/site/sub1/dummy') )
+        self.failIf( has_path(ctool, '/bar/site/sub2/dummy') )
+        self.failIf( has_path(ctool, '/bar/site/sub3/dummy') )
 
         cookie = sub1.manage_copyObjects( ids = ( 'dummy', ) )
         # Waaa! force sub2 to allow paste of Dummy object.
@@ -513,9 +539,9 @@
         self.failUnless( 'dummy' in sub2.contentIds() )
         self.failIf( 'dummy' in sub3.objectIds() )
         self.failIf( 'dummy' in sub3.contentIds() )
-        self.failUnless( has_path(ctool._catalog, '/bar/site/sub1/dummy') )
-        self.failUnless( has_path(ctool._catalog, '/bar/site/sub2/dummy') )
-        self.failIf( has_path(ctool._catalog, '/bar/site/sub3/dummy') )
+        self.failUnless( has_path(ctool, '/bar/site/sub1/dummy') )
+        self.failUnless( has_path(ctool, '/bar/site/sub2/dummy') )
+        self.failIf( has_path(ctool, '/bar/site/sub3/dummy') )
 
         transaction.savepoint(optimistic=True)
         cookie = sub1.manage_cutObjects( ids = ('dummy',) )
@@ -530,9 +556,9 @@
         self.failUnless( 'dummy' in sub2.contentIds() )
         self.failUnless( 'dummy' in sub3.objectIds() )
         self.failUnless( 'dummy' in sub3.contentIds() )
-        self.failIf( has_path(ctool._catalog, '/bar/site/sub1/dummy') )
-        self.failUnless( has_path(ctool._catalog, '/bar/site/sub2/dummy') )
-        self.failUnless( has_path(ctool._catalog, '/bar/site/sub3/dummy') )
+        self.failIf( has_path(ctool, '/bar/site/sub1/dummy') )
+        self.failUnless( has_path(ctool, '/bar/site/sub2/dummy') )
+        self.failUnless( has_path(ctool, '/bar/site/sub3/dummy') )
 
 
 class ContentFilterTests(unittest.TestCase):



More information about the Checkins mailing list