Commit a270c222 authored by Aurel's avatar Aurel

Implement support of deletion detection by ERP5SyncML & improve workflow usage on signatures

parent badb7a60
<?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>categories</string> </key>
<value>
<tuple>
<string>elementary_type/boolean</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Indicate if it check deleted object at the end or with the splitted activities that check data changes</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>preferred_check_delete_at_end_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>preference</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: True</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -2,38 +2,37 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/boolean</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
<value> <string>Define if indexation of source data will be splitted in activity or not</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>not_synchronized</string> </value>
<value> <string>preferred_split_indexation_property</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Not Synchronized</string> </value>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>change_to_conflict</string>
<string>change_to_partial</string>
<string>do_sync</string>
<string>synchronize</string>
</tuple>
</value>
<key> <string>preference</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
<key> <string>property_default</string> </key>
<value> <string>python: False</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -50,39 +50,48 @@
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>signature = state_change[\'object\']\n
<value> <string>if not len(path_list):\n
return\n
restrictedTraverse = context.getPortalObject().restrictedTraverse\n
argument_getter_dict = {}\n
if subscription_path:\n
subscription = restrictedTraverse(subscription_path)\n
getId = subscription.getGidFromObject\n
getData = subscription.getDataFromDocument\n
else:\n
getId = getData = None\n
\n
edit_kw = {}\n
method = context.z_catalog_syncml_document_list\n
\n
temporary_data = signature.getTemporaryData()\n
if temporary_data is not None:\n
# This happens when we have sent the xml\n
# and we just get the confirmation\n
signature.setData(temporary_data)\n
edit_kw["temporary_data"] = None\n
parameter_append_list = []\n
append = parameter_append_list.append\n
parameter_dict = {}\n
for property in method.arguments_src.split():\n
parameter_dict[property] = parameter_value_list = []\n
if property == \'getData\':\n
getter = getData\n
elif property == \'getId\':\n
getter = getId\n
else:\n
getter = None\n
if getter is None:\n
getter = lambda obj, property=property: getattr(obj, property)()\n
append((parameter_value_list, getter))\n
\n
if signature.isForce():\n
edit_kw["force"] = False\n
if signature.hasPartialData():\n
edit_kw["partial_data"] = None\n
if signature.hasSubscriberXupdate():\n
edit_kw["subscriber_xupdate"] = None\n
if signature.hasPublisherXupdate():\n
edit_kw["publisher_xupdate"] = None\n
\n
if len(edit_kw):\n
signature.edit(**edit_kw)\n
\n
context.SyncMLSignature_resetConflictList(state_change)\n
for path in path_list:\n
obj = restrictedTraverse(path)\n
for value_list, getter in parameter_append_list:\n
value_list.append(getter(obj))\n
method(**parameter_dict)\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change</string> </value>
<value> <string>path_list, subscription_path=None, activate_kw=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SyncMLSignature_afterSynchronize</string> </value>
<value> <string>SQLCatalog_indexSyncMLDocumentList</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -89,6 +89,8 @@
<value>
<list>
<string>my_preferred_sync_action_per_activity_count</string>
<string>my_preferred_check_delete_at_end</string>
<string>my_preferred_split_indexation</string>
</list>
</value>
</item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_preferred_check_delete_at_end</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>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_checkbox</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Check Deletion at End</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_preferred_split_indexation</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>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_checkbox</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Split Source Indexation</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>getId\r\n
getPath\r\n
getData\r\n
</string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_catalog_syncml_document_list</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
REPLACE INTO\n
syncml (`path`, `gid`, `data`)\n
VALUES\n
<dtml-in prefix="loop" expr="_.range(_.len(getPath))">\n
(\n
<dtml-sqlvar expr="getPath[loop_item]" type="string">,\n
<dtml-sqlvar expr="getId[loop_item]" type="string">,\n
<dtml-sqlvar expr="getData[loop_item]" type="string">\n
)<dtml-unless sequence-end>,</dtml-unless>\n
</dtml-in>\n
]]></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_col</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_create_syncml</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string>CREATE TABLE `syncml` (\n
`path` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT \'\',\n
`gid` varchar(255) COLLATE utf8_unicode_ci DEFAULT \'\',\n
`data` LONGBLOB NULL,\n
PRIMARY KEY (`path`),\n
KEY `gid` (`gid`,`path`)\n
) ENGINE=InnoDB;\n
</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -2,37 +2,36 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
<key> <string>arguments_src</string> </key>
<value> <string>path</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>partial</string> </value>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Partial</string> </value>
<key> <string>id</string> </key>
<value> <string>z_delete_data_from_path</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>change_to_conflict</string>
<string>do_sync</string>
<string>synchronize</string>
</tuple>
</value>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
DELETE FROM syncml\n
WHERE\n
path = <dtml-sqlvar path type="string">\n
]]></string> </value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -2,36 +2,35 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<key> <string>_col</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>synchronized</string> </value>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Synchronized</string> </value>
<key> <string>id</string> </key>
<value> <string>z_drop_syncml</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>change_to_conflict</string>
<string>drift</string>
</tuple>
</value>
<key> <string>src</string> </key>
<value> <string>DROP TABLE IF EXISTS syncml</string> </value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>signature_path\r\n
source_path</string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_get_syncml_deleted_gid_list</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
SELECT gid \n
FROM syncml\n
WHERE\n
path like <dtml-sqlvar signature_path type="string">\n
AND\n
gid not in (SELECT gid FROM syncml WHERE\n
path like <dtml-sqlvar source_path type="string">)
]]></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -2,55 +2,85 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
<key> <string>allow_simple_one_argument_traversal</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
<key> <string>arguments_src</string> </key>
<value> <string>strict_min_gid\r\n
min_gid\r\n
max_gid\r\n
path\r\n
limit</string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string>SyncMLSignature_afterSynchronize</string> </value>
<key> <string>class_name_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<key> <string>connection_hook</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>synchronize</string> </value>
<value> <string>z_get_syncml_gid_list</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>synchronized</string> </value>
<key> <string>max_cache_</string> </key>
<value> <int>100</int> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
<key> <string>max_rows_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
SELECT \n
gid\n
FROM syncml\n
WHERE\n
path like <dtml-sqlvar path type="string">\n
<dtml-if strict_min_gid>\n
AND gid > <dtml-sqlvar strict_min_gid type="string">\n
</dtml-if>\n
<dtml-if min_gid>\n
AND gid >= <dtml-sqlvar min_gid type="string">\n
</dtml-if>\n
<dtml-if max_gid>\n
AND gid <= <dtml-sqlvar max_gid type="string">\n
</dtml-if>\n
ORDER BY gid\n
<dtml-if limit>\n
LIMIT <dtml-var limit>\n
</dtml-if>\n
]]></string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -2,55 +2,75 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
<key> <string>allow_simple_one_argument_traversal</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
<key> <string>arguments_src</string> </key>
<value> <string>min_gid\r\n
max_gid\r\n
path</string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<key> <string>cache_time_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>class_file_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string>SyncMLSignature_afterDrift</string> </value>
<key> <string>class_name_</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<key> <string>connection_hook</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>drift</string> </value>
<value> <string>z_get_syncml_path_list</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>not_synchronized</string> </value>
<key> <string>max_cache_</string> </key>
<value> <int>100</int> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
<key> <string>max_rows_</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
SELECT path, gid, data \n
FROM syncml\n
WHERE\n
path like <dtml-sqlvar path type="string">\n
<dtml-if min_gid>\n
AND gid >= <dtml-sqlvar min_gid type="string">\n
</dtml-if>\n
<dtml-if max_gid>\n
AND gid <= <dtml-sqlvar max_gid type="string">\n
</dtml-if>\n
]]></string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>arguments_src</string> </key>
<value> <string>path</string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>z_unindex_syncml_data</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
DELETE FROM syncml\n
WHERE\n
path like <dtml-sqlvar path type="string">\n
]]></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -34,7 +34,7 @@
</item>
<item>
<key> <string>initial_state</string> </key>
<value> <string>not_synchronized</string> </value>
<value> <string>no_conflict</string> </value>
</item>
<item>
<key> <string>manager_bypass</string> </key>
......
<?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>signature = state_change[\'object\']\n
if signature.hasPartialData():\n
signature.setPartialData(None)\n
if signature.hasTemporaryData():\n
signature.setTemporaryData(None)\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>SyncMLSignature_afterDrift</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>signature = state_change[\'object\']\n
signature.resetConflictList()\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>SyncMLSignature_resetConflictList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -22,10 +22,9 @@
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>drift</string>
<string>no_conflict</string>
<string>resolve_conflict_with_client_command_winning</string>
<string>resolve_conflict_with_merge</string>
<string>synchronize</string>
</tuple>
</value>
</item>
......
......@@ -24,6 +24,7 @@
<tuple>
<string>change_to_partial</string>
<string>do_sync</string>
<string>no_conflict</string>
<string>synchronize</string>
</tuple>
</value>
......
......@@ -24,6 +24,7 @@
<tuple>
<string>change_to_partial</string>
<string>do_sync</string>
<string>no_conflict</string>
<string>synchronize</string>
</tuple>
</value>
......
......@@ -8,22 +8,21 @@
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
<value> <string>Default state of signature</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>syncing</string> </value>
<value> <string>no_conflict</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Syncing</string> </value>
<value> <string>No Conflict</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>change_to_conflict</string>
<string>synchronize</string>
</tuple>
</value>
</item>
......
......@@ -28,7 +28,7 @@
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
<value> <string>Reset to default state</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
......@@ -38,11 +38,11 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>do_sync</string> </value>
<value> <string>no_conflict</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>syncing</string> </value>
<value> <string>no_conflict</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
<value> <string>No Conflict</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
......
......@@ -10,6 +10,10 @@
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
......@@ -20,7 +24,7 @@
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string>SyncMLSignature_resetConflictList</string> </value>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
......
94
\ No newline at end of file
118
......@@ -42,6 +42,8 @@ class testSyncMLAsynchronousEngine(TestERP5SyncMLMixin):
This is ran before anything, used to set the environment
"""
self.sync_tool = self.portal.portal_synchronizations
self.portal.z_drop_syncml()
self.portal.z_create_syncml()
# here, you can create the categories and objects your test will depend on
def _initSyncModule(self):
......
......@@ -4,8 +4,8 @@
<item>source_section</item>
</portal_type>
<portal_type id="Integration Site">
<item>resource</item>
<item>destination_payment</item>
<item>resource</item>
<item>source_account</item>
<item>source_payment</item>
</portal_type>
......
......@@ -28,10 +28,6 @@
<key> <string>id</string> </key>
<value> <string>SynchronizationConfiguratorItem</string> </value>
</item>
<item>
<key> <string>last_id</string> </key>
<value> <string>2</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
......
......@@ -28,10 +28,6 @@
<key> <string>id</string> </key>
<value> <string>WebServiceConnector</string> </value>
</item>
<item>
<key> <string>last_id</string> </key>
<value> <string>1</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
......
......@@ -28,10 +28,6 @@
<key> <string>id</string> </key>
<value> <string>WebServiceRequest</string> </value>
</item>
<item>
<key> <string>last_id</string> </key>
<value> <string>11</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
......
......@@ -67,75 +67,14 @@ if account.getReference() and account.getValidationState() != \'deleted\':\n
return account_list\n
</string> </value>
</item>
<item>
<key> <string>_code</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>id="", gid=""</string> </value>
</item>
<item>
<key> <string>errors</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>func_code</string> </key>
<value>
<object>
<klass>
<global name="FuncCode" module="Shared.DC.Scripts.Signature"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>co_argcount</string> </key>
<value> <int>2</int> </value>
</item>
<item>
<key> <string>co_varnames</string> </key>
<value>
<tuple>
<string>id</string>
<string>gid</string>
<string>account_list</string>
<string>_getiter_</string>
<string>_getattr_</string>
<string>context</string>
<string>account</string>
<string>getattr</string>
</tuple>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>func_defaults</string> </key>
<value>
<tuple>
<string></string>
<string></string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Account_getAccountValueList</string> </value>
</item>
<item>
<key> <string>warnings</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
......
......@@ -67,75 +67,14 @@ if accounting.getReference() and accounting.getStartDate() and accounting.getSim
return accounting_list\n
</string> </value>
</item>
<item>
<key> <string>_code</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>id="", gid=""</string> </value>
</item>
<item>
<key> <string>errors</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>func_code</string> </key>
<value>
<object>
<klass>
<global name="FuncCode" module="Shared.DC.Scripts.Signature"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>co_argcount</string> </key>
<value> <int>2</int> </value>
</item>
<item>
<key> <string>co_varnames</string> </key>
<value>
<tuple>
<string>id</string>
<string>gid</string>
<string>accounting_list</string>
<string>_getiter_</string>
<string>_getattr_</string>
<string>context</string>
<string>accounting</string>
<string>getattr</string>
</tuple>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>func_defaults</string> </key>
<value>
<tuple>
<string></string>
<string></string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Accounting_getAccountingValueList</string> </value>
</item>
<item>
<key> <string>warnings</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
......
......@@ -74,77 +74,14 @@ return context.REQUEST.RESPONSE.redirect(\n
url_quote(\'Request sent.\')))\n
</string> </value>
</item>
<item>
<key> <string>_code</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>*args, **kw</string> </value>
</item>
<item>
<key> <string>errors</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>func_code</string> </key>
<value>
<object>
<klass>
<global name="FuncCode" module="Shared.DC.Scripts.Signature"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>co_argcount</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>co_varnames</string> </key>
<value>
<tuple>
<string>args</string>
<string>kw</string>
<string>_getitem_</string>
<string>selection_name</string>
<string>Products.PythonScripts.standard</string>
<string>url_quote</string>
<string>_getattr_</string>
<string>context</string>
<string>uids</string>
<string>len</string>
<string>conduit_id</string>
<string>_getiter_</string>
<string>uid</string>
</tuple>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>func_defaults</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>IntegrationModule_doTestCreate</string> </value>
</item>
<item>
<key> <string>warnings</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
......
......@@ -53,69 +53,14 @@
<value> <string>return context.getPortalObject()[context.getId()].contentValues()\n
</string> </value>
</item>
<item>
<key> <string>_code</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>*args, **kw</string> </value>
</item>
<item>
<key> <string>errors</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>func_code</string> </key>
<value>
<object>
<klass>
<global name="FuncCode" module="Shared.DC.Scripts.Signature"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>co_argcount</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>co_varnames</string> </key>
<value>
<tuple>
<string>args</string>
<string>kw</string>
<string>_getattr_</string>
<string>_getitem_</string>
<string>context</string>
</tuple>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>func_defaults</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>IntegrationModule_getTestObjectList</string> </value>
</item>
<item>
<key> <string>warnings</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
......
......@@ -98,6 +98,7 @@
<value>
<list>
<string>my_id</string>
<string>my_title</string>
<string>my_int_index</string>
<string>my_source_section_title</string>
<string>my_destination_section_title</string>
......
......@@ -62,69 +62,14 @@ category_list.sort()\n
return category_list\n
</string> </value>
</item>
<item>
<key> <string>_code</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>errors</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>func_code</string> </key>
<value>
<object>
<klass>
<global name="FuncCode" module="Shared.DC.Scripts.Signature"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>co_argcount</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>co_varnames</string> </key>
<value>
<tuple>
<string>category_list</string>
<string>_getiter_</string>
<string>_getattr_</string>
<string>context</string>
<string>category</string>
</tuple>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>func_defaults</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>TransactionLine_getCategoryList</string> </value>
</item>
<item>
<key> <string>warnings</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
......
......@@ -28,10 +28,6 @@
<key> <string>id</string> </key>
<value> <string>portal_integrations</string> </value>
</item>
<item>
<key> <string>last_id</string> </key>
<value> <string>27</string> </value>
</item>
</dictionary>
</pickle>
</record>
......
Integration Module | destination_section
Integration Module | source_section
Integration Site | resource
Integration Site | destination_payment
Integration Site | resource
Integration Site | source_account
Integration Site | source_payment
Synchronization Configurator Item | destination_section
......
......@@ -53,14 +53,16 @@
<value> <string encoding="cdata"><![CDATA[
# First retrieve the document\n
doc_list = context.getPortalObject().document_module.searchFolder(reference=reference,\n
sort_on = ((\'version\', \'DESC\'),),\n
limit=1)\n
portal = context.getPortalObject()\n
document_list = portal.document_module.searchFolder(\n
reference=reference,\n
validation_state="shared",\n
sort_on=[(\'version\', \'DESC\')],\n
)\n
if len(document_list) != 1:\n
raise ValueError, "Impossible to find document with reference %s" %(reference)\n
document = document_list[0].getObject()\n
\n
if not len(doc_list) == 1:\n
raise ValueError, "Impossible to find document with reference %r" %(reference)\n
\n
import_file = doc_list[0]\n
\n
# Then parse it\n
from Products.ERP5OOo.OOoUtils import OOoParser\n
......@@ -102,7 +104,7 @@ def getIDFromString(string=None):\n
\n
return clean_id\n
\n
parser.openFromString(str(import_file.getData()))\n
parser.openFromString(str(document.getData()))\n
\n
# Extract tables from the speadsheet file\n
filename = parser.getFilename()\n
......@@ -127,22 +129,23 @@ for table_name in spreadsheet_list.keys():\n
property_map[column_index] = column_id\n
column_index += 1\n
# This path_element_list help us to reconstruct the absolute path\n
context.log("line_id = %r" %(line_id))\n
if line_id is not None:\n
line_list = [sheet[int(line_id)-1],]\n
line_index = int(line_id)\n
else:\n
line_list = sheet[1:]\n
line_index = 2\n
context.log(\'line_list = %s\' %(line_list))\n
line_list = line_list[:limit]\n
for line in line_list:\n
if id_list and str(line_index) not in id_list:\n
continue\n
# Exclude empty lines\n
context.log("\\tgot line", line)\n
if line.count(\'\') + line.count(None) == len(line):\n
continue\n
\n
# Prefetch line datas\n
line_data = {"id" : str(line_index)}\n
if not id_only:\n
path_defined = []\n
for cell_index, cell in enumerate(line):\n
# Get the property corresponding to the cell data\n
......@@ -172,7 +175,7 @@ return spreadsheet_line_list\n
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>reference, table, line_id=None</string> </value>
<value> <string>reference, table, limit, id_only, line_id=None, id_list=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
1
\ No newline at end of file
4
\ No newline at end of file
......@@ -5155,7 +5155,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
from Products.ERP5VCS.WorkingCopy import NotAWorkingCopyError
try:
self.setRevision(self.getVcsTool().newRevision())
except NotAWorkingCopyError:
except (NotAWorkingCopyError, IOError):
raise ImportError
except ImportError:
self.updateRevisionNumber()
......
......@@ -31,11 +31,12 @@ from hashlib import md5
from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type import Permissions
from Products.ERP5Type import PropertySheet
from Products.ERP5SyncML.Utils import PdataHelper
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
_MARKER = []
......@@ -50,7 +51,6 @@ class SyncMLSignature(XMLObject):
"""
meta_type = 'ERP5 Signature'
portal_type = 'SyncML Signature'
isIndexable = ConstantGetter('isIndexable', value=False)
security = ClassSecurityInfo()
......@@ -66,6 +66,32 @@ class SyncMLSignature(XMLObject):
, PropertySheet.Document
, PropertySheet.SyncMLSignature )
security.declareProtected(Permissions.ModifyPortalContent, 'synchronize')
def synchronize(self):
"""
This is call when subscription get confirmation of the data synchronization
This copy & reset some properties if needed
"""
edit_kw = {}
temporary_data = self.getTemporaryData()
if temporary_data is not None:
# This happens when we have sent the xml
# and we just get the confirmation
self.setData(temporary_data)
edit_kw["temporary_data"] = None
if self.isForce():
edit_kw["force"] = False
if self.hasPartialData():
edit_kw["partial_data"] = None
if self.hasSubscriberXupdate():
edit_kw["subscriber_xupdate"] = None
if self.hasPublisherXupdate():
edit_kw["publisher_xupdate"] = None
if len(edit_kw):
self.edit(**edit_kw)
security.declareProtected(Permissions.ModifyPortalContent, 'setData')
def setData(self, value):
"""
......@@ -249,24 +275,6 @@ class SyncMLSignature(XMLObject):
else:
return self._baseGetPublisherXupdate(default)
security.declareProtected(Permissions.ModifyPortalContent,
'reset')
def reset(self, no_conflict=False):
"""
Clear Signature and change validation_state to not_synchronized
no_conflict : prevent the reset of signature for which conflict
has not been marked resolved, this is usefull when
resetting all signature at the beginning of a sync process
XXX Use a better name and a positive value by default
"""
if no_conflict and self.getValidationState() in (
'conflict',
'conflict_resolved_with_merge',
'conflict_resolved_with_client_command_winning'):
return
if self.getValidationState() != 'not_synchronized':
self.drift()
security.declareProtected(Permissions.ModifyPortalContent,
'getConflictList')
def getConflictList(self):
......@@ -275,22 +283,6 @@ class SyncMLSignature(XMLObject):
"""
return self.contentValues()
security.declareProtected(Permissions.ModifyPortalContent,
'setConflictList')
def setConflictList(self, conflict_list):
"""
XXX is it still usefull ?
"""
return
security.declareProtected(Permissions.ModifyPortalContent,
'resetConflictList')
def resetConflictList(self):
"""
XXX is it still usefull ?
"""
return
security.declareProtected(Permissions.ModifyPortalContent,
'delConflict')
def delConflict(self, conflict):
......
......@@ -65,7 +65,7 @@ class SyncMLAsynchronousEngine(EngineMixin):
subscription.sendModifications() # Worfklow action
syncml_response = None
tag = subscription_path = subscription.getRelativeUrl()
tag = subscription.getRelativeUrl()
# Do action according to synchronization state
if subscription.getSynchronizationState() == "initializing":
......@@ -75,10 +75,13 @@ class SyncMLAsynchronousEngine(EngineMixin):
if subscription.getSyncmlAlertCode() in ("one_way_from_server",
"refresh_from_server_only"):
# We only get data from server
syncml_response = self._generateBaseResponse(subscription)
syncml_response = subscription.generateBaseResponse()
syncml_response.addFinal()
else:
self.runGetAndActivate(subscription=subscription, tag=tag)
# Make sure it is launched after indexation step
self.runGetAndActivate(subscription=subscription, tag=tag,
after_method_id=("getAndIndex",
"SQLCatalog_indexSyncMLSignatureList"))
syncml_logger.info("X-> Client is sendind modification in activities")
# As we generated all activities to send data at once, process must not
# go back here, go into processing state thus status will be applied and
......@@ -99,7 +102,7 @@ class SyncMLAsynchronousEngine(EngineMixin):
% (len(syncml_request.sync_command_list)))
if syncml_request.isFinal:
if not syncml_response:
syncml_response = self._generateBaseResponse(subscription)
syncml_response = subscription.generateBaseResponse()
# We got and process all sync command from server
# notify it that all modifications were applied
syncml_response.addFinal()
......@@ -163,10 +166,10 @@ class SyncMLAsynchronousEngine(EngineMixin):
# Apply command & send modifications
# Apply status about object send & synchronized if any
sync_status_counter = self._readStatusList(syncml_request, subscriber,
self._readStatusList(syncml_request, subscriber,
generate_alert=True)
syncml_response = None
tag = subscription_path = subscriber.getRelativeUrl()
tag = subscriber.getRelativeUrl()
after_method_id = None
if subscriber.getSynchronizationState() == "sending_modifications":
if syncml_request.isFinal:
......@@ -202,15 +205,13 @@ class SyncMLAsynchronousEngine(EngineMixin):
if syncml_request.isFinal:
# Server then sends its modifications
subscriber.sendModifications()
# Now that everything is ok, init sync information
if subscriber.getSyncmlAlertCode() not in ("one_way_from_client",
"refresh_from_client_only"):
# Reset signature only if we have to check modifications on server side
subscriber.initialiseSynchronization()
# Run indexation only once client have sent its modifications
subscriber.indexSourceData()
# Start to send modification only once we have processed
# all message from client
after_method_id='processServerSynchronization',
after_method_id=('processServerSynchronization',
'SQLCatalog_indexSyncMLDocumentList')
# XXX after tag might also be required to make sure all data are indexed
tag = (tag, "%s_reset" % subscriber.getPath(),)
# Do not continue in elif, as sending modifications is done in the same
# package as sending notifications
......@@ -230,7 +231,7 @@ class SyncMLAsynchronousEngine(EngineMixin):
# Server has no modification to send to client, return final message
syncml_logger.info("X-> Server sending final message")
if not syncml_response:
syncml_response = self._generateBaseResponse(subscriber)
syncml_response = subscriber.generateBaseResponse()
syncml_response.addFinal()
if subscriber.getSynchronizationState() == "finished":
......@@ -242,10 +243,9 @@ class SyncMLAsynchronousEngine(EngineMixin):
after_tag=tag).sendMessage(
xml=str(syncml_response))
def runGetAndActivate(self, subscription, tag, after_method_id=None):
"""
Generate tag and method parameter and call the getAndActivate method
Launch the browsing of GID that will call the generation of syncml commands
"""
activate_kw = {
'activity' : 'SQLQueue',
......@@ -253,20 +253,20 @@ class SyncMLAsynchronousEngine(EngineMixin):
'tag' :tag,
'priority' :ACTIVITY_PRIORITY
}
method_kw = {
'subscription_path' : subscription.getRelativeUrl(),
}
pref = getSite().portal_preferences
count = subscription.getAndActivate(
subscription.getAndActivate(
callback="sendSyncCommand",
method_kw=method_kw,
activate_kw=activate_kw,
packet_size=pref.getPreferredDocumentRetrievedPerActivityCount(),
activity_count=pref.getPreferredRetrievalActivityCount(),
)
# Then get deleted document
# this will send also the final message of this sync part
subscription.activate(after_tag=tag)._getDeletedData()
# then send the final message of this sync part
if pref.getPreferredCheckDeleteAtEnd():
subscription.activate(after_tag=tag,
priority=ACTIVITY_PRIORITY+1).getDeletedSyncMLData()
else:
subscription.activate(after_tag=tag,
priority=ACTIVITY_PRIORITY+1)._sendFinalMessage()
return True
......@@ -284,9 +284,9 @@ class SyncMLAsynchronousEngine(EngineMixin):
response_id_list = [None for x in
xrange(len(syncml_request.sync_command_list))]
split = getSite().portal_preferences.getPreferredSyncActionPerActivityCount()
if not split:
if not split: # We do not use activities
if send_response:
syncml_response = self._generateBaseResponse(subscription)
syncml_response = subscription.generateBaseResponse()
else:
syncml_response = None
subscription.applyActionList(syncml_request, syncml_response)
......@@ -295,10 +295,9 @@ class SyncMLAsynchronousEngine(EngineMixin):
activity="SQLQueue",
priority=ACTIVITY_PRIORITY,
tag=subscription.getRelativeUrl()).sendMessage(xml=str(syncml_response))
else:
# XXX For now always split by one
activate = subscription.getPortalObject().portal_synchronizations.activate
activate = subscription.activate
activate_kw = {
"activity" :"SQLQueue",
"priority" : ACTIVITY_PRIORITY,
......@@ -309,10 +308,9 @@ class SyncMLAsynchronousEngine(EngineMixin):
for action in syncml_request.sync_command_list:
syncml_logger.info("---> launch action in activity %s" %(action,))
activate(**activate_kw).applySyncCommand(
subscription_path=subscription.getRelativeUrl(),
response_message_id=response_id_list.pop(),
activate_kw=activate_kw,
action=action,
request_message_id=syncml_request.header["message_id"],
simulate=False)
# XXX Response is not send here
# Response is sent by the activity
......@@ -46,15 +46,6 @@ class EngineMixin(object):
security = ClassSecurityInfo()
def _generateBaseResponse(self, subscription):
syncml_response = SyncMLResponse()
syncml_response.addHeader(
session_id=subscription.getSessionId(),
message_id=subscription.getNextMessageId(),
target=subscription.getUrlString(),
source=subscription.getSubscriptionUrlString())
syncml_response.addBody()
return syncml_response
security.declarePrivate('_readStatusList')
def _readStatusList(self, syncml_request, domain, syncml_response=None,
......@@ -63,6 +54,7 @@ class EngineMixin(object):
Read status (answer to command) and act according to them
"""
sync_status_counter = 0
path_list = []
for status in syncml_request.status_list:
if status["command"] == "SyncHdr": # Check for authentication
if domain.getSynchronizationState() != "initializing":
......@@ -83,7 +75,7 @@ class EngineMixin(object):
status['authentication_type']))
# XXX Not working To Review !
raise NotImplementedError("Adding credentials")
syncml_response = self._generateBaseResponse(domain)
syncml_response = domain.generateBaseResponse()
syncml_response.addCredentialMessage(domain)
return syncml_response
elif status['status_code'] == \
......@@ -124,7 +116,7 @@ class EngineMixin(object):
'conflict_resolved_with_merge'):
# We will have to apply the update, and we should not care
# about conflicts, so we have to force the update
signature.drift()
signature.noConflict()
signature.setForce(True)
syncml_logger.error("\tObject merged %s" %
(status['source'] or status['target']))
......@@ -134,20 +126,25 @@ class EngineMixin(object):
'conflict_resolved_with_client_command_winning')):
syncml_logger.error("\tObject synchronized %s" %
(status['source'] or status['target'],))
if signature.getValidationState() != "no_conflict":
signature.noConflict()
signature.synchronize()
elif status['status_code'] == resolveSyncmlStatusCode('chunk_accepted'):
syncml_logger.info("Chunk was accepted for %s" % (object_gid,))
else:
raise ValueError("Unknown status code : %r" % (status['status_code'],))
# Index signature now to fill the data column
path_list.append(signature.getPath())
elif status['command'] == 'Delete':
sync_status_counter += 1
object_gid = status['source'] or status['target']
signature = domain.getSignatureFromGid(object_gid)
if status['status_code'] == resolveSyncmlStatusCode('success'):
if signature:
domain.z_delete_data_from_path(path=signature.getPath())
domain._delObject(signature.getId())
else:
raise ValueError("Found no signature to delete")
raise ValueError("Found no signature to delete for gid %s" %(object_gid,))
else:
raise ValueError("Unknown status code : %r" % (status['status_code'],))
syncml_logger.error("\tObject deleted %s" %
......@@ -155,6 +152,8 @@ class EngineMixin(object):
else:
raise ValueError("Unknown status command : %r" % (status['command'],))
if len(path_list):
domain.SQLCatalog_indexSyncMLDocumentList(path_list)
return sync_status_counter
#
......@@ -191,10 +190,8 @@ class EngineMixin(object):
if subscription.getAuthenticationState() != 'logged_in':
# Workflow action
subscription.login()
if subscription.getSyncmlAlertCode() not in ("one_way_from_server",
"refresh_from_server_only"):
# Reset signature only if client send its modification to server
subscription.initialiseSynchronization()
subscription.indexSourceData(client=True)
# Create the package 1
syncml_response = SyncMLResponse()
......@@ -301,7 +298,9 @@ class EngineMixin(object):
'one_way_from_server',
'refresh_from_client_only',
'one_way_from_client'):
# XXX Why re-editing here ?
# Make sure we update configuration based on publication data
# so that manual edition is propagated
# XXX Must check all properties that must be setted
subscriber.setXmlBindingGeneratorMethodId(
publication.getXmlBindingGeneratorMethodId())
subscriber.setConduitModuleId(publication.getConduitModuleId())
......
......@@ -54,7 +54,7 @@ class SyncMLSynchronousEngine(EngineMixin):
# Must check what server tell about database synchronization
# and update the mode if required
syncml_response = self._generateBaseResponse(subscription)
syncml_response = subscription.generateBaseResponse()
# Read & apply status about databases & synchronizations
try:
......@@ -80,16 +80,16 @@ class SyncMLSynchronousEngine(EngineMixin):
"refresh_from_server_only"):
# We only get data from server
finished = True
syncml_response.addFinal()
else:
finished = subscription._getSyncMLData(
syncml_response=syncml_response,
)
finished = subscription._getSyncMLData(syncml_response=syncml_response,
min_gid=None, max_gid=None)
if finished:
# Delete message will contain final tag
subscription.getDeletedSyncMLData(syncml_response=syncml_response)
syncml_logger.info("-> Client sendind modification, finished %s" % (finished,))
if finished:
# Add deleted objets
subscription._getDeletedData(syncml_response=syncml_response)
# Notify that all modifications were sent
syncml_response.addFinal()
# Will then start processing sync commands from server
subscription.processSyncRequest()
......@@ -149,7 +149,8 @@ class SyncMLSynchronousEngine(EngineMixin):
raise ValueError("Authentication failed, impossible to sync data")
# Apply command & send modifications
syncml_response = self._generateBaseResponse(subscriber)
# XXX This can be called on subscription instead
syncml_response = subscriber.generateBaseResponse()
# Apply status about object send & synchronized if any
self._readStatusList(syncml_request, subscriber, syncml_response, True)
......@@ -191,10 +192,8 @@ class SyncMLSynchronousEngine(EngineMixin):
if syncml_request.isFinal:
# Server will now send its modifications
subscriber.sendModifications()
if subscriber.getSyncmlAlertCode() not in ("one_way_from_client",
"refresh_from_client_only"):
# Reset signature only if we have to check modifications on server side
subscriber.initialiseSynchronization()
# Run indexation only once client has sent its modifications
subscriber.indexSourceData()
# Do not continue in elif, as sending modifications is done in the same
# package as sending notifications
......@@ -204,13 +203,16 @@ class SyncMLSynchronousEngine(EngineMixin):
"refresh_from_client_only"):
# We only get data from client
finished = True
syncml_response.addFinal()
else:
finished = subscriber._getSyncMLData(
syncml_response=syncml_response)
finished = subscriber._getSyncMLData(syncml_response=syncml_response,
min_gid=None, max_gid=None)
if finished:
# Delete message will contain final tag
subscriber.getDeletedSyncMLData(syncml_response=syncml_response)
syncml_logger.info("-> Server sendind data, finished %s" % (finished,))
if finished:
subscriber._getDeletedData(syncml_response=syncml_response)
syncml_response.addFinal()
subscriber.waitNotifications()
# Do not go into finished here as we must wait for
# notifications from client
......
......@@ -28,51 +28,14 @@
##############################################################################
# Namespaces.
SYNCML_NAMESPACE = 'SYNCML:SYNCML1.2'
# In SyncML Representation Protocol OMA
# we use URN as format of namespace
# List namespaces supported
URN_LIST = ('SYNCML:SYNCML1.1', 'SYNCML:SYNCML1.2')
SYNCML_NAMESPACE = 'SYNCML:SYNCML1.2'
NSMAP = {'syncml': SYNCML_NAMESPACE}
## SyncML Alert Codes
#TWO_WAY = 200
#SLOW_SYNC = 201 # This means we get the data from the publication
#ONE_WAY_FROM_SERVER = 204
#CODE_LIST = (TWO_WAY, ONE_WAY_FROM_SERVER,)
# SyncML Status Codes
#SUCCESS = 200
#ITEM_ADDED = 201
#WAITING_DATA = 214
#REFRESH_REQUIRED = 508
#CHUNK_OK = 214
#CONFLICT = 409 # A conflict is detected
#CONFLICT_MERGE = 207 # We have merged the two versions, sending
## whatever is needed to change(replace)
#CONFLICT_CLIENT_WIN = 208 # The client is the "winner", we keep
## the version of the client
#UNAUTHORIZED = 401
#AUTH_REQUIRED = 407
#AUTH_ACCEPTED = 212
NULL_ANCHOR = '00000000T000000Z'
# ERP5 Sync Codes for Signatures
SYNCHRONIZED = 1
#SENT = 2
#NOT_SENT = 3
PARTIAL = 4
NOT_SYNCHRONIZED = 5
PUB_CONFLICT_MERGE = 6
PUB_CONFLICT_CLIENT_WIN = 8
#MAX_LINES = 5000
MAX_OBJECTS = 300
MAX_LEN = 1<<16
MAX_DOCUMENT_PER_MESSAGE = 2
XUPDATE_INSERT_LIST = ('xupdate:insert-after', 'xupdate:insert-before')
XUPDATE_ADD = 'xupdate:append'
......@@ -84,20 +47,7 @@ XUPDATE_INSERT_OR_ADD_LIST = XUPDATE_INSERT_LIST + (XUPDATE_ADD,)
ADD_ACTION = 'Add'
REPLACE_ACTION = 'Replace'
##media types :
#MEDIA_TYPE = {}
#MEDIA_TYPE['TEXT_XML'] = 'text/xml'
#MEDIA_TYPE['TEXT_VCARD'] = 'text/vcard'
#MEDIA_TYPE['TEXT_XVCARD'] = 'text/x-vcard'
##content types :
#CONTENT_TYPE = {}
#CONTENT_TYPE['SYNCML_XML'] = 'application/vnd.syncml+xml'
#CONTENT_TYPE['SYNCML_WBXML'] = 'application/vnd.syncml+wbxml'
#Activity priority
ACTIVITY_PRIORITY = 5
class SynchronizationError(Exception):
pass
......@@ -579,9 +579,14 @@ class SyncMLRequest(object):
sync_command_kw["xml_data"] = etree.tostring(xml_data[0])
else:
# If not xml, return raw data
# XXX this is unicode and can be a problem for activity
sync_command_kw["raw_data"] = sync_command.xpath(
'string(.//syncml:Item/syncml:Data)',
# XXX This must be CDATA type
data = sync_command.xpath('string(.//syncml:Item/syncml:Data)',
namespaces=self.data.nsmap)
if isinstance(data, etree.CDATA):
parser = etree.XMLParser(strip_cdata=False)
cdata = etree.XML(data, parser)
data = cdata.text
# XXX this is unicode and can be a problem for activity
sync_command_kw["raw_data"] = data
append(sync_command_kw)
......@@ -26,7 +26,6 @@
##############################################################################
from os import path
from lxml import etree
from logging import getLogger, Formatter
from AccessControl import ClassSecurityInfo
......@@ -36,26 +35,14 @@ from Products.ERP5Type import Permissions
from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5SyncML.SyncMLConstant import ACTIVITY_PRIORITY, \
SynchronizationError
from Products.ERP5SyncML.SyncMLMessage import SyncMLResponse, SyncMLRequest
from Products.ERP5SyncML.SyncMLMessage import SyncMLRequest
from Products.ERP5SyncML.Engine.SynchronousEngine import SyncMLSynchronousEngine
from Products.ERP5SyncML.Engine.AsynchronousEngine import SyncMLAsynchronousEngine
from Products.ERP5SyncML.Transport.HTTP import HTTPTransport
from Products.ERP5SyncML.Transport.File import FileTransport
from Products.ERP5SyncML.Transport.Mail import MailTransport
from Products.ERP5.ERP5Site import getSite
synchronous_engine = SyncMLSynchronousEngine()
asynchronous_engine = SyncMLAsynchronousEngine()
transport_scheme_dict = {
"http" : HTTPTransport(),
"https" : HTTPTransport(),
"file" : FileTransport(),
"mail" : MailTransport(),
}
parser = etree.XMLParser(remove_blank_text=True)
# Logging channel definitions
# Main logging channel
syncml_logger = getLogger('ERP5SyncML')
......@@ -390,7 +377,6 @@ class SynchronizationTool(BaseTool):
return engine.processClientSynchronization(syncml_request, subscription)
# Send the message
# XXX This must depends on activity enables property, maybe use engine
if subscription.getIsActivityEnabled():
subscription.activate(
after_tag="%s_reset" %(subscription.getPath(),),
......@@ -402,79 +388,4 @@ class SynchronizationTool(BaseTool):
return str(syncml_response)
def applySyncCommand(self, subscription_path, response_message_id,
activate_kw, **kw):
"""
This methods is intented to be called by asynchronous engine in activity to
apply sync commands for a subset of data
As engines are not zodb object, the tool acts as a placeholder for method
that need to be called in activities
"""
subscription = self.restrictedTraverse(subscription_path)
assert subscription is not None, "Impossible to find subscription %s" \
% (subscription_path)
# Build Message
if response_message_id:
syncml_response = SyncMLResponse()
syncml_response.addHeader(
session_id=subscription.getSessionId(),
message_id=response_message_id,
target=subscription.getUrlString(),
source=subscription.getSubscriptionUrlString())
syncml_response.addBody()
else:
syncml_response = None
subscription.applySyncCommand(syncml_response=syncml_response, **kw)
# Send the message in activity to prevent recomputing data in case of
# transport failure
if syncml_response:
syncml_logger("---- %s sending %s notifications of sync"
% (subscription.getTitle(),
syncml_response.sync_confirmation_counter))
subscription.activate(activity="SQLQueue",
# group_method_id=None,
# group_method_cost=.05,
tag=activate_kw).sendMessage(xml=str(syncml_response))
def sendSyncCommand(self, id_list, message_id, subscription_path,
activate_kw, is_final_message=False):
"""
This methods is intented to be called by asynchronous engine in activity to
send sync commands for a subset of data
As engines are not zodb object, the tool acts as a placeholder for method
that need to be called in activities
"""
subscription = self.restrictedTraverse(subscription_path)
assert subscription is not None, "Impossible to find subscription %s" \
% (subscription_path)
# Build Message
syncml_response = SyncMLResponse()
syncml_response.addHeader(
session_id=subscription.getSessionId(),
message_id=message_id,
target=subscription.getUrlString(),
source=subscription.getSubscriptionUrlString())
syncml_response.addBody()
subscription._getSyncMLData(
syncml_response=syncml_response,
id_list=id_list,
)
if is_final_message:
# Notify that all modifications were sent
syncml_response.addFinal()
# Send the message in activity to prevent recomputing data in case of
# transport failure
# activate_kw["group_method_id"] = None
# activate_kw["group_method_cost"] = .05
subscription.activate(**activate_kw).sendMessage(xml=str(syncml_response))
InitializeClass(SynchronizationTool)
......@@ -118,6 +118,8 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
def afterSetUp(self):
"""Setup."""
self.login()
self.portal.z_drop_syncml()
self.portal.z_create_syncml()
self.addPublications()
self.addSubscriptions()
self.portal = self.getPortal()
......@@ -316,12 +318,12 @@ class TestERP5DocumentSyncMLMixin(TestERP5SyncMLMixin):
for document in document_server.objectValues():
state_list = self.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], 'synchronized')
self.assertEqual(state[1], 'no_conflict')
document_client1 = self.getDocumentClient1()
for document in document_client1.objectValues():
state_list = self.getSynchronizationState(document)
for state in state_list:
self.assertEqual(state[1], 'synchronized')
self.assertEqual(state[1], 'no_conflict')
# Check for each signature that the tempXML is None
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
for m in sub.contentValues():
......@@ -418,13 +420,7 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
def getTitle(self):
return "ERP5 Document SyncML"
def setupPublicationAndSubscriptionIdGenerator(self):
portal_sync = self.getSynchronizationTool()
sub1 = portal_sync[self.sub_id1]
pub = portal_sync[self.pub_id]
def checkSynchronizationStateIsConflict(self, portal_type='Text'):
portal_sync = self.getSynchronizationTool()
document_server = self.getDocumentServer()
for document in document_server.objectValues():
if document.getId()==self.id1:
......@@ -641,7 +637,6 @@ class TestERP5DocumentSyncML(TestERP5DocumentSyncMLMixin):
recognize objects (because by default, getGid==getId. Here, we will see
if it also works with a somewhat strange getGid
"""
self.setupPublicationAndSubscriptionIdGenerator()
nb_document = self.createDocumentServerList()
# This will test adding object
self.synchronize(self.sub_id1)
......
......@@ -98,6 +98,8 @@ class TestERP5SyncMLMixin(TestMixin):
def afterSetUp(self):
"""Setup."""
self.login()
self.portal.z_drop_syncml()
self.portal.z_create_syncml()
# This test creates Person inside Person, so we modifiy type information to
# allow anything inside Person (we'll cleanup on teardown)
self.getTypesTool().getTypeInfo('Person').filter_content_types = 0
......@@ -228,6 +230,7 @@ class TestERP5SyncMLMixin(TestMixin):
result = portal_sync.processClientSynchronization(subscription.getPath())
self.tic()
nb_message += 1
self.tic()
return nb_message
def synchronizeWithBrokenMessage(self, id):
......@@ -329,33 +332,33 @@ class TestERP5SyncMLMixin(TestMixin):
for person in person_server.objectValues():
state_list = self.getSynchronizationState(person)
for state in state_list:
self.assertEquals(state[1], 'synchronized')
self.assertEquals(state[1], 'no_conflict')
person_client1 = self.getPersonClient1()
for person in person_client1.objectValues():
state_list = self.getSynchronizationState(person)
for state in state_list:
self.assertEquals(state[1], 'synchronized')
self.assertEquals(state[1], 'no_conflict')
person_client2 = self.getPersonClient2()
for person in person_client2.objectValues():
state_list = self.getSynchronizationState(person)
for state in state_list:
self.assertEquals(state[1], 'synchronized')
self.assertEquals(state[1], 'no_conflict')
# Check for each signature that the tempXML is None
for sub in portal_sync.contentValues(portal_type='SyncML Subscription'):
for m in sub.contentValues():
self.assertEquals(m.getTemporaryData(), None)
self.assertEquals(m.getPartialData(), None)
self.assertEquals(m.getValidationState(), "synchronized")
self.assertEquals(m.getValidationState(), "no_conflict")
for pub in portal_sync.contentValues(portal_type='SyncML Publication'):
for sub in pub.contentValues(portal_type='SyncML Subscription'):
for m in sub.contentValues():
self.assertEquals(m.getPartialData(), None)
self.assertEquals(m.getValidationState(), "synchronized")
self.assertEquals(m.getValidationState(), "no_conflict")
def verifyFirstNameAndLastNameAreNotSynchronized(self, first_name,
last_name, person_server, person_client):
"""
verify that the first and last name are NOT synchronized
verify that the first and last name are NOT no_conflict
"""
self.assertNotEqual(person_server.getFirstName(), first_name)
self.assertNotEqual(person_server.getLastName(), last_name)
......@@ -481,7 +484,6 @@ class TestERP5SyncML(TestERP5SyncMLMixin):
pub.setConduitModuleId('ERP5ConduitTitleGid')
def checkSynchronizationStateIsConflict(self):
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
for person in person_server.objectValues():
if person.getId()==self.id1:
......@@ -751,7 +753,6 @@ return [context[%r]]
# We will try to get the state of objects
# that are just synchronized
self.test_08_FirstSynchronization()
portal_sync = self.getSynchronizationTool()
person_server = self.getPersonServer()
person1_s = person_server._getOb(self.id1)
state_list_s = self.getSynchronizationState(person1_s)
......@@ -782,6 +783,8 @@ return [context[%r]]
kw = {'first_name':self.first_name1,'last_name':self.last_name1}
person1_c.edit(**kw)
#person1_c.setModificationDate(DateTime()+1)
# import ipdb
# ipdb.set_trace()
self.synchronize(self.sub_id1)
self.checkSynchronizationStateIsSynchronized()
person1_s = person_server._getOb(self.id1)
......@@ -1543,7 +1546,7 @@ return [context[%r]]
self.assertEquals(client_person.getLastName(), self.last_name1)
# reset for refresh sync
# after synchronize, the client object retrieve value of server
# after synchronization, the client retrieves value from server
self.resetSignaturePublicationAndSubscription()
self.synchronize(self.sub_id1)
......@@ -1596,7 +1599,7 @@ return [context[%r]]
publication = self.addPublication()
self.addRefreshFormClientOnlySubscription()
nb_person = self.populatePersonClient1()
self.populatePersonClient1()
portal_sync = self.getSynchronizationTool()
subscription1 = portal_sync[self.sub_id1]
self.assertEquals(subscription1.getSyncmlAlertCode(),
......
......@@ -28,9 +28,7 @@
#
##############################################################################
from testERP5SyncML import TestERP5SyncMLMixin
from zLOG import LOG
class TestERP5SyncMLVCard(TestERP5SyncMLMixin):
......@@ -45,6 +43,10 @@ class TestERP5SyncMLVCard(TestERP5SyncMLMixin):
"""
return ('erp5_base', 'erp5_syncml',)
def afterSetUp(self):
self.portal.z_drop_syncml()
self.portal.z_create_syncml()
def getTitle(self):
return 'testERP5SyncMLVCard'
......
......@@ -69,15 +69,15 @@ class ERP5NodeConduit(TioSafeBaseConduit):
we can filter person based on the plugin they came from
"""
site = self.getIntegrationSite(kw['domain'])
default_stc = site.getSourceTrade()
# try to find the corresponding STC
stc_list = object.getPortalObject().sale_trade_condition_module.searchFolder(title="%s %s" %(site.getReference(), object.getTitle()),
validation_state="validated"
)
stc_list = object.getPortalObject().sale_trade_condition_module.searchFolder(
title="%s %s" %(site.getReference(), object.getTitle()),
validation_state="validated")
if len(stc_list) == 0:
self._createSaleTradeCondition(object, **kw)
elif len(stc_list) > 1:
raise ValueError, "Multiple trade condition (%s) retrieve for %s" %([x.path for x in stc_list], object.getTitle())
raise ValueError, "Multiple trade condition (%s) retrieved for %s" \
% ([x.path for x in stc_list], object.getTitle())
else:
stc = stc_list[0].getObject()
stc.edit(
......@@ -519,7 +519,10 @@ class ERP5NodeConduit(TioSafeBaseConduit):
elif tag == "email":
current_value = str(document.getDefaultEmailText(""))
else:
try:
current_value = getattr(document, tag)
except AttributeError:
current_value = None
if current_value:
current_value = current_value.encode('utf-8')
......
......@@ -33,6 +33,7 @@ from Products.ERP5Type.Core.Folder import Folder
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
from zLOG import LOG, INFO, ERROR, WARNING
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
class IntegrationSite(Folder):
......@@ -187,7 +188,12 @@ class IntegrationSite(Folder):
base_mapping = the base property mapping
property = string of the property we want the mapping
"""
mapping_line = base_mapping.searchFolder(portal_type='Integration Property Mapping',
tv = getTransactionalVariable()
key = "%s-%s" % (base_mapping.getPath(), property_name)
try:
mapping_line = tv[key]
except KeyError:
tv[key] = mapping_line = base_mapping.searchFolder(portal_type='Integration Property Mapping',
path = "%s%%" %(base_mapping.getPath()),
destination_reference=property_name,
)
......
......@@ -854,14 +854,12 @@ def setDefaultClassProperties(property_holder):
)
}
from Globals import Persistent, PersistentMapping
def importLocalDocument(class_id, path=None, class_path=None):
"""Imports a document class and registers it in ERP5Type Document
repository ( Products.ERP5Type.Document )
"""
import Products.ERP5Type.Document
import Permissions
if class_path:
assert path is 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