Commit e84d2b51 authored by Arnaud Fontaine's avatar Arnaud Fontaine

ZODB Components: Likewise Document, add Mixin (erp5.component.mixin) and...

ZODB Components: Likewise Document, add Mixin (erp5.component.mixin) and Interface (erp5.component.interface).

* One Mixin/Interface class per ZODB Component.
  => Already the case for FS Mixin, not for Interfaces.
* ZODB Components module name ('reference' property) and class name:
  + Mixin: FooMixin.
  + Interface: IFoo.

Rationale:
  + Avoid current FS hacks: registry (Mixins, mixin_class_registry) or import
    all classes explicitly in __init__.py (Products.ERP5Type.interfaces).
  + Consistent naming.
  + Consistent with ZODB Documents Components.

Also, modify pylint checker to handle Zope Interfaces:
  + E: 4, 0: Inheriting 'Interface', which is not a class. (inherit-non-class)
  + E: 5, 2: Method has no argument (no-method-argument)
parent c0cd8524
<tal:block tal:define='site_root python: here.getWebSiteValue() or here.getPortalObject();
portal_url python: site_root.absolute_url();
portal_type python: here.getPortalType();
div_id string:${id}_ace;
mode python: here.Base_getAceEditorMode();
container_div_id string:${div_id}_container;
......@@ -150,6 +151,7 @@
ace_editor_container_div = null;
ace_editor = null;
var mode = '${mode}';
var portal_type = '${portal_type}';
var params = '${params}';
function maximizeFullscreenRemoveSaveMessage() {
......@@ -394,6 +396,7 @@
ace.require('ace/ext/language_tools');
ace_editor.setOptions({ enableBasicAutocompletion: true, enableSnippets: true });
var data_options = {};
data_options.portal_type = portal_type;
if (params !== 'None') {
data_options.bound_names = ['context','container','script','traverse_subpath','printed','same_type','string','sequence','random','DateTime','whrandom','reorder','sets','test','math'],
data_options.params = params;
......
......@@ -200,7 +200,7 @@ def checkPythonSourceCodeAsJSON(self, data, REQUEST=None):
else:
body = data['code']
message_list = checkPythonSourceCode(body.encode('utf8'))
message_list = checkPythonSourceCode(body.encode('utf8'), data.get('portal_type'))
for message_dict in message_list:
if is_python_script:
message_dict['row'] = message_dict['row'] - 2
......
......@@ -444,7 +444,8 @@
update_check_running = false;
function checkPythonSourceCode(text, updateLinting, options, cm) {
update_check_text = text;
checker_parameters = {code: text};
checker_parameters = {code: text,
portal_type: '<dtml-var name="portal_type">'};
<dtml-if bound_names>
checker_parameters['bound_names'] = <dtml-var name="bound_names">;
checker_parameters['params'] = $('input[name="params"]').val();
......
......@@ -135,7 +135,9 @@ SEPARATELY_EXPORTED_PROPERTY_DICT = {
"Extension Component": ("py", 0, "text_content"),
"File": (None, 0, "data"),
"Image": (None, 0, "data"),
"Interface Component": ("py", 0, "text_content"),
"OOoTemplate": ("oot", 1, "_text"),
"Mixin Component": ("py", 0, "text_content"),
"PDF": ("pdf", 0, "data"),
"PDFForm": ("pdf", 0, "data"),
"PyData Script": ("py", 0, "_body"),
......@@ -1160,6 +1162,14 @@ class ObjectTemplateItem(BaseTemplateItem):
"""
pass
def afterUninstall(self):
"""
Uninstallation hook.
Called right before returning in "uninstall" method.
Can be overridden by subclasses.
"""
pass
def onNewObject(self, obj):
"""
Installation hook.
......@@ -1642,6 +1652,7 @@ class ObjectTemplateItem(BaseTemplateItem):
# object is already backup and/or removed
pass
BaseTemplateItem.uninstall(self, context, **kw)
self.afterUninstall()
class PathTemplateItem(ObjectTemplateItem):
"""
......@@ -3795,7 +3806,7 @@ class ModuleTemplateItem(BaseTemplateItem):
pass
# XXX-arnau: when everything has been migrated to Components, this class
# should be renamed to DocumentTemplateItem
# should be removed and only _ZodbComponentTemplateItem should remain
class FilesystemDocumentTemplateItem(BaseTemplateItem):
local_file_reader_name = staticmethod(readLocalDocument)
local_file_writer_name = staticmethod(writeLocalDocument)
......@@ -4003,12 +4014,6 @@ class FilesystemToZodbTemplateItem(FilesystemDocumentTemplateItem,
return FilesystemDocumentTemplateItem._importFile(self, file_name,
*args, **kw)
def afterUninstall(self, already_migrated=False):
"""
Hook called after uninstall
"""
pass
def uninstall(self, *args, **kw):
# Only for uninstall, the path of objects can be given as a
# parameter, otherwise it fallbacks on '_archive'
......@@ -4018,14 +4023,11 @@ class FilesystemToZodbTemplateItem(FilesystemDocumentTemplateItem,
else:
object_keys = self._archive.keys()
already_migrated = self._is_already_migrated(object_keys)
if already_migrated:
if self._is_already_migrated(object_keys):
ObjectTemplateItem.uninstall(self, *args, **kw)
else:
FilesystemDocumentTemplateItem.uninstall(self, *args, **kw)
self.afterUninstall(already_migrated)
def remove(self, context, **kw):
"""
Conversion of magically uniqued paths to real ones
......@@ -4207,9 +4209,74 @@ class ConstraintTemplateItem(FilesystemDocumentTemplateItem):
local_file_importer_name = staticmethod(importLocalConstraint)
local_file_remover_name = staticmethod(removeLocalConstraint)
from Products.ERP5Type.Core.DocumentComponent import DocumentComponent
class _ZodbComponentTemplateItem(ObjectTemplateItem):
@staticmethod
def _getZodbObjectId(id):
raise NotImplementedError
def __init__(self, id_list, tool_id='portal_components', **kw):
ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw)
def isKeepWorkflowObjectLastHistoryOnly(self, path):
"""
Component Validation Workflow last History of ZODB Components must always be
kept, without explicitly adding them to the field which requires an extra
action for developers
"""
return True
def _removeAllButLastWorkflowHistory(self, obj):
"""
Only export the last state of component_validation_workflow, because only
the source code and its state to load it is necessary for ZODB Components
and too much history would be exported (edit_workflow)
"""
for wf_id in obj.workflow_history.keys():
if wf_id != 'component_validation_workflow':
del obj.workflow_history[wf_id]
continue
wf_history = obj.workflow_history[wf_id][-1]
# Remove useless modifcation 'time' and 'actor' (conflicts with VCSs)
wf_history.pop('time', None)
wf_history.pop('actor', None)
wf_history.pop('comment', None)
class DocumentTemplateItem(FilesystemToZodbTemplateItem):
obj.workflow_history[wf_id] = WorkflowHistoryList([wf_history])
def afterInstall(self):
"""
Reset component on the fly, because it is possible that those components
are required in the middle of the transaction. For example:
- A method in a component is called while installing.
- A document component is used in a different business template, and
those business templates are installed in a single transaction by
upgrader.
This reset is called at most 3 times in one business template
installation. (for Document, Test, Extension)
"""
self.portal_components.reset(force=True)
def afterUninstall(self):
self.portal_components.reset(force=True,
reset_portal_type_at_transaction_boundary=True)
from Products.ERP5Type.Core.InterfaceComponent import InterfaceComponent
class InterfaceTemplateItem(_ZodbComponentTemplateItem):
@staticmethod
def _getZodbObjectId(id):
return InterfaceComponent.getIdPrefix() + '.' + id
from Products.ERP5Type.Core.MixinComponent import MixinComponent
class MixinTemplateItem(_ZodbComponentTemplateItem):
@staticmethod
def _getZodbObjectId(id):
return MixinComponent.getIdPrefix() + '.' + id
from Products.ERP5Type.Core.DocumentComponent import DocumentComponent
class DocumentTemplateItem(FilesystemToZodbTemplateItem,
_ZodbComponentTemplateItem):
"""
Documents are now stored in ZODB rather than on the filesystem. However,
some Business Templates may still have filesystem Documents which need to be
......@@ -4230,12 +4297,14 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
This allows to keep Git history and having readable source code instead of
being crippled into an XML file
"""
_tool_id = 'portal_components'
@staticmethod
def _getZodbObjectId(id):
return DocumentComponent.getIdPrefix() + '.' + id
## All the methods/attributes below are for FS compatibility *only* and
## should be removed when all bt5s have been migrated
_tool_id = 'portal_components'
@staticmethod
def _getFilesystemPath(class_id):
from App.config import getConfiguration
......@@ -4250,25 +4319,6 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
action for developers
"""
return path.startswith(self._tool_id + '/')
def _removeAllButLastWorkflowHistory(self, obj):
"""
Only export the last state of component_validation_workflow, because only
the source code and its state to load it is necessary for ZODB Components
and too much history would be exported (edit_workflow)
"""
for wf_id in obj.workflow_history.keys():
if wf_id != 'component_validation_workflow':
del obj.workflow_history[wf_id]
continue
wf_history = obj.workflow_history[wf_id][-1]
# Remove useless modifcation 'time' and 'actor' (conflicts with VCSs)
wf_history.pop('time', None)
wf_history.pop('actor', None)
wf_history.pop('comment', None)
obj.workflow_history[wf_id] = WorkflowHistoryList([wf_history])
# XXX temporary should be eliminated from here
def _importFile(self, file_name, file_obj):
......@@ -4289,6 +4339,7 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
if not self._archive:
return
# After running the migration script, update bt5 property accordingly
if not self._is_already_migrated(self._archive.keys()):
document_id_list = self.getTemplateIdList()
if document_id_list[0] not in getattr(context.getPortalObject(),
......@@ -4307,23 +4358,16 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
issue as there are not so many Documents in bt5...
"""
if self._is_already_migrated(self._objects.keys()):
ObjectTemplateItem.install(self, context, **kw)
# Reset component on the fly, because it is possible that those
# components are required in the middle of the transaction. For example:
# - A method in a component is called while installing.
# - A document component is used in a different business template,
# and those business templates are installed in a single transaction
# by upgrader.
# This reset is called at most 3 times in one business template
# installation. (for Document, Test, Extension)
self.portal_components.reset(force=True)
_ZodbComponentTemplateItem.install(self, context, **kw)
else:
FilesystemDocumentTemplateItem.install(self, context, **kw)
def afterUninstall(self, already_migrated=False):
if already_migrated:
self.portal_components.reset(force=True,
reset_portal_type_at_transaction_boundary=True)
_removeAllButLastWorkflowHistory = _ZodbComponentTemplateItem._removeAllButLastWorkflowHistory
# Only for ObjectTemplateItem (ZODB Components) and thus no need to check
# whether they have already been migrated or not
afterInstall = _ZodbComponentTemplateItem.afterInstall
afterUninstall = _ZodbComponentTemplateItem.afterUninstall
from Products.ERP5Type.Core.ExtensionComponent import ExtensionComponent
......@@ -5117,6 +5161,10 @@ Business Template is a set of definitions, such as skins, portal types and categ
ModuleTemplateItem(self.getTemplateModuleIdList())
self._document_item = \
DocumentTemplateItem(self.getTemplateDocumentIdList())
self._interface_item = \
InterfaceTemplateItem(self.getTemplateInterfaceIdList())
self._mixin_item = \
MixinTemplateItem(self.getTemplateMixinIdList())
self._property_sheet_item = \
PropertySheetTemplateItem(self.getTemplatePropertySheetIdList(),
context=self)
......@@ -5938,6 +5986,8 @@ Business Template is a set of definitions, such as skins, portal types and categ
'PropertySheet' : '_property_sheet_item',
'Constraint' : '_constraint_item',
'Document' : '_document_item',
'Interface': '_interface_item',
'Mixin': '_mixin_item',
'Extension' : '_extension_item',
'Test' : '_test_item',
'Role' : '_role_item',
......@@ -6048,7 +6098,8 @@ Business Template is a set of definitions, such as skins, portal types and categ
# Text objects (no need to export them into XML)
# XXX Bad naming
item_list_3 = ['_document_item', '_property_sheet_item',
item_list_3 = ['_document_item', '_interface_item', '_mixin_item',
'_property_sheet_item',
'_constraint_item', '_extension_item',
'_test_item', '_message_translation_item',]
......
......@@ -58,6 +58,8 @@ item_name_list = (
'workflow',
'product',
'document',
'interface',
'mixin',
'property_sheet',
'constraint',
'extension',
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>View</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/ComponentMixin_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: object is not None and not object.isWebMode()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>View</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/ComponentMixin_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: object is not None and not object.isWebMode()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -51,6 +51,8 @@
<portal_type id="Component Tool">
<item>Document Component</item>
<item>Extension Component</item>
<item>Interface Component</item>
<item>Mixin Component</item>
<item>Test Component</item>
</portal_type>
<portal_type id="Contribution Registry Tool">
......
......@@ -41,6 +41,12 @@
<portal_type id="Extension Component">
<item>SortIndex</item>
</portal_type>
<portal_type id="Interface Component">
<item>SortIndex</item>
</portal_type>
<portal_type id="Mixin Component">
<item>SortIndex</item>
</portal_type>
<portal_type id="Property Existence Constraint">
<item>ConstraintType</item>
</portal_type>
......
<?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>_property_domain_dict</string> </key>
<value>
<dictionary>
<item>
<key> <string>short_title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>acquire_local_roles</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>content_icon</string> </key>
<value> <string>web_page.png</string> </value>
</item>
<item>
<key> <string>content_meta_type</string> </key>
<value> <string>ERP5 Text Document</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>An Interface Component is just a specific Document Component for Zope Interfaces in ZODB. </string> </value>
</item>
<item>
<key> <string>factory</string> </key>
<value> <string>addDocumentComponent</string> </value>
</item>
<item>
<key> <string>filter_content_types</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Interface Component</string> </value>
</item>
<item>
<key> <string>init_script</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>permission</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>InterfaceComponent</string> </value>
</item>
<item>
<key> <string>type_interface</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_mixin</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value> <string>erp5_content</string> </value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>short_title</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<tuple>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value> <string>erp5_content</string> </value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>title</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>_property_domain_dict</string> </key>
<value>
<dictionary>
<item>
<key> <string>short_title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>acquire_local_roles</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>content_icon</string> </key>
<value> <string>web_page.png</string> </value>
</item>
<item>
<key> <string>content_meta_type</string> </key>
<value> <string>ERP5 Text Document</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>A Mixin Component is just a specific Document Component for ERP5 mixins in ZODB. </string> </value>
</item>
<item>
<key> <string>factory</string> </key>
<value> <string>addDocumentComponent</string> </value>
</item>
<item>
<key> <string>filter_content_types</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Mixin Component</string> </value>
</item>
<item>
<key> <string>init_script</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>permission</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>MixinComponent</string> </value>
</item>
<item>
<key> <string>type_interface</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_mixin</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value> <string>erp5_content</string> </value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>short_title</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<tuple>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value> <string>erp5_content</string> </value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>title</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -107,6 +107,10 @@
<type>Extension Component</type>
<workflow>component_validation_workflow, dynamic_class_generation_interaction_workflow, edit_workflow</workflow>
</chain>
<chain>
<type>Interface Component</type>
<workflow>component_validation_workflow, dynamic_class_generation_interaction_workflow, edit_workflow</workflow>
</chain>
<chain>
<type>Mapped Value</type>
<workflow>edit_workflow</workflow>
......@@ -115,6 +119,10 @@
<type>Memcached Plugin</type>
<workflow>memcached_plugin_interaction_workflow</workflow>
</chain>
<chain>
<type>Mixin Component</type>
<workflow>component_validation_workflow, dynamic_class_generation_interaction_workflow, edit_workflow</workflow>
</chain>
<chain>
<type>Predicate</type>
<workflow>edit_workflow</workflow>
......
......@@ -64,6 +64,8 @@
<list>
<string>my_template_role_list</string>
<string>my_template_site_property_id_list</string>
<string>my_template_interface_id_list</string>
<string>my_template_mixin_id_list</string>
<string>my_template_document_id_list</string>
<string>my_template_property_sheet_id_list</string>
<string>my_template_extension_id_list</string>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="LinesField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_template_interface_id_list</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>line_too_long</string> </key>
<value> <string>A line was too long.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>too_long</string> </key>
<value> <string>You entered too many characters.</string> </value>
</item>
<item>
<key> <string>too_many_lines</string> </key>
<value> <string>You entered too many lines.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>view_separator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>view_separator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <int>5</int> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Interface Classes</string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>view_separator</string> </key>
<value> <string encoding="cdata"><![CDATA[
<br />
]]></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <int>40</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="LinesField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_template_mixin_id_list</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>line_too_long</string> </key>
<value> <string>A line was too long.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>too_long</string> </key>
<value> <string>You entered too many characters.</string> </value>
</item>
<item>
<key> <string>too_many_lines</string> </key>
<value> <string>You entered too many lines.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>view_separator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>view_separator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <int>5</int> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_linelength</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_lines</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Mixin Classes</string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>view_separator</string> </key>
<value> <string encoding="cdata"><![CDATA[
<br />
]]></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <int>40</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -72,6 +72,8 @@
<list>
<string>Document Component</string>
<string>Extension Component</string>
<string>Interface Component</string>
<string>Mixin Component</string>
<string>Test Component</string>
</list>
</value>
......
......@@ -71,10 +71,18 @@
<list>
<string>Document Component</string>
<string>Extension Component</string>
<string>Interface Component</string>
<string>Mixin Component</string>
<string>Test Component</string>
</list>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>script_name</string> </key>
<value>
......
......@@ -85,8 +85,10 @@ Dynamic Category Property | view
Extension Component | view
Folder | view
Id Tool | view
Interface Component | view
Memcached Plugin | view
Memcached Tool | view
Mixin Component | view
Predicate | view
Preference Tool Type | jump_property_sheets
Preference Tool Type | view
......
......@@ -21,6 +21,8 @@ Category Tool | Base Category
Category | Category
Component Tool | Document Component
Component Tool | Extension Component
Component Tool | Interface Component
Component Tool | Mixin Component
Component Tool | Test Component
Contribution Registry Tool | Contribution Predicate
Domain Tool | Base Domain
......
......@@ -48,9 +48,11 @@ Event
Extension Component
Folder
Id Tool
Interface Component
Mapped Value
Memcached Plugin
Memcached Tool
Mixin Component
Movement
Notification Tool
Order Tool
......
......@@ -12,6 +12,8 @@ Category Related Membership State Constraint | ConstraintType
Content Existence Constraint | ConstraintType
Document Component | SortIndex
Extension Component | SortIndex
Interface Component | SortIndex
Mixin Component | SortIndex
Property Existence Constraint | ConstraintType
Property Type Validity Constraint | ConstraintType
Python Script | CatalogFilter
......
......@@ -32,8 +32,14 @@ Dynamic Category Property | dynamic_class_generation_interaction_workflow
Extension Component | component_validation_workflow
Extension Component | dynamic_class_generation_interaction_workflow
Extension Component | edit_workflow
Interface Component | component_validation_workflow
Interface Component | dynamic_class_generation_interaction_workflow
Interface Component | edit_workflow
Mapped Value | edit_workflow
Memcached Plugin | memcached_plugin_interaction_workflow
Mixin Component | component_validation_workflow
Mixin Component | dynamic_class_generation_interaction_workflow
Mixin Component | edit_workflow
Predicate | edit_workflow
Preference | edit_workflow
Preference | preference_workflow
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_local_properties</string> </key>
<value>
<tuple>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>mode</string> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>string</string> </value>
</item>
</dictionary>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/lines</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>A list of ids of Interfaces used by this template</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>template_interface_id_property</string> </value>
</item>
<item>
<key> <string>mode</string> </key>
<value> <string>w</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: ()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_local_properties</string> </key>
<value>
<tuple>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>mode</string> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>string</string> </value>
</item>
</dictionary>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/lines</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>A list of ids of Mixins used by this template</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>template_mixin_id_property</string> </value>
</item>
<item>
<key> <string>mode</string> </key>
<value> <string>w</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: ()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -7558,7 +7558,208 @@ class TestBusinessTemplate(BusinessTemplateMixin):
from Products.ERP5Type.Core.DocumentComponent import DocumentComponent
class TestDocumentTemplateItem(BusinessTemplateMixin):
class _TestZodbOnlyComponentTemplateItemMixin:
def stepCreateZodbDocument(self, sequence=None, **kw):
document_id = self.component_id_prefix + '.erp5.' + self.document_title
component = self.portal.portal_components.newContent(
id=document_id,
version='erp5',
reference=self.document_title,
text_content=self.document_data,
portal_type=self.component_portal_type)
component.validate()
sequence.edit(document_title=self.document_title,
document_id=document_id,
document_data=self.document_data)
def stepAddZodbDocumentToBusinessTemplate(self, sequence=None, **kw):
sequence['current_bt'].setProperty(self.template_property,
sequence['document_id'])
def stepCheckZodbDocumentWorkflowHistoryUnchanged(self, sequence=None, **kw):
component = getattr(self.getPortalObject().portal_components,
sequence['document_id'], None)
self.assertNotEqual(component, None)
all_wf_history_dict = component.workflow_history
self.assertEqual(sorted(list(all_wf_history_dict)),
['component_validation_workflow', 'edit_workflow'])
wf_history_dict_list = all_wf_history_dict['component_validation_workflow']
self.assertFalse(len(wf_history_dict_list) <= 1)
for wf_history_dict in wf_history_dict_list:
self.assertNotEqual(wf_history_dict.get('time'), None)
self.assertNotEqual(wf_history_dict.get('actor'), None)
self.assertNotEqual(wf_history_dict.get('comment'), None)
def stepCheckZodbDocumentExistsAndValidated(self, sequence=None, **kw):
component = getattr(self.getPortalObject().portal_components,
sequence['document_id'], None)
self.assertNotEqual(component, None)
self.assertEqual(component.getValidationState(), 'validated')
# Only the last Workflow History should have been exported and without
# 'time' as it is not needed and create conflicts with VCSs
self.assertEqual(list(component.workflow_history),
['component_validation_workflow'])
validation_state_only_list = []
for validation_dict in component.workflow_history['component_validation_workflow']:
validation_state_only_list.append(validation_dict.get('validation_state'))
self.assertEqual(validation_dict.get('time'), None)
self.assertEqual(validation_dict.get('actor'), None)
self.assertEqual(validation_dict.get('comment'), None)
self.assertEqual(validation_state_only_list, ['validated'])
def stepCheckZodbDocumentRemoved(self, sequence=None, **kw):
component_tool = self.getPortalObject().portal_components
self.assertNotIn(sequence['document_id'], component_tool.objectIds())
def stepRemoveZodbDocument(self, sequence=None, **kw):
self.portal.portal_components.manage_delObjects([sequence['document_id']])
def stepCheckForkedMigrationExport(self, sequence=None, **kw):
"""
After saving a Business Template, two files should have been created for
each Component, one is the Python source code (ending with '.py') and the
other one is the metadata (ending with '.xml')
"""
component_bt_tool_path = os.path.join(sequence['template_path'],
self.__class__.__name__[len('Test'):],
'portal_components')
self.assertTrue(os.path.exists(component_bt_tool_path))
component_id = self.component_id_prefix + '.erp5.' + sequence['document_title']
base_path = os.path.join(component_bt_tool_path, component_id)
python_source_code_path = base_path + '.py'
self.assertTrue(os.path.exists(python_source_code_path))
source_code = sequence['document_data']
with open(python_source_code_path) as f:
self.assertEqual(f.read(), source_code)
xml_path = base_path + '.xml'
self.assertTrue(os.path.exists(xml_path))
first_line = source_code.split('\n', 1)[0]
with open(xml_path) as f:
for line in f:
self.assertNotIn(first_line, line)
def test_BusinessTemplateWithZodbDocument(self):
sequence_list = SequenceList()
sequence_string = '\
CreateZodbDocument \
CreateNewBusinessTemplate \
UseExportBusinessTemplate \
AddZodbDocumentToBusinessTemplate \
CheckModifiedBuildingState \
CheckNotInstalledInstallationState \
BuildBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckObjectPropertiesInBusinessTemplate \
SaveBusinessTemplate \
CheckForkedMigrationExport \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckZodbDocumentWorkflowHistoryUnchanged \
RemoveZodbDocument \
RemoveBusinessTemplate \
RemoveAllTrashBins \
ImportBusinessTemplate \
UseImportBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
InstallWithoutForceBusinessTemplate \
Tic \
CheckInstalledInstallationState \
CheckBuiltBuildingState \
CheckNoTrashBin \
CheckSkinsLayers \
CheckZodbDocumentExistsAndValidated \
UninstallBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckZodbDocumentRemoved \
SaveBusinessTemplate \
CheckForkedMigrationExport \
'
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
def test_BusinessTemplateWithZodbDocumentNonExistingBefore(self):
sequence_list = SequenceList()
sequence_string = '\
CreateNewBusinessTemplate \
UseExportBusinessTemplate \
CheckModifiedBuildingState \
CheckNotInstalledInstallationState \
BuildBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckObjectPropertiesInBusinessTemplate \
SaveBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
RemoveBusinessTemplate \
RemoveAllTrashBins \
ImportBusinessTemplate \
UseImportBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
InstallWithoutForceBusinessTemplate \
Tic \
CheckInstalledInstallationState \
CheckBuiltBuildingState \
CheckNoTrashBin \
CheckSkinsLayers \
\
CreateZodbDocument \
CreateNewBusinessTemplate \
UseExportBusinessTemplate \
AddZodbDocumentToBusinessTemplate \
CheckModifiedBuildingState \
CheckNotInstalledInstallationState \
BuildBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckObjectPropertiesInBusinessTemplate \
SaveBusinessTemplate \
CheckForkedMigrationExport \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckZodbDocumentWorkflowHistoryUnchanged \
RemoveZodbDocument \
RemoveBusinessTemplate \
RemoveAllTrashBins \
ImportBusinessTemplate \
UseImportBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
InstallWithoutForceBusinessTemplate \
Tic \
CheckInstalledInstallationState \
CheckBuiltBuildingState \
CheckNoTrashBin \
CheckSkinsLayers \
CheckZodbDocumentExistsAndValidated \
UninstallBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckZodbDocumentRemoved \
'
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
class TestDocumentTemplateItem(BusinessTemplateMixin,
_TestZodbOnlyComponentTemplateItemMixin):
document_title = 'UnitTest'
document_data = """class UnitTest:
meta_type = 'ERP5 Unit Test'
......@@ -7853,208 +8054,6 @@ class TestDocumentTemplateItem(BusinessTemplateMixin):
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
component_id_prefix = DocumentComponent.getIdPrefix()
component_portal_type = DocumentComponent.portal_type
def stepCreateZodbDocument(self, sequence=None, **kw):
document_id = self.component_id_prefix + '.erp5.' + self.document_title
component = self.portal.portal_components.newContent(
id=document_id,
version='erp5',
reference=self.document_title,
text_content=self.document_data,
portal_type=self.component_portal_type)
component.validate()
sequence.edit(document_title=self.document_title,
document_id=document_id,
document_data=self.document_data)
def stepAddZodbDocumentToBusinessTemplate(self, sequence=None, **kw):
sequence['current_bt'].setProperty(self.template_property,
sequence['document_id'])
def stepCheckZodbDocumentWorkflowHistoryUnchanged(self, sequence=None, **kw):
component = getattr(self.getPortalObject().portal_components,
sequence['document_id'], None)
self.assertNotEqual(component, None)
all_wf_history_dict = component.workflow_history
self.assertEqual(sorted(list(all_wf_history_dict)),
['component_validation_workflow', 'edit_workflow'])
wf_history_dict_list = all_wf_history_dict['component_validation_workflow']
self.assertFalse(len(wf_history_dict_list) <= 1)
for wf_history_dict in wf_history_dict_list:
self.assertNotEqual(wf_history_dict.get('time'), None)
self.assertNotEqual(wf_history_dict.get('actor'), None)
self.assertNotEqual(wf_history_dict.get('comment'), None)
def stepCheckZodbDocumentExistsAndValidated(self, sequence=None, **kw):
component = getattr(self.getPortalObject().portal_components,
sequence['document_id'], None)
self.assertNotEqual(component, None)
self.assertEqual(component.getValidationState(), 'validated')
# Only the last Workflow History should have been exported and without
# 'time' as it is not needed and create conflicts with VCSs
self.assertEqual(list(component.workflow_history),
['component_validation_workflow'])
validation_state_only_list = []
for validation_dict in component.workflow_history['component_validation_workflow']:
validation_state_only_list.append(validation_dict.get('validation_state'))
self.assertEqual(validation_dict.get('time'), None)
self.assertEqual(validation_dict.get('actor'), None)
self.assertEqual(validation_dict.get('comment'), None)
self.assertEqual(validation_state_only_list, ['validated'])
def stepCheckZodbDocumentRemoved(self, sequence=None, **kw):
component_tool = self.getPortalObject().portal_components
self.assertNotIn(sequence['document_id'], component_tool.objectIds())
def stepRemoveZodbDocument(self, sequence=None, **kw):
self.portal.portal_components.manage_delObjects([sequence['document_id']])
def stepCheckForkedMigrationExport(self, sequence=None, **kw):
"""
After saving a Business Template, two files should have been created for
each Component, one is the Python source code (ending with '.py') and the
other one is the metadata (ending with '.xml')
"""
component_bt_tool_path = os.path.join(sequence['template_path'],
self.__class__.__name__[len('Test'):],
'portal_components')
self.assertTrue(os.path.exists(component_bt_tool_path))
component_id = self.component_id_prefix + '.erp5.' + sequence['document_title']
base_path = os.path.join(component_bt_tool_path, component_id)
python_source_code_path = base_path + '.py'
self.assertTrue(os.path.exists(python_source_code_path))
source_code = sequence['document_data']
with open(python_source_code_path) as f:
self.assertEqual(f.read(), source_code)
xml_path = base_path + '.xml'
self.assertTrue(os.path.exists(xml_path))
first_line = source_code.split('\n', 1)[0]
with open(xml_path) as f:
for line in f:
self.assertNotIn(first_line, line)
def test_BusinessTemplateWithZodbDocument(self):
sequence_list = SequenceList()
sequence_string = '\
CreateZodbDocument \
CreateNewBusinessTemplate \
UseExportBusinessTemplate \
AddZodbDocumentToBusinessTemplate \
CheckModifiedBuildingState \
CheckNotInstalledInstallationState \
BuildBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckObjectPropertiesInBusinessTemplate \
SaveBusinessTemplate \
CheckForkedMigrationExport \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckZodbDocumentWorkflowHistoryUnchanged \
RemoveZodbDocument \
RemoveBusinessTemplate \
RemoveAllTrashBins \
ImportBusinessTemplate \
UseImportBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
InstallWithoutForceBusinessTemplate \
Tic \
CheckInstalledInstallationState \
CheckBuiltBuildingState \
CheckNoTrashBin \
CheckSkinsLayers \
CheckZodbDocumentExistsAndValidated \
UninstallBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckZodbDocumentRemoved \
SaveBusinessTemplate \
CheckForkedMigrationExport \
'
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
def test_BusinessTemplateWithZodbDocumentNonExistingBefore(self):
sequence_list = SequenceList()
sequence_string = '\
CreateNewBusinessTemplate \
UseExportBusinessTemplate \
CheckModifiedBuildingState \
CheckNotInstalledInstallationState \
BuildBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckObjectPropertiesInBusinessTemplate \
SaveBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
RemoveBusinessTemplate \
RemoveAllTrashBins \
ImportBusinessTemplate \
UseImportBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
InstallWithoutForceBusinessTemplate \
Tic \
CheckInstalledInstallationState \
CheckBuiltBuildingState \
CheckNoTrashBin \
CheckSkinsLayers \
\
CreateZodbDocument \
CreateNewBusinessTemplate \
UseExportBusinessTemplate \
AddZodbDocumentToBusinessTemplate \
CheckModifiedBuildingState \
CheckNotInstalledInstallationState \
BuildBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckObjectPropertiesInBusinessTemplate \
SaveBusinessTemplate \
CheckForkedMigrationExport \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckZodbDocumentWorkflowHistoryUnchanged \
RemoveZodbDocument \
RemoveBusinessTemplate \
RemoveAllTrashBins \
ImportBusinessTemplate \
UseImportBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
InstallWithoutForceBusinessTemplate \
Tic \
CheckInstalledInstallationState \
CheckBuiltBuildingState \
CheckNoTrashBin \
CheckSkinsLayers \
CheckZodbDocumentExistsAndValidated \
UninstallBusinessTemplate \
CheckBuiltBuildingState \
CheckNotInstalledInstallationState \
CheckZodbDocumentRemoved \
'
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
def stepCopyAndMigrateDocumentBusinessTemplate(self, sequence=None, **kw):
"""
Simulate migration from filesystem to ZODB
......@@ -8092,6 +8091,9 @@ class TestDocumentTemplateItem(BusinessTemplateMixin):
self.assertEqual(component.getPortalType(), self.component_portal_type)
sequence.edit(document_id=component_id)
component_id_prefix = DocumentComponent.getIdPrefix()
component_portal_type = DocumentComponent.portal_type
def test_BusinessTemplateWithZodbDocumentMigrated(self):
"""
Checks that if Business Template defines filesystem Document, they are
......@@ -8314,6 +8316,37 @@ class TestDocumentTemplateItem(BusinessTemplateMixin):
sequence_list.addSequenceString(sequence_string)
sequence_list.play(self)
from Products.ERP5Type.Core.InterfaceComponent import InterfaceComponent
class TestInterfaceTemplateItem(BusinessTemplateMixin,
_TestZodbOnlyComponentTemplateItemMixin):
document_title = 'IUnitTest'
document_data = '''from zope.interface import Interface
class IUnitTest(Interface):
def foo():
"""
Anything
"""
'''
template_property = 'template_interface_id_list'
component_id_prefix = InterfaceComponent.getIdPrefix()
component_portal_type = InterfaceComponent.portal_type
from Products.ERP5Type.Core.MixinComponent import MixinComponent
class TestMixinTemplateItem(BusinessTemplateMixin,
_TestZodbOnlyComponentTemplateItemMixin):
document_title = 'UnitTestMixin'
document_data = '''class UnitTestMixin:
def foo(self):
"""
Anything
"""
return '42'
'''
template_property = 'template_mixin_id_list'
component_id_prefix = MixinComponent.getIdPrefix()
component_portal_type = MixinComponent.portal_type
class TestConstraintTemplateItem(TestDocumentTemplateItem):
document_title = 'UnitTest'
document_data = ' \nclass UnitTest: \n """ \n Fake constraint for unit test \n \
......@@ -8469,6 +8502,8 @@ def test_suite():
suite.addTest(unittest.makeSuite(TestBusinessTemplate))
suite.addTest(unittest.makeSuite(TestConstraintTemplateItem))
suite.addTest(unittest.makeSuite(TestDocumentTemplateItem))
suite.addTest(unittest.makeSuite(TestInterfaceTemplateItem))
suite.addTest(unittest.makeSuite(TestMixinTemplateItem))
suite.addTest(unittest.makeSuite(TestExtensionTemplateItem))
suite.addTest(unittest.makeSuite(TestTestTemplateItem))
return suite
......@@ -110,12 +110,14 @@ class EditorWidget(Widget.TextAreaWidget):
elif portal_type == "Web Style":
mode = "css"
site_root = here.getWebSiteValue() or here.getPortalObject()
portal_type = here.getPortalType()
return code_mirror_support(field=field,
content=value,
field_id=key,
portal_url=site_root.absolute_url(),
mode=mode,
keymap=site_root.portal_preferences.getPreferredSourceCodeEditorKeymap())
keymap=site_root.portal_preferences.getPreferredSourceCodeEditorKeymap(),
portal_type=portal_type)
elif text_editor != 'text_area':
return here.fckeditor_wysiwyg_support.pt_render(
extra_context= {
......
......@@ -31,6 +31,7 @@ from Products.ERP5Type.mixin.component import ComponentMixin
from Products.ERP5Type.mixin.text_content_history import TextContentHistoryMixin
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage
import zope.interface
from Products.ERP5Type.interfaces.component import IComponent
......@@ -62,3 +63,35 @@ class DocumentComponent(ComponentMixin, TextContentHistoryMixin):
@staticmethod
def getIdPrefix():
return 'document'
_message_reference_class_not_defined = "Class ${reference} must be defined"
def checkConsistency(self, *args, **kw):
"""
Per convention, a Document Component must have at least a class whose name
is the same as the Reference so that it can be assigned to Portal Types.
XXX: Very basic check for now.
"""
error_list = super(DocumentComponent, self).checkConsistency(*args ,**kw)
reference = self.getReference()
text_content = self.getTextContent()
# Already checked in the parent class
if reference and text_content:
class_definition_str = 'class %s' % reference
try:
sep = text_content[text_content.index(class_definition_str) +
len(class_definition_str)]
except (ValueError, IndexError):
pass
else:
if (sep == ':' or # old-style class
sep == '('): # new-style class
return error_list
error_list.append(ConsistencyMessage(
self,
self.getRelativeUrl(),
message=self._message_reference_class_not_defined,
mapping={'reference': reference}))
return error_list
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2018 Nexedi SA and Contributors. All Rights Reserved.
# Arnaud Fontaine <arnaud.fontaine@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from Products.ERP5Type.Core.DocumentComponent import DocumentComponent
from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage
class InterfaceComponent(DocumentComponent):
"""
ZODB Component for interfaces
"""
meta_type = 'ERP5 Interface Component'
portal_type = 'Interface Component'
@staticmethod
def _getDynamicModuleNamespace():
return 'erp5.component.interface'
@staticmethod
def getIdPrefix():
return 'interface'
_message_reference_wrong_naming = "Interface Reference must start with 'I'"
def checkConsistency(self, *args, **kw):
"""
Per convention, an Interface class must start with 'I'
"""
error_list = super(InterfaceComponent, self).checkConsistency(*args, **kw)
reference = self.getReference()
if (reference and # Already checked in the parent class
not reference.startswith('I')):
error_list.append(ConsistencyMessage(
self,
self.getRelativeUrl(),
message=self._message_reference_wrong_naming,
mapping={}))
return error_list
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2018 Nexedi SA and Contributors. All Rights Reserved.
# Arnaud Fontaine <arnaud.fontaine@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from Products.ERP5Type.Core.DocumentComponent import DocumentComponent
from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage
class MixinComponent(DocumentComponent):
"""
ZODB Component for mixins
"""
meta_type = 'ERP5 Mixin Component'
portal_type = 'Mixin Component'
@staticmethod
def _getDynamicModuleNamespace():
return 'erp5.component.mixin'
@staticmethod
def getIdPrefix():
return 'mixin'
_message_reference_wrong_naming = "Mixin Reference must end with 'Mixin'"
def checkConsistency(self, *args, **kw):
"""
Per convention, a Mixin class must end with 'Mixin'
"""
error_list = super(MixinComponent, self).checkConsistency(*args, **kw)
reference = self.getReference()
if (reference and # Already checked in the parent class
not reference.endswith('Mixin')):
error_list.append(ConsistencyMessage(
self,
self.getRelativeUrl(),
message=self._message_reference_wrong_naming,
mapping={}))
return error_list
......@@ -200,36 +200,6 @@ class TypesTool(TypeProvider):
return None
return getattr(self, portal_type, None)
security.declareProtected(Permissions.AccessContentsInformation, 'getDocumentTypeList')
def getDocumentTypeList(self):
"""
Return a list of Document types (including filesystem and ZODB Component
Documents) that can be used as Base classes
"""
from Products.ERP5Type import document_class_registry
document_type_set = set(document_class_registry)
import erp5.component.document
portal = self.getPortalObject()
version_priority_set = set(portal.getVersionPriorityNameList())
# objectValues should not be used for a large number of objects, but
# this is only done upon reset, moreover using the Catalog is too risky
# as it lags behind and depends upon objects being reindexed
for component in portal.portal_components.objectValues(portal_type='Document Component'):
# Only consider modified or validated states as state transition will
# be handled by component_validation_workflow which will take care of
# updating the registry
validation_state_tuple = component.getValidationState()
if validation_state_tuple in ('modified', 'validated'):
version = component.getVersion(validated_only=True)
# The versions should have always been set on ERP5Site property
# beforehand
if version in version_priority_set:
document_type_set.add(component.getReference(validated_only=True))
return sorted(document_type_set)
security.declareProtected(Permissions.AccessContentsInformation, 'getPortalTypeClass')
def getPortalTypeClass(self, context, temp=False):
"""
......@@ -258,13 +228,44 @@ class TypesTool(TypeProvider):
module = erp5.portal_type
return getattr(module, portal_type, None)
def _getTypeList(self, component_portal_type, fs_type_list):
portal = self.getPortalObject()
version_priority_set = set(portal.getVersionPriorityNameList())
# objectValues should not be used for a large number of objects, but
# this is only done upon reset, moreover using the Catalog is too risky
# as it lags behind and depends upon objects being reindexed
type_set = set(fs_type_list)
for component in portal.portal_components.objectValues(portal_type=component_portal_type):
# Only consider modified or validated states as state transition will
# be handled by component_validation_workflow which will take care of
# updating the registry
validation_state_tuple = component.getValidationState()
if validation_state_tuple in ('modified', 'validated'):
version = component.getVersion(validated_only=True)
# The versions should have always been set on ERP5Site property
# beforehand
if version in version_priority_set:
type_set.add(component.getReference(validated_only=True))
return sorted(type_set)
security.declareProtected(Permissions.AccessContentsInformation, 'getDocumentTypeList')
def getDocumentTypeList(self):
"""
Return a list of Document types (including filesystem and ZODB Component
Documents) that can be used as Base classes
"""
from Products.ERP5Type import document_class_registry
return self._getTypeList('Document Component', document_class_registry)
security.declareProtected(Permissions.AccessContentsInformation, 'getMixinTypeList')
def getMixinTypeList(self):
"""
Return a list of class names that can be used as Mixins
"""
from Products.ERP5Type import mixin_class_registry
return sorted(mixin_class_registry)
return self._getTypeList('Mixin Component', mixin_class_registry)
security.declareProtected(Permissions.AccessContentsInformation, 'getInterfaceTypeList')
def getInterfaceTypeList(self):
......@@ -272,7 +273,9 @@ class TypesTool(TypeProvider):
Return a list of class names that can be used as Interfaces
"""
from Products.ERP5Type import interfaces
return [name for name, cls in inspect.getmembers(interfaces, inspect.isclass)]
return self._getTypeList(
'Interface Component',
[name for name, _ in inspect.getmembers(interfaces, inspect.isclass)])
security.declareProtected(Permissions.ModifyPortalContent,
'resetDynamicDocumentsOnceAtTransactionBoundary')
......
......@@ -423,7 +423,7 @@ def fill_args_from_request(*optional_args):
_pylint_message_re = re.compile(
'^(?P<type>[CRWEF]):\s*(?P<row>\d+),\s*(?P<column>\d+):\s*(?P<message>.*)$')
def checkPythonSourceCode(source_code_str):
def checkPythonSourceCode(source_code_str, portal_type=None):
"""
Check source code with pylint or compile() builtin if not available.
......@@ -511,6 +511,15 @@ def checkPythonSourceCode(source_code_str):
'--disable=W0212',
# string module does not only contain deprecated functions...
'--deprecated-modules=regsub,TERMIOS,Bastion,rexec']
if portal_type == 'Interface Component':
# Interface inherits from InterfaceClass:
# E: 4, 0: Inheriting 'Interface', which is not a class. (inherit-non-class)
args.append('--disable=E0239')
# Interfaces methods have no arguments:
# E: 5, 2: Method has no argument (no-method-argument)
args.append('--disable=E0211')
try:
from pylint.extensions.bad_builtin import __name__ as ext
args.append('--load-plugins=' + ext)
......
......@@ -182,6 +182,10 @@ def initializeDynamicModules():
holds ZODB Component packages
erp5.component.document:
holds Document modules previously found in bt5 in $INSTANCE_HOME/Document
erp5.component.interface:
holds Interface modules previously found in Products.NAME.interfaces
erp5.component.mixin:
holds Mixin modules previously found in Products.NAME.mixin
erp5.component.extension:
holds Extension modules previously found in bt5 in
$INSTANCE_HOME/Extensions
......@@ -236,6 +240,12 @@ def initializeDynamicModules():
erp5.component.document = ComponentDynamicPackage('erp5.component.document',
'Document Component')
erp5.component.interface = ComponentDynamicPackage('erp5.component.interface',
'Interface Component')
erp5.component.mixin = ComponentDynamicPackage('erp5.component.mixin',
'Mixin Component')
erp5.component.test = ComponentDynamicPackage('erp5.component.test',
'Test Component')
finally:
......
......@@ -201,9 +201,8 @@ def generatePortalTypeClass(site, portal_type_name):
klass = getattr(module, type_class)
except AttributeError:
LOG("ERP5Type.dynamic", WARNING,
"Could not get class '%s' in Component module '%s'" % \
(type_class,
module))
"Could not get class '%s' in Component module %r, fallback on filesystem" %
(type_class, module))
if klass is None:
type_class_path = document_class_registry.get(type_class)
......@@ -244,18 +243,58 @@ def generatePortalTypeClass(site, portal_type_name):
# "Filled accessor holder list for portal_type %s (%s)" % \
# (portal_type_name, accessor_holder_list))
mixin_path_list = []
mixin_class_list = []
if mixin_list:
mixin_path_list = map(mixin_class_registry.__getitem__, mixin_list)
mixin_class_list = map(_importClass, mixin_path_list)
# Only one Mixin class per ZODB Component (!= FS) where module_name ==
# class_name, name ending with 'Mixin'.
#
# Rationale: same as Document/Interface; consistent naming; avoid a
# registry like there used to be with FS.
import erp5.component.mixin
for mixin in mixin_list:
mixin_module = erp5.component.mixin.find_load_module(mixin)
mixin_class = None
if mixin_module is not None:
try:
mixin_class = getattr(mixin_module, mixin)
except AttributeError:
LOG("ERP5Type.dynamic", WARNING,
"Could not get class '%s' in Component module %r, fallback on filesystem" %
(mixin, mixin_module))
if mixin_class is None:
mixin_class = _importClass(mixin_class_registry[mixin])
mixin_class_list.append(mixin_class)
base_class_list = [klass] + accessor_holder_list + mixin_class_list
interface_class_list = []
if interface_list:
from Products.ERP5Type import interfaces
interface_class_list = [getattr(interfaces, name)
for name in interface_list]
# Filesystem Interfaces may have defined several Interfaces in one file
# but only *one* Interface per ZODB Component where module_name ==
# class_name, name starting with 'I'.
#
# Rationale: same as Document/Mixin; consistent naming; avoid a registry
# like there used to be for Mixin or importing all class in
# Products.ERP5Type.interfaces.__init__.py.
import erp5.component.interface
from Products.ERP5Type import interfaces as filesystem_interfaces
for interface in interface_list:
interface_module = erp5.component.interface.find_load_module(interface)
interface_class = None
if interface_module is not None:
try:
interface_class = getattr(interface_module, interface)
except AttributeError:
LOG("ERP5Type.dynamic", WARNING,
"Could not get class '%s' in Component module %r, fallback on filesystem" %
(interface, interface_module))
if interface_class is None:
interface_class = getattr(filesystem_interfaces, interface)
interface_class_list.append(interface_class)
if portal_type_name in core_portal_type_class_dict:
core_portal_type_class_dict[portal_type_name]['generating'] = False
......
......@@ -306,7 +306,7 @@ class ComponentMixin(PropertyRecordableMixin, Base):
Check Component source code through Pylint or compile() builtin if not
available
"""
return checkPythonSourceCode(self.getTextContent())
return checkPythonSourceCode(self.getTextContent(), self.getPortalType())
security.declareProtected(Permissions.ModifyPortalContent, 'PUT')
def PUT(self, REQUEST, RESPONSE):
......
......@@ -83,6 +83,7 @@ def manage_page_footer(self):
if not textarea_selector:
return default
portal_type = document.meta_type
if editor == 'codemirror' and getattr(portal, 'code_mirror_support', None) is not None:
keymap = portal.portal_preferences.getPreferredSourceCodeEditorKeymap()
return '''<script type="text/javascript" src="%s/jquery/core/jquery.min.js"></script>
......@@ -93,7 +94,8 @@ def manage_page_footer(self):
portal_url=portal_url,
bound_names=bound_names,
mode=mode,
keymap=keymap))
keymap=keymap,
portal_type=portal_type))
else:
return '''
<script type="text/javascript" src="%(portal_url)s/jquery/core/jquery.min.js"></script>
......@@ -133,7 +135,8 @@ $(document).ready(function() {
{'data': JSON.stringify(
{ code: editor.getSession().getValue(),
bound_names: %(bound_names)s,
params: $('input[name="params"]').val() })},
params: $('input[name="params"]').val(),
portal_type: %(portal_type)s })},
function(data){
editor.getSession().setAnnotations(data.annotations);
}
......
......@@ -1372,13 +1372,22 @@ class _TestZodbComponent(SecurityTestCase):
self._component_tool.reset(force=True,
reset_portal_type_at_transaction_boundary=True)
def _newComponent(self, reference, text_content, version='erp5', id_=None):
def _generateReference(self, base_name):
return base_name
def _getValidSourceCode(self, class_name):
raise NotImplementedError
def _newComponent(self, reference, text_content=None, version='erp5', id_=None):
"""
Create new Component
"""
if id_ is None:
id_ = '%s.%s.%s' % (self._document_class.getIdPrefix(), version, reference)
if text_content is None:
text_content = self._getValidSourceCode(reference)
return self._component_tool.newContent(
id=id_,
version=version,
......@@ -1465,9 +1474,8 @@ class _TestZodbComponent(SecurityTestCase):
'', ['Manager', 'Member', 'Assignee',
'Assignor', 'Author', 'Auditor', 'Associate'], [])
test_component = self._newComponent(
'TestValidateInvalidateComponent',
'def foobar(*args, **kwargs):\n return "ValidateInvalidate"')
reference = self._generateReference('TestValidateInvalidateComponent')
test_component = self._newComponent(reference)
self.failIfUserCanPassWorkflowTransition('ERP5TypeTestCase_NonDeveloper',
'validate_action',
......@@ -1487,11 +1495,11 @@ class _TestZodbComponent(SecurityTestCase):
finally:
setSecurityManager(sm)
self.failIfModuleImportable('TestValidateInvalidateComponent')
self.failIfModuleImportable(reference)
self.portal.portal_workflow.doActionFor(test_component, 'validate_action')
self.tic()
self.assertModuleImportable('TestValidateInvalidateComponent')
self.assertModuleImportable(reference)
self.failIfUserCanPassWorkflowTransition('ERP5TypeTestCase_NonDeveloper',
'invalidate_action',
......@@ -1511,11 +1519,11 @@ class _TestZodbComponent(SecurityTestCase):
self.portal.portal_workflow.doActionFor(test_component, 'validate_action')
self.tic()
self.assertModuleImportable('TestValidateInvalidateComponent')
self.assertModuleImportable(reference)
self.portal.portal_workflow.doActionFor(test_component, 'invalidate_action')
self.tic()
self.failIfModuleImportable('TestValidateInvalidateComponent')
self.failIfModuleImportable(reference)
sm = getSecurityManager()
try:
......@@ -1528,9 +1536,9 @@ class _TestZodbComponent(SecurityTestCase):
self.portal.portal_workflow.doActionFor(test_component, 'delete_action')
self.tic()
self.failIfModuleImportable('TestValidateInvalidateComponent')
self.failIfModuleImportable(reference)
self.assertEqual([o for o in self.portal.portal_components.contentValues()
if o.getReference() == 'TestValidateInvalidateComponent'],
if o.getReference() == reference],
[])
def testInvalidId(self):
......@@ -1538,27 +1546,23 @@ class _TestZodbComponent(SecurityTestCase):
Check whether checkConsistency has been properly implemented for checking
Component ID which should follow the format 'getIdPrefix().VERSION.REFERENCE'
"""
id_prefix = self._document_class.getIdPrefix()
version = "erp5"
reference = "TestWithInvalidId"
valid_id = "%s.%s.%s" % (id_prefix, version, reference)
component = self._newComponent(reference,
'def foobar():\n return 42',
version,
valid_id)
valid_reference = self._generateReference("TestWithInvalidId")
component = self._newComponent(valid_reference)
self.tic()
self.assertEqual(component.checkConsistency(), [])
for invalid_id in ("INVALID_PREFIX.%s.%s" % (version, reference),
"%s.INVALID_VERSION.%s" % (id_prefix, reference),
"%s.%s.INVALID_REFERENCE" % (id_prefix, version)):
valid_id_prefix = self._document_class.getIdPrefix()
valid_id = component.getId()
valid_version = component.getVersion()
for invalid_id in ("INVALID_PREFIX.%s.%s" % (valid_version, valid_reference),
"%s.INVALID_VERSION.%s" % (valid_id_prefix, valid_reference),
"%s.%s.INVALID_REFERENCE" % (valid_id_prefix, valid_version)):
component.setId(invalid_id)
self.tic()
self.assertEqual(
[m.getMessage().translate() for m in component.checkConsistency()],
[self.portal.Base_translateString(ComponentMixin._message_invalid_id,
mapping={'id_prefix': id_prefix})])
mapping={'id_prefix': valid_id_prefix})])
component.setId(valid_id)
self.tic()
......@@ -1573,12 +1577,10 @@ class _TestZodbComponent(SecurityTestCase):
validated but not when an error was encountered (implemented in
dynamic_class_generation_interaction_workflow)
"""
valid_reference = 'TestReferenceWithReservedKeywords'
valid_reference = self._generateReference('TestReferenceWithReservedKeywords')
ComponentTool.reset = assertResetCalled
try:
component = self._newComponent(valid_reference,
'def foobar():\n return 42')
component = self._newComponent(valid_reference)
component.validate()
self.tic()
......@@ -1609,25 +1611,23 @@ class _TestZodbComponent(SecurityTestCase):
'find_module': ComponentMixin._message_invalid_reference,
'load_module': ComponentMixin._message_invalid_reference}
invalid_id_error_message = self.portal.Base_translateString(
ComponentMixin._message_invalid_id,
mapping={'id_prefix': self._document_class.getIdPrefix()})
for invalid_reference, error_message in invalid_reference_dict.iteritems():
# Reset should not be performed
ComponentTool.reset = assertResetNotCalled
try:
component.setReference(invalid_reference)
# 'Class XXX must be defined' error from Document Components checked
# in its own test method
component.setTextContent(self._getValidSourceCode(invalid_reference))
self.tic()
finally:
ComponentTool.reset = ComponentTool._original_reset
# Should be in modified state as an error has been encountered
self.assertEqual(component.getValidationState(), 'modified')
self.assertEqual([m.getMessage().translate()
for m in component.checkConsistency()],
[invalid_id_error_message,
error_message])
self.assertTrue(error_message in [m.getMessage().translate()
for m in component.checkConsistency()])
self.assertEqual(component.getTextContentErrorMessageList(), [])
self.assertEqual(component.getTextContentWarningMessageList(), [])
self.assertEqual(component.getReference(), invalid_reference)
......@@ -1638,9 +1638,12 @@ class _TestZodbComponent(SecurityTestCase):
# Set a valid reference and check that the Component is in validated state
# and no error was raised
self.tic()
ComponentTool.reset = assertResetCalled
try:
component.setReference(valid_reference)
# 'Class XXX must be defined' error from Document Components
component.setTextContent(self._getValidSourceCode(valid_reference))
self.tic()
self.assertEqual(ComponentTool._reset_performed, True)
......@@ -1665,14 +1668,11 @@ class _TestZodbComponent(SecurityTestCase):
validated but not when an error was encountered (implemented in
dynamic_class_generation_interaction_workflow)
"""
reference = 'TestVersionWithReservedKeywords'
reference = self._generateReference('TestVersionWithReservedKeywords')
valid_version = 'erp5'
ComponentTool.reset = assertResetCalled
try:
component = self._newComponent(reference,
'def foobar():\n return 42',
valid_version)
component = self._newComponent(reference, version=valid_version)
component.validate()
self.tic()
......@@ -1755,20 +1755,24 @@ class _TestZodbComponent(SecurityTestCase):
"""
# Error/Warning properties must be set everytime the source code is
# modified, even in Draft state
component = self._newComponent('TestComponentWithSyntaxError', 'print "ok"')
reference = self._generateReference('TestComponentWithSyntaxError')
component = self._newComponent(reference)
valid_code = component.getTextContent()
self.tic()
self.assertEqual(component.checkConsistency(), [])
self.assertEqual(component.getTextContentErrorMessageList(), [])
self.assertEqual(component.getTextContentWarningMessageList(), [])
component.setTextContent('import sys')
component.setTextContent("""import sys
""" + valid_code)
self.tic()
self.assertEqual(component.checkConsistency(), [])
self.assertEqual(component.getTextContentErrorMessageList(), [])
self.assertEqual(component.getTextContentWarningMessageList(),
["W: 1, 0: Unused import sys (unused-import)"])
component.setTextContent('import unexistent_module')
component.setTextContent("""import unexistent_module
""" + valid_code)
self.tic()
self.assertEqual(
[m.getMessage().translate() for m in component.checkConsistency()],
......@@ -1778,7 +1782,6 @@ class _TestZodbComponent(SecurityTestCase):
self.assertEqual(component.getTextContentWarningMessageList(),
["W: 1, 0: Unused import unexistent_module (unused-import)"])
valid_code = 'def foobar():\n return 42'
ComponentTool.reset = assertResetCalled
try:
component.setTextContent(valid_code)
......@@ -1796,7 +1799,7 @@ class _TestZodbComponent(SecurityTestCase):
self.assertEqual(component.getTextContentWarningMessageList(), [])
self.assertEqual(component.getTextContent(), valid_code)
self.assertEqual(component.getTextContent(validated_only=True), valid_code)
self.assertModuleImportable('TestComponentWithSyntaxError')
self.assertModuleImportable(reference)
# Check that checkConsistency returns the proper error message for the
# following Python errors
......@@ -1807,15 +1810,18 @@ class _TestZodbComponent(SecurityTestCase):
[ComponentMixin._message_text_content_not_set],
[],
[]),
('def foobar(*args, **kwargs)\n return 42',
("""def foobar(*args, **kwargs)
return 42
""" + valid_code,
["Error in Source Code: E: 1, 0: invalid syntax (syntax-error)"],
["E: 1, 0: invalid syntax (syntax-error)"],
[]),
# Make sure that foobar NameError is at the end to make sure that after
# defining foobar function, it is not available at all
('foobar',
["Error in Source Code: E: 1, 0: Undefined variable 'foobar' (undefined-variable)"],
["E: 1, 0: Undefined variable 'foobar' (undefined-variable)"],
("""foobar2
""" + valid_code,
["Error in Source Code: E: 1, 0: Undefined variable 'foobar2' (undefined-variable)"],
["E: 1, 0: Undefined variable 'foobar2' (undefined-variable)"],
["W: 1, 0: Statement seems to have no effect (pointless-statement)"]))
for (invalid_code,
......@@ -1842,7 +1848,7 @@ class _TestZodbComponent(SecurityTestCase):
self.assertEqual(component.getTextContent(validated_only=True), valid_code)
self._component_tool.reset(force=True,
reset_portal_type_at_transaction_boundary=True)
self.assertModuleImportable('TestComponentWithSyntaxError')
self.assertModuleImportable(reference)
# Set a valid source code and check that the Component is in validated
# state and no error was raised
......@@ -1862,7 +1868,7 @@ class _TestZodbComponent(SecurityTestCase):
self.assertEqual(component.getTextContentWarningMessageList(), [])
self.assertEqual(component.getTextContent(), valid_code)
self.assertEqual(component.getTextContent(validated_only=True), valid_code)
self.assertModuleImportable('TestComponentWithSyntaxError')
self.assertModuleImportable(reference)
def testImportVersionedComponentOnly(self):
"""
......@@ -1870,9 +1876,11 @@ class _TestZodbComponent(SecurityTestCase):
sometimes it may be useful to import a specific version of a Component,
available as erp5.component.XXX.VERSION_version.COMPONENT_NAME.
"""
imported_reference = self._generateReference('TestImportedVersionedComponentOnly')
component = self._newComponent(
'TestImportedVersionedComponentOnly',
"""def foo(*args, **kwargs):
imported_reference,
self._getValidSourceCode(imported_reference) + """
def foo(*args, **kwargs):
return "TestImportedVersionedComponentOnly"
""")
......@@ -1883,49 +1891,46 @@ class _TestZodbComponent(SecurityTestCase):
# Create a new Component which uses a specific version of the previously
# created Component
reference = self._generateReference('TestImportVersionedComponentOnly')
component_import = self._newComponent(
'TestImportVersionedComponentOnly',
"""from %s.erp5_version.TestImportedVersionedComponentOnly import foo
reference,
self._getValidSourceCode(reference) + """
from %s.erp5_version.%s import foo
def bar(*args, **kwargs):
return 'Bar' + foo(*args, **kwargs)
""" % top_module_name)
""" % (top_module_name, imported_reference))
component_import.validate()
self.tic()
# Versioned package and its alias must be available
self.assertModuleImportable('TestImportVersionedComponentOnly',
self.assertModuleImportable(reference,
expected_default_version='erp5_version')
# Versioned Component of imported Component must be importable and check
# later that the module has not been added to the top-level package
self.assertModuleImportable('erp5_version.TestImportedVersionedComponentOnly')
self.assertModuleImportable('erp5_version.%s' % imported_reference)
top_module = __import__(top_module_name, level=0,
fromlist=[top_module_name])
self._importModule('erp5_version.TestImportedVersionedComponentOnly')
self._importModule('erp5_version.%s' % imported_reference)
# Function defined in versioned Component must be available and callable
self.assertHasAttribute(
top_module.erp5_version.TestImportedVersionedComponentOnly, 'foo')
self.assertEqual(
top_module.erp5_version.TestImportedVersionedComponentOnly.foo(),
'TestImportedVersionedComponentOnly')
versioned_module = getattr(top_module.erp5_version, imported_reference)
self.assertHasAttribute(versioned_module, 'foo')
self.assertEqual(versioned_module.foo(), 'TestImportedVersionedComponentOnly')
# The alias module on the top-level package must not have been created as
# only the versioned Component has been used
self.failIfHasAttribute(top_module, 'TestImportedVersionedComponentOnly')
self.failIfHasAttribute(top_module, imported_reference)
# As well as functions defined on unversioned Component
self._importModule('TestImportVersionedComponentOnly')
self.assertHasAttribute(top_module.TestImportVersionedComponentOnly, 'bar')
self.assertEqual(
top_module.TestImportVersionedComponentOnly.bar(),
'BarTestImportedVersionedComponentOnly')
self._importModule(reference)
module = getattr(top_module, reference)
self.assertHasAttribute(module, 'bar')
self.assertEqual(module.bar(), 'BarTestImportedVersionedComponentOnly')
def testVersionPriority(self):
"""
......@@ -1933,9 +1938,13 @@ def bar(*args, **kwargs):
version priorities on ERP5Site and checking whether the proper Component
is loaded
"""
reference = self._generateReference('TestVersionPriority')
source_code = self._getValidSourceCode(reference)
component_erp5_version = self._newComponent(
'TestVersionPriority',
"""def function_foo(*args, **kwargs):
reference,
source_code + """
def function_foo(*args, **kwargs):
return "TestERP5VersionPriority"
""")
......@@ -1943,8 +1952,9 @@ def bar(*args, **kwargs):
self.tic()
component_foo_version = self._newComponent(
'TestVersionPriority',
"""def function_foo(*args, **kwargs):
reference,
source_code + """
def function_foo(*args, **kwargs):
return "TestFooVersionPriority"
""",
'foo')
......@@ -1952,21 +1962,21 @@ def bar(*args, **kwargs):
component_foo_version.validate()
self.tic()
self.assertModuleImportable('TestVersionPriority',
self.assertModuleImportable(reference,
expected_default_version='erp5_version')
# Component for 'foo_version' must not be importable as 'foo' has not been
# added to ERP5Site version priorities
self.failIfModuleImportable('foo_version.TestVersionPriority')
self.failIfModuleImportable('foo_version.%s' % reference)
top_module_name = self._document_class._getDynamicModuleNamespace()
top_module = __import__(top_module_name, level=0,
fromlist=[top_module_name])
self._importModule('TestVersionPriority')
self.assertHasAttribute(top_module.TestVersionPriority, 'function_foo')
self.assertEqual(top_module.TestVersionPriority.function_foo(),
"TestERP5VersionPriority")
self._importModule(reference)
module = getattr(top_module, reference)
self.assertHasAttribute(module, 'function_foo')
self.assertEqual(module.function_foo(), "TestERP5VersionPriority")
from Products.ERP5.ERP5Site import getSite
site = getSite()
......@@ -1981,14 +1991,14 @@ def bar(*args, **kwargs):
self.assertEqual(ComponentTool._reset_performed, True)
self.assertModuleImportable(
'TestVersionPriority',
reference,
expected_default_version='foo_version',
expected_additional_version_tuple=('erp5_version',))
self._importModule('TestVersionPriority')
self.assertHasAttribute(top_module.TestVersionPriority, 'function_foo')
self.assertEqual(top_module.TestVersionPriority.function_foo(),
"TestFooVersionPriority")
self._importModule(reference)
module = getattr(top_module, reference)
self.assertHasAttribute(module, 'function_foo')
self.assertEqual(module.function_foo(), "TestFooVersionPriority")
finally:
ComponentTool.reset = ComponentTool._original_reset
......@@ -2001,9 +2011,7 @@ def bar(*args, **kwargs):
XXX-arnau: test with different users and workflows
"""
component = self._newComponent('TestDeveloperRoleSecurity',
'def foo():\n print "ok"')
component = self._newComponent(self._generateReference('TestDeveloperRoleSecurity'))
self.tic()
# Anonymous should not even be able to view/access Component Tool
......@@ -2055,6 +2063,11 @@ class TestZodbExtensionComponent(_TestZodbComponent):
_portal_type = 'Extension Component'
_document_class = ExtensionComponent
def _getValidSourceCode(self, *_):
return '''def foobar(*args, **kwargs):
return 'Anything'
'''
def testExternalMethod(self):
"""
Check that ExternalMethod monkey-patch to use ZODB Components works well
......@@ -2153,6 +2166,34 @@ class TestZodbDocumentComponent(_TestZodbComponent):
_portal_type = 'Document Component'
_document_class = DocumentComponent
def _getValidSourceCode(self, class_name):
return '''from Products.ERP5.Document.Person import Person
class %s(Person):
pass
''' % class_name
def testAtLeastOneClassNamedAfterReference(self):
component = self._newComponent(
self._generateReference('TestClassNamedAfterReference'))
self.tic()
self.assertEqual(component.checkConsistency(), [])
self.assertEqual(component.getTextContentErrorMessageList(), [])
self.assertEqual(component.getTextContentWarningMessageList(), [])
component.setTextContent("""from Products.ERP5.Document.Person import Person
class DifferentFromReference(Person):
pass
""")
self.assertEqual(
[m.getMessage().translate() for m in component.checkConsistency()],
[self.portal.Base_translateString(
self._document_class._message_reference_class_not_defined,
mapping={'reference': component.getReference()})])
self.assertEqual(component.getTextContentErrorMessageList(), [])
self.assertEqual(component.getTextContentWarningMessageList(), [])
def testAssignToPortalTypeClass(self):
"""
Create a new Document Component inheriting from Person Document and try to
......@@ -2172,7 +2213,7 @@ class TestZodbDocumentComponent(_TestZodbComponent):
test_component = self._newComponent(
'TestPortalType',
"""
from Products.ERP5Type.Document.Person import Person
from Products.ERP5.Document.Person import Person
class TestPortalType(Person):
def test42(self):
......@@ -2225,52 +2266,35 @@ class TestPortalType(Person):
person_type.setTypeClass('Person')
self.commit()
def testDocumentWithImport(self):
def testImportFromAnotherComponent(self):
"""
Create two new Components and check whether one can import the other one
after the latter has been validated
"""
self.failIfModuleImportable('TestDocumentWithImport')
self.failIfModuleImportable('TestDocumentImported')
# Create a new Document Component inheriting from Person Document which
# defines only one additional method (meaningful to make sure that the
# class (and not the module) has been added to the class when the
# TypeClass is changed)
test_imported_component = self._newComponent(
'TestDocumentImported',
"""
from Products.ERP5Type.Document.Person import Person
class TestDocumentImported(Person):
def test42(self):
return 42
""")
self.failIfModuleImportable('TestWithImport')
self.failIfModuleImportable('TestImported')
test_imported_component = self._newComponent('TestImported')
test_component = self._newComponent(
'TestDocumentWithImport',
'TestWithImport',
"""
from Products.ERP5.Document.Person import Person
from erp5.component.document.TestDocumentImported import TestDocumentImported
class TestDocumentWithImport(TestDocumentImported):
def test42(self):
return 4242
""")
from %s.TestImported import TestImported
class TestWithImport(TestImported):
pass
""" % self._document_class._getDynamicModuleNamespace())
self.tic()
self.failIfModuleImportable('TestDocumentWithImport')
self.failIfModuleImportable('TestDocumentImported')
self.failIfModuleImportable('TestWithImport')
self.failIfModuleImportable('TestImported')
test_imported_component.validate()
test_component.validate()
self.tic()
# TestPortalWithImport must be imported first to check if
# TestPortalImported could be imported without being present before
self.assertModuleImportable('TestDocumentWithImport')
self.assertModuleImportable('TestDocumentImported')
# TestWithImport must be imported first to check if TestImported could be
# imported without being present before
self.assertModuleImportable('TestWithImport')
self.assertModuleImportable('TestImported')
from Products.ERP5Type.Core.TestComponent import TestComponent
......@@ -2282,14 +2306,8 @@ class TestZodbTestComponent(_TestZodbComponent):
_portal_type = 'Test Component'
_document_class = TestComponent
def testRunLiveTest(self):
"""
Create a new ZODB Test Component and try to run it as a live tests and
check the expected output
"""
# First try with a test which run successfully
source_code = '''
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
def _getValidSourceCode(self, *_):
return '''from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
class Test(ERP5TypeTestCase):
def getTitle(self):
......@@ -2314,6 +2332,13 @@ class Test(ERP5TypeTestCase):
self.assertEqual(0, 0)
'''
def testRunLiveTest(self):
"""
Create a new ZODB Test Component and try to run it as a live tests and
check the expected output
"""
# First try with a test which run successfully
source_code = self._getValidSourceCode()
component = self._newComponent('testRunLiveTest', source_code)
component.validate()
self.tic()
......@@ -2391,6 +2416,165 @@ class Test(ERP5TypeTestCase):
types_tool._delObject(name)
self.commit()
from Products.ERP5Type.Core.InterfaceComponent import InterfaceComponent
class TestZodbInterfaceComponent(TestZodbDocumentComponent):
"""
Tests specific to ZODB Interface Component.
"""
_portal_type = 'Interface Component'
_document_class = InterfaceComponent
def _generateReference(self, base_name):
return 'I' + base_name
def _getValidSourceCode(self, class_name):
return '''from zope.interface import Interface
class %s(Interface):
def test42():
"""
Return 42
"""
''' % class_name
def testNamingConsistency(self):
valid_reference = self._generateReference('TestNaming')
component = self._newComponent(valid_reference)
component.validate()
self.tic()
self.assertEqual(component.getValidationState(), 'validated')
self.assertEqual(component.checkConsistency(), [])
self.assertEqual(component.getTextContentErrorMessageList(), [])
self.assertEqual(component.getTextContentWarningMessageList(), [])
self.assertEqual(component.getReference(), valid_reference)
self.assertEqual(component.getReference(validated_only=True), valid_reference)
self.assertModuleImportable(valid_reference)
invalid_reference = 'TestNaming'
ComponentTool.reset = assertResetNotCalled
try:
component.setReference(invalid_reference)
# Checked in another test methods
component.setId("%s.%s.%s" % (self._document_class.getIdPrefix(),
component.getVersion(),
invalid_reference))
component.setTextContent(self._getValidSourceCode(invalid_reference))
self.tic()
finally:
ComponentTool.reset = ComponentTool._original_reset
self.assertEqual(component.getValidationState(), 'modified')
self.assertEqual([m.getMessage().translate()
for m in component.checkConsistency()],
[self._document_class._message_reference_wrong_naming])
self.assertEqual(component.getTextContentErrorMessageList(), [])
self.assertEqual(component.getTextContentWarningMessageList(), [])
self.assertEqual(component.getReference(), invalid_reference)
self.assertEqual(component.getReference(validated_only=True), valid_reference)
def testAssignToPortalTypeClass(self):
"""
Create a new Document Component inheriting from Person Document and try to
assign it to Person Portal Type, then create a new Person and check
whether it has been successfully added to its Portal Type class bases and
that the newly-defined function on ZODB Component can be called as well as
methods from Person Document
"""
import erp5.portal_type
person_type = self.portal.portal_types.Person
person_type_class = erp5.portal_type.Person
component = self._newComponent('ITestPortalType')
self.tic()
self.failIfModuleImportable('ITestPortalType')
self.assertFalse('ITestPortalType' in person_type.getInterfaceTypeList())
component.validate()
self.assertModuleImportable('ITestPortalType')
self.assertTrue('ITestPortalType' in person_type.getInterfaceTypeList())
from erp5.component.interface.ITestPortalType import ITestPortalType
person_type_class.loadClass()
implemented_by_list = list(implementedBy(person_type_class))
self.assertFalse(ITestPortalType in implemented_by_list)
person_original_interface_type_list = list(person_type.getTypeInterfaceList())
try:
person_type.setTypeInterfaceList(person_original_interface_type_list +
['ITestPortalType'])
self.commit()
self.assertEqual(person_type_class.__isghost__, True)
person_type_class.loadClass()
implemented_by_list = list(implementedBy(person_type_class))
self.assertTrue(ITestPortalType in implemented_by_list)
finally:
person_type.setTypeInterfaceList(person_original_interface_type_list)
self.commit()
from Products.ERP5Type.Core.MixinComponent import MixinComponent
class TestZodbMixinComponent(TestZodbInterfaceComponent):
"""
Tests specific to ZODB Mixin Component.
"""
_portal_type = 'Mixin Component'
_document_class = MixinComponent
def _generateReference(self, base_name):
return base_name + 'Mixin'
def _getValidSourceCode(self, class_name):
return '''class %s:
def test42(self):
"""
Return 42
"""
''' % class_name
def testAssignToPortalTypeClass(self):
"""
Create a new Document Component inheriting from Person Document and try to
assign it to Person Portal Type, then create a new Person and check
whether it has been successfully added to its Portal Type class bases and
that the newly-defined function on ZODB Component can be called as well as
methods from Person Document
"""
import erp5.portal_type
person_type = self.portal.portal_types.Person
person_type_class = erp5.portal_type.Person
component = self._newComponent('TestPortalTypeMixin')
self.tic()
self.failIfModuleImportable('TestPortalTypeMixin')
self.assertFalse('TestPortalTypeMixin' in person_type.getMixinTypeList())
component.validate()
self.assertModuleImportable('TestPortalTypeMixin')
self.assertTrue('TestPortalTypeMixin' in person_type.getMixinTypeList())
from erp5.component.mixin.TestPortalTypeMixin import TestPortalTypeMixin
person_type_class.loadClass()
person_type_class_mro_list = person_type_class.__mro__
self.assertFalse(TestPortalTypeMixin in person_type_class_mro_list)
person_original_mixin_type_list = list(person_type.getTypeMixinList())
try:
person_type.setTypeMixinList(person_original_mixin_type_list +
['TestPortalTypeMixin'])
self.commit()
self.assertEqual(person_type_class.__isghost__, True)
person_type_class.loadClass()
person_type_class_mro_list = person_type_class.__mro__
from erp5.component.mixin.TestPortalTypeMixin import TestPortalTypeMixin
self.assertTrue(TestPortalTypeMixin in person_type_class_mro_list)
finally:
person_type.setTypeMixinList(person_original_mixin_type_list)
self.commit()
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestPortalTypeClass))
......@@ -2398,4 +2582,6 @@ def test_suite():
suite.addTest(unittest.makeSuite(TestZodbExtensionComponent))
suite.addTest(unittest.makeSuite(TestZodbDocumentComponent))
suite.addTest(unittest.makeSuite(TestZodbTestComponent))
suite.addTest(unittest.makeSuite(TestZodbInterfaceComponent))
suite.addTest(unittest.makeSuite(TestZodbMixinComponent))
return suite
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