From 7447262a9acd21e6fced205f3b3d999f22bada87 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Fri, 7 May 2010 12:32:13 +0000
Subject: [PATCH] Extends Types Tool to support multiple types providers. A
 type provider is a container for type informations, by default we only have
 portal_types, but other types providers can be registered.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@35109 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5Type/Tool/TypesTool.py        | 51 +++++++++++++++++++--
 product/ERP5Type/interfaces/__init__.py   |  1 +
 product/ERP5Type/interfaces/types_tool.py | 55 +++++++++++++++++++++++
 product/ERP5Type/tests/testERP5Type.py    | 43 ++++++++++++++++--
 4 files changed, 144 insertions(+), 6 deletions(-)
 create mode 100644 product/ERP5Type/interfaces/types_tool.py

diff --git a/product/ERP5Type/Tool/TypesTool.py b/product/ERP5Type/Tool/TypesTool.py
index ac07accc15..d513d42126 100644
--- a/product/ERP5Type/Tool/TypesTool.py
+++ b/product/ERP5Type/Tool/TypesTool.py
@@ -21,14 +21,26 @@ from Acquisition import aq_base
 from AccessControl import ClassSecurityInfo
 from OFS.Folder import Folder as OFSFolder
 import transaction
-from Products.CMFCore import TypesTool as CMFCore_TypesTool
+from Products.CMFCore import TypesTool as CMFCore_TypesToolModule
 from Products.ERP5Type.Tool.BaseTool import BaseTool
 from Products.ERP5Type import Permissions
 from Products.ERP5Type.ERP5Type import ERP5TypeInformation
 from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
 from zLOG import LOG, WARNING, PANIC
+from Products.ERP5Type.interfaces import ITypeProvider, ITypesTool
 
-class TypesTool(BaseTool, CMFCore_TypesTool.TypesTool):
+
+CMFCore_TypesTool = CMFCore_TypesToolModule.TypesTool
+
+class TypeProvider(BaseTool, CMFCore_TypesTool):
+  """Provides portal content types
+  """
+  zope.interface.implements(ITypeProvider)
+
+
+_MARKER = []
+
+class TypesTool(TypeProvider):
   """Provides a configurable registry of portal content types
   """
   id = 'portal_types'
@@ -36,9 +48,40 @@ class TypesTool(BaseTool, CMFCore_TypesTool.TypesTool):
   portal_type = 'Types Tool'
   allowed_types = ()
 
+  zope.interface.implements(ITypesTool)
+
+  # TODO: UI to configure this is missing
+  type_provider_list = ( 'portal_types', )
+
   security = ClassSecurityInfo()
   security.declareObjectProtected(Permissions.AccessContentsInformation)
 
+  def listTypeInfo(self, container=None):
+    """List type information from all providers
+    """
+    listTypeInfo = CMFCore_TypesTool.listTypeInfo
+    type_info_list = []
+    for provider in self.type_provider_list:
+      provider_value = getattr(self, provider, None)
+      if provider_value is not None:
+        type_info_list.extend(
+            listTypeInfo(provider_value, container=container))
+    return type_info_list
+
+  def _getOb(self, id, default=_MARKER):
+    """Get a type information from a provider
+    """
+    _getOb = CMFCore_TypesTool._getOb
+    for provider in self.type_provider_list:
+      provider_value = getattr(self, provider, None)
+      if provider_value is not None:
+        ob = _getOb(provider_value, id, default=default)
+        if ob is not default:
+          return ob
+    if ob is _MARKER:
+      raise AttributeError, id
+    return ob
+    
   security.declarePrivate('getActionListFor')
   def getActionListFor(self, ob=None):
     """Return all actions applicable to the object"""
@@ -238,4 +281,6 @@ class OldTypesTool(OFSFolder):
       return OFSFolder.__of__(self, parent)
     return UnrestrictedMethod(base_self._migrateTypesTool)(parent)
 
-CMFCore_TypesTool.TypesTool = OldTypesTool
+# Change the CMFCore's TypesTool to automatically migrate to ERP5Type's
+# TypesTool
+CMFCore_TypesToolModule.TypesTool = OldTypesTool
diff --git a/product/ERP5Type/interfaces/__init__.py b/product/ERP5Type/interfaces/__init__.py
index b422b9c0b8..54bba28f86 100644
--- a/product/ERP5Type/interfaces/__init__.py
+++ b/product/ERP5Type/interfaces/__init__.py
@@ -10,3 +10,4 @@ from category_access_provider import ICategoryAccessProvider
 from value_access_provider import IValueAccessProvider
 from constraint import IConstraint
 from role_provider import ILocalRoleAssignor, ILocalRoleGenerator
+from types_tool import ITypesTool, ITypeProvider
diff --git a/product/ERP5Type/interfaces/types_tool.py b/product/ERP5Type/interfaces/types_tool.py
new file mode 100644
index 0000000000..6e351d8fae
--- /dev/null
+++ b/product/ERP5Type/interfaces/types_tool.py
@@ -0,0 +1,55 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SARL and Contributors. All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# guarantees and support are strongly advised to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+"""Types Tool Interfaces.
+"""
+
+from zope.interface import Interface
+from zope.interface import Attribute
+try:
+  from Products.CMFCore.interfaces import ITypesTool as ICMFCoreTypesTool
+except ImportError:
+  # on CMF 1.5, this interface is generated by Five, and is not available at
+  # this point
+  class ICMFCoreTypesTool(Interface):
+    pass
+    
+
+class ITypesTool(ICMFCoreTypesTool):
+  """ERP5Type's types tool.
+
+  ERP5Type's types tool extends CMF types tool, by looking up requested types
+  into registered types providers.
+  """
+  type_provider_list = Attribute('type_provider_list',
+                                 'List of ids of types providers')
+
+class ITypeProvider(ICMFCoreTypesTool):
+  """A type provider contains type information, and conforms to CMF's
+  TypesTool interface, especially listTypeInfo and getTypeInfo methods.
+  """
+
diff --git a/product/ERP5Type/tests/testERP5Type.py b/product/ERP5Type/tests/testERP5Type.py
index adab7c8dac..5e44c49ad0 100644
--- a/product/ERP5Type/tests/testERP5Type.py
+++ b/product/ERP5Type/tests/testERP5Type.py
@@ -147,13 +147,13 @@ class TestERP5Type(PropertySheetTestCase, LogInterceptor):
       return str(randint(-10000000,100000000))
 
     def getTemplateTool(self):
-      return getattr(self.getPortal(), 'portal_templates', None)
+      return getattr(self.portal, 'portal_templates', None)
 
     def getCategoryTool(self):
-      return getattr(self.getPortal(), 'portal_categories', None)
+      return getattr(self.portal, 'portal_categories', None)
 
     def getTypeTool(self):
-      return getattr(self.getPortal(), 'portal_types', None)
+      return getattr(self.portal, 'portal_types', None)
 
     # Here are the tests
     def testHasTemplateTool(self):
@@ -2735,6 +2735,43 @@ class TestPropertySheet:
       self.assertNotEquals(None, method)
       self.assertTrue(method())
 
+    def test_type_provider(self):
+      from Products.ERP5Type.Tool.TypesTool import TypeProvider
+      class DummyTypeProvider(TypeProvider):
+        id = 'dummy_type_provider'
+       # portal_type = 'Dummy Type Provider'
+      
+      self.portal._setObject('dummy_type_provider', DummyTypeProvider())
+      types_tool = self.portal.portal_types
+      # register our dummy type provider
+      types_tool.type_provider_list = types_tool.type_provider_list + (
+                                            'dummy_type_provider',)
+      
+      # types created in our type provider are available
+      dummy_type = self.portal.dummy_type_provider.newContent(
+              portal_type='Base Type',
+              id='Dummy Type',
+              type_factory_method_id='addFolder', )
+
+      # our type is available from types tool
+      self.assertNotEquals(None, types_tool.getTypeInfo('Dummy Type'))
+      self.assertTrue('Dummy Type' in [ti.getId() for ti in
+                                        types_tool.listTypeInfo()])
+
+      # not existing types are not an error
+      self.assertEquals(None, types_tool.getTypeInfo(self.id()))
+
+      # we can create instances from our type provider
+      container = self.portal.newContent(portal_type='Folder', id='test_folder')
+      dummy_instance = container.newContent(portal_type='Dummy Type')
+      
+      # and use generated accessors on them
+      dummy_type.edit(type_property_sheet_list=('Reference', ))
+
+      dummy_instance.setReference('test')
+      self.assertEquals('test', dummy_instance.getReference())
+
+
 class TestAccessControl(ERP5TypeTestCase):
   # Isolate test in a dedicaced class in order not to break other tests
   # when this one fails.
-- 
2.30.9