Commit 6ef81933 authored by Ayush Tiwari's avatar Ayush Tiwari

ERP5 Diff Tool

ERP5 Diff Tool is used to get beautified diff between any 2 ERP5 objects as well as different revisions of a single ERP5 object **(both in XHTML as well as renderJS UI)**

This would be helpful in:
--------------------------------
- ZODB History Tab to check the edit workflow history.
- Revision of the ERP5 object, for example: web pages, documents, etc
- Business Template installation

Diff Tool will also make it able to patch one ERP5 object from the diff *(for version 1, we only patch objects which are of same portal type)*. The current version of Diff Tool uses `deepdiff` python library which provides us with patch which can complies(not completely) with CRDT datatype, thanks to which patch application can be less painful.

Diff Tool is able to differentiate between the diff according to type of user, for example, we do not want a normal user to be looking at diff of some internal properties of object(for ex: workflows), but for a developer user, we are fine with it.

How we calculate the Diff of any two objects:
-----------------------------------------------------------
1. Convert the objects to their JSON format.
2. Use `deepdiff` to find out the diff for the 2 JSON objects.
3. Return a beautified JSON Diff

### NOTE:  This MR succeeds the MR related to create correct URL for History tabs: !695 !683

TODO:
--------
- [x] Add tests for beautified diff and patch.
- [x] Diff tool should be working in new UI.
- [x] Fix the pagination.

Example Diff Tool in both UIs:
--------------------------------------
![XHTML UI Diff](/uploads/846b6b7806939a64922e364d309c028c/Screenshot_2018-07-04_at_6.07.46_PM.png)

![renderJS UI Diff](/uploads/ae7021fbf9cf9f952a7d2d4044df8a78/Screenshot_2018-07-05_at_10.34.01_AM.png)

/reviewed-on nexedi/erp5!686
parents f3519857 a8eb8620
No related merge requests found
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<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_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>testDiffActionOnModule</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test Generic Search Dialog</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">Test Portal Diff<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/expected_failure_for_anonymous_selection" /></td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/PTZuite_CommonTemplate/macros/init" />
<tr>
<td>open</td>
<td tal:content="string:${here/portal_url}/bar_module/FooModule_createObjects?start:int=1&num:int=1"/>
<td></td>
</tr>
<tr>
<td>assertTextPresent</td>
<td>Created Successfully</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td tal:content="string:${here/portal_url}/bar_module/FooModule_createObjects?start:int=2&num:int=1"/>
<td></td>
</tr>
<tr>
<td>assertTextPresent</td>
<td>Created Successfully</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>${base_url}/bar_module/Zuite_waitForActivities</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>${base_url}/bar_module/view</td>
<td></td>
</tr>
<tr>
<td>clickAndWait</td>
<td>Folder_show:method</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//*[@class="listbox-data-line-0 DataA"]//input[@type="checkbox"]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//*[@class="listbox-data-line-1 DataB"]//input[@type="checkbox"]</td>
<td></td>
</tr>
<!-- Click on the action to check the diff between the 2 objects -->
<tr>
<td>selectAndWait</td>
<td>select_action</td>
<td>Diff Module Objects</td>
</tr>
<!-- Check the diff content -->
<tr>
<td>verifyValue</td>
<td>field_your_first_path</td>
<td>bar_module/2</td>
</tr>
<tr>
<td>verifyValue</td>
<td>field_your_second_path</td>
<td>bar_module/1</td>
</tr>
<tr>
<td>assertText</td>
<td>//a[@class="listbox_title"]</td>
<td>exact:Diff</td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//tr[@class='listbox-data-line-2 DataA']</td>
<td></td>
</tr>
<tr>
<td>assertText</td>
<td>//tr[@class='listbox-data-line-2 DataA']/td[2]</td>
<td>title</td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//tr[@class='listbox-data-line-2 DataA']/td[3]/div[@data-gadget-sandbox="public"]</td>
<td></td>
</tr>
<tr>
<td>storeEval</td>
<td>document.querySelector('#selenium_myiframe').contentWindow.document.evaluate("//tr[@class='listbox-data-line-2 DataA']/td[3]/div[@data-gadget-sandbox='public']", document.querySelector('#selenium_myiframe').contentWindow.document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getAttribute('data-gadget-value');</td>
<td>_data_gadget_value</td>
</tr>
<tr>
<td>verifyEval</td>
<td>storedVars['_data_gadget_value'].replace(/(\r\n\t|\n|\r\t)/gm,"").replace(/\s+/g, '')</td>
<td>---+++@@-1+1@@-Title2+Title1</td>
</tr>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<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_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>testDiffActionOnObject</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test Generic Search Dialog</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">Test Portal Diff<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/expected_failure_for_anonymous_selection" /></td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/PTZuite_CommonTemplate/macros/init" />
<tr>
<td>open</td>
<td tal:content="string:${here/portal_url}/bar_module/FooModule_createObjects?start:int=1&num:int=1"/>
<td></td>
</tr>
<tr>
<td>assertTextPresent</td>
<td>Created Successfully</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td tal:content="string: ${here/portal_url}/bar_module/1/view">/erp5/foo_module/1/view</td>
<td></td>
</tr>
<!-- Update the title of the object and save -->
<tr>
<td>type</td>
<td>name=field_my_title</td>
<td>Title 2</td>
</tr>
<tr>
<td>clickAndWait</td>
<td>//button[@title='Save']</td>
<td></td>
</tr>
<tr>
<td>verifyPortalStatusMessage</td>
<td>Data updated.</td>
<td></td>
</tr>
<tr>
<td>verifyValue</td>
<td>field_my_title</td>
<td>Title 2</td>
</tr>
<!-- Click on the action to check the diff between the current and last version -->
<tr>
<td>selectAndWait</td>
<td>select_action</td>
<td>Diff Object</td>
</tr>
<!-- Check the diff content -->
<tr>
<td>verifyValue</td>
<td>field_your_first_path</td>
<td>bar_module/1</td>
</tr>
<tr>
<td>verifyValue</td>
<td>field_your_second_path</td>
<td>bar_module/1</td>
</tr>
<tr>
<td>assertText</td>
<td>//a[@class="listbox_title"]</td>
<td>exact:Diff</td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//tr[@class='listbox-data-line-4 DataA']</td>
<td></td>
</tr>
<tr>
<td>assertText</td>
<td>//tr[@class='listbox-data-line-4 DataA']/td[2]</td>
<td>title</td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//tr[@class='listbox-data-line-4 DataA']/td[3]/div[@data-gadget-sandbox="public"]</td>
<td></td>
</tr>
<tr>
<td>storeEval</td>
<td>document.querySelector('#selenium_myiframe').contentWindow.document.evaluate("//tr[@class='listbox-data-line-4 DataA']/td[3]/div[@data-gadget-sandbox='public']", document.querySelector('#selenium_myiframe').contentWindow.document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getAttribute('data-gadget-value');</td>
<td>_data_gadget_value</td>
</tr>
<tr>
<td>verifyEval</td>
<td>storedVars['_data_gadget_value'].replace(/(\r\n\t|\n|\r\t)/gm,"").replace(/\s+/g, '')</td>
<td>---+++@@-1+1@@-Title1+Title2</td>
</tr>
</body>
</html>
\ No newline at end of file
...@@ -16,13 +16,13 @@ ...@@ -16,13 +16,13 @@
<key> <string>categories</string> </key> <key> <string>categories</string> </key>
<value> <value>
<tuple> <tuple>
<string>action_type/object_view</string> <string>action_type/object_action</string>
</tuple> </tuple>
</value> </value>
</item> </item>
<item> <item>
<key> <string>category</string> </key> <key> <string>category</string> </key>
<value> <string>object_view</string> </value> <value> <string>object_action</string> </value>
</item> </item>
<item> <item>
<key> <string>condition</string> </key> <key> <string>condition</string> </key>
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>View</string> </value> <value> <string>View Diff</string> </value>
</item> </item>
<item> <item>
<key> <string>visible</string> </key> <key> <string>visible</string> </key>
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>text</string> </key> <key> <string>text</string> </key>
<value> <string>string:${object_url}/DiffTool_view</string> </value> <value> <string>string:${object_url}/ERP5Site_viewDiffTwoObjectDialog</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>diff_multiple_object_action</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>106.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Diff Module Objects</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: object.absolute_url() + \'/ERP5Site_viewDiffTwoObjectDialog\'</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: object.getPortalType() in portal.getPortalModuleTypeList()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>diff_object_action</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>105.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Diff Object</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string encoding="cdata"><![CDATA[
python: object.absolute_url() + \'/ERP5Site_viewDiffTwoObjectDialog?your_first_path=\' + str(object.getRelativeUrl())+\'&your_second_path=\' + str(object.getRelativeUrl())
]]></string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: object.getPortalType() not in portal.getPortalModuleTypeList()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
############################################################################## ##############################################################################
# #
# Copyright (c) 2014 Nexedi KK and Contributors. All Rights Reserved. # Copyright (c) 2018 Nexedi KK and Contributors. All Rights Reserved.
# Yusei Tahara <yusei@nexedi.com> # Yusei Tahara <yusei@nexedi.com>
# Tatuya Kamada <tatuya@nexedi.com> # Tatuya Kamada <tatuya@nexedi.com>
# Ayush Tiwari <ayush.tiwari@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential # programmers who take the whole responsability of assessing all potential
...@@ -138,3 +139,27 @@ def getChangeHistoryList(document, size=50, attribute_name=None): ...@@ -138,3 +139,27 @@ def getChangeHistoryList(document, size=50, attribute_name=None):
history.sort(key=lambda x:x['datetime']) history.sort(key=lambda x:x['datetime'])
return history return history
def getHistoricalRevisionsDateList(document, size=50):
"""
Returns the list dates in float format for the last
50 versions of the document.
"""
result = document._p_jar.db().history(document._p_oid, size)
return [d_['time'] for d_ in result]
def getRevisionFromDate(document, date, size=50):
"""
Returns the attribute/property dict of an object from its revision date
params:
date - It shold be in float format (or float converted to string)
size -- How long history do you need
"""
result = document._p_jar.db().history(document._p_oid, size)
# Get the version of object using the date
result_obj = [l for l in result if str(l['time']) == str(date)][0]
# Return the history state of the object
connection = document._p_jar
return connection.oldstate(document, result_obj['tid'])
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
<list> <list>
<string>columns</string> <string>columns</string>
<string>count_method</string> <string>count_method</string>
<string>lines</string>
<string>list_method</string> <string>list_method</string>
<string>selection_name</string> <string>selection_name</string>
<string>title</string> <string>title</string>
...@@ -95,6 +96,10 @@ ...@@ -95,6 +96,10 @@
<key> <string>form_id</string> </key> <key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value> <value> <string>Base_viewFieldLibrary</string> </value>
</item> </item>
<item>
<key> <string>lines</string> </key>
<value> <int>0</int> </value>
</item>
<item> <item>
<key> <string>list_method</string> </key> <key> <string>list_method</string> </key>
<value> <value>
......
"""
This script returns the default value for second_date field.
"""
portal = context.getPortalObject()
request = context.REQUEST
history_size = portal.portal_preferences.getPreferredHtmlStyleZodbHistorySize()
first_path = request.get('your_first_path', None)
second_path = request.get('your_second_path', None)
second_date = request.get('your_second_date', None)
if second_date:
return second_date
# In case both the paths are same, return the 2nd item from the date list.
# This is because its the case where we are trying to diff current and last
# revision of the same object.
elif first_path not in (None, '') and first_path == second_path:
obj = portal.restrictedTraverse(first_path)
date_list = obj.Base_getRevisionDateList(obj, size=history_size)
return str(date_list[1])
return second_date
<?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>_params</string> </key>
<value> <string>**kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getDefaultRevisionDate</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
"""
Script that returns the list of diff of 2 historical states of ERP5 object(s).
This should is used as a 'list method' as it returns a list of temp base object
which have its properties as : `path` and `diff`.
"""
from Products.ERP5Type.Document import newTempBase
portal = context.getPortalObject()
portal_diff = portal.portal_diff
request = context.REQUEST
object_revision_list = []
history_size = portal.portal_preferences.getPreferredHtmlStyleZodbHistorySize()
# In case the request has been made via action on any ERP5 object, we get the
# paths as `your_<field_name>` as this is what use as field in dialog. Hence,
# its better to check for path in the request also.
if first_path is None:
first_path = request.get('your_first_path', None)
if second_path is None:
second_path = request.get('your_second_path', None)
# Case I: When we try to access the dialog directly from `portal_diff` or from
# selections in a module. If the paths are None, then return an empty list
# or try to get the paths from selection and then return the diff.
if first_path is None and second_path is None:
list_selection_name = request.get('list_selection_name', None)
# In case the list_selection_name is there, it can be the case of selection
# from the module, hence we get the paths from the selection and use them to
# create diff.
if list_selection_name is not None:
selected_obj_list = portal.portal_selections.getSelectionCheckedValueList(selection_name=list_selection_name)
object_revision_list.extend(selected_obj_list)
else:
# Return an empty list here. This would be the case when we first access
# the dialog and then it try to get list of items to dipslay on the listbox
return []
# Case II: Get object revision from the date of the revisions.
# The default dates for revision are the 1st and 2nd
# revision dates.
if first_date not in (None, ''):
first_obj = portal.restrictedTraverse(first_path)
old_state_revision = context.Base_getRevisionFromDate(first_obj, first_date)
object_revision_list.append(old_state_revision)
if second_date not in (None, ''):
second_obj = portal.restrictedTraverse(second_path)
new_state_revision = context.Base_getRevisionFromDate(second_obj, second_date)
object_revision_list.append(new_state_revision)
# Case III: When the paths exist, but there is no revision given
# for the paths, we diff the current revision
if first_path != second_path:
# Diff the current verison if both the paths are different
if first_path is not None and first_date in (None, ''):
object_revision_list.append(portal.restrictedTraverse(first_path))
if second_path is not None and second_date in (None, ''):
object_revision_list.append(portal.restrictedTraverse(second_path))
else:
# If both the paths are same, diff between the current version
# and the last version(if it exists)
# Get the historical revisions for the object and check if there
# is more than one revision
obj = portal.restrictedTraverse(first_path)
revision_date_list = obj.Base_getRevisionDateList(obj, size=history_size)
if len(revision_date_list) > 1:
object_revision_list.append(obj.Base_getRevisionFromDate(obj, revision_date_list[1]))
object_revision_list.append(obj.Base_getRevisionFromDate(obj, revision_date_list[0]))
# Use DiffTool to get the diff between the 2 objects in object_revision_list List.
# These 2 objects can be revisions of same object, 2 different revisions of
# different objects or 2 current ERP5 object.
if len(object_revision_list) > 1:
# Using this last 2 Historical revision dicts, create a Diff
diff_list = portal_diff.diffPortalObject(object_revision_list[0], object_revision_list[1]).asBeautifiedJSONDiff()
# Return a list of TempBase objects which can be displayed in a listbox
uid = 1
tempbase_list = []
for diff_unit in diff_list:
temp_obj = newTempBase(portal,
diff_unit['path'],
**diff_unit)
temp_obj.setUid(uid)
uid = uid + 1
tempbase_list.append(temp_obj)
return tempbase_list
return []
<?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>_params</string> </key>
<value> <string>first_date=None, second_date=None, first_path=None, second_path=None, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getObjectDiffList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
portal = context.getPortalObject()
# There would be multiple cases where this script will be called, but the only
# case where we use selections to get the diff. Hence, we check for the condition
# that list_selection_name is not None.
if list_selection_name is not None:
# Get the selcted values for the web page selection
selected_obj_list = portal.portal_selections.getSelectionCheckedValueList(selection_name=list_selection_name)
# Return the 1st object path from the selected_obj_list
return selected_obj_list[selection_index].getRelativeUrl()
<?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>_params</string> </key>
<value> <string>selection_index, list_selection_name=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getObjectPathFromSelection</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
"""
Script to get the list of dates for Historical Revisions for
any object inside ERP5. It is used in the 'items' field for Portal Diff
view for the list field regarding the Dates.
Params:
field_name - Name of the the field which gives us the path
selection_index - Index of the object we want to get from the selection list
in case we don't have any path from field
"""
from DateTime import DateTime
portal = context.getPortalObject()
request = context.REQUEST
date_list = []
history_size = portal.portal_preferences.getPreferredHtmlStyleZodbHistorySize()
# This will be the case when we try to refresh/update the diff, in
# that case we will be getting the second_object_path from the request.
# When we refresh or update the diff, we try to access the value for this field
# in the list method before validation, hence we need it to get it via using
# 'field_<field_name>'
# XXX: Not a good way to get the field value before validation.
path = request.form.get('field_your_%s' % field_name, None)
# If the path is still empty, we try to get it from request
if path is None:
path = request.get('your_%s' % field_name, None)
if path :
# When both paths are same, in case the action call is made
# from an ERP5 object
obj = portal.restrictedTraverse(path)
date_list = obj.Base_getRevisionDateList(obj, size=history_size)
# If there is no path from the field value, then we check for selections
else:
# In case we are tryng to get the object path from selection,
# for example when we try to diff 2 objects from one module
list_selection_name = request.get('list_selection_name')
# Get the selected values for the web page selection
if list_selection_name:
selected_obj_list = portal.portal_selections.getSelectionCheckedValueList(selection_name=list_selection_name)
obj = selected_obj_list[selection_index]
date_list = obj.Base_getRevisionDateList(obj, size=history_size)
return [(str(DateTime(date).strftime("%Y-%m-%d %H:%M")), str(date)) for date in date_list]
<?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>_params</string> </key>
<value> <string>field_name, selection_index, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getRevisionDateItemList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>getHistoricalRevisionsDateList</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>ZODBHistory</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getRevisionDateList</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="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>getRevisionFromDate</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>ZODBHistory</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getRevisionFromDate</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
</item> </item>
<item> <item>
<key> <string>action</string> </key> <key> <string>action</string> </key>
<value> <string></string> </value> <value> <string>ERP5Site_viewDiffTwoObjectDialog</string> </value>
</item> </item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
...@@ -53,7 +53,9 @@ ...@@ -53,7 +53,9 @@
<item> <item>
<key> <string>bottom</string> </key> <key> <string>bottom</string> </key>
<value> <value>
<list/> <list>
<string>listbox</string>
</list>
</value> </value>
</item> </item>
<item> <item>
...@@ -65,21 +67,27 @@ ...@@ -65,21 +67,27 @@
<item> <item>
<key> <string>hidden</string> </key> <key> <string>hidden</string> </key>
<value> <value>
<list/> <list>
<string>listbox_diff</string>
</list>
</value> </value>
</item> </item>
<item> <item>
<key> <string>left</string> </key> <key> <string>left</string> </key>
<value> <value>
<list> <list>
<string>my_title</string> <string>your_first_path</string>
<string>your_first_date</string>
</list> </list>
</value> </value>
</item> </item>
<item> <item>
<key> <string>right</string> </key> <key> <string>right</string> </key>
<value> <value>
<list/> <list>
<string>your_second_path</string>
<string>your_second_date</string>
</list>
</value> </value>
</item> </item>
</dictionary> </dictionary>
...@@ -87,7 +95,7 @@ ...@@ -87,7 +95,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>DiffTool_view</string> </value> <value> <string>ERP5Site_viewDiffTwoObjectDialog</string> </value>
</item> </item>
<item> <item>
<key> <string>method</string> </key> <key> <string>method</string> </key>
...@@ -95,11 +103,11 @@ ...@@ -95,11 +103,11 @@
</item> </item>
<item> <item>
<key> <string>name</string> </key> <key> <string>name</string> </key>
<value> <string>DiffTool_view</string> </value> <value> <string>ERP5Site_viewDiffTwoObjectDialog</string> </value>
</item> </item>
<item> <item>
<key> <string>pt</string> </key> <key> <string>pt</string> </key>
<value> <string>form_view</string> </value> <value> <string>form_dialog</string> </value>
</item> </item>
<item> <item>
<key> <string>row_length</string> </key> <key> <string>row_length</string> </key>
...@@ -111,7 +119,7 @@ ...@@ -111,7 +119,7 @@
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>Diff Tool</string> </value> <value> <string>View Diff</string> </value>
</item> </item>
<item> <item>
<key> <string>unicode_mode</string> </key> <key> <string>unicode_mode</string> </key>
......
<?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>columns</string>
<string>count_method</string>
<string>lines</string>
<string>list_method</string>
<string>selection_name</string>
<string>title</string>
<string>url_columns</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox</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>
</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>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>columns</string> </key>
<value>
<list>
<tuple>
<string>path</string>
<string>Property Name</string>
</tuple>
<tuple>
<string>diff</string>
<string>Diff Viewer</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>count_method</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_list_mode_listbox</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>lines</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>list_method</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>selection_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Diff</string> </value>
</item>
<item>
<key> <string>url_columns</string> </key>
<value>
<list>
<tuple>
<string>path</string>
<string></string>
</tuple>
<tuple>
<string>diff</string>
<string></string>
</tuple>
</list>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Method" module="Products.Formulator.MethodField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>method_name</string> </key>
<value> <string>Base_getObjectDiffList</string> </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>css_class</string>
<string>default</string>
<string>gadget_url</string>
<string>js_sandbox</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_diff</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>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<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>gadget_url</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>js_sandbox</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>css_class</string> </key>
<value> <string>listbox-gadget</string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_gadget_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>gadget_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>js_sandbox</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Diff Viewer</string> </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: cell.diff</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: field.restrictedTraverse(\'gadget_erp5_side_by_side_diff.html\').absolute_url()</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: context.Base_getDiffGadgetSandbox()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -10,14 +10,14 @@ ...@@ -10,14 +10,14 @@
<key> <string>delegated_list</string> </key> <key> <string>delegated_list</string> </key>
<value> <value>
<list> <list>
<string>editable</string> <string>items</string>
<string>title</string> <string>title</string>
</list> </list>
</value> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>my_title</string> </value> <value> <string>your_first_date</string> </value>
</item> </item>
<item> <item>
<key> <string>message_values</string> </key> <key> <string>message_values</string> </key>
...@@ -57,6 +57,16 @@ ...@@ -57,6 +57,16 @@
<key> <string>form_id</string> </key> <key> <string>form_id</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item>
<key> <string>items</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary> </dictionary>
</value> </value>
</item> </item>
...@@ -64,21 +74,23 @@ ...@@ -64,21 +74,23 @@
<key> <string>values</string> </key> <key> <string>values</string> </key>
<value> <value>
<dictionary> <dictionary>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item> <item>
<key> <string>field_id</string> </key> <key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value> <value> <string>my_list_field</string> </value>
</item> </item>
<item> <item>
<key> <string>form_id</string> </key> <key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value> <value> <string>Base_viewFieldLibrary</string> </value>
</item> </item>
<item>
<key> <string>items</string> </key>
<value>
<list/>
</value>
</item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>Title</string> </value> <value> <string>First Document Revision</string> </value>
</item> </item>
</dictionary> </dictionary>
</value> </value>
...@@ -86,4 +98,17 @@ ...@@ -86,4 +98,17 @@
</dictionary> </dictionary>
</pickle> </pickle>
</record> </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: context.Base_getRevisionDateItemList(\'first_path\', 0)</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData> </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>default</string>
<string>display_width</string>
<string>required</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>your_first_path</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>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<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>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Path of First Document</string> </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: context.Base_getObjectPathFromSelection(0, list_selection_name=REQUEST.get(\'list_selection_name\', None))</string> </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>default</string>
<string>items</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>your_second_date</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>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<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>items</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_list_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>items</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Second Document Revision</string> </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: context.Base_getDefaultRevisionDate()</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: context.Base_getRevisionDateItemList(\'second_path\', 1)</string> </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>default</string>
<string>display_width</string>
<string>required</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>your_second_path</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>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<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>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Path of Second Document</string> </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: context.Base_getObjectPathFromSelection(1, list_selection_name=REQUEST.get(\'list_selection_name\', None))</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -236,7 +236,8 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional): ...@@ -236,7 +236,8 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
'title': 'Configure Alarms'}, 'title': 'Configure Alarms'},
{'title': 'Undo', 'id': 'undo'}], {'title': 'Undo', 'id': 'undo'}],
'object': [], 'object': [],
'object_action': [{'id': 'post_query', 'title': 'Post a Query'}], 'object_action': [{'id': 'post_query', 'title': 'Post a Query'},
{'id': 'diff_object_action', 'title': 'Diff Object'}],
'object_hidden': [{'id': 'view_historical_comparison', 'object_hidden': [{'id': 'view_historical_comparison',
'title': 'View Historical Comparison'}], 'title': 'View Historical Comparison'}],
'object_jump': [{'id': 'jump_related_object', 'title': 'Related Objects'}, 'object_jump': [{'id': 'jump_related_object', 'title': 'Related Objects'},
...@@ -260,6 +261,7 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional): ...@@ -260,6 +261,7 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
expected = {'folder': [], expected = {'folder': [],
'global': [], 'global': [],
'object': [], 'object': [],
'object_action': [{'id': 'diff_object_action', 'title': 'Diff Object'}],
'object_hidden': [{'id': 'view_historical_comparison', 'object_hidden': [{'id': 'view_historical_comparison',
'title': 'View Historical Comparison'}], 'title': 'View Historical Comparison'}],
'object_jump': [{'id': 'jump_related_object', 'title': 'Related Objects'}], 'object_jump': [{'id': 'jump_related_object', 'title': 'Related Objects'}],
...@@ -278,6 +280,7 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional): ...@@ -278,6 +280,7 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
expected = {'folder': [], expected = {'folder': [],
'global': [], 'global': [],
'object': [], 'object': [],
'object_action': [{'id': 'diff_object_action', 'title': 'Diff Object'}],
'object_hidden': [{'id': 'view_historical_comparison', 'object_hidden': [{'id': 'view_historical_comparison',
'title': 'View Historical Comparison'}], 'title': 'View Historical Comparison'}],
'object_jump': [{'id': 'jump_related_object', 'title': 'Related Objects'}], 'object_jump': [{'id': 'jump_related_object', 'title': 'Related Objects'}],
......
...@@ -50,7 +50,8 @@ class TestXHTMLMixin(ERP5TypeTestCase): ...@@ -50,7 +50,8 @@ class TestXHTMLMixin(ERP5TypeTestCase):
# some forms have intentionally empty listbox selections like RSS generators # some forms have intentionally empty listbox selections like RSS generators
FORM_LISTBOX_EMPTY_SELECTION_PATH_LIST = ['erp5_web_widget_library/WebSection_viewContentListAsRSS', FORM_LISTBOX_EMPTY_SELECTION_PATH_LIST = ['erp5_web_widget_library/WebSection_viewContentListAsRSS',
'erp5_core/Base_viewHistoricalComparison',] 'erp5_core/Base_viewHistoricalComparison',
'erp5_diff/ERP5Site_viewDiffTwoObjectDialog',]
JSL_IGNORE_FILE_LIST = ( JSL_IGNORE_FILE_LIST = (
'diff2html.js', 'diff2html.js',
'diff2html-ui.js', 'diff2html-ui.js',
......
...@@ -86,6 +86,7 @@ from Products.ERP5Type.Accessor.TypeDefinition import asDate ...@@ -86,6 +86,7 @@ from Products.ERP5Type.Accessor.TypeDefinition import asDate
from Products.ERP5Type.Message import Message from Products.ERP5Type.Message import Message
from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod, super_user from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod, super_user
from Products.ERP5Type.mixin.json_representable import JSONRepresentableMixin
from zope.interface import classImplementsOnly, implementedBy from zope.interface import classImplementsOnly, implementedBy
...@@ -675,7 +676,8 @@ class Base( CopyContainer, ...@@ -675,7 +676,8 @@ class Base( CopyContainer,
ActiveObject, ActiveObject,
OFS.History.Historical, OFS.History.Historical,
ERP5PropertyManager, ERP5PropertyManager,
PropertyTranslatableBuiltInDictMixIn PropertyTranslatableBuiltInDictMixIn,
JSONRepresentableMixin,
): ):
""" """
This is the base class for all ERP5 Zope objects. This is the base class for all ERP5 Zope objects.
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2017 Nexedi SA and Contributors. All Rights Reserved.
# Ayush Tiwari <ayush.tiwari@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from zope.interface import Interface
class IJSONRepresentable(Interface):
"""
An interface for objects that can be converted to JSON
and back to an ERP5 object.
This can be useful if we want to use JSON as much
as possible in ERP5 in the future and ensure
that certain objects (not all) can be converted to JSON
back and forth
"""
def asJSON():
"""
Returns a JSON representation based on
propertysheets and portal properties
"""
def fromJSON():
"""
Updates an object based on a JSON representation
"""
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2017 Nexedi SA and Contributors. All Rights Reserved.
# Ayush Tiwari <ayush.tiwari@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import json
import xmltodict
import zope.interface
from OFS import XMLExportImport
from StringIO import StringIO
from AccessControl import ClassSecurityInfo
from Products.ERP5Type.interfaces.json_representable import IJSONRepresentable
from Products.ERP5Type import Permissions
from Products.ERP5Type.Globals import InitializeClass
class JSONRepresentableMixin:
"""
An implementation for IJSONRepresentable
"""
# Declarative Security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
zope.interface.implements(IJSONRepresentable)
security.declareProtected(Permissions.AccessContentsInformation, 'asJSON')
def asJSON(self):
"""
Generate a JSON representable content for ERP5 object
Currently we use `XMLExportImport` to first convert the object to its XML
respresentation and then use xmltodict to convert it to dict and JSON
format finally
"""
dict_value = self._asDict()
# Convert the XML to json representation
return json.dumps(dict_value)
def _asDict(self):
"""
Gets the dict representation of the object
"""
# Use OFS exportXML to first export to xml
f = StringIO()
XMLExportImport.exportXML(self._p_jar, self._p_oid, f)
# Get the value of exported XML
xml_value = f.getvalue()
return xmltodict.parse(xml_value)
security.declareProtected(Permissions.AccessContentsInformation, 'fromJSON')
def fromJSON(self, val):
"""
Updates an object, based on a JSON representation
"""
dict_value = json.loads(val)
# Convert the dict_value to XML representation
xml_value = xmltodict.unparse(dict_value)
f = StringIO(xml_value)
return XMLExportImport.importXML(self._p_jar, f)
InitializeClass(JSONRepresentableMixin)
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