Commit c86107bf authored by Arnaud Fontaine's avatar Arnaud Fontaine

ZODB Components: providesIFoo() getters were only created for FS Interfaces (MR !1099).

Add them to BaseAccessorHolder (like `Base Category` accessors) instead of
Base class so they can be regenerated on reset while not having to remove
ZODB Components from Base class on reset.

This means that providesIFoo() will only be available on Portal Type classes
whereas before it was working for direct Document instanciation, but the latter
has been banned for several years anyway and the few remaining ones have been
fixed (23b2b5fd, e791d08a).

Also, instead of Base.provides() being a CachingMethod() (does not work with
resets), create a method dynamically on the Portal Type class (erp5.portal_type.Foo),
as not caching at all is ~18 times slower.
parent 18f47f4e
...@@ -800,17 +800,34 @@ class Base( ...@@ -800,17 +800,34 @@ class Base(
self.reindexObject() self.reindexObject()
security.declarePublic('provides') security.declarePublic('provides')
@classmethod
def provides(cls, interface_name): def provides(cls, interface_name):
""" """
Check if the current class provides a particular interface from ERP5Type's Check if the current class provides a particular interface from Interface
interfaces registry Components and fallback on ERP5Type's interfaces registry
""" """
interface = getattr(interfaces, interface_name, None) from Products.ERP5Type.dynamic.portal_type_class import _importComponentClass
if interface is not None: import erp5.component.interface
return interface.implementedBy(cls) interface = _importComponentClass(erp5.component.interface, interface_name)
if interface is None:
try:
interface = getattr(interfaces, interface_name)
except AttributeError:
# provides() is public (DoS)
return False return False
provides = classmethod(CachingMethod(provides, 'Base.provides',
cache_factory='erp5_ui_long')) return_value = interface.implementedBy(cls)
# XXX: All classes should already be 'erp5.portal_type'...
if cls.__module__ == 'erp5.portal_type':
# provides() is usually called through providesI<interface_name>() and
# not directly, so optimize this common use case
#
# XXX:
# - Optimize provides() too?
# - Create PortalTypeClassCachingMethod() if caching to erp5.portal_type.XXX
# is useful elsewhere?
setattr(cls, 'provides' + interface_name, lambda _: return_value)
return return_value
def _aq_key(self): def _aq_key(self):
return (self.portal_type, self.__class__) return (self.portal_type, self.__class__)
......
...@@ -721,15 +721,15 @@ def registerBaseCategories(property_sheet): ...@@ -721,15 +721,15 @@ def registerBaseCategories(property_sheet):
base_category_dict[bc] = 1 base_category_dict[bc] = 1
def importLocalInterface(module_id, path = None, is_erp5_type=False): def importLocalInterface(module_id, path = None, is_erp5_type=False):
def provides(class_id): """
# Create interface getter Import filesystem Interface and add it to Products.ERP5Type.interfaces,
accessor_name = 'provides' + class_id only meaningful for filesystem Interface as they can all be loaded at
setattr(BaseClass, accessor_name, lambda self: self.provides(class_id)) ERP5 startup.
BaseClass.security.declarePublic(accessor_name)
Corresponding providesI<class_id> accessor is added to BaseAccessorHolder.
"""
class_id = "I" + convertToUpperCase(module_id) class_id = "I" + convertToUpperCase(module_id)
if is_erp5_type: if not is_erp5_type:
provides(class_id)
else:
if path is None: if path is None:
instance_home = getConfiguration().instancehome instance_home = getConfiguration().instancehome
path = os.path.join(instance_home, "interfaces") path = os.path.join(instance_home, "interfaces")
...@@ -742,7 +742,6 @@ def importLocalInterface(module_id, path = None, is_erp5_type=False): ...@@ -742,7 +742,6 @@ def importLocalInterface(module_id, path = None, is_erp5_type=False):
for k, v in module.__dict__.iteritems(): for k, v in module.__dict__.iteritems():
if type(v) is InterfaceClass and v is not Interface: if type(v) is InterfaceClass and v is not Interface:
setattr(interfaces, k, v) setattr(interfaces, k, v)
provides(class_id)
def importLocalConstraint(class_id, path = None): def importLocalConstraint(class_id, path = None):
import Products.ERP5Type.Constraint import Products.ERP5Type.Constraint
......
...@@ -151,6 +151,14 @@ def _generateBaseAccessorHolder(portal): ...@@ -151,6 +151,14 @@ def _generateBaseAccessorHolder(portal):
base_category_id, base_category_id,
category_tool) category_tool)
# Create providesIFoo() getters of ZODB/FS Interface classes
def provides(class_id):
accessor_name = 'provides' + class_id
setattr(accessor_holder, accessor_name, lambda self: self.provides(class_id))
accessor_holder.security.declarePublic(accessor_name)
for class_id in portal.portal_types.getInterfaceTypeList():
provides(class_id)
erp5.accessor_holder.registerAccessorHolder(accessor_holder) erp5.accessor_holder.registerAccessorHolder(accessor_holder)
return accessor_holder return accessor_holder
......
...@@ -3036,6 +3036,7 @@ class %s(Interface): ...@@ -3036,6 +3036,7 @@ class %s(Interface):
methods from Person Document methods from Person Document
""" """
import erp5.portal_type import erp5.portal_type
import erp5.accessor_holder
person_type = self.portal.portal_types.Person person_type = self.portal.portal_types.Person
person_type_class = erp5.portal_type.Person person_type_class = erp5.portal_type.Person
...@@ -3043,6 +3044,8 @@ class %s(Interface): ...@@ -3043,6 +3044,8 @@ class %s(Interface):
self.tic() self.tic()
self.failIfModuleImportable('ITestPortalType') self.failIfModuleImportable('ITestPortalType')
self.assertFalse('ITestPortalType' in person_type.getInterfaceTypeList()) self.assertFalse('ITestPortalType' in person_type.getInterfaceTypeList())
self.failIfHasAttribute(erp5.accessor_holder.BaseAccessorHolder,
'providesITestPortalType')
component.validate() component.validate()
self.assertModuleImportable('ITestPortalType') self.assertModuleImportable('ITestPortalType')
...@@ -3053,6 +3056,16 @@ class %s(Interface): ...@@ -3053,6 +3056,16 @@ class %s(Interface):
person_type_class.loadClass() person_type_class.loadClass()
implemented_by_list = list(implementedBy(person_type_class)) implemented_by_list = list(implementedBy(person_type_class))
self.assertFalse(ITestPortalType in implemented_by_list) self.assertFalse(ITestPortalType in implemented_by_list)
self.assertHasAttribute(erp5.accessor_holder.BaseAccessorHolder,
'providesITestPortalType')
self.assertHasAttribute(person_type_class, 'providesITestPortalType')
new_person = self.portal.person_module.newContent(portal_type='Person')
self.assertFalse('providesITestPortalType' in person_type_class.__dict__)
self.assertFalse(new_person.providesITestPortalType())
self.assertTrue('providesITestPortalType' in person_type_class.__dict__)
# Called again to check the alias created on erp5.portal_type.Person on
# the first call of providesITestPortalType() (optimization)
self.assertFalse(new_person.providesITestPortalType())
person_original_interface_type_list = list(person_type.getTypeInterfaceList()) person_original_interface_type_list = list(person_type.getTypeInterfaceList())
try: try:
person_type.setTypeInterfaceList(person_original_interface_type_list + person_type.setTypeInterfaceList(person_original_interface_type_list +
...@@ -3064,9 +3077,15 @@ class %s(Interface): ...@@ -3064,9 +3077,15 @@ class %s(Interface):
implemented_by_list = list(implementedBy(person_type_class)) implemented_by_list = list(implementedBy(person_type_class))
self.assertTrue(ITestPortalType in implemented_by_list) self.assertTrue(ITestPortalType in implemented_by_list)
self.assertFalse('providesITestPortalType' in person_type_class.__dict__)
self.assertTrue(new_person.providesITestPortalType())
self.assertTrue('providesITestPortalType' in person_type_class.__dict__)
self.assertTrue(new_person.providesITestPortalType())
finally: finally:
person_type.setTypeInterfaceList(person_original_interface_type_list) person_type.setTypeInterfaceList(person_original_interface_type_list)
self.commit() self.commit()
self.assertFalse(new_person.providesITestPortalType())
from Products.ERP5Type.Core.MixinComponent import MixinComponent from Products.ERP5Type.Core.MixinComponent import MixinComponent
class TestZodbMixinComponent(TestZodbInterfaceComponent): class TestZodbMixinComponent(TestZodbInterfaceComponent):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment