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(
self.reindexObject()
security.declarePublic('provides')
@classmethod
def provides(cls, interface_name):
"""
Check if the current class provides a particular interface from ERP5Type's
interfaces registry
Check if the current class provides a particular interface from Interface
Components and fallback on ERP5Type's interfaces registry
"""
interface = getattr(interfaces, interface_name, None)
if interface is not None:
return interface.implementedBy(cls)
return False
provides = classmethod(CachingMethod(provides, 'Base.provides',
cache_factory='erp5_ui_long'))
from Products.ERP5Type.dynamic.portal_type_class import _importComponentClass
import erp5.component.interface
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_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):
return (self.portal_type, self.__class__)
......
......@@ -721,15 +721,15 @@ def registerBaseCategories(property_sheet):
base_category_dict[bc] = 1
def importLocalInterface(module_id, path = None, is_erp5_type=False):
def provides(class_id):
# Create interface getter
accessor_name = 'provides' + class_id
setattr(BaseClass, accessor_name, lambda self: self.provides(class_id))
BaseClass.security.declarePublic(accessor_name)
"""
Import filesystem Interface and add it to Products.ERP5Type.interfaces,
only meaningful for filesystem Interface as they can all be loaded at
ERP5 startup.
Corresponding providesI<class_id> accessor is added to BaseAccessorHolder.
"""
class_id = "I" + convertToUpperCase(module_id)
if is_erp5_type:
provides(class_id)
else:
if not is_erp5_type:
if path is None:
instance_home = getConfiguration().instancehome
path = os.path.join(instance_home, "interfaces")
......@@ -742,7 +742,6 @@ def importLocalInterface(module_id, path = None, is_erp5_type=False):
for k, v in module.__dict__.iteritems():
if type(v) is InterfaceClass and v is not Interface:
setattr(interfaces, k, v)
provides(class_id)
def importLocalConstraint(class_id, path = None):
import Products.ERP5Type.Constraint
......
......@@ -151,6 +151,14 @@ def _generateBaseAccessorHolder(portal):
base_category_id,
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)
return accessor_holder
......
......@@ -3036,6 +3036,7 @@ class %s(Interface):
methods from Person Document
"""
import erp5.portal_type
import erp5.accessor_holder
person_type = self.portal.portal_types.Person
person_type_class = erp5.portal_type.Person
......@@ -3043,6 +3044,8 @@ class %s(Interface):
self.tic()
self.failIfModuleImportable('ITestPortalType')
self.assertFalse('ITestPortalType' in person_type.getInterfaceTypeList())
self.failIfHasAttribute(erp5.accessor_holder.BaseAccessorHolder,
'providesITestPortalType')
component.validate()
self.assertModuleImportable('ITestPortalType')
......@@ -3053,6 +3056,16 @@ class %s(Interface):
person_type_class.loadClass()
implemented_by_list = list(implementedBy(person_type_class))
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())
try:
person_type.setTypeInterfaceList(person_original_interface_type_list +
......@@ -3064,9 +3077,15 @@ class %s(Interface):
implemented_by_list = list(implementedBy(person_type_class))
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:
person_type.setTypeInterfaceList(person_original_interface_type_list)
self.commit()
self.assertFalse(new_person.providesITestPortalType())
from Products.ERP5Type.Core.MixinComponent import MixinComponent
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