Commit 34e13b7e authored by Sebastien Robin's avatar Sebastien Robin Committed by Georgios Dagkakis

BusinessTemplate: fix installation of portal type and instance of that type

Such error was raised :
BrokenModified: Can't change broken objects

We were installing a broken object even though the portal type was just
installed. This was due to a missing reset of components.
parent 889d73b9
...@@ -62,6 +62,7 @@ from Products.ERP5Type.Utils import readLocalTest, \ ...@@ -62,6 +62,7 @@ from Products.ERP5Type.Utils import readLocalTest, \
from Products.ERP5Type.Utils import convertToUpperCase from Products.ERP5Type.Utils import convertToUpperCase
from Products.ERP5Type import Permissions, PropertySheet, interfaces from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.XMLObject import XMLObject from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type.dynamic.lazy_class import ERP5BaseBroken
from Products.ERP5Type.dynamic.portal_type_class import synchronizeDynamicModules from Products.ERP5Type.dynamic.portal_type_class import synchronizeDynamicModules
from Products.ERP5Type.Core.PropertySheet import PropertySheet as PropertySheetDocument from Products.ERP5Type.Core.PropertySheet import PropertySheet as PropertySheetDocument
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
...@@ -656,6 +657,21 @@ class BaseTemplateItem(Implicit, Persistent): ...@@ -656,6 +657,21 @@ class BaseTemplateItem(Implicit, Persistent):
else: else:
return context return context
def _resetDynamicModules(self):
# before any import, flush all ZODB caches to force a DB reload
# otherwise we could have objects trying to get commited while
# holding reference to a class that is no longer the same one as
# the class in its import location and pickle doesn't tolerate it.
# First we do a savepoint to dump dirty objects to temporary
# storage, so that all references to them can be freed.
transaction.savepoint(optimistic=True)
# Then we need to flush from all caches, not only the one from this
# connection
portal = self.getPortalObject()
portal._p_jar.db().cacheMinimize()
synchronizeDynamicModules(portal, force=True)
gc.collect()
class ObjectTemplateItem(BaseTemplateItem): class ObjectTemplateItem(BaseTemplateItem):
""" """
This class is used for generic objects and as a subclass. This class is used for generic objects and as a subclass.
...@@ -1001,6 +1017,10 @@ class ObjectTemplateItem(BaseTemplateItem): ...@@ -1001,6 +1017,10 @@ class ObjectTemplateItem(BaseTemplateItem):
root_document_path = '/%s/%s' % (portal.getId(), root_path) root_document_path = '/%s/%s' % (portal.getId(), root_path)
recursiveUnindex(catalog, item_path, root_document_path) recursiveUnindex(catalog, item_path, root_document_path)
def fixBrokenObject(self, obj):
if isinstance(obj, ERP5BaseBroken):
self._resetDynamicModules()
def install(self, context, trashbin, **kw): def install(self, context, trashbin, **kw):
self.beforeInstall() self.beforeInstall()
update_dict = kw.get('object_to_update') update_dict = kw.get('object_to_update')
...@@ -1109,6 +1129,7 @@ class ObjectTemplateItem(BaseTemplateItem): ...@@ -1109,6 +1129,7 @@ class ObjectTemplateItem(BaseTemplateItem):
# install object # install object
obj = self._objects[path] obj = self._objects[path]
self.fixBrokenObject(obj)
# XXX Following code make Python Scripts compile twice, because # XXX Following code make Python Scripts compile twice, because
# _getCopy returns a copy without the result of the compilation. # _getCopy returns a copy without the result of the compilation.
# A solution could be to add a specific _getCopy method to # A solution could be to add a specific _getCopy method to
...@@ -2165,7 +2186,6 @@ class PortalTypeTemplateItem(ObjectTemplateItem): ...@@ -2165,7 +2186,6 @@ class PortalTypeTemplateItem(ObjectTemplateItem):
self._workflow_chain_archive[portal_type] self._workflow_chain_archive[portal_type]
context.portal_workflow.manage_changeWorkflows(default_chain, context.portal_workflow.manage_changeWorkflows(default_chain,
props=chain_dict) props=chain_dict)
# XXX : this method is kept temporarily, but can be removed once all bt5 are # XXX : this method is kept temporarily, but can be removed once all bt5 are
# re-exported with separated workflow-chain information # re-exported with separated workflow-chain information
def _importFile(self, file_name, file): def _importFile(self, file_name, file):
...@@ -3524,21 +3544,6 @@ class FilesystemDocumentTemplateItem(BaseTemplateItem): ...@@ -3524,21 +3544,6 @@ class FilesystemDocumentTemplateItem(BaseTemplateItem):
{self._getKey(path) : ['Removed', self.__class__.__name__[:-12]]}) {self._getKey(path) : ['Removed', self.__class__.__name__[:-12]]})
return modified_object_list return modified_object_list
def _resetDynamicModules(self):
# before any import, flush all ZODB caches to force a DB reload
# otherwise we could have objects trying to get commited while
# holding reference to a class that is no longer the same one as
# the class in its import location and pickle doesn't tolerate it.
# First we do a savepoint to dump dirty objects to temporary
# storage, so that all references to them can be freed.
transaction.savepoint(optimistic=True)
# Then we need to flush from all caches, not only the one from this
# connection
portal = self.getPortalObject()
portal._p_jar.db().cacheMinimize()
synchronizeDynamicModules(portal, force=True)
gc.collect()
def install(self, context, trashbin, **kw): def install(self, context, trashbin, **kw):
update_dict = kw.get('object_to_update') update_dict = kw.get('object_to_update')
force = kw.get('force') force = kw.get('force')
......
...@@ -39,6 +39,7 @@ from Products.ERP5Type.tests.Sequence import SequenceList, Sequence ...@@ -39,6 +39,7 @@ from Products.ERP5Type.tests.Sequence import SequenceList, Sequence
from urllib import pathname2url from urllib import pathname2url
from Products.ERP5Type.Globals import PersistentMapping from Products.ERP5Type.Globals import PersistentMapping
from Products.CMFCore.Expression import Expression from Products.CMFCore.Expression import Expression
from Products.ERP5Type.dynamic.lazy_class import ERP5BaseBroken
from Products.ERP5Type.tests.utils import LogInterceptor from Products.ERP5Type.tests.utils import LogInterceptor
from Products.ERP5Type.Workflow import addWorkflowByType from Products.ERP5Type.Workflow import addWorkflowByType
import shutil import shutil
...@@ -6337,6 +6338,32 @@ class TestBusinessTemplate(BusinessTemplateMixin): ...@@ -6337,6 +6338,32 @@ class TestBusinessTemplate(BusinessTemplateMixin):
# check both File instances no longer behave like being overriden # check both File instances no longer behave like being overriden
self.assertFalse(getattr(portal.another_file, 'isClassOverriden', False)) self.assertFalse(getattr(portal.another_file, 'isClassOverriden', False))
def test_168_CheckPortalTypeAndPathInSameBusinessTemplate(self):
"""
Make sure we can define a portal type and instance of that portal type
in same bt. It already happened that this failed with error :
BrokenModified: Can't change broken objects
It might sound similar to test_167, but we had cases not working even
though test_167 was running fine (due to additional steps that were
doing more reset of components)
"""
template_tool = self.portal.portal_templates
bt_path = os.path.join(os.path.dirname(__file__), 'test_data',
'BusinessTemplate_test_168_CheckPortalTypeAndPathInSameBusinessTemplate')
bt = template_tool.download(bt_path)
foo_in_bt = bt._path_item._objects["foo"]
# Force evaluation of a method to force unghosting of the class
getattr(foo_in_bt, "getTitle", None)
self.assertTrue(isinstance(foo_in_bt, ERP5BaseBroken))
self.commit()
bt.install(force=1)
self.commit()
foo_in_portal = self.portal.foo
self.assertFalse(isinstance(foo_in_portal, ERP5BaseBroken))
self.assertEqual("Foo", foo_in_portal.getPortalType())
self.uninstallBusinessTemplate('test_168_CheckPortalTypeAndPathInSameBusinessTemplate')
def test_type_provider(self): def test_type_provider(self):
self.portal.newContent(id='dummy_type_provider', portal_type="Types Tool") self.portal.newContent(id='dummy_type_provider', portal_type="Types Tool")
type_provider = self.portal.dummy_type_provider type_provider = self.portal.dummy_type_provider
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Foo" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>foo</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Base Type" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>Foo</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Foo Type</string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>XMLObject</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
test_168_CheckPortalTypeAndPathInSameBusinessTemplate
\ No newline at end of file
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