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
from Shared.DC.ZRDB.Connection import Connection as RDBConnection
from Products.ERP5Type.DiffUtils import DiffFile
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 Products.CMFCore.utils import getToolByName
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
......@@ -854,11 +854,12 @@ class ObjectTemplateItem(BaseTemplateItem):
"""
pass
def onNewObject(self):
def onNewObject(self, obj):
"""
Installation hook.
Called when installation process determined that object to install is
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.
"""
pass
......@@ -947,9 +948,10 @@ class ObjectTemplateItem(BaseTemplateItem):
saved_uid_dict = {}
subobjects_dict = {}
portal_type_dict = {}
# Object already exists
old_obj = container._getOb(object_id, None)
object_existed = old_obj is not None
if old_obj is not None:
# Object already exists
recurse(saveHook, old_obj)
if getattr(aq_base(old_obj), 'groups', None) is not None:
# we must keep original order groups
......@@ -969,8 +971,7 @@ class ObjectTemplateItem(BaseTemplateItem):
portal_type_dict['workflow_chain'] = \
getChainByType(context)[1].get('chain_' + object_id, '')
container.manage_delObjects([object_id])
else:
self.onNewObject()
# install object
obj = self._objects[path]
if getattr(obj, 'meta_type', None) == 'Script (Python)':
......@@ -997,6 +998,11 @@ class ObjectTemplateItem(BaseTemplateItem):
LOG("BT, install", 0, object_id)
raise
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
# we can implement logical for reseting or not attributes (i.e reference).
self.REQUEST.set('is_business_template_installation', 1)
......@@ -1414,7 +1420,7 @@ class CategoryTemplateItem(ObjectTemplateItem):
def beforeInstall(self):
self._installed_new_category = False
def onNewObject(self):
def onNewObject(self, obj):
self._installed_new_category = True
def afterInstall(self):
......@@ -1753,6 +1759,10 @@ class PortalTypeTemplateItem(ObjectTemplateItem):
p = context.getPortalObject()
for relative_url in self._archive.keys():
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 is in ghost state and an attribute must be accessed
# so that obj.__dict__ does not return an empty dict
......@@ -1769,6 +1779,17 @@ class PortalTypeTemplateItem(ObjectTemplateItem):
self._objects[relative_url] = obj
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
# re-exported with separated workflow-chain information
def install(self, context, trashbin, **kw):
......@@ -1938,7 +1959,7 @@ class PortalTypeWorkflowChainTemplateItem(BaseTemplateItem):
(wf_id, portal_type)
chain_dict[chain_key] = self._objects[path]
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 '
'portal type %r' % (self._chain_string_separator\
.join(self._objects[path])
......@@ -2043,16 +2064,15 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):
def build(self, context, **kw):
types_tool = getToolByName(self.getPortalObject(), 'portal_types')
types_list = list(types_tool.objectIds())
for key in self._archive.keys():
try:
portal_type, allowed_type = key.split(' | ')
except ValueError:
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
if not portal_type in types_list:
if ob is None:
raise ValueError, "Portal Type %s not found in site" %(portal_type,)
ob = types_tool._getOb(portal_type)
prop_value = getattr(ob, self.class_property, ())
if not allowed_type in prop_value and not self.is_bt_for_diff:
raise ValueError, "%s %s not found in portal type %s" % (
......@@ -2151,15 +2171,14 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):
action = update_dict[key]
if action == 'nothing':
continue
try:
portal_id = key.split('/')[-1]
portal_type = types_tool._getOb(portal_id)
except (AttributeError, KeyError):
type_information = types_tool.getTypeInfo(portal_id)
if type_information is None:
raise AttributeError, "Portal type '%s' not found while " \
"installing %s" % (portal_id, self.getTitle())
property_list = self._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
# for example:
# * current value : [A,B,C]
......@@ -2169,7 +2188,7 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):
for id in object_property_list:
if id not in property_list and id not in old_property_list:
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):
object_path = kw.get('object_path', None)
......@@ -2180,19 +2199,19 @@ class PortalTypeAllowedContentTypeTemplateItem(BaseTemplateItem):
else:
object_key_list = self._objects.keys()
for key in object_key_list:
try:
portal_id = key.split('/')[-1]
portal_type = types_tool._getOb(portal_id)
except (AttributeError, KeyError):
LOG("portal types not found : ", 100, portal_id)
type_information = types_tool.getTypeInfo(portal_id)
if type_information is None:
LOG("BusinessTemplate", WARNING,
"Portal type %r not found while uninstalling" % (portal_id,))
continue
property_list = self._objects[key]
original_property_list = list(getattr(portal_type,
original_property_list = list(getattr(type_information,
self.class_property, ()))
for id in property_list:
if id in original_property_list:
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):
......@@ -2588,7 +2607,7 @@ class ActionTemplateItem(ObjectTemplateItem):
Gets action copy from action provider given the action id or reference
"""
# Several tools still use CMF actions
if obj.getParentId() == 'portal_types':
if interfaces.ITypeProvider.providedBy(obj.getParentValue()):
return self._getPortalTypeActionCopy(obj, value)
else:
return self._getPortalToolActionCopy(obj, context, value)
......@@ -2600,6 +2619,8 @@ class ActionTemplateItem(ObjectTemplateItem):
url, value = id.split(' | ')
url = posixpath.split(url)
obj = p.unrestrictedTraverse(url)
# normalize url
url = obj.getPhysicalPath()[-2:]
action = self._getActionCopy(obj, context, value)
if action is None:
if self.is_bt_for_diff:
......@@ -2625,7 +2646,7 @@ class ActionTemplateItem(ObjectTemplateItem):
path, id = id.rsplit('/', 1)
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'
reference = getattr(obj, 'reference', None) or obj.id
portal_type_dict.setdefault(path, {})[reference] = obj
......@@ -2742,7 +2763,7 @@ class ActionTemplateItem(ObjectTemplateItem):
obj = p.unrestrictedTraverse(relative_url, None)
# Several tools still use CMF actions
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'
else:
relative_url, key, value = self._splitPath(id)
......@@ -2753,8 +2774,8 @@ class ActionTemplateItem(ObjectTemplateItem):
if getattr(action_list[index], key, None) == value:
obj.deleteActions(selections=(index,))
break
LOG('BusinessTemplate', 100,
'unable to uninstall action at %s, ignoring' % relative_url )
LOG('BusinessTemplate', WARNING,
'Unable to uninstall action at %s, ignoring' % relative_url )
BaseTemplateItem.uninstall(self, context, **kw)
class PortalTypeRolesTemplateItem(BaseTemplateItem):
......@@ -2768,6 +2789,8 @@ class PortalTypeRolesTemplateItem(BaseTemplateItem):
for relative_url in self._archive.keys():
obj = p.unrestrictedTraverse("portal_types/%s" %
relative_url.split('/', 1)[1])
# normalize url
relative_url = '%s/%s' % (obj.getPhysicalPath()[-2:])
self._objects[relative_url] = type_role_list = []
for role in obj.getRoleInformationList():
type_role_dict = {}
......@@ -2880,6 +2903,9 @@ class PortalTypeRolesTemplateItem(BaseTemplateItem):
type_roles_list = self._objects[roles_path] or []
for role_property_dict in type_roles_list:
obj._importRole(role_property_dict)
else:
raise AttributeError("Path '%r' not found while "
"installing roles" % (path, ))
def uninstall(self, context, **kw):
p = context.getPortalObject()
......
......@@ -43,6 +43,7 @@ from urllib import pathname2url
from Products.ERP5Type.Globals import PersistentMapping
from Products.CMFCore.Expression import Expression
from Products.ERP5Type.tests.utils import LogInterceptor
from Products.ERP5Type.Tool.TypesTool import TypeProvider
from Products.ERP5Type.Workflow import addWorkflowByType
from Products.ERP5Type.tests.backportUnittest import expectedFailure
import shutil
......@@ -50,12 +51,18 @@ import os
import gc
import random
import string
import tempfile
import glob
from MethodObject import Method
from Persistence import Persistent
WORKFLOW_TYPE = 'erp5_workflow'
class DummyTypeProvider(TypeProvider):
id = 'dummy_type_provider'
class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor):
"""
Test these operations:
......@@ -6378,6 +6385,124 @@ class TestBusinessTemplate(ERP5TypeTestCase, LogInterceptor):
self.assertFalse(getattr(portal.some_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():
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