diff --git a/product/ERP5Type/dynamic/component_package.py b/product/ERP5Type/dynamic/component_package.py index da05db985bed881855cb54e273018a0d6f056def..0e8fdea74fa64b0dbf51935bba6c4bc9eab0ba41 100644 --- a/product/ERP5Type/dynamic/component_package.py +++ b/product/ERP5Type/dynamic/component_package.py @@ -123,7 +123,9 @@ class ComponentDynamicPackage(ModuleType): # beforehand if version in version_priority_set: reference = component.getReference(validated_only=True) - self.__registry_dict.setdefault(reference, {})[version] = component.getId() + self.__registry_dict.setdefault(reference, {})[version] = ( + component.getId(), + component.getUid()) return self.__registry_dict @@ -281,7 +283,7 @@ class ComponentDynamicPackage(ModuleType): (fullname, self._namespace, error)) try: - component_id = self._registry_dict[name][version] + component_id = self._registry_dict[name][version][0] except KeyError: raise ImportError("%s: version %s of Component %s could not be found" % \ (fullname, version, name)) @@ -296,8 +298,9 @@ class ComponentDynamicPackage(ModuleType): # Version priority name list is ordered in descending order for version in site.getVersionPriorityNameList(): - component_id = component_version_dict.get(version) - if component_id is not None: + component_id_uid_tuple = component_version_dict.get(version) + if component_id_uid_tuple is not None: + component_id = component_id_uid_tuple[0] break else: raise ImportError("%s: no version of Component %s in Site priority" % \ diff --git a/product/ERP5Type/mixin/component.py b/product/ERP5Type/mixin/component.py index 1c9785687fdb9c9f09d2e41ee8d829a5ed3a60f1..e68ccbbb6bc80025231e9b0f96e9666c784add82 100644 --- a/product/ERP5Type/mixin/component.py +++ b/product/ERP5Type/mixin/component.py @@ -177,6 +177,9 @@ class ComponentMixin(PropertyRecordableMixin, Base): _message_version_not_set = "Version must be set" _message_invalid_version = "Version cannot start with '_'" + _message_duplicated_version_reference = "${id} is validated has the same "\ + "Reference and Version" + _message_text_content_not_set = "No source code" _message_text_content_error = "Error in Source Code: ${error_message}" @@ -195,9 +198,10 @@ class ComponentMixin(PropertyRecordableMixin, Base): """ error_list = super(ComponentMixin, self).checkConsistency(*args, **kw) object_relative_url = self.getRelativeUrl() - reference = self.getReference() + reference_has_error = False if not reference: + reference_has_error = True error_list.append( ConsistencyMessage(self, object_relative_url, @@ -207,6 +211,7 @@ class ComponentMixin(PropertyRecordableMixin, Base): elif (reference.endswith('_version') or reference[0] == '_' or reference in ('find_module', 'load_module', 'reset')): + reference_has_error = True error_list.append( ConsistencyMessage(self, object_relative_url, @@ -224,6 +229,26 @@ class ComponentMixin(PropertyRecordableMixin, Base): object_relative_url, message=self._message_invalid_version, mapping={})) + else: + package = __import__(self._getDynamicModuleNamespace(), globals(), + fromlist=[self._getDynamicModuleNamespace()], level=0) + component_id = None + component_uid = None + from Products.ERP5Type.dynamic import aq_method_lock + with aq_method_lock: + component_id_uid_tuple = package._registry_dict.get( + self.getReference(), {}).get(self.getVersion(), None) + if component_id_uid_tuple: + component_id, component_uid = component_id_uid_tuple + + if (component_id is not None and component_uid is not None and + not reference_has_error and + component_uid != self.getUid() and component_id != self.getId()): + error_list.append( + ConsistencyMessage(self, + object_relative_url, + message=self._message_duplicated_version_reference, + mapping=dict(id=component_id))) text_content = self.getTextContent() if not text_content: diff --git a/product/ERP5Type/tests/testDynamicClassGeneration.py b/product/ERP5Type/tests/testDynamicClassGeneration.py index 3f278ba44817be4aa49cc3d7310fc2400a9ec9b5..b631e48176fa4d2f39eba6f8d2278c8661f8b65c 100644 --- a/product/ERP5Type/tests/testDynamicClassGeneration.py +++ b/product/ERP5Type/tests/testDynamicClassGeneration.py @@ -1261,12 +1261,23 @@ class _TestZodbComponent(SecurityTestCase): self._component_tool.reset(force=True, reset_portal_type_at_transaction_boundary=True) - @abc.abstractmethod - def _newComponent(self, reference, text_content, version='erp5'): + def _newComponent(self, reference, text_content, version='erp5', id_=None): """ - Abstract method to create a new Component + Create new Component """ - pass + full_id = '%s.%s.%s' % (self._getComponentModuleName(), + version + '_version', + reference) + + if id_ is not None: + full_id += '.%s' % id_ + + return self._component_tool.newContent( + id=full_id, + version=version, + reference=reference, + text_content=text_content, + portal_type=self._component_portal_type) @abc.abstractmethod def _getComponentModuleName(self): @@ -1895,6 +1906,45 @@ def bar(*args, **kwargs): self.assertUserCanModifyDocument(user_id, component) self.assertUserCanDeleteDocument(user_id, component) + def testValidateComponentWithSameReferenceVersionAlreadyValidated(self): + reference = 'ValidateComponentWithSameReferenceVersionAlreadyValidated' + + component = self._newComponent(reference, 'def foo():\n print "ok"') + component.validate() + self.tic() + + component_dup = self._newComponent(reference, 'def foo():\n print "ok"', + id_='duplicated') + + self.tic() + + from Products.DCWorkflow.DCWorkflow import ValidationFailed + self.assertRaises(ValidationFailed, + self.portal.portal_workflow.doActionFor, + component_dup, 'validate_action') + + self.assertEquals(component_dup.getValidationState(), 'draft') + + component_dup.setReference(reference + '_copy') + component_dup.validate() + self.tic() + + component_dup.setReference(reference) + self.tic() + self.assertEquals(component_dup.getValidationState(), 'modified') + self.assertEquals(component_dup.getReference(), reference) + self.assertEquals(component_dup.getReference(validated_only=True), + reference + '_copy') + + component_dup.invalidate() + self.tic() + component_dup.setReference(reference) + self.assertRaises(ValidationFailed, + self.portal.portal_workflow.doActionFor, + component_dup, 'validate_action') + + self.assertEquals(component_dup.getValidationState(), 'invalidated') + from Products.ERP5Type.Core.ExtensionComponent import ExtensionComponent class TestZodbExtensionComponent(_TestZodbComponent): @@ -1902,15 +1952,7 @@ class TestZodbExtensionComponent(_TestZodbComponent): Tests specific to ZODB Extension Component (previously defined in bt5 and installed on the filesystem in $INSTANCE_HOME/Extensions) """ - def _newComponent(self, reference, text_content, version='erp5'): - return self._component_tool.newContent( - id='%s.%s.%s' % (self._getComponentModuleName(), - version + '_version', - reference), - version=version, - reference=reference, - text_content=text_content, - portal_type='Extension Component') + _component_portal_type = 'Extension Component' def _getComponentModuleName(self): return ExtensionComponent._getDynamicModuleNamespace() @@ -1987,14 +2029,7 @@ class TestZodbDocumentComponent(_TestZodbComponent): previously defined in bt5 and installed on the filesystem in $INSTANCE_HOME/Document. Later on, Product Documents will also be migrated """ - def _newComponent(self, reference, text_content, version='erp5'): - return self._component_tool.newContent( - id='%s.%s.%s' % (self._getComponentModuleName(), - version + '_version', reference), - reference=reference, - version=version, - text_content=text_content, - portal_type='Document Component') + _component_portal_type = 'Document Component' def _getComponentModuleName(self): return DocumentComponent._getDynamicModuleNamespace() @@ -2125,14 +2160,7 @@ class TestZodbTestComponent(_TestZodbComponent): Tests specific to ZODB Test Component (known as Live Tests, and previously defined in bt5 and installed in $INSTANCE_HOME/test) """ - def _newComponent(self, reference, text_content, version='erp5'): - return self._component_tool.newContent( - id='%s.%s.%s' % (self._getComponentModuleName(), - version + '_version', reference), - reference=reference, - version=version, - text_content=text_content, - portal_type='Test Component') + _component_portal_type = 'Test Component' def _getComponentModuleName(self): return TestComponent._getDynamicModuleNamespace()