Commit 94df7891 authored by Tomáš Peterka's avatar Tomáš Peterka Committed by Tomáš Peterka

[hal_json] Support has_changed parameter to Dialog Methods

/reviewed-on nexedi/erp5!652
parent 8e3cbb95
...@@ -16,6 +16,7 @@ return context.ERP5Document_getHateoas( ...@@ -16,6 +16,7 @@ return context.ERP5Document_getHateoas(
select_list=select_list, select_list=select_list,
limit=limit, limit=limit,
form=form, form=form,
form_data=form_data,
relative_url=relative_url, relative_url=relative_url,
list_method=list_method, list_method=list_method,
default_param_json=default_param_json, default_param_json=default_param_json,
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>REQUEST=None, response=None, view=None, mode=\'root\', query=None, select_list=None, limit=10, local_roles=None, form=None, relative_url=None, list_method=None, default_param_json=None, form_relative_url=None, bulk_list="[]", sort_on=None, selection_domain=None, extra_param_json=None, portal_status_message=\'\', portal_status_level=None</string> </value> <value> <string>REQUEST=None, response=None, view=None, mode=\'root\', query=None, select_list=None, limit=10, local_roles=None, form=None, form_data=None, relative_url=None, list_method=None, default_param_json=None, form_relative_url=None, bulk_list="[]", sort_on=None, selection_domain=None, extra_param_json=None, portal_status_message=\'\', portal_status_level=None</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
...@@ -83,6 +83,7 @@ if dialog_method == 'Folder_delete': ...@@ -83,6 +83,7 @@ if dialog_method == 'Folder_delete':
md5_object_uid_list=kw['md5_object_uid_list']) md5_object_uid_list=kw['md5_object_uid_list'])
form = getattr(context, dialog_id) form = getattr(context, dialog_id)
form_data = None
extra_param = json.loads(extra_param_json or "{}") extra_param = json.loads(extra_param_json or "{}")
# form can be a python script that returns the form # form can be a python script that returns the form
...@@ -95,7 +96,7 @@ try: ...@@ -95,7 +96,7 @@ try:
# data. Otherwise, field appears as non editable. # data. Otherwise, field appears as non editable.
editable_mode = request.get('editable_mode', 1) editable_mode = request.get('editable_mode', 1)
request.set('editable_mode', 1) request.set('editable_mode', 1)
form.validate_all_to_request(request) form_data = form.validate_all_to_request(request)
request.set('editable_mode', editable_mode) request.set('editable_mode', editable_mode)
default_skin = portal.portal_skins.getDefaultSkin() default_skin = portal.portal_skins.getDefaultSkin()
allowed_styles = ("ODT", "ODS", "Hal", "HalRestricted") allowed_styles = ("ODT", "ODS", "Hal", "HalRestricted")
...@@ -108,7 +109,8 @@ try: ...@@ -108,7 +109,8 @@ try:
return context.Base_renderForm(dialog_id, return context.Base_renderForm(dialog_id,
message=translate('Only ODT, ODS, Hal and HalRestricted skins are allowed for reports '\ message=translate('Only ODT, ODS, Hal and HalRestricted skins are allowed for reports '\
'in Preferences - User Interface - Report Style'), 'in Preferences - User Interface - Report Style'),
level=WARNING) level=WARNING,
form_data=form_data)
except FormValidationError as validation_errors: except FormValidationError as validation_errors:
# Pack errors into the request # Pack errors into the request
...@@ -186,7 +188,8 @@ elif query == "" and select_all == 0 and dialog_method != update_method: # do n ...@@ -186,7 +188,8 @@ elif query == "" and select_all == 0 and dialog_method != update_method: # do n
message=translate("All documents are selected! Submit again to proceed or Cancel and narrow down your search."), message=translate("All documents are selected! Submit again to proceed or Cancel and narrow down your search."),
level=WARNING, level=WARNING,
keep_items={'_select_all': 1}, keep_items={'_select_all': 1},
query=query) query=query,
form_data=form_data)
# The old way was to set inquire kw for "list_selection_name" and update # The old way was to set inquire kw for "list_selection_name" and update
# it with kw["uids"] which means a long URL to call this script # it with kw["uids"] which means a long URL to call this script
...@@ -195,6 +198,11 @@ elif query == "" and select_all == 0 and dialog_method != update_method: # do n ...@@ -195,6 +198,11 @@ elif query == "" and select_all == 0 and dialog_method != update_method: # do n
if dialog_category == "object_search" : if dialog_category == "object_search" :
portal.portal_selections.setSelectionParamsFor(kw['selection_name'], kw) portal.portal_selections.setSelectionParamsFor(kw['selection_name'], kw)
# Notify the underlying script whether user did modifications
form_hash = form.hash_validated_data(form_data)
if "form_hash" in extra_param:
kw['has_changed'] = (form_hash != extra_param.pop('form_hash'))
# Add rest of extra param into arguments of the target method # Add rest of extra param into arguments of the target method
kw.update(extra_param) kw.update(extra_param)
...@@ -279,7 +287,7 @@ if True: ...@@ -279,7 +287,7 @@ if True:
meta_type = "" meta_type = ""
if meta_type in ("ERP5 Form", "ERP5 Report"): if meta_type in ("ERP5 Form", "ERP5 Report"):
return context.ERP5Document_getHateoas(REQUEST=request, form=dialog_form, mode="form") return context.ERP5Document_getHateoas(REQUEST=request, form=dialog_form, mode="form", form_data=form_data)
return dialog_form(**kw) return dialog_form(**kw)
......
...@@ -26,6 +26,10 @@ Parameters for mode == 'search' ...@@ -26,6 +26,10 @@ Parameters for mode == 'search'
Parameters for mode == 'form' Parameters for mode == 'form'
:param form: {instace} of a form - obviously this call can be only internal (Script-to-Script) :param form: {instace} of a form - obviously this call can be only internal (Script-to-Script)
:param form_data: {dict} cleaned (validated) form data stored in dict where the key is (prefixed) field.id. We do not use it to
obtain the value of the field because of how the validation itself work. Take a look in
Formulator/Form.validata_all_to_request where REQUEST is modified inplace and in case of first error
an exception is thrown which prevents the return thus form_data are empty in case of partial success.
:param extra_param_json: {dict} extra fields to be added to the rendered form :param extra_param_json: {dict} extra fields to be added to the rendered form
Parameters for mode == 'traverse' Parameters for mode == 'traverse'
...@@ -1239,6 +1243,11 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti ...@@ -1239,6 +1243,11 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
# end-if report_section # end-if report_section
if form.pt == "form_dialog": if form.pt == "form_dialog":
# Insert hash of current values into the form so scripts can see whether data has
# changed if they provide multi-step process
if form_data is not None:
extra_param_json["form_hash"] = form.hash_validated_data(form_data)
# extra_param_json is a special field in forms (just like form_id). extra_param_json field holds JSON # extra_param_json is a special field in forms (just like form_id). extra_param_json field holds JSON
# metadata about the form (its hash and dynamic fields) # metadata about the form (its hash and dynamic fields)
renderHiddenField(response_dict, 'extra_param_json', json.dumps(extra_param_json)) renderHiddenField(response_dict, 'extra_param_json', json.dumps(extra_param_json))
......
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>REQUEST=None, response=None, view=None, mode=\'root\', query=None, select_list=None, limit=10, local_roles=None, form=None, relative_url=None, restricted=0, list_method=None, default_param_json=None, form_relative_url=None, bulk_list="[]", sort_on=None, selection_domain=None, extra_param_json=None, portal_status_message=\'\', portal_status_level=None</string> </value> <value> <string>REQUEST=None, response=None, view=None, mode=\'root\', query=None, select_list=None, limit=10, local_roles=None, form=None, form_data=None, relative_url=None, restricted=0, list_method=None, default_param_json=None, form_relative_url=None, bulk_list="[]", sort_on=None, selection_domain=None, extra_param_json=None, portal_status_message=\'\', portal_status_level=None</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
...@@ -2,4 +2,12 @@ from Products.ERP5Type.Log import log ...@@ -2,4 +2,12 @@ from Products.ERP5Type.Log import log
log("Folder method received dialog_id, form_id, uids and {!s}".format(kwargs.keys())) log("Folder method received dialog_id, form_id, uids and {!s}".format(kwargs.keys()))
return context.Base_redirect(form_id, keep_items={"portal_status_message": "Did nothing."}) if 'has_changed' not in kwargs or kwargs['has_changed'] is None:
message = "Did nothing."
else:
if kwargs['has_changed']:
message = "Data has changed."
else:
message = "Data the same."
return context.Base_redirect(form_id, keep_items={"portal_status_message": message})
...@@ -73,7 +73,9 @@ ...@@ -73,7 +73,9 @@
<item> <item>
<key> <string>left</string> </key> <key> <string>left</string> </key>
<value> <value>
<list/> <list>
<string>custom_variable</string>
</list>
</value> </value>
</item> </item>
<item> <item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StringField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>custom_variable</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>too_long</string> </key>
<value> <string>Too much input was given.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string>text</string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>custom_variable</string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -68,6 +68,11 @@ ...@@ -68,6 +68,11 @@
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tbody/tr[3]/td[1]//p</td> <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tbody/tr[3]/td[1]//p</td>
<td>22</td></tr> <td>22</td></tr>
<tr><td>type</td>
<td>//input[@name="field_custom_variable"]</td>
<td>couscous</td></tr>
<tr><td>waitForElementPresent</td> <tr><td>waitForElementPresent</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/a[@data-i18n="Next"]</td><td></td></tr> <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/a[@data-i18n="Next"]</td><td></td></tr>
<tr><td>click</td> <tr><td>click</td>
...@@ -86,6 +91,7 @@ ...@@ -86,6 +91,7 @@
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tbody/tr[3]/td[1]//p</td> <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tbody/tr[3]/td[1]//p</td>
<td>14</td></tr> <td>14</td></tr>
<tr><th rowspan="1" colspan="3">Updating the dialog must not trigger warning about all selected</th></tr> <tr><th rowspan="1" colspan="3">Updating the dialog must not trigger warning about all selected</th></tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/update_dialog" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/update_dialog" />
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
...@@ -100,10 +106,51 @@ ...@@ -100,10 +106,51 @@
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block> </tal:block>
<tr><td>type</td>
<td>//input[@name="field_custom_variable"]</td>
<td>couscous</td></tr>
<tr><th rowspan="1" colspan="3">Second submission must work as advertised</th></tr> <tr><th rowspan="1" colspan="3">Second submission must work as advertised</th></tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/submit_dialog" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/submit_dialog" />
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
<tal:block tal:define="notification_configuration python: {'class': 'success', 'text': 'Did nothing.'}"> <tal:block tal:define="notification_configuration python: {'class': 'success', 'text': 'Data the same.'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block>
<tr><th rowspan="1" colspan="3">Let's try has_changed to kick in on simple data change</th></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-role="header"]//a[@data-i18n="Actions"]</td><td></td></tr>
<tr><td>click</td>
<td>//div[@data-role="header"]//a[@data-i18n="Actions"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//ul[@data-role="listview"]//a[@data-i18n="Empty Mass Action"]</td><td></td></tr>
<tr><td>click</td>
<td>//ul[@data-role="listview"]//a[@data-i18n="Empty Mass Action"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//input[@name="field_custom_variable"]</td><td></td></tr>
<tr><td>type</td>
<td>//input[@name="field_custom_variable"]</td>
<td>tajine</td></tr>
<tr><th rowspan="1" colspan="3">Warn the user and compute form_hash in the background</th></tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/submit_dialog" />
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
<tal:block tal:define="notification_configuration python: {'class': 'error', 'text': 'All documents are selected! Submit again to proceed or Cancel and narrow down your search.'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block>
<tr><td>waitForElementPresent</td>
<td>//input[@name="field_custom_variable"]</td><td></td></tr>
<tr><td>type</td>
<td>//input[@name="field_custom_variable"]</td>
<td>couscous</td></tr>
<tr><th rowspan="1" colspan="3">Submit BUT pass has_changed true to the dialog method</th></tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/submit_dialog" />
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
<tal:block tal:define="notification_configuration python: {'class': 'success', 'text': 'Data has changed.'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" /> <tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block> </tal:block>
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# #
############################################################################## ##############################################################################
import hashlib
from copy import deepcopy from copy import deepcopy
...@@ -766,6 +767,15 @@ class ERP5Form(Base, ZMIForm, ZopePageTemplate): ...@@ -766,6 +767,15 @@ class ERP5Form(Base, ZMIForm, ZopePageTemplate):
raise FormValidationError(errors, result) raise FormValidationError(errors, result)
return result return result
security.declareProtected('View', 'hash_validated_data')
def hash_validated_data(self, validated_data):
return hashlib.sha256(
"".join(
str(validated_data[key])
for key in sorted(validated_data.keys())
if isinstance(validated_data[key], (str, unicode, int, long, float, DateTime)))
).hexdigest()
# FTP/DAV Access # FTP/DAV Access
manage_FTPget = ZMIForm.get_xml manage_FTPget = ZMIForm.get_xml
......
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