Commit 2f121c19 authored by Ayush Tiwari's avatar Ayush Tiwari Committed by Ayush Tiwari

[erp5_core]: Use Diff Tool to Beautify History Tab for ERP5 objects

The Diff view in new UI will be using IFrames to display the diff.

Also, add changes in tests:

erp5_ui_test: Update history test according to use of diff gadget
erp5_web_renderjs_ui_test: Update tests according to new Diff view for Historical Revisions
testXHTML: Add Historical Comparison View to the list of listbox with no selection name
testXHTML: Simplify test_html_file and include way to ignore single html file
parent e68be972
...@@ -130,20 +130,21 @@ ...@@ -130,20 +130,21 @@
<td>title</td> <td>title</td>
</tr> </tr>
<tr> <tr>
<td>assertText</td> <td>assertElementPresent</td>
<td>//tr[@class='listbox-data-line-0 DataA']/td[2]</td> <td>//tr[@class='listbox-data-line-0 DataA']/td[2]/div[@data-gadget-sandbox="public"]</td>
<td>Title 0</td> <!-- before --> <td></td>
</tr> </tr>
<tr> <tr>
<td>assertText</td> <td>storeEval</td>
<td>//tr[@class='listbox-data-line-0 DataA']/td[3]</td> <td>document.querySelector('#selenium_myiframe').contentWindow.document.evaluate("//tr[@class='listbox-data-line-0 DataA']/td[2]/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>Version 1</td> <!-- after --> <td>_data_gadget_value</td>
</tr> </tr>
<tr> <tr>
<td>assertText</td> <td>verifyEval</td>
<td>//tr[@class='listbox-data-line-0 DataA']/td[4]</td> <td>storedVars['_data_gadget_value'].replace(/(\r\n\t|\n|\r\t)/gm,"").replace(/\s+/g, '')</td>
<td>Version 3</td> <!-- now --> <td>---+++@@-1+1@@-Title0+Version1</td>
</tr> </tr>
</tal:block> </tal:block>
<tal:block tal:condition="python: context.TestTool_getSkinName()=='Mobile'"> <tal:block tal:condition="python: context.TestTool_getSkinName()=='Mobile'">
...@@ -190,20 +191,21 @@ ...@@ -190,20 +191,21 @@
<td>title</td> <td>title</td>
</tr> </tr>
<tr> <tr>
<td>assertText</td> <td>assertElementPresent</td>
<td>//tr[@class='listbox-data-line-0 DataA']/td[2]</td> <td>//tr[@class='listbox-data-line-0 DataA']/td[2]/div[@data-gadget-sandbox="public"]</td>
<td>Version 1</td> <!-- before --> <td></td>
</tr> </tr>
<tr> <tr>
<td>assertText</td> <td>storeEval</td>
<td>//tr[@class='listbox-data-line-0 DataA']/td[3]</td> <td>document.querySelector('#selenium_myiframe').contentWindow.document.evaluate("//tr[@class='listbox-data-line-0 DataA']/td[2]/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>Version 2</td> <!-- after --> <td>_data_gadget_value</td>
</tr> </tr>
<tr> <tr>
<td>assertText</td> <td>verifyEval</td>
<td>//tr[@class='listbox-data-line-0 DataA']/td[4]</td> <td>storedVars['_data_gadget_value'].replace(/(\r\n\t|\n|\r\t)/gm,"").replace(/\s+/g, '')</td>
<td>Version 3</td> <!-- now --> <td>---+++@@-1+1@@-Version1+Version2</td>
</tr> </tr>
</tal:block> </tal:block>
<tal:block tal:condition="python: context.TestTool_getSkinName()=='Mobile'"> <tal:block tal:condition="python: context.TestTool_getSkinName()=='Mobile'">
...@@ -268,20 +270,21 @@ ...@@ -268,20 +270,21 @@
<td>title</td> <td>title</td>
</tr> </tr>
<tr> <tr>
<td>assertText</td> <td>assertElementPresent</td>
<td>//tr[@class='listbox-data-line-0 DataA']/td[2]</td> <td>//tr[@class='listbox-data-line-0 DataA']/td[2]/div[@data-gadget-sandbox="public"]</td>
<td>Version 2</td> <!-- before --> <td></td>
</tr> </tr>
<tr> <tr>
<td>assertText</td> <td>storeEval</td>
<td>//tr[@class='listbox-data-line-0 DataA']/td[3]</td> <td>document.querySelector('#selenium_myiframe').contentWindow.document.evaluate("//tr[@class='listbox-data-line-0 DataA']/td[2]/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>Version 3</td> <!-- after --> <td>_data_gadget_value</td>
</tr> </tr>
<tr> <tr>
<td>assertText</td> <td>verifyEval</td>
<td>//tr[@class='listbox-data-line-0 DataA']/td[4]</td> <td>storedVars['_data_gadget_value'].replace(/(\r\n\t|\n|\r\t)/gm,"").replace(/\s+/g, '')</td>
<td>Version 3</td> <!-- now --> <td>---+++@@-1+1@@-Version2+Version3</td>
</tr> </tr>
</tal:block> </tal:block>
<tal:block tal:condition="python: context.TestTool_getSkinName()=='Mobile'"> <tal:block tal:condition="python: context.TestTool_getSkinName()=='Mobile'">
......
...@@ -130,17 +130,30 @@ ...@@ -130,17 +130,30 @@
<tr> <tr>
<td>verifyText</td> <td>verifyText</td>
<td>//div[@data-gadget-scope='field_listbox']//table/thead/tr[1]/th[2]</td> <td>//div[@data-gadget-scope='field_listbox']//table/thead/tr[1]/th[2]</td>
<td>Old Value</td> <td>Diff Viewer</td>
</tr> </tr>
<tr> <tr>
<td>verifyText</td> <td>waitForElementPresent</td>
<td>//div[@data-gadget-scope='field_listbox']//table/thead/tr[1]/th[3]</td> <td>//div[@data-gadget-scope='field_listbox']//table/tbody/tr[4]/td[2]//iframe</td>
<td>New Value</td> <td></td>
</tr> </tr>
<tr> <tr>
<td>verifyText</td> <td>pause</td>
<td>//div[@data-gadget-scope='field_listbox']//table/thead/tr[1]/th[4]</td> <td>1000</td>
<td>Current Value</td> <td></td>
</tr>
<!-- Check for the value of diff in Iframe -->
<tr>
<td>storeEval</td>
<td>document.querySelector('#selenium_myiframe').contentWindow.document.evaluate("//div[@data-gadget-scope='field_listbox']//table/tbody/tr[4]/td[2]//iframe", document.querySelector('#selenium_myiframe').contentWindow.document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.contentWindow.document.body.textContent</td>
<td>diff_content</td>
</tr>
<tr>
<td>verifyEval</td>
<!-- Verify the value after removing the spaces and line breaks -->
<td>storedVars['diff_content'].replace(/(\r\n\t|\n|\r\t)/gm,"").replace(/\s+/g, '')</td>
<td>1-1+Anewfoo</td>
</tr> </tr>
</tbody></table> </tbody></table>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>View</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/DiffTool_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2018 Nexedi SARL 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 deepdiff
import difflib
from deepdiff import DeepDiff
from unidiff import PatchSet
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions
from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type import interfaces
from Products.PythonScripts.PythonScript import PythonScript
class DiffTool(BaseTool):
"""
A portal tool that provides all kinds of utilities to
compare objects.
"""
id = 'portal_diff'
title = 'Diff Tool'
meta_type = 'ERP5 Diff Tool'
portal_type = 'Diff Tool'
allowed_types = ()
# Declarative Security
security = ClassSecurityInfo()
security.declareProtected(Permissions.AccessContentsInformation,
'diffPortalObject')
def diffPortalObject(self, old_value, new_value, path=None, patch_format="deepdiff"):
"""
Returns a PortalPatch instance with the appropriate format
path -- optional path to specify which property to diff
patch_format -- optional format (rfc6902 or deepdiff)
"""
return PortalPatch(old_value, new_value, path, patch_format)
security.declarePrivate('patchPortalObject')
def patchPortalObject(self, old, diff_list):
"""
Receives the dict with old object, diff value and returns a new object from
the diff and the old value
"""
copy_data = old.aq_parent.manage_copyObjects([old.id,])
new_id = old.aq_parent.manage_pasteObjects(copy_data)[0]['new_id']
new_obj = old.aq_parent[new_id]
for diff in diff_list:
setattr(new_obj, diff['path'], diff['t2'])
return new_obj
class PortalPatch:
"""
Provides an abstraction to a patch that
depends on the patch format.
In the case of deepdiff, the abstraction can
lead to a commutative merge system.
In the case of rfc6902, the abstraction can not
lead to a commutative merge system but may be
useful to some UI applications.
"""
# Declarative Security
security = ClassSecurityInfo()
def __init__(self, old_value, new_value, path=None, patch_format="deepdiff"):
"""
Intialises the class from a deepdiff or
a rfc6902 patch. deepdiff is the default.
old_value -- Old Value (can be an object, dict, string or other object type)
which we want to compare the changes from
new_value -- New Value which we want to see what has been changed from old
value
"""
self.old_value = old_value
self.new_value = new_value
self.patch_format = patch_format
security.declareProtected(Permissions.AccessContentsInformation,
'asBeautifiedJSONDiff')
def asBeautifiedJSONDiff(self):
"""
Returns beautified JSON diff in format:
{
'diff': <diff>
't1': <old_value>
't2': <new_value>
'path', <property_name>
}
"""
old_value_dict = self.removePropertyList(self.old_value, export=True)
new_value_dict = self.removePropertyList(self.new_value, export=True)
# Get the DeepDiff in tree format.
tree_diff = DeepDiff(old_value_dict,
new_value_dict,
view='tree')
diff_tree_list = []
# Flatten the list of DiffValues
for key, subset in tree_diff.items():
if isinstance(subset, set):
sublist = list(subset)
for item in sublist:
# XXX: This is important as the subsets with iterable item removed
# do always have items which are diffing the items in list and not the
# complete list, hence we get paths as root[<list_name>][<index_no>]
# which is inconsistent to manage in string formatting, thus we have
# decided to use the parent list by using .up
if key in ('iterable_item_removed',):
diff_tree_list.append(item.up)
else:
diff_tree_list.append(item)
# Create a beautified list from the diff
diff_list = []
for val in diff_tree_list:
new_val = {}
diff = val.additional.get('diff', None)
# If there is diff in additional property, save it separately
if diff:
# Add space in front of each newline character as it validates the diff
diff = diff.replace('\n', ' \n')
patch = PatchSet(diff)
# Get the 1st item from the PatchSet which is the patch object
patch = patch[0]
# In case there are multiple hunks, do create separate value dict for
# each of them
for l in patch:
new_val = {}
# Add the headers on top of every patch as they are needed for
# rendering as pretty HTML
new_val['diff'] = "--- \n+++ \n" + str(l)
new_val['t1'] = val.t1
new_val['t2'] = val.t2
new_val['path'] = val.path()[6:-2]
diff_list.append(new_val)
else:
old_value = val.t1
new_value = val.t2
if (val.t1 == None) or isinstance(val.t1, deepdiff.helper.NotPresent):
old_value = ''
if (val.t2 == None) or isinstance(val.t2, deepdiff.helper.NotPresent):
new_value = ''
try:
# Create unified diff for single string cases
new_val['diff'] = ''.join(difflib.unified_diff([str(old_value)+'\n'], [str(new_value)+'\n'])).replace('\n', ' \n')
except ValueError:
# Show empty diff in case of error in new or old value type
new_val['diff'] = None
new_val['path'] = val.path()[6:-2]
new_val['t1'] = val.t1
new_val['t2'] = val.t2
diff_list.append(new_val)
# Sort the list of dictionaries according to the path
sorted_diff_list = sorted(diff_list, key=lambda k: k['path'])
return sorted_diff_list
security.declarePrivate('removePropertyList')
def removePropertyList(self,
obj,
export,
property_list=None,
keep_workflow_history=False,
keep_workflow_history_last_history_only=False):
"""
This function removes un-necessary properties and attributes from the
object dict.
"""
if isinstance(obj, dict):
obj_dict = obj.copy()
export = False
else:
obj._p_activate()
klass = obj.__class__
classname = klass.__name__
obj_dict = obj.showDict().copy()
attribute_set = {'_dav_writelocks', '_filepath', '_owner', '_related_index',
'last_id', 'uid', '_mt_index', '_count', '_tree',
'__ac_local_roles__', '__ac_local_roles_group_id_dict__',
'workflow_history', 'subject_set_uid_dict', 'security_uid_dict',
'filter_dict', '_max_uid', 'isIndexable', 'id', 'modification_date',
'data'}
# Update the list of properties which were explicitly given in parameters
if property_list:
for prop in property_list:
if prop.endswith('_list'):
prop = prop[:-5]
attribute_set.add(prop)
if export:
if keep_workflow_history_last_history_only:
self._removeAllButLastWorkflowHistory(obj)
elif not keep_workflow_history:
attribute_set.add('workflow_history')
# PythonScript covers both Zope Python scripts
# and ERP5 Python Scripts
if isinstance(obj, PythonScript):
attribute_set.update(('func_code', 'func_defaults', '_code',
'_lazy_compilation', 'Python_magic', 'errors',
'warnings', '_proxy_roles'))
elif classname in ('File', 'Image'):
attribute_set.update(('_EtagSupport__etag', 'size'))
elif classname == 'SQL' and klass.__module__ == 'Products.ZSQLMethods.SQL':
attribute_set.update(('_arg', 'template'))
elif interfaces.IIdGenerator.providedBy(obj):
attribute_set.update(('last_max_id_dict', 'last_id_dict'))
elif classname == 'Types Tool' and klass.__module__ == 'erp5.portal_type':
attribute_set.add('type_provider_list')
for attribute in list(obj_dict.keys()):
if attribute in attribute_set or attribute.startswith('_cache_cookie_') or attribute.startswith('_v'):
del obj_dict[attribute]
return obj_dict
InitializeClass(DiffTool)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>DiffTool</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>A tool than can be used to Diff any 2 objects of same module.</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.DiffTool</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Base Type" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>content_icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Diff Tool</string> </value>
</item>
<item>
<key> <string>init_script</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>permission</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Base Type</string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>DiffTool</string> </value>
</item>
<item>
<key> <string>type_interface</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_mixin</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from Products.PythonScripts.standard import Object from Products.ERP5Type.Document import newTempBase
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
from zExceptions import Unauthorized from zExceptions import Unauthorized
Base_translateString = context.Base_translateString Base_translateString = context.Base_translateString
portal = context.getPortalObject()
portal_diff = portal.portal_diff
try: try:
context.HistoricalRevisions[serial] context.HistoricalRevisions[serial]
except (ConflictError, Unauthorized): except (ConflictError, Unauthorized):
raise raise
except Exception: # POSKeyError except Exception:
return [Object(property_name=Base_translateString('Historical revisions are' return [newTempBase(portal, Base_translateString('Historical revisions are'
' not available, maybe the database has been packed'))] ' not available, maybe the database has been packed'))]
if next_serial == '0.0.0.0': if next_serial == '0.0.0.0':
# In case the next serial is 0.0.0.0, we should always be considering the
# new object as the current context
new_getProperty = context.getProperty new_getProperty = context.getProperty
new = context
else: else:
new = context.HistoricalRevisions[next_serial] new = context.HistoricalRevisions[next_serial]
new_getProperty = new.getProperty new_getProperty = new.getProperty
old = context.HistoricalRevisions[serial] old = context.HistoricalRevisions[serial]
result = [] result = []
binary_data_explanation = Base_translateString("Binary data can't be displayed") # XXX: Instead of creating a separate property list here, we can use DiffTool
base_error_message = Base_translateString('(value retrieval failed)') # to directly find out the beautified diff and send it to the listbox
for prop_dict in context.getPropertyMap(): diff = portal_diff.diffPortalObject(old, new).asBeautifiedJSONDiff()
prop = prop_dict['id']
error = False
try:
current_value = context.getProperty(prop)
except TypeError:
error = True
current_value = base_error_message
try:
old_value = old.getProperty(prop)
except TypeError:
error = True
old_value = base_error_message
try:
new_value = new_getProperty(prop)
except TypeError:
error = True
new_value = base_error_message
if new_value != old_value or error:
# check if values are unicode convertible (binary are not)
if isinstance(new_value, (str, unicode)):
try:
unicode(str(new_value), 'utf-8')
except UnicodeDecodeError:
new_value = binary_data_explanation
if isinstance(old_value, (str, unicode)):
try:
unicode(str(old_value), 'utf-8')
except UnicodeDecodeError:
old_value = binary_data_explanation
if isinstance(current_value, (str, unicode)):
try:
unicode(str(current_value), 'utf-8')
except UnicodeDecodeError:
current_value = binary_data_explanation
result.append( Object( property_name=prop, tempbase_list = []
new_value=new_value, uid = 900
old_value=old_value, for x in diff:
current_value=current_value)) temp_obj = newTempBase(portal,
return result x['path'],
**x)
temp_obj.setUid('new_%s' % uid)
uid = uid + 1
tempbase_list.append(temp_obj)
return tempbase_list
...@@ -90,7 +90,9 @@ ...@@ -90,7 +90,9 @@
<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>
......
<?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>css_class</string> </key>
<value> <string></string> </value>
</item>
<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>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Folder" module="OFS.Folder"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>erp5_diff</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
# This script returns the sandbox for the gadget displaying
# the ERP5. For the XHTML UI, we use 'public' and for the
# renderJS UI, we use 'iframe'
if context.REQUEST.get('web_site_value', None):
return 'iframe'
return 'public'
<?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></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getDiffGadgetSandbox</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>my_title</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>DiffTool_view</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>DiffTool_view</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_view</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Diff Tool</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_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="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>editable</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_title</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>editable</string> </key>
<value> <int>0</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>title</string> </key>
<value> <string>Title</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>diff2html-ui.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></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="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>diff2html.css</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/css</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></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="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>diff2html.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
CACHE MANIFEST
# generated on Fri, 14 June 2018 16:00:00 GMT+0200
# XXX + fonts
# images/ajax-loader.gif
CACHE:
diff2html-ui.js
diff2html.css
diff2html.js
gadget_erp5_side_by_side_diff.js
renderjs.js
NETWORK:
*
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>gadget_erp5_side_by_side_diff.appcache</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/cache-manifest</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html manifest="gadget_erp5_side_by_side_diff.appcache">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<title>ERP5 Diff View</title>
<!-- renderjs -->
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<!-- External Javascripts -->
<script type="text/javascript" src="diff2html-ui.js"></script>
<script type="text/javascript" src="diff2html.js"></script>
<!-- custom script -->
<script src="gadget_erp5_side_by_side_diff.js" type="text/javascript"></script>
<!-- CSS -->
<link rel="stylesheet" type="text/css" href="diff2html.css">
</head>
<body>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>gadget_erp5_side_by_side_diff.html</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*global rJS, RSVP, Diff2Html, window */
/*jslint nomen: true, indent: 2, maxerr: 3 */
/**
Side by side Diff is a gadget whcih we use to display diff between any 2 ERP5
objects in side by side view
**/
(function (rJS, RSVP, Diff2Html, window) {
"use strict";
rJS(window)
.declareMethod('render', function (options) {
this.element.innerHTML = Diff2Html.getPrettyHtml(options.value, {
outputFormat: 'side-by-side',
matching: 'lines'
});
});
}(rJS, RSVP, Diff2Html, window));
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>gadget_erp5_side_by_side_diff.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></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="Diff Tool" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>portal_diff</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
2018-06-21
* Add Beautified Diff using Diff Tool for Historical Revision Tab
2015-06-08 arnaud.fontaine 2015-06-08 arnaud.fontaine
* Allow to jump from a Portal Type to its currently set Property Sheets. * Allow to jump from a Portal Type to its currently set Property Sheets.
......
...@@ -76,6 +76,7 @@ Content Existence Constraint | predicate ...@@ -76,6 +76,7 @@ Content Existence Constraint | predicate
Content Existence Constraint | view Content Existence Constraint | view
Contribution Predicate | view Contribution Predicate | view
Contribution Registry Tool | view Contribution Registry Tool | view
Diff Tool | view
Distributed Ram Cache | view Distributed Ram Cache | view
Document Component | view Document Component | view
Document | view Document | view
......
document.erp5.DiffTool
document.erp5.ScriptConstraint document.erp5.ScriptConstraint
document.erp5.TransformOdtToDocx document.erp5.TransformOdtToDocx
document.erp5.TransformOdtToHtml document.erp5.TransformOdtToHtml
......
...@@ -36,6 +36,7 @@ Content Existence Constraint ...@@ -36,6 +36,7 @@ Content Existence Constraint
Contribution Predicate Contribution Predicate
Contribution Registry Tool Contribution Registry Tool
Delivery Tool Delivery Tool
Diff Tool
Distributed Ram Cache Distributed Ram Cache
Document Document
Document Component Document Component
......
erp5_auto_logout erp5_auto_logout
erp5_core erp5_core
erp5_diff
\ No newline at end of file
mimetypes_registry mimetypes_registry
portal_callables portal_callables
portal_contribution_registry portal_contribution_registry
portal_diff
portal_notifications portal_notifications
portal_sessions portal_sessions
portal_transforms portal_transforms
\ No newline at end of file
...@@ -49,8 +49,11 @@ from xml.dom import minidom ...@@ -49,8 +49,11 @@ from xml.dom import minidom
class TestXHTMLMixin(ERP5TypeTestCase): 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',]
JSL_IGNORE_FILE_LIST = ( JSL_IGNORE_FILE_LIST = (
'diff2html.js',
'diff2html-ui.js',
'dream_graph_editor/lib/handlebars.min.js', 'dream_graph_editor/lib/handlebars.min.js',
'dream_graph_editor/lib/jquery-ui.js', 'dream_graph_editor/lib/jquery-ui.js',
'dream_graph_editor/lib/jquery.js', 'dream_graph_editor/lib/jquery.js',
...@@ -86,6 +89,22 @@ class TestXHTMLMixin(ERP5TypeTestCase): ...@@ -86,6 +89,22 @@ class TestXHTMLMixin(ERP5TypeTestCase):
'erp5_svg_editor', 'erp5_svg_editor',
) )
HTML_IGNORE_FILE_LIST = (
'gadget_erp5_side_by_side_diff.html',
)
# NOTE: Here the difference between the JSL_IGNORE_SKIN_LIST is that we also
# consider the folders inside the skin. In this way, we can include multiple
# HTML files at once which are inside some folder in any skin folder.
HTML_IGNORE_SKIN_FOLDER_LIST = (
'erp5_jquery',
'erp5_fckeditor',
'erp5_ckeditor',
'erp5_svg_editor',
'erp5_jquery_ui',
'erp5_dms/pdf_js',
'erp5_test_result/test_result_js',
)
def changeSkin(self, skin_name): def changeSkin(self, skin_name):
""" """
Change current Skin Change current Skin
...@@ -229,13 +248,12 @@ class TestXHTMLMixin(ERP5TypeTestCase): ...@@ -229,13 +248,12 @@ class TestXHTMLMixin(ERP5TypeTestCase):
path_list = [] path_list = []
for script_path, script in skins_tool.ZopeFind( for script_path, script in skins_tool.ZopeFind(
skins_tool, obj_metatypes=['File'], search_sub=1): skins_tool, obj_metatypes=['File'], search_sub=1):
is_required_check_path = True
ignore_bts = ['erp5_jquery', 'erp5_fckeditor', 'erp5_ckeditor',
'erp5_svg_editor', 'erp5_jquery_ui',
'erp5_dms/pdf_js', 'erp5_test_result/test_result_js']
if script_path.endswith('.html'): if script_path.endswith('.html'):
for ignore_bt_name in ignore_bts: x = script_path.split('/', 1)
if script_path.startswith(ignore_bt_name): if not x[1] in self.HTML_IGNORE_FILE_LIST:
is_required_check_path = False
for ignore_folder_name in self.HTML_IGNORE_SKIN_FOLDER_LIST:
if script_path.startswith(ignore_folder_name):
is_required_check_path = False is_required_check_path = False
break; break;
if is_required_check_path: if is_required_check_path:
......
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