Commit 7a0542c2 authored by Jérome Perrin's avatar Jérome Perrin

When exporting a type information, export it in the context in its type

provider. When installing a type information, register its provider if it's not
already registered.



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@35110 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 7447262a
...@@ -31,7 +31,7 @@ import fnmatch, gc, imp, os, re, shutil, sys ...@@ -31,7 +31,7 @@ import fnmatch, gc, imp, os, re, shutil, sys
from Shared.DC.ZRDB.Connection import Connection as RDBConnection from Shared.DC.ZRDB.Connection import Connection as RDBConnection
from Products.ERP5Type.DiffUtils import DiffFile from Products.ERP5Type.DiffUtils import DiffFile
from Products.ERP5Type.Globals import Persistent, PersistentMapping from Products.ERP5Type.Globals import Persistent, PersistentMapping
from Acquisition import Implicit, aq_base from Acquisition import Implicit, aq_base, aq_inner, aq_parent
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
...@@ -854,11 +854,12 @@ class ObjectTemplateItem(BaseTemplateItem): ...@@ -854,11 +854,12 @@ class ObjectTemplateItem(BaseTemplateItem):
""" """
pass pass
def onNewObject(self): def onNewObject(self, obj):
""" """
Installation hook. Installation hook.
Called when installation process determined that object to install is Called when installation process determined that object to install is
new on current site (it's not replacing an existing object). new on current site (it's not replacing an existing object).
`obj` parameter is the newly created object in its acquisition context.
Can be overridden by subclasses. Can be overridden by subclasses.
""" """
pass pass
...@@ -947,9 +948,10 @@ class ObjectTemplateItem(BaseTemplateItem): ...@@ -947,9 +948,10 @@ class ObjectTemplateItem(BaseTemplateItem):
saved_uid_dict = {} saved_uid_dict = {}
subobjects_dict = {} subobjects_dict = {}
portal_type_dict = {} portal_type_dict = {}
# Object already exists
old_obj = container._getOb(object_id, None) old_obj = container._getOb(object_id, None)
object_existed = old_obj is not None
if old_obj is not None: if old_obj is not None:
# Object already exists
recurse(saveHook, old_obj) recurse(saveHook, old_obj)
if getattr(aq_base(old_obj), 'groups', None) is not None: if getattr(aq_base(old_obj), 'groups', None) is not None:
# we must keep original order groups # we must keep original order groups
...@@ -969,8 +971,7 @@ class ObjectTemplateItem(BaseTemplateItem): ...@@ -969,8 +971,7 @@ class ObjectTemplateItem(BaseTemplateItem):
portal_type_dict['workflow_chain'] = \ portal_type_dict['workflow_chain'] = \
getChainByType(context)[1].get('chain_' + object_id, '') getChainByType(context)[1].get('chain_' + object_id, '')
container.manage_delObjects([object_id]) container.manage_delObjects([object_id])
else:
self.onNewObject()
# install object # install object
obj = self._objects[path] obj = self._objects[path]
if getattr(obj, 'meta_type', None) == 'Script (Python)': if getattr(obj, 'meta_type', None) == 'Script (Python)':
...@@ -997,6 +998,11 @@ class ObjectTemplateItem(BaseTemplateItem): ...@@ -997,6 +998,11 @@ class ObjectTemplateItem(BaseTemplateItem):
LOG("BT, install", 0, object_id) LOG("BT, install", 0, object_id)
raise raise
obj = container._getOb(object_id) obj = container._getOb(object_id)
if not object_existed:
# A new object was added, call the hook
self.onNewObject(obj)
# mark a business template installation so in 'PortalType_afterClone' scripts # mark a business template installation so in 'PortalType_afterClone' scripts
# we can implement logical for reseting or not attributes (i.e reference). # we can implement logical for reseting or not attributes (i.e reference).
self.REQUEST.set('is_business_template_installation', 1) self.REQUEST.set('is_business_template_installation', 1)
...@@ -1414,7 +1420,7 @@ class CategoryTemplateItem(ObjectTemplateItem): ...@@ -1414,7 +1420,7 @@ class CategoryTemplateItem(ObjectTemplateItem):
def beforeInstall(self): def beforeInstall(self):
self._installed_new_category = False self._installed_new_category = False
def onNewObject(self): def onNewObject(self, obj):
self._installed_new_category = True self._installed_new_category = True
def afterInstall(self): def afterInstall(self):
...@@ -1753,6 +1759,10 @@ class PortalTypeTemplateItem(ObjectTemplateItem): ...@@ -1753,6 +1759,10 @@ class PortalTypeTemplateItem(ObjectTemplateItem):
p = context.getPortalObject() p = context.getPortalObject()
for relative_url in self._archive.keys(): for relative_url in self._archive.keys():
obj = p.unrestrictedTraverse(relative_url) obj = p.unrestrictedTraverse(relative_url)
# normalize relative_url, not all type informations are stored in
# "portal_types"
relative_url = '%s/%s' % (obj.getPhysicalPath()[-2:])
obj = obj._getCopy(context) obj = obj._getCopy(context)
# obj is in ghost state and an attribute must be accessed # obj is in ghost state and an attribute must be accessed
# so that obj.__dict__ does not return an empty dict # so that obj.__dict__ does not return an empty dict
...@@ -1768,6 +1778,17 @@ class PortalTypeTemplateItem(ObjectTemplateItem): ...@@ -1768,6 +1778,17 @@ class PortalTypeTemplateItem(ObjectTemplateItem):
delattr(obj, attr) delattr(obj, attr)
self._objects[relative_url] = obj self._objects[relative_url] = obj
obj.wl_clearLocks() obj.wl_clearLocks()
def onNewObject(self, obj):
""" When we install a type which is contained in a type provider not
registered on types tool, register the type provider.
"""
portal = obj.getPortalObject()
types_tool = portal.portal_types
type_container_id = obj.getParentId()
if type_container_id not in types_tool.type_provider_list:
types_tool.type_provider_list = tuple(types_tool.type_provider_list) + (
type_container_id,)
# 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
...@@ -1938,7 +1959,7 @@ class PortalTypeWorkflowChainTemplateItem(BaseTemplateItem): ...@@ -1938,7 +1959,7 @@ class PortalTypeWorkflowChainTemplateItem(BaseTemplateItem):
(wf_id, portal_type) (wf_id, portal_type)
chain_dict[chain_key] = self._objects[path] chain_dict[chain_key] = self._objects[path]
else: else:
if portal_type not in context.portal_types.objectIds(): if context.portal_types.getTypeInfo(portal_type) is None:
raise ValueError('Cannot chain workflow %r to non existing ' raise ValueError('Cannot chain workflow %r to non existing '
'portal type %r' % (self._chain_string_separator\ 'portal type %r' % (self._chain_string_separator\
.join(self._objects[path]) .join(self._objects[path])
...@@ -2043,16 +2064,15 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem): ...@@ -2043,16 +2064,15 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):
def build(self, context, **kw): def build(self, context, **kw):
types_tool = getToolByName(self.getPortalObject(), 'portal_types') types_tool = getToolByName(self.getPortalObject(), 'portal_types')
types_list = list(types_tool.objectIds())
for key in self._archive.keys(): for key in self._archive.keys():
try: try:
portal_type, allowed_type = key.split(' | ') portal_type, allowed_type = key.split(' | ')
except ValueError: except ValueError:
raise ValueError('Invalid item %r in %s' % (key, self.name)) raise ValueError('Invalid item %r in %s' % (key, self.name))
ob = types_tool.getTypeInfo(portal_type)
# check properties corresponds to what is defined in site # check properties corresponds to what is defined in site
if not portal_type in types_list: if ob is None:
raise ValueError, "Portal Type %s not found in site" %(portal_type,) raise ValueError, "Portal Type %s not found in site" %(portal_type,)
ob = types_tool._getOb(portal_type)
prop_value = getattr(ob, self.class_property, ()) prop_value = getattr(ob, self.class_property, ())
if not allowed_type in prop_value and not self.is_bt_for_diff: if not allowed_type in prop_value and not self.is_bt_for_diff:
raise ValueError, "%s %s not found in portal type %s" % ( raise ValueError, "%s %s not found in portal type %s" % (
...@@ -2151,15 +2171,14 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem): ...@@ -2151,15 +2171,14 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):
action = update_dict[key] action = update_dict[key]
if action == 'nothing': if action == 'nothing':
continue continue
try: portal_id = key.split('/')[-1]
portal_id = key.split('/')[-1] type_information = types_tool.getTypeInfo(portal_id)
portal_type = types_tool._getOb(portal_id) if type_information is None:
except (AttributeError, KeyError):
raise AttributeError, "Portal type '%s' not found while " \ raise AttributeError, "Portal type '%s' not found while " \
"installing %s" % (portal_id, self.getTitle()) "installing %s" % (portal_id, self.getTitle())
property_list = self._objects.get(key, []) property_list = self._objects.get(key, [])
old_property_list = old_objects.get(key, ()) old_property_list = old_objects.get(key, ())
object_property_list = getattr(portal_type, self.class_property, ()) object_property_list = getattr(type_information, self.class_property, ())
# merge differences between portal types properties # merge differences between portal types properties
# for example: # for example:
# * current value : [A,B,C] # * current value : [A,B,C]
...@@ -2169,7 +2188,7 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem): ...@@ -2169,7 +2188,7 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):
for id in object_property_list: for id in object_property_list:
if id not in property_list and id not in old_property_list: if id not in property_list and id not in old_property_list:
property_list.append(id) property_list.append(id)
setattr(portal_type, self.class_property, tuple(property_list)) setattr(type_information, self.class_property, tuple(property_list))
def uninstall(self, context, **kw): def uninstall(self, context, **kw):
object_path = kw.get('object_path', None) object_path = kw.get('object_path', None)
...@@ -2180,19 +2199,19 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem): ...@@ -2180,19 +2199,19 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):
else: else:
object_key_list = self._objects.keys() object_key_list = self._objects.keys()
for key in object_key_list: for key in object_key_list:
try: portal_id = key.split('/')[-1]
portal_id = key.split('/')[-1] type_information = types_tool.getTypeInfo(portal_id)
portal_type = types_tool._getOb(portal_id) if type_information is None:
except (AttributeError, KeyError): LOG("BusinessTemplate", WARNING,
LOG("portal types not found : ", 100, portal_id) "Portal type %r not found while uninstalling" % (portal_id,))
continue continue
property_list = self._objects[key] property_list = self._objects[key]
original_property_list = list(getattr(portal_type, original_property_list = list(getattr(type_information,
self.class_property, ())) self.class_property, ()))
for id in property_list: for id in property_list:
if id in original_property_list: if id in original_property_list:
original_property_list.remove(id) original_property_list.remove(id)
setattr(portal_type, self.class_property, tuple(original_property_list)) setattr(type_information, self.class_property, tuple(original_property_list))
class PortalTypeHiddenContentTypeTemplateItem(PortalTypeAllowedContentTypeTemplateItem): class PortalTypeHiddenContentTypeTemplateItem(PortalTypeAllowedContentTypeTemplateItem):
...@@ -2588,7 +2607,7 @@ class ActionTemplateItem(ObjectTemplateItem): ...@@ -2588,7 +2607,7 @@ class ActionTemplateItem(ObjectTemplateItem):
Gets action copy from action provider given the action id or reference Gets action copy from action provider given the action id or reference
""" """
# Several tools still use CMF actions # Several tools still use CMF actions
if obj.getParentId() == 'portal_types': if interfaces.ITypeProvider.providedBy(obj.getParentValue()):
return self._getPortalTypeActionCopy(obj, value) return self._getPortalTypeActionCopy(obj, value)
else: else:
return self._getPortalToolActionCopy(obj, context, value) return self._getPortalToolActionCopy(obj, context, value)
...@@ -2600,6 +2619,8 @@ class ActionTemplateItem(ObjectTemplateItem): ...@@ -2600,6 +2619,8 @@ class ActionTemplateItem(ObjectTemplateItem):
url, value = id.split(' | ') url, value = id.split(' | ')
url = posixpath.split(url) url = posixpath.split(url)
obj = p.unrestrictedTraverse(url) obj = p.unrestrictedTraverse(url)
# normalize url
url = obj.getPhysicalPath()[-2:]
action = self._getActionCopy(obj, context, value) action = self._getActionCopy(obj, context, value)
if action is None: if action is None:
if self.is_bt_for_diff: if self.is_bt_for_diff:
...@@ -2625,7 +2646,7 @@ class ActionTemplateItem(ObjectTemplateItem): ...@@ -2625,7 +2646,7 @@ class ActionTemplateItem(ObjectTemplateItem):
path, id = id.rsplit('/', 1) path, id = id.rsplit('/', 1)
container = p.unrestrictedTraverse(path) container = p.unrestrictedTraverse(path)
if container.getParentId() == 'portal_types': if interfaces.ITypeProvider.providedBy(aq_parent(aq_inner(container))):
# XXX future BT should use 'reference' instead of 'id' # XXX future BT should use 'reference' instead of 'id'
reference = getattr(obj, 'reference', None) or obj.id reference = getattr(obj, 'reference', None) or obj.id
portal_type_dict.setdefault(path, {})[reference] = obj portal_type_dict.setdefault(path, {})[reference] = obj
...@@ -2742,7 +2763,7 @@ class ActionTemplateItem(ObjectTemplateItem): ...@@ -2742,7 +2763,7 @@ class ActionTemplateItem(ObjectTemplateItem):
obj = p.unrestrictedTraverse(relative_url, None) obj = p.unrestrictedTraverse(relative_url, None)
# Several tools still use CMF actions # Several tools still use CMF actions
if obj is not None: if obj is not None:
is_new_action = obj.getParentId() == 'portal_types' is_new_action = interfaces.ITypeProvider.providedBy(obj.getParentValue())
key = is_new_action and 'reference' or 'id' key = is_new_action and 'reference' or 'id'
else: else:
relative_url, key, value = self._splitPath(id) relative_url, key, value = self._splitPath(id)
...@@ -2753,8 +2774,8 @@ class ActionTemplateItem(ObjectTemplateItem): ...@@ -2753,8 +2774,8 @@ class ActionTemplateItem(ObjectTemplateItem):
if getattr(action_list[index], key, None) == value: if getattr(action_list[index], key, None) == value:
obj.deleteActions(selections=(index,)) obj.deleteActions(selections=(index,))
break break
LOG('BusinessTemplate', 100, LOG('BusinessTemplate', WARNING,
'unable to uninstall action at %s, ignoring' % relative_url ) 'Unable to uninstall action at %s, ignoring' % relative_url )
BaseTemplateItem.uninstall(self, context, **kw) BaseTemplateItem.uninstall(self, context, **kw)
class PortalTypeRolesTemplateItem(BaseTemplateItem): class PortalTypeRolesTemplateItem(BaseTemplateItem):
...@@ -2768,6 +2789,8 @@ class PortalTypeRolesTemplateItem(BaseTemplateItem): ...@@ -2768,6 +2789,8 @@ class PortalTypeRolesTemplateItem(BaseTemplateItem):
for relative_url in self._archive.keys(): for relative_url in self._archive.keys():
obj = p.unrestrictedTraverse("portal_types/%s" % obj = p.unrestrictedTraverse("portal_types/%s" %
relative_url.split('/', 1)[1]) relative_url.split('/', 1)[1])
# normalize url
relative_url = '%s/%s' % (obj.getPhysicalPath()[-2:])
self._objects[relative_url] = type_role_list = [] self._objects[relative_url] = type_role_list = []
for role in obj.getRoleInformationList(): for role in obj.getRoleInformationList():
type_role_dict = {} type_role_dict = {}
...@@ -2880,6 +2903,9 @@ class PortalTypeRolesTemplateItem(BaseTemplateItem): ...@@ -2880,6 +2903,9 @@ class PortalTypeRolesTemplateItem(BaseTemplateItem):
type_roles_list = self._objects[roles_path] or [] type_roles_list = self._objects[roles_path] or []
for role_property_dict in type_roles_list: for role_property_dict in type_roles_list:
obj._importRole(role_property_dict) obj._importRole(role_property_dict)
else:
raise AttributeError("Path '%r' not found while "
"installing roles" % (path, ))
def uninstall(self, context, **kw): def uninstall(self, context, **kw):
p = context.getPortalObject() p = context.getPortalObject()
......
...@@ -43,6 +43,7 @@ from urllib import pathname2url ...@@ -43,6 +43,7 @@ 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.tests.utils import LogInterceptor from Products.ERP5Type.tests.utils import LogInterceptor
from Products.ERP5Type.Tool.TypesTool import TypeProvider
from Products.ERP5Type.Workflow import addWorkflowByType from Products.ERP5Type.Workflow import addWorkflowByType
from Products.ERP5Type.tests.backportUnittest import expectedFailure from Products.ERP5Type.tests.backportUnittest import expectedFailure
import shutil import shutil
...@@ -50,12 +51,18 @@ import os ...@@ -50,12 +51,18 @@ import os
import gc import gc
import random import random
import string import string
import tempfile
import glob
from MethodObject import Method from MethodObject import Method
from Persistence import Persistent from Persistence import Persistent
WORKFLOW_TYPE = 'erp5_workflow' WORKFLOW_TYPE = 'erp5_workflow'
class DummyTypeProvider(TypeProvider):
id = 'dummy_type_provider'
class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor): class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor):
""" """
Test these operations: Test these operations:
...@@ -6378,6 +6385,124 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor): ...@@ -6378,6 +6385,124 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor):
self.assertFalse(getattr(portal.some_file, 'isClassOverriden', False)) self.assertFalse(getattr(portal.some_file, 'isClassOverriden', False))
self.assertFalse(getattr(portal.another_file, 'isClassOverriden', False)) self.assertFalse(getattr(portal.another_file, 'isClassOverriden', False))
def test_type_provider(self):
self.portal._setObject('dummy_type_provider', DummyTypeProvider())
type_provider = self.portal.dummy_type_provider
types_tool = self.portal.portal_types
registered_type_provider_list = types_tool.type_provider_list
# register this type provider
types_tool.type_provider_list = (
'dummy_type_provider',) + registered_type_provider_list
dummy_type = type_provider.newContent(
portal_type='Base Type',
id='Dummy Type',
type_factory_method_id='addFolder',
type_property_sheet_list=('Reference',),
type_base_category_list=('source',),
type_allowed_content_type_list=('Dummy Type',),
type_hidden_content_type_list=('Dummy Type',) )
dummy_type.newContent(portal_type='Action Information',
reference='view',
title='View', )
dummy_type.newContent(portal_type='Role Information',
title='Dummy Role Definition',
role_name_list=('Assignee', ))
pw = self.getWorkflowTool()
cbt = pw._chains_by_type.copy()
props = {}
for id, wf_ids in cbt.items():
props['chain_%s' % id] = ','.join(wf_ids)
props['chain_Dummy Type'] = 'edit_workflow'
pw.manage_changeWorkflows('', props=props)
self.assertEquals(('edit_workflow', ), pw.getChainFor('Dummy Type'))
bt = self.portal.portal_templates.newContent(
portal_type='Business Template',
title='test_bt',
template_tool_id_list=('dummy_type_provider', ),
template_portal_type_id_list=('Dummy Type',),
template_portal_type_role_list=('Dummy Type', ),
template_portal_type_workflow_chain_list=(
'Dummy Type | edit_workflow',),
template_portal_type_allowed_content_type_list=(
'Dummy Type | Dummy Type',),
template_portal_type_hidden_content_type_list=(
'Dummy Type | Dummy Type',),
template_portal_type_property_sheet_list=(
'Dummy Type | Reference',),
template_portal_type_base_category_list=(
'Dummy Type | source',),
template_action_path_list=(
'Dummy Type | view',),)
self.stepTic()
bt.build()
self.stepTic()
export_dir = tempfile.mkdtemp()
try:
bt.export(path=export_dir, local=True)
self.stepTic()
# portal type template item are exported in their physical location
for template_item in ('PortalTypeTemplateItem',
'ActionTemplateItem',):
self.assertEquals(['dummy_type_provider'],
[os.path.basename(f) for f in
glob.glob('%s/%s/*' % (export_dir, template_item))])
new_bt = self.portal.portal_templates.download(
url='file:/%s' % export_dir)
finally:
shutil.rmtree(export_dir)
# uninstall the workflow chain
pw._chains_by_type = cbt
# unregister type provider
types_tool.type_provider_list = registered_type_provider_list
# uninstall the type provider (this will also uninstall the contained types)
self.portal.manage_delObjects(['dummy_type_provider'])
self.stepTic()
new_bt.install()
type_provider = self.portal._getOb('dummy_type_provider', None)
self.assertNotEqual(None, type_provider)
# This type provider, will be automatically registered on types tool during
# business template installation, because it contains type information
self.assertTrue('dummy_type_provider' in types_tool.type_provider_list)
# The type is reinstalled
self.assertTrue('Dummy Type' in type_provider.objectIds())
# is available from types tool
self.assertTrue('Dummy Type' in [ti.getId() for
ti in types_tool.listTypeInfo()])
dummy_type = types_tool.getTypeInfo('Dummy Type')
self.assertNotEquals(None, dummy_type)
# all the configuration from the type is still here
self.assertEquals(['Reference'], dummy_type.getTypePropertySheetList())
self.assertEquals(['source'], dummy_type.getTypeBaseCategoryList())
self.assertEquals(['Dummy Type'], dummy_type.getTypeAllowedContentTypeList())
self.assertEquals(['Dummy Type'], dummy_type.getTypeHiddenContentTypeList())
action_list = dummy_type.contentValues(portal_type='Action Information')
self.assertEquals(['View'], [action.getTitle() for action in action_list])
self.assertEquals(['view'], [action.getReference() for action in action_list])
role_list = dummy_type.contentValues(portal_type='Role Information')
self.assertEquals(['Dummy Role Definition'],
[role.getTitle() for role in role_list])
self.assertEquals(('edit_workflow',), pw.getChainFor('Dummy Type'))
# and our type can be used
instance = self.portal.newContent(portal_type='Dummy Type',
id='test_document')
instance.setSourceReference('OK')
self.assertEquals('OK', instance.getSourceReference())
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
......
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