Commit 03831389 authored by Tomáš Peterka's avatar Tomáš Peterka Committed by Tomáš Peterka

[hal_json+renderjs] Bug fixes

-  [hal_json] distinguishes between REQUEST.form and REQUEST.other
-  [hal_json] byteify all keys and values before putting them into REQUEST
-  [renderjs_ui] dialog does not go to parent when validation fails
-  [hal_json] does not crash on direct traverse of a Script instead of a Dialog
-  [renderjs_ui_test] move the logging-out test to its isolated zuite so it does not cause problems
parent 1d4b96ae
......@@ -61,7 +61,6 @@ from Products.ERP5Type.Message import Message
from Products.ERP5Type.Utils import UpperCase
from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery
from collections import OrderedDict
from urlparse import urlparse
MARKER = []
......@@ -126,6 +125,8 @@ def byteify(string):
return {byteify(key): byteify(value) for key, value in string.iteritems()}
elif isinstance(string, list):
return [byteify(element) for element in string]
elif isinstance(string, tuple):
return tuple(byteify(element) for element in string)
elif isinstance(string, unicode):
return string.encode('utf-8')
else:
......@@ -151,7 +152,7 @@ time_iso_re = re.compile(r'^(\d{2}):(\d{2}):(\d{2}).*$')
def ensureDeserialized(obj):
"""Deserialize classes serialized by our own `ensureSerializable`.
Method `biteify` must not be called on the result because it would revert out
Method `byteify` must not be called on the result because it would revert out
deserialization by calling __str__ on constructed classes.
"""
if isinstance(obj, dict):
......@@ -560,7 +561,7 @@ def getFieldDefault(form, field, key, value=None):
Previously we used Formulator.Field._get_default which is (for no reason) private.
"""
if value is None:
value = REQUEST.get(key, MARKER)
value = REQUEST.form.get(key, MARKER)
# use marker because default value can be intentionally empty string
if value is MARKER:
value = field.get_value('default', request=REQUEST, REQUEST=REQUEST)
......@@ -582,7 +583,7 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
# thus setting the field_id is optional and controlled by `request_field` argument
if request_field:
previous_request_field = REQUEST.other.pop('field_id', None)
REQUEST.set('field_id', field.id)
REQUEST.other['field_id'] = field.id
if meta_type is None:
meta_type = field.meta_type
......@@ -1125,7 +1126,7 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
if form.pt == 'form_dialog':
# overwrite "form_id" field's value because old UI does that by passing
# the form_id in query string and hidden fields
renderHiddenField(response_dict, "form_id", REQUEST.get('form_id') or form.id)
renderHiddenField(response_dict, "form_id", last_form_id or REQUEST.get('form_id', '') or form.id)
# dialog_id is a mandatory field in any form_dialog
renderHiddenField(response_dict, 'dialog_id', form.id)
# some dialog actions use custom cancel_url
......@@ -1242,7 +1243,7 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
# metadata about the form (its hash and dynamic fields)
renderHiddenField(response_dict, 'extra_param_json', json.dumps(extra_param_json))
for key, value in previous_request_other.items():
for key, value in byteify(previous_request_other.items()):
if value is not None:
REQUEST.set(key, value)
......@@ -1403,8 +1404,8 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
if isinstance(extra_param_json, str):
extra_param_json = ensureDeserialized(json.loads(urlsafe_b64decode(extra_param_json)))
for key, value in extra_param_json.items():
REQUEST.set(key, value)
for k, v in byteify(extra_param_json.items()):
REQUEST.set(k, v)
# Add a link to the portal type if possible
if not is_portal:
......@@ -1457,37 +1458,32 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
}
}
if view_instance.pt == "form_dialog":
# If there is a "form_id" in the REQUEST then it means that last view was actually a form
# and we are most likely in a dialog. We save previous form into `last_form_id` ...
last_form_id = REQUEST.get('form_id', "") if REQUEST is not None else ""
# Put all query parameters (?reset:int=1&workflow_action=start_action) in request to mimic usual form display
# Request is later used for method's arguments discovery so set URL params into REQUEST (just like it was sent by form)
for query_key, query_value in current_action['params'].items():
for query_key, query_value in byteify(current_action['params'].items()):
REQUEST.set(query_key, query_value)
# If our "form" is actually a Script (nothing is sure in ERP5) then execute it here
try:
if "Script" in view_instance.meta_type:
# we suppose that the script takes only what is given in the URL params
return view_instance(**current_action['params'])
except AttributeError:
# if renderer form does not have attr meta_type then it is not a document
# but most likely bound instance method. Some form_ids do actually point to methods.
# If our "form" is actually a Script (nothing is sure in ERP5) or anything else than Form
# (e.g. function or bound class method will) not have .meta_type thus be considered a Script
# then we execute it directly
if "Script" in getattr(view_instance, "meta_type", "Script"):
# we suppose that the script takes only what is given in the URL params
returned_value = view_instance(**current_action['params'])
# returned value is usually REQUEST.RESPONSE.redirect()
log('ERP5Document_getHateoas', 'HAL_JSON cannot handle returned value "{!s}" from {}({!s})'.format(
returned_value, current_action['view_id'], current_action['params']), 100)
status_message = Base_translateString('Operation executed')
if isinstance(returned_value, (str, unicode)) and returned_value.startswith('http'):
parsed_url = urlparse(returned_value)
parsed_query = parse_qs(parsed_url.query)
if len(parsed_query.get('portal_status_message', ())) > 0:
status_message = parsed_query.get('portal_status_message')[0]
status_message = Base_translateString('Operation executed.')
if isinstance(returned_value, str) and "portal_status_message=" in returned_value: # it is an URL
status_message = re.match(r'portal_status_message=([^&]*)', returned_value).group(1).replace('+', ' ')
return traversed_document.Base_redirect(keep_items={
'portal_status_message': status_message})
if view_instance.pt == "form_dialog":
# If there is a "form_id" in the REQUEST then it means that last view was actually a form
# and we are most likely in a dialog. We save previous form into `last_form_id` ...
last_form_id = REQUEST.get('form_id', "")
# Sometimes callables (form dialog's method, listbox's list method...) do not touch
# REQUEST but expect all (formerly) URL query parameters to appear in their **kw
# thus we send extra_param_json (=rjs way of passing parameters to REQUEST) as
......@@ -1721,7 +1717,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
if isinstance(extra_param_json, str):
extra_param_json = ensureDeserialized(json.loads(urlsafe_b64decode(extra_param_json)))
for key, value in extra_param_json.items():
for key, value in byteify(extra_param_json.items()):
REQUEST.set(key, value)
# in case we have custom list method
......
<?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>dialog_id, form_id, uids, **kwargs</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Folder_doNothing</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -38,10 +38,10 @@
splitted_current_jio_key_list,
command,
i;
if (is_updating) {
if (is_updating || !jio_key) {
return;
}
if (!jio_key || gadget.state.redirect_to_parent) {
if (gadget.state.redirect_to_parent) {
return gadget.redirect({command: 'history_previous'});
}
if (gadget.state.jio_key === jio_key) {
......
......@@ -224,7 +224,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>966.62936.1765.16691</string> </value>
<value> <string>966.63118.5474.36386</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -242,7 +242,7 @@
</tuple>
<state>
<tuple>
<float>1524144844.96</float>
<float>1524572973.21</float>
<string>UTC</string>
</tuple>
</state>
......
<?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>testFormDialogWorkflowScriptDirectly</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 Form View Editable Save Action</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">Test Default Module View</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/PTZuite_CommonTemplate/macros/init" />
<tr>
<td>open</td>
<td>${base_url}/web_site_module/renderjs_runner/#/foo_module/1?editable=1</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//input[@name="field_my_title"]</td>
<td></td>
</tr>
<tr>
<td>assertValue</td>
<td>//input[@name="field_my_title"]</td>
<td>Title 1</td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//a[text()='Actions']</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//a[text()='Actions']</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//a[@data-i18n='Custom Action No Dialog']</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//a[@data-i18n='Custom Action No Dialog']</td>
<td></td>
</tr>
<tr>
<td rowspan="1" colspan="3">
Action should succeed and redirect back to the context.<br/>
It will not show messages because we do not parse the response.
</td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//input[@name="field_my_title"]</td>
<td></td>
</tr>
<tr>
<td>assertValue</td>
<td>//input[@name="field_my_title"]</td>
<td>Title 1</td>
</tr>
</tbody></table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Zuite" module="Products.Zelenium.zuite"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>renderjs_ui_recover_password_zuite</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>WARNING - tests which log you out</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
##############################################################################
#
# Copyright (c) 2011 Nexedi SARL and Contributors. All Rights Reserved.
# Kazuhiko <kazuhiko@nexedi.com>
# Rafael Monnerat <rafael@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 unittest
from Products.ERP5Type.tests.ERP5TypeFunctionalTestCase import ERP5TypeFunctionalTestCase
class TestRenderJSUIRecoverPassword(ERP5TypeFunctionalTestCase):
foreground = 0
run_only = "renderjs_ui_recover_password_zuite"
def getBusinessTemplateList(self):
return (
'erp5_web_renderjs_ui',
'erp5_web_renderjs_ui_test',
'erp5_ui_test_core',
'erp5_accounting',
'erp5_test_result',
)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestRenderJSUIRecoverPassword))
return suite
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test 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>testFunctionalRJSRecoverPassword</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testFunctionalRJSRecoverPassword</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test 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>
......@@ -32,6 +32,8 @@ portal_tests/renderjs_ui_page_zuite
portal_tests/renderjs_ui_page_zuite/**
portal_tests/renderjs_ui_radio_field_zuite
portal_tests/renderjs_ui_radio_field_zuite/**
portal_tests/renderjs_ui_recover_password_zuite
portal_tests/renderjs_ui_recover_password_zuite/**
portal_tests/renderjs_ui_relation_field_zuite
portal_tests/renderjs_ui_relation_field_zuite/**
portal_tests/renderjs_ui_router_zuite
......
......@@ -18,4 +18,5 @@ test.erp5.testFunctionalRJSTranslation
test.erp5.testFunctionalRJSLogoutTranslation
test.erp5.testFunctionalRJSNotification
test.erp5.testFunctionalRJSMatrixbox
test.erp5.testFunctionalRJSEditorGadget
\ No newline at end of file
test.erp5.testFunctionalRJSEditorGadget
test.erp5.testFunctionalRJSRecoverPassword
\ No newline at end of file
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