Commit 739b1e93 authored by Arnaud Fontaine's avatar Arnaud Fontaine

Set the Component state to modified upon modification, then check the consistency.

This allows to check consistency of reference and version as well (through
PropertyRecordableMixin rather than fiddling with the workflow history). Also,
split up state and error message into two separate fields.
parent 655ee640
......@@ -106,7 +106,8 @@
<value>
<list>
<string>my_description</string>
<string>my_translated_validation_state_title_with_error_message</string>
<string>my_translated_validation_state_title</string>
<string>my_error_message_list</string>
</list>
</value>
</item>
......
<?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_error_message_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>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</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>0</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>3</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>Errors</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>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: here.getValidationState() == \'modified\'</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -14,7 +14,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_translated_validation_state_title_with_error_message</string> </value>
<value> <string>my_translated_validation_state_title</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="InteractionDefinition" module="Products.ERP5.Interaction"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>activate_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>before_commit_script_name</string> </key>
<value>
<list>
<string>Component_validateAfterModified</string>
</list>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Component_modify</string> </value>
</item>
<item>
<key> <string>method_id</string> </key>
<value>
<list>
<string>_setTextContent*</string>
<string>_setReference*</string>
<string>_setVersion*</string>
</list>
</value>
</item>
<item>
<key> <string>once_per_transaction</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<value>
<list>
<string>Document Component</string>
<string>Extension Component</string>
</list>
</value>
</item>
<item>
<key> <string>script_name</string> </key>
<value>
<list>
<string>Component_setModifiedState</string>
</list>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Guard" module="Products.DCWorkflow.Guard"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</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: here.getValidationState() in (\'validated\', \'modified\')</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>state_change[\'object\'].modified()\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Component_setModifiedState</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>state_change[\'object\'].checkConsistencyAndValidate()\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Component_validateAfterModified</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -31,7 +31,6 @@
<string>invalidate</string>
<string>invalidate_action</string>
<string>modified</string>
<string>validate</string>
</tuple>
</value>
</item>
......
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
<value> <string>Modified</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>text_content</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
2012-02-22 arnaud.fontaine
* Split up state and error message into two separate fields.
* Always set the Component state to modified upon modification, then check the consistency.
2012-02-21 arnaud.fontaine
* Do not update automatically the component module registry for reliability's sake.
......
41004
\ No newline at end of file
41005
\ No newline at end of file
......@@ -111,9 +111,9 @@ class ComponentDynamicPackage(ModuleType):
# be handled by component_validation_workflow which will take care of
# updating the registry
if component.getValidationState() in ('modified', 'validated'):
reference = component.getReference()
self.__registry_dict.setdefault(
reference, {})[component.getVersion()] = component
reference = component.getReference(validated_only=True)
version = component.getVersion(validated_only=True)
self.__registry_dict.setdefault(reference, {})[version] = component
return self.__registry_dict
......
......@@ -32,6 +32,7 @@
from __future__ import absolute_import
from AccessControl import ClassSecurityInfo
from Products.ERP5.mixin.property_recordable import PropertyRecordableMixin
from Products.ERP5Type import Permissions
from Products.ERP5Type.Base import Base
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
......@@ -39,7 +40,7 @@ from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage
from zLOG import LOG, INFO
class ComponentMixin(Base):
class ComponentMixin(PropertyRecordableMixin, Base):
isPortalContent = 1
isRADContent = 1
isDelivery = ConstantGetter('isDelivery', value=True)
......@@ -58,121 +59,166 @@ class ComponentMixin(Base):
'TextDocument')
security.declareProtected(Permissions.ModifyPortalContent, 'checkConsistency')
def checkConsistency(self, text_content=None, *args, **kw):
def checkConsistency(self, *args, **kw):
"""
XXX-arnau: should probably be in a separate Constraint class?
"""
if text_content is None:
text_content = self.getTextContent()
error_list = []
object_relative_url = self.getRelativeUrl()
reference = self.getReference()
if not reference:
error_list.append(
ConsistencyMessage(self,
object_relative_url,
message="Reference must be set",
mapping={}))
elif (reference.endswith('_version') or
reference[0] == '_' or
reference in ('find_module', 'load_module')):
error_list.append(
ConsistencyMessage(self,
object_relative_url,
message="Reference cannot end with '_version' or "\
"start with '_' or be equal to find_module or "\
"load_module",
mapping={}))
version = self.getVersion()
if not version:
error_list.append(ConsistencyMessage(self,
object_relative_url,
message="Version must be set",
mapping={}))
elif version[0] == '_':
error_list.append(ConsistencyMessage(self,
object_relative_url,
message="Version cannot start with '_'",
mapping={}))
text_content = self.getTextContent()
if not text_content:
return [ConsistencyMessage(self,
object_relative_url=self.getRelativeUrl(),
message="No source code",
mapping={})]
message = None
try:
self.load(text_content=text_content)
except SyntaxError, e:
mapping = dict(error_message=str(e),
line_number=e.lineno,
column_number=e.offset)
message = "Syntax error in source code: ${error_message} " \
"(line: ${line_number}, column: ${column_number})"
except Exception, e:
mapping = dict(message=str(e))
message = "Source code: ${error_message}"
if message:
return [ConsistencyMessage(self,
object_relative_url=self.getRelativeUrl(),
message=message,
mapping=mapping)]
return []
def _setTextContent(self, text_content):
error_list.append(
ConsistencyMessage(self,
object_relative_url=object_relative_url,
message="No source code",
mapping={}))
else:
message = None
try:
self.load(text_content=text_content)
except SyntaxError, e:
mapping = dict(error_message=str(e),
line_number=e.lineno,
column_number=e.offset)
message = "Syntax error in source code: ${error_message} " \
"(line: ${line_number}, column: ${column_number})"
except Exception, e:
mapping = dict(error_message=str(e))
message = "Source code: ${error_message}"
if message:
error_list.append(
ConsistencyMessage(self,
object_relative_url=self.getRelativeUrl(),
message=message,
mapping=mapping))
return error_list
def _recordPropertyDecorator(accessor_name, property_name):
def inner(self, property_value):
"""
Everytime either 'reference', 'version' or 'text_content' are
modified when a Component is in modified or validated state, the
Component is set to modified state by component interaction
workflow, then in this method, the current property value is
recorded in order to handle any error returned when checking
consistency before the new value is set. At the end, through
component interaction workflow, the Component is validated only
if checkConsistency returns no error
The recorded property will be used upon loading the Component
whereas the new value set is displayed in Component view.
"""
if self.getValidationState() in ('modified', 'validated'):
self.recordProperty(property_name)
return getattr(super(ComponentMixin, self), accessor_name)(property_value)
return inner
security.declareProtected(Permissions.ModifyPortalContent, '_setReference')
_setReference = _recordPropertyDecorator('_setReference', 'reference')
security.declareProtected(Permissions.ModifyPortalContent, '_setVersion')
_setVersion = _recordPropertyDecorator('_setVersion', 'version')
security.declareProtected(Permissions.ModifyPortalContent, '_setTextContent')
_setTextContent = _recordPropertyDecorator('_setTextContent', 'text_content')
def checkConsistencyAndValidate(self):
"""
When the validation state is already 'validated', set the new value to
'text_content_non_validated' property instead of 'text_content' for the
following reasons:
1/ It allows to validate the source code through Component validation
workflow rather than after each edition;
2/ It avoids dirty hacks to call checkConsistency upon edit and deal with
error messages, instead use workflow as it makes more sense.
Then, when the user revalidates the Component through a workflow action,
'text_content_non_validated' property is copied back to 'text_content'.
XXX-arnau: the workflow history bit is really ugly and should be moved to
an interaction workflow instead
When a Component is in validated or modified validation state and
it is modified, modified state is set then this checks whether the
Component can be validated again if checkConsistency returns no
error
"""
validation_state = self.getValidationState()
if validation_state in ('validated', 'modified'):
error_message_list = self.checkConsistency(text_content=text_content)
if error_message_list:
self.modified()
validation_workflow = self.workflow_history['component_validation_workflow']
last_validation_workflow = validation_workflow[-1]
last_validation_workflow['error_message'] = error_message_list[0]
last_validation_workflow['text_content'] = text_content
previous_validation_workflow = validation_workflow[-2]
previous_validation_workflow['error_message'] = ''
previous_validation_workflow['text_content'] = ''
else:
super(ComponentMixin, self)._setTextContent(text_content)
self.validate()
if validation_state == 'modified':
# XXX-arnau: copy/paste
validation_workflow = self.workflow_history['component_validation_workflow']
previous_validation_workflow = validation_workflow[-2]
previous_validation_workflow['error_message'] = ''
previous_validation_workflow['text_content'] = ''
error_list = self.checkConsistency()
if error_list:
workflow = self.workflow_history['component_validation_workflow'][-1]
workflow['error_list'] = error_list
else:
return super(ComponentMixin, self)._setTextContent(text_content)
self.clearRecordedProperty('reference')
self.clearRecordedProperty('version')
self.clearRecordedProperty('text_content')
self.validate()
def _getRecordedPropertyDecorator(accessor_name, property_name):
def inner(self, validated_only=False):
"""
When validated_only is True, then returns the property recorded if the
Component has been modified but there was an error upon consistency
checking
"""
if validated_only:
try:
return self.getRecordedProperty(property_name)
# AttributeError when this property has never been recorded before
# (_recorded_property_dict) and KeyError if the property has been
# recorded before but is not anymore
except (AttributeError, KeyError):
pass
return getattr(super(ComponentMixin, self), accessor_name)()
return inner
security.declareProtected(Permissions.AccessContentsInformation,
'getTextContent')
def getTextContent(self, validated_only=False):
"""
Return the source code of the validated source code (if validated_only is
True), meaningful when generating the Component, or the non-validated
source code (when a Component is modified when it has already been
validated), meaningful when editing a Component or checking consistency
"""
if not validated_only:
text_content_non_validated = \
self.workflow_history['component_validation_workflow'][-1].get('text_content',
None)
if text_content_non_validated:
return text_content_non_validated
'getReference')
getReference = _getRecordedPropertyDecorator('getReference', 'reference')
return super(ComponentMixin, self).getTextContent()
security.declareProtected(Permissions.AccessContentsInformation, 'getVersion')
getVersion = _getRecordedPropertyDecorator('getVersion', 'version')
def _getErrorMessage(self):
current_workflow = self.workflow_history['component_validation_workflow'][-1]
return current_workflow['error_message']
security.declareProtected(Permissions.AccessContentsInformation,
'getTextContent')
getTextContent = _getRecordedPropertyDecorator('getTextContent',
'text_content')
security.declareProtected(Permissions.AccessContentsInformation,
'getTranslatedValidationStateTitleWithErrorMessage')
def getTranslatedValidationStateTitleWithErrorMessage(self):
validation_state_title = self.getTranslatedValidationStateTitle()
error_message = self._getErrorMessage()
if error_message:
return "%s (%s)" % (validation_state_title,
str(error_message.getTranslatedMessage()))
return validation_state_title
'getErrorMessageList')
def getErrorMessageList(self):
"""
Return the checkConsistency errors which may have occurred when
the Component has been modified after being validated once
"""
current_workflow = self.workflow_history['component_validation_workflow'][-1]
return [str(error.getTranslatedMessage())
for error in current_workflow['error_list']]
security.declareProtected(Permissions.ModifyPortalContent, 'load')
def load(self, namespace_dict={}, validated_only=False, text_content=None):
......
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