Commit 5fea47d1 authored by Arnaud Fontaine's avatar Arnaud Fontaine

ZODB Components: Products import compatibility (MR !1271).

This implements Products import compatibility based on Component.source_reference
using existing import hooks so that Products.ERP5.Document.Person for example
is importable (actually returning erp5.component.document.Person module).

This only works with non-FS modules (IOW when the ZODB is accessible and
portal.portal_components is available).
parent 08b48c6f
......@@ -6380,6 +6380,45 @@ class TestBusinessTemplate(BusinessTemplateMixin):
self.assertEqual("FooBar", getattr(foo_in_portal, "title"))
self.uninstallBusinessTemplate('test_168_CheckPortalTypeAndPathInSameBusinessTemplate')
def test_legacy_products_erp5_document_compatibility(self):
"""Check we can import a business template referencing classes from
Products.ERP5Type.Document namespace and that the classes are migrated
"""
import Products.ERP5.tests
bt_path = os.path.join(
os.path.dirname(Products.ERP5.tests.__file__),
'test_data',
'BusinessTemplate_test_legacy_products_erp5_document_compatibility')
bt = self.portal.portal_templates.download(bt_path)
bt.install()
self.tic()
# when loaded, the legacy classes have been updated to use
# erp5.portal_type namespace
self.assertEqual(
str(self.portal.person_module.test_person.__class__),
"<class 'erp5.portal_type.Person'>")
self.assertEqual(
str(self.portal.person_module.test_person.default_address.__class__),
"<class 'erp5.portal_type.Address'>")
self.assertEqual(
str(self.portal.person_module.test_person.default_career.__class__),
"<class 'erp5.portal_type.Career'>")
self.assertEqual(
str(self.portal.person_module.test_person.default_email.__class__),
"<class 'erp5.portal_type.Email'>")
self.assertEqual(
str(self.portal.person_module.test_person.default_fax.__class__),
"<class 'erp5.portal_type.Fax'>")
self.assertEqual(
str(self.portal.person_module.test_person.default_telephone.__class__),
"<class 'erp5.portal_type.Telephone'>")
self.assertEqual(
str(self.portal.person_module.test_person.default_link.__class__),
"<class 'erp5.portal_type.Link'>")
self.uninstallBusinessTemplate('BusinessTemplate_test_legacy_products_erp5_document_compatibility')
def test_169_CheckPortalTypeAndPathInSameBusinessTemplateAndBrokenObjectModification(self):
"""
Make sure we have possibility to change broken
......
......@@ -56,6 +56,7 @@
<key> <string>method_id</string> </key>
<value>
<list>
<string>_setSourceReference</string>
<string>validate</string>
<string>invalidate</string>
</list>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Person" module="Products.ERP5Type.Document.Person"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>first_name</string> </key>
<value> <string>Test</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_person</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>last_name</string> </key>
<value> <string>Person</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Person</string> </value>
</item>
<item>
<key> <string>user_id</string> </key>
<value> <string>P6</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="GeographicAddress" module="Products.ERP5Type.Document.GeographicAddress"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>city</string> </key>
<value> <string>city</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>default_address</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Address</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>street_address</string> </key>
<value> <string>the address</string> </value>
</item>
<item>
<key> <string>zip_code</string> </key>
<value> <string>zip code</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Career" module="Products.ERP5Type.Document.Career"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>default_career</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Career</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Url" module="Products.ERP5Type.Document.Url"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>coordinate_text</string> </key>
<value> <string>mail@example.com</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>default_email</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Email</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Telephone" module="Products.ERP5Type.Document.Telephone"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>coordinate_text</string> </key>
<value> <string>123456</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>default_fax</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Fax</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Url" module="Products.ERP5Type.Document.Url"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>default_link</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Link</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>1</string> </value>
</item>
<item>
<key> <string>url_string</string> </key>
<value> <string>https://example.com</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Telephone" module="Products.ERP5Type.Document.Telephone"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>coordinate_text</string> </key>
<value> <string>123456</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>default_telephone</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Telephone</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
A business templates with documents exported with their __class__ from Products.ERP5Type.Document
person_module/test_person
person_module/test_person/**
\ No newline at end of file
BusinessTemplate_test_legacy_products_erp5_document_compatibility
\ No newline at end of file
......@@ -153,6 +153,7 @@ class ComponentTool(BaseTool):
package.reset()
component_package_list.append(package.__name__)
erp5.component.filesystem_import_dict = None
erp5.component.ref_manager.gc()
# Clear pylint cache
......
......@@ -35,6 +35,7 @@ import imp
import collections
from Products.ERP5.ERP5Site import getSite
from Products.ERP5Type import product_path as ERP5Type_product_path
from . import aq_method_lock
from types import ModuleType
from zLOG import LOG, BLATHER, WARNING
......@@ -109,10 +110,20 @@ class ComponentDynamicPackage(ModuleType):
perhaps because the Finder of another Component Package could do it or
because this is a filesystem module...
"""
# Ignore imports with a path which are filesystem-only and any
# absolute imports which does not start with this package prefix,
# None there means that "normal" sys.path will be used
if path or not fullname.startswith(self._namespace_prefix):
import erp5.component
# ZODB Components
if not path:
if not fullname.startswith(self._namespace_prefix):
return None
# FS import backward compatibility
else:
try:
fullname = erp5.component.filesystem_import_dict[fullname]
except (TypeError, KeyError):
return None
else:
if not fullname.startswith(self._namespace_prefix):
return None
import_lock_held = True
......@@ -124,6 +135,26 @@ class ComponentDynamicPackage(ModuleType):
try:
site = getSite()
if erp5.component.filesystem_import_dict is None:
filesystem_import_dict = {}
try:
component_tool = aq_base(site.portal_components)
except AttributeError:
# For old sites, just use FS Documents...
return None
else:
for component in component_tool.objectValues():
if component.getValidationState() == 'validated':
component_module_name = '%s.%s' % (component._getDynamicModuleNamespace(),
component.getReference())
if component.getSourceReference() is not None:
filesystem_import_dict[component.getSourceReference()] = component_module_name
if component.getPortalType() == 'Document Component':
filesystem_import_dict[('Products.ERP5Type.Document.' +
component.getReference())] = component_module_name
erp5.component.filesystem_import_dict = filesystem_import_dict
# __import__ will first try a relative import, for example
# erp5.component.XXX.YYY.ZZZ where erp5.component.XXX.YYY is the current
# Component where an import is done
......@@ -139,13 +170,7 @@ class ComponentDynamicPackage(ModuleType):
id_ = "%s.%s.%s" % (self._id_prefix, version, name)
# aq_base() because this should not go up to ERP5Site and trigger
# side-effects, after all this only check for existence...
try:
component_tool = aq_base(site.portal_components)
except AttributeError:
# For old sites, just use FS Documents...
return None
component = getattr(component_tool, id_, None)
component = getattr(aq_base(site.portal_components), id_, None)
if component is None or component.getValidationState() not in ('modified',
'validated'):
return None
......@@ -161,12 +186,7 @@ class ComponentDynamicPackage(ModuleType):
# name=REFERENCE
else:
try:
component_tool = aq_base(site.portal_components)
except AttributeError:
# For old sites, just use FS Documents...
return None
for version in site.getVersionPriorityNameList():
id_ = "%s.%s.%s" % (self._id_prefix, version, name)
component = getattr(component_tool, id_, None)
......@@ -219,6 +239,14 @@ class ComponentDynamicPackage(ModuleType):
module for any reason...
"""
site = getSite()
if fullname.startswith('Products.'):
module_fullname_filesystem = fullname
import erp5.component
fullname = erp5.component.filesystem_import_dict[module_fullname_filesystem]
else:
module_fullname_filesystem = None
name = fullname[len(self._namespace_prefix):]
# if only Version package (erp5.component.XXX.VERSION_version) is
......@@ -268,6 +296,9 @@ class ComponentDynamicPackage(ModuleType):
setattr(self, name, module)
sys.modules[module_fullname_alias] = module
MNAME_MAP[module_fullname_alias] = module.__name__
if module_fullname_filesystem:
sys.modules[module_fullname_filesystem] = module
MNAME_MAP[module_fullname_filesystem] = module.__name__
return module
component = getattr(site.portal_components, component_id)
......@@ -288,6 +319,8 @@ class ComponentDynamicPackage(ModuleType):
sys.modules[module_fullname] = module
if module_fullname_alias:
sys.modules[module_fullname_alias] = module
if module_fullname_filesystem:
sys.modules[module_fullname_filesystem] = module
# This must be set for imports at least (see PEP 302)
module.__file__ = '<' + relative_url + '>'
......@@ -308,6 +341,8 @@ class ComponentDynamicPackage(ModuleType):
del sys.modules[module_fullname]
if module_fullname_alias:
del sys.modules[module_fullname_alias]
if module_fullname_filesystem:
del sys.modules[module_fullname_filesystem]
raise ImportError(
"%s: cannot load Component %s (%s)" % (fullname, name, error)), \
......@@ -319,6 +354,8 @@ class ComponentDynamicPackage(ModuleType):
if module_fullname_alias:
setattr(self, name, module)
MNAME_MAP[module_fullname_alias] = module_fullname
if module_fullname_filesystem:
MNAME_MAP[module_fullname_filesystem] = module.__name__
import erp5.component
erp5.component.ref_manager.add_module(module)
......@@ -408,10 +445,14 @@ class ComponentDynamicPackage(ModuleType):
for k in modsec_dict.keys():
if k.startswith(self._namespace):
del modsec_dict[k]
for k in MNAME_MAP.keys():
if k.startswith(self._namespace):
for k, v in MNAME_MAP.items():
if v.startswith(self._namespace):
del MNAME_MAP[k]
# Products import compatibility (module_fullname_filesystem)
if k.startswith('Products.'):
del sys.modules[k]
for name, module in package.__dict__.items():
if name[0] == '_' or not isinstance(module, ModuleType):
continue
......
......@@ -120,6 +120,9 @@ class ComponentPackageType(PackageType):
the top level, otherwise a module being relied upon may have a
different API after reset, thus it may fail...
"""
# 'Products.ERP5.Document.Person' => 'erp5.component.document.Person'
filesystem_import_dict = None
def __init__(self, *args, **kwargs):
super(ComponentPackageType, self).__init__(*args, **kwargs)
self.ref_manager = RefManager()
......
......@@ -178,7 +178,11 @@ class ComponentMixin(PropertyRecordableMixin, Base):
'description': BaseAccessor.Getter('getDescription',
'description',
'string',
default='')
default=''),
'source_reference': BaseAccessor.Getter('getSourceReference',
'source_reference',
'string',
storage_id='default_source_reference'),
}
_message_invalid_id = "ID is invalid, should be '${id_prefix}.VERSION.REFERENCE'"
......
......@@ -2655,25 +2655,11 @@ def foobar(self, a, b="portal_type"):
from Products.ERP5Type.Core.DocumentComponent import DocumentComponent
class TestZodbDocumentComponent(_TestZodbComponent):
class _TestZodbDocumentComponentMixin(_TestZodbComponent):
"""
Tests specific to ZODB Document Component. This is only for Document
previously defined in bt5 and installed on the filesystem in
$INSTANCE_HOME/Document. Later on, Product Documents will also be migrated
Common to all Component class inheriting from Document Component (so
Interface and Mixin)
"""
_portal_type = 'Document Component'
_document_class = DocumentComponent
def _getValidSourceCode(self, class_name):
return '''from erp5.component.document.Person import Person
class %sAnything:
pass
class %s(Person):
pass
''' % (class_name, class_name)
def testAtLeastOneClassNamedAfterReference(self):
component = self._newComponent(
self._generateReference('TestClassNamedAfterReference'))
......@@ -2921,6 +2907,124 @@ class TestGC(XMLObject):
'gc: collectable <Implements 0x%x>\n' % Implements_id],
sorted(found_line_list))
class TestZodbDocumentComponent(_TestZodbDocumentComponentMixin):
"""
Tests specific to ZODB Document Component. This is only for Document
previously defined in bt5 and installed on the filesystem in
$INSTANCE_HOME/Document. Later on, Product Documents will also be migrated
"""
_portal_type = 'Document Component'
_document_class = DocumentComponent
def _getValidSourceCode(self, class_name):
return '''from erp5.component.document.Person import Person
class %sAnything:
pass
class %s(Person):
pass
''' % (class_name, class_name)
def testProductsERP5DocumentCompatibility(self):
"""Check that document class also exist in its original namespace (source_reference)
Document Component that were moved from file system Products/*/Document needs
to be still importable from their initial location, as there might be classes
in the database of these instances.
There is no such test for Mixin/Interface/Tool because the code is the
same for all of them (component_package.py).
"""
self.failIfModuleImportable('TestProductsERP5DocumentCompatibility')
test_component = self._newComponent(
'TestProductsERP5DocumentCompatibility',
"""\
from Products.ERP5Type.Base import Base
class TestProductsERP5DocumentCompatibility(Base):
portal_type = 'Test ProductsERP5Document Compatibility'
test_attribute = 'TestProductsERP5DocumentCompatibility'
"""
)
test_component.setSourceReference('Products.ERP5.Document.TestProductsERP5DocumentCompatibility')
test_component.validate()
self.tic()
self.assertModuleImportable('TestProductsERP5DocumentCompatibility')
from Products.ERP5.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility # pylint:disable=import-error,no-name-in-module
self.assertEqual(TestProductsERP5DocumentCompatibility.test_attribute, 'TestProductsERP5DocumentCompatibility')
# this also exist in Products.ERP5Type.Document
from Products.ERP5Type.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility as TestProductsERP5DocumentCompatibility_from_ProductsERP5Type # pylint:disable=import-error,no-name-in-module
self.assertIs(TestProductsERP5DocumentCompatibility_from_ProductsERP5Type, TestProductsERP5DocumentCompatibility)
# another component can also import the migrated component from its original name
test_component_importing = self._newComponent(
'TestComponentImporting',
"""\
from Products.ERP5.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility
class TestComponentImporting(TestProductsERP5DocumentCompatibility):
pass
"""
)
test_component_importing.validate()
self.tic()
self.assertModuleImportable('TestComponentImporting')
from erp5.component.document.TestComponentImporting import TestComponentImporting # pylint:disable=import-error,no-name-in-module
from Products.ERP5.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility # pylint:disable=import-error,no-name-in-module
self.assertTrue(issubclass(TestComponentImporting, TestProductsERP5DocumentCompatibility))
test_component.invalidate()
self.tic()
# after invalidating the component, the legacy modules are no longer importable
with self.assertRaises(ImportError):
from Products.ERP5.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility # pylint:disable=import-error,no-name-in-module
with self.assertRaises(ImportError):
from Products.ERP5Type.Document.TestProductsERP5DocumentCompatibility import TestProductsERP5DocumentCompatibility # pylint:disable=import-error,no-name-in-module
def testProductsERP5TypeDocumentCompatibility(self):
"""Check that document class also exist in Products.ERP5Type.Document namespace
for compatibility.
We also check that this module is properly reloaded when a document component
is modified.
"""
self.failIfModuleImportable('TestProductsERP5TypeDocumentCompatibility')
test_component = self._newComponent(
'TestProductsERP5TypeDocumentCompatibility',
"""\
from Products.ERP5Type.Base import Base
class TestProductsERP5TypeDocumentCompatibility(Base):
portal_type = 'Test ProductsERP5TypeDocument Compatibility'
generation = 1
"""
)
test_component.validate()
self.tic()
self.assertModuleImportable('TestProductsERP5TypeDocumentCompatibility')
from Products.ERP5Type.Document.TestProductsERP5TypeDocumentCompatibility import TestProductsERP5TypeDocumentCompatibility # pylint:disable=import-error,no-name-in-module
self.assertEqual(TestProductsERP5TypeDocumentCompatibility.generation, 1)
test_component.setTextContent(
"""\
from Products.ERP5Type.Base import Base
class TestProductsERP5TypeDocumentCompatibility(Base):
portal_type = 'Test ProductsERP5TypeDocument Compatibility'
generation = 2
""")
self.tic()
self.assertModuleImportable('TestProductsERP5TypeDocumentCompatibility')
from Products.ERP5Type.Document.TestProductsERP5TypeDocumentCompatibility import TestProductsERP5TypeDocumentCompatibility # pylint:disable=import-error,no-name-in-module
self.assertEqual(TestProductsERP5TypeDocumentCompatibility.generation, 2)
from Products.ERP5Type.Core.TestComponent import TestComponent
class TestZodbTestComponent(_TestZodbComponent):
......@@ -3091,7 +3195,7 @@ class Test(ERP5TypeTestCase):
self.commit()
from Products.ERP5Type.Core.InterfaceComponent import InterfaceComponent
class TestZodbInterfaceComponent(TestZodbDocumentComponent):
class TestZodbInterfaceComponent(_TestZodbDocumentComponentMixin):
"""
Tests specific to ZODB Interface Component.
"""
......
......@@ -33,6 +33,7 @@ of Portal Type as Classes and ZODB Components
import unittest
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from AccessControl.ZopeGuards import guarded_import
from Products.ERP5Type.tests.utils import LogInterceptor
class TestERP5Type(ERP5TypeTestCase, LogInterceptor):
......@@ -205,6 +206,41 @@ class TestERP5Type(ERP5TypeTestCase, LogInterceptor):
subdocument_record = sql_catalog.getRecordForUid(subdocument.uid)
self.assertEqual(subdocument.getPath(), subdocument_record.path)
def test_products_document_legacy(self):
"""check document classes defined in Products/*/Document/*.py
"""
# note: this assertion below checks Alarm is really a legacy document class.
# if one day Alarm is moved to component, then this test needs to be updated
# with another module that lives on the file system.
import Products.ERP5.Document.Alarm
self.assertIn('product/ERP5/Document/Alarm.py', Products.ERP5.Document.Alarm.__file__)
# document classes are also dynamically loaded in Products.ERP5Type.Document module
from Products.ERP5Type.Document.Alarm import Alarm as Alarm_from_ERP5Type # pylint:disable=import-error,no-name-in-module
self.assertIs(Alarm_from_ERP5Type, Products.ERP5.Document.Alarm.Alarm)
# a new temp constructor is created
from Products.ERP5Type.Document import newTempAlarm # pylint:disable=import-error,no-name-in-module
self.assertIn(Alarm_from_ERP5Type, newTempAlarm(self.portal, '').__class__.mro())
# temp constructors are deprecated, they issue a warning when called
import mock
with mock.patch('Products.ERP5Type.Utils.warnings.warn') as warn:
newTempAlarm(self.portal, '')
warn.assert_called_with(
'newTemp*(self, ID) will be removed, use self.newContent(temp_object=True, id=ID, portal_type=...)',
DeprecationWarning, 2)
def test_03_NewTempObject(self):
# Products.ERP5Type.Document.newTempBase is another (not recommended) way
# of creating temp objects
import Products.ERP5Type.Document
o = Products.ERP5Type.Document.newTempBase(self.portal, 'id')
self.assertEqual(o.getId(), 'id')
self.assertEqual(o.getPortalType(), 'Base Object')
self.assertTrue(o.isTempObject())
self.assertTrue(guarded_import("Products.ERP5Type.Document", fromlist=["newTempBase"]))
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestERP5Type))
......
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