Commit 1d4b96ae authored by Tomáš Peterka's avatar Tomáš Peterka Committed by Tomáš Peterka

[hal_json_style+renderjs_ui] Support Actions on multiple objects

-  introspect any dialog method for `uids` argument
-  introspect any list_method inside dialog for `uid` argument
-  module-level Object JIO Actions are templated and they expect `query` parameter
-  use `form_id` to obtain previous search result
-  warn user when applying an action on unrestricted set of documents
parent 2f41d248
......@@ -97,8 +97,7 @@ try:
request.set('editable_mode', 1)
form.validate_all_to_request(request)
request.set('editable_mode', editable_mode)
default_skin = context.getPortalObject().portal_skins.getDefaultSkin()
default_skin = portal.portal_skins.getDefaultSkin()
allowed_styles = ("ODT", "ODS", "Hal", "HalRestricted")
if getattr(getattr(context, dialog_method), 'pt', None) == "report_view" and \
request.get('your_portal_skin', default_skin) not in allowed_styles:
......@@ -106,9 +105,9 @@ try:
# Form is OK, it's just this field - style so we return back form-wide error
# for which we don't have support out-of-the-box thus we manually craft it
# XXX TODO: Form-wide validation errors
return context.Base_renderMessage(
translate('Only ODT, ODS, Hal and HalRestricted skins are allowed for reports '\
'in Preferences - User Interface - Report Style'),
return context.Base_renderForm(dialog_id,
message=translate('Only ODT, ODS, Hal and HalRestricted skins are allowed for reports '\
'in Preferences - User Interface - Report Style'),
level=WARNING)
except FormValidationError as validation_errors:
......@@ -168,42 +167,43 @@ if len(listbox_id_list):
listbox_line_list = tuple(listbox_line_list)
kw[listbox_id] = request_form[listbox_id] = listbox_line_list
# Check if the selection changed
if hasattr(kw, 'previous_md5_object_uid_list'):
selection_list = context.portal_selections.callSelectionFor(kw['list_selection_name'], context=context)
if selection_list is not None:
object_uid_list = map(lambda x:x.getObject().getUid(), selection_list)
error = context.portal_selections.selectionHasChanged(kw['previous_md5_object_uid_list'], object_uid_list)
if error:
error_message = context.Base_translateString("Sorry, your selection has changed.")
# Handle selection the new way
# First check for an query in form parameters - if they are there
# that means previous view was a listbox with selected stuff so recover here
query = extra_param.get("query", None)
select_all = int(extra_param.pop("_select_all", "0"))
if query != "" or (query == "" and select_all > 0):
listbox = getattr(context, form_id).Form_getListbox()
if listbox is not None:
kw['uids'] = [int(getattr(document, "uid"))
for document in context.Base_searchUsingListbox(listbox, query)]
else:
log('Action {} should not specify `uids` as its parameters when it does not take object list from the previous view!'.format(dialog_method), level=ERROR)
elif query == "" and select_all == 0 and dialog_method != update_method: # do not interrupt on UPDATE
return context.Base_renderForm(
dialog_id,
message=translate("All documents are selected! Submit again to proceed or Cancel and narrow down your search."),
level=WARNING,
keep_items={'_select_all': 1},
query=query)
# 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
# if dialog_category is object_search, then edit the selection
if dialog_category == "object_search" :
context.portal_selections.setSelectionParamsFor(kw['selection_name'], kw)
# if we have checked line in listbox, modify the selection
listbox_uid = kw.get('listbox_uid', None)
# In some cases, the listbox exists, is editable, but the selection name
# has no meaning, for example fast input dialogs.
# In such cases, we must not try to update a non-existing selection.
if listbox_uid is not None and kw.has_key('list_selection_name'):
uids = kw.get('uids')
selected_uids = context.portal_selections.updateSelectionCheckedUidList(
kw['list_selection_name'],
listbox_uid, uids)
# Remove empty values for make_query.
clean_kw = dict((k, v) for k, v in kw.items() if v not in (None, [], ()))
portal.portal_selections.setSelectionParamsFor(kw['selection_name'], kw)
# Add rest of extra param into arguments of the target method
kw.update(extra_param)
# Finally we will call the Dialog Method
# Handle deferred style, unless we are executing the update action
if dialog_method != update_method and clean_kw.get('deferred_style', 0):
clean_kw['deferred_portal_skin'] = clean_kw.get('portal_skin', None)
if dialog_method != update_method and kw.get('deferred_style', 0):
kw['deferred_portal_skin'] = kw.get('portal_skin', None)
# XXX Hardcoded Deferred style name
clean_kw['portal_skin'] = 'Deferred'
kw['portal_skin'] = 'Deferred'
page_template = getattr(getattr(context, dialog_method), 'pt', None)
......@@ -211,21 +211,19 @@ if dialog_method != update_method and clean_kw.get('deferred_style', 0):
# Limit Reports in Deferred style to known working styles
if request_form.get('your_portal_skin', None) not in ("ODT", "ODS"):
# RJS own validation - deferred option works here only with ODS/ODT skins
return context.Base_renderMessage(
translate('Deferred reports are possible only with preference '\
'"Report Style" set to "ODT" or "ODS"'),
return context.Base_renderForm(dialog_id,
message=translate('Deferred reports are possible only with preference '\
'"Report Style" set to "ODT" or "ODS"'),
level=WARNING)
# If the action form has report_view as it's method, it
if page_template != 'report_view':
# use simple wrapper
clean_kw['deferred_style_dialog_method'] = dialog_method
kw['deferred_style_dialog_method'] = dialog_method
kw['deferred_style_dialog_method'] = dialog_method
request.set('deferred_style_dialog_method', dialog_method)
dialog_method = 'Base_activateSimpleView'
url_params_string = make_query(clean_kw)
# Never redirect in JSON style - do as much as possible here.
# At this point the 'dialog_method' should point to a form (if we are in report)
# if we are not in Deferred mode - then it points to `Base_activateSimpleView`
......@@ -234,11 +232,11 @@ if True:
if dialog_method != update_method:
# When we are not executing the update action, we have to change the skin
# manually,
if 'portal_skin' in clean_kw:
new_skin_name = clean_kw['portal_skin']
context.getPortalObject().portal_skins.changeSkin(new_skin_name)
if 'portal_skin' in kw:
new_skin_name = kw['portal_skin']
portal.portal_skins.changeSkin(new_skin_name)
request.set('portal_skin', new_skin_name)
deferred_portal_skin = clean_kw.get('deferred_portal_skin')
deferred_portal_skin = kw.get('deferred_portal_skin')
if deferred_portal_skin:
# has to be either ODS or ODT because only those contain `form_list`
request.set('deferred_portal_skin', deferred_portal_skin)
......@@ -258,17 +256,21 @@ if True:
# with the content of REQUEST.URL
request.set('URL', '%s/%s' % (context.absolute_url(), dialog_method))
# RJS: If we are in deferred mode - call the form directly and return
# dialog method is now `Base_activateSimpleView` - the only script in
# deferred portal_skins folder
if clean_kw.get('deferred_style', 0):
return dialog_form(**kw) # deferred form should return redirect with a message
# RJS: If skin selection is different than Hal* then ERP5Document_getHateoas
# does not exist and we call form method directly
# If update_method was clicked and the target is the original dialog form then we must not call dialog_form directly because it returns HTML
if clean_kw.get("portal_skin", context.getPortalObject().portal_skins.getDefaultSkin()) not in ("Hal", "HalRestricted", "View"):
return dialog_form(**kw)
# Only in case we are not updating but executing - then proceed with direct
# execution based on Skin selection
if dialog_method != update_method:
# RJS: If we are in deferred mode - call the form directly and return
# dialog method is now `Base_activateSimpleView` - the only script in
# deferred portal_skins folder
if kw.get('deferred_style', 0):
return dialog_form(**kw) # deferred form should return redirect with a message
# RJS: If skin selection is different than Hal* then ERP5Document_getHateoas
# does not exist and we call form method directly
# If update_method was clicked and the target is the original dialog for
# then we must not call dialog_form directly because it returns HTML
if kw.get("portal_skin", portal.portal_skins.getDefaultSkin()) not in ("Hal", "HalRestricted", "View"):
return dialog_form(**kw)
# dialog_form can be anything from a pure python function, class method to ERP5 Form or Python Script
try:
......
......@@ -33,6 +33,9 @@ Traverse renders arbitrary View. It can be a Form or a Script.
:param relative_url: string, MANDATORY for obtaining the traversed_document. Calling this script directly on an object should be
forbidden in code (but it is not now).
:param view: {str} mandatory. the view reference as defined on a Portal Type (e.g. "view" or "publish_view")
:param query: {str} optional, is a remaining from the search on a previous view. Query is used to replace selections.
It provides complete information together with listbox configuration so we are able to pass a list of UIDs
to methods which require it. This allows dialogs to show selection from previous view.
:param extra_param_json: {str} BASE64 encoded JSON with parameters for getHateoas script. Content will be put to the REQUEST so
it is accessible to all Scripts and TALES expressions. If view contains embedded **dialog** form then
fields will be added to that form to preserve the values for the next step.
......@@ -458,6 +461,8 @@ url_template_dict = {
"&relative_url=%(relative_url)s&view=%(view)s",
"traverse_generator_action": "%(root_url)s/%(script_id)s?mode=traverse" + \
"&relative_url=%(relative_url)s&view=%(view)s&extra_param_json=%(extra_param_json)s",
"traverse_generator_action_module": "%(root_url)s/%(script_id)s?mode=traverse" + \
"&relative_url=%(relative_url)s&view=%(view)s&extra_param_json=%(extra_param_json)s{&query}",
"traverse_template": "%(root_url)s/%(script_id)s?mode=traverse" + \
"{&relative_url,view}",
......@@ -465,10 +470,15 @@ url_template_dict = {
"search_template": "%(root_url)s/%(script_id)s?mode=search" + \
"{&query,select_list*,limit*,sort_on*,local_roles*,selection_domain*}",
"worklist_template": "%(root_url)s/%(script_id)s?mode=worklist",
# Custom search comes with Listboxes where "list_method" is specified. We pass even listbox's
# own URL so the search can resolve template fields for proper rendering/formatting/editability
# of the results (because they will be backed up with real documents).
# :param extra_param_json: contains mainly previous form id to replicate previous search (it is a replacement for Selections)
"custom_search_template": "%(root_url)s/%(script_id)s?mode=search" + \
"&relative_url=%(relative_url)s" \
"&form_relative_url=%(form_relative_url)s" \
"&list_method=%(list_method)s" \
"&extra_param_json=%(extra_param_json)s" \
"&default_param_json=%(default_param_json)s" \
"{&query,select_list*,limit*,sort_on*,local_roles*,selection_domain*}",
# Non-editable searches suppose the search results will be rendered as-is and no template
......@@ -495,6 +505,12 @@ default_document_uri_template = url_template_dict["jio_get_template"]
Base_translateString = context.getPortalObject().Base_translateString
def lazyUidList(traversed_document, listbox, query):
"""Clojure providing lazy list of UIDs selected from a previous Listbox."""
return lambda: [int(getattr(document, "uid"))
for document in traversed_document.Base_searchUsingListbox(listbox, query)]
def getRealRelativeUrl(document):
return '/'.join(portal.portal_url.getRelativeContentPath(document))
......@@ -871,7 +887,10 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
"form_relative_url": "%s/%s" % (getFormRelativeUrl(form), field.id),
"list_method": list_method_name,
"default_param_json": urlsafe_b64encode(
json.dumps(ensureSerializable(list_method_query_dict)))
json.dumps(ensureSerializable(list_method_query_dict))),
# in case of a dialog the form_id points to previous form, otherwise current form
"extra_param_json": urlsafe_b64encode(
json.dumps(ensureSerializable({"form_id": REQUEST.get('form_id', form.id)})))
}
# once we imprint `default_params` into query string of 'list method' we
# don't want them to propagate to the query as well
......@@ -1044,6 +1063,19 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
# 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 = extra_param_json.pop("form_id", "") or REQUEST.get("form_id", "")
last_listbox = None
# ... so we can do some magic with it (especially embedded listbox if exists)!
try:
if last_form_id:
last_form = getattr(context, last_form_id)
last_listbox = last_form.Form_getListbox()
# In order not to use Selections we need to pass all search attributes to *a listbox inside the form dialog*
# in case there was a listbox in the previous form. No other case!
if last_listbox:
# If a Lisbox's list_method takes `uid` as input parameter then it will be ready in the request. But the actual
# computation is too expensive so we make it lazy (and evaluate any callable at `selectKwargsForCallable`)
# UID will be used in (Listbox's|RelationField's) list_method as a constraint if defined in parameters
REQUEST.set("uid", lazyUidList(traversed_document, last_listbox, query))
except AttributeError:
pass
......@@ -1100,6 +1132,18 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
if REQUEST.get('cancel_url', None):
renderHiddenField(response_dict, "cancel_url", REQUEST.get('cancel_url'))
# Let's support Selections!
# There are two things needed
# - `uid` parameter of list_method of listbox (if present) in this dialog
# - `uids` or `brain` to a Dialog Form Action Method
#
# We can serialize `uids` into a hidden form field but we cannot do it with brain so we
# simply put "query" in a hidden field and construct `brain` from it in Base_callDialogMethod
dialog_method_kwargs = selectKwargsForCallable(getattr(traversed_document, form.action), {}, {'brain': None, 'uids': None})
if 'uids' in dialog_method_kwargs:
# If we do not have "query" in the REQUEST but the Dialog Method requires uids
# then we still should inject empty "query" in the dialog call
extra_param_json["query"] = query or REQUEST.get("query", "")
else:
# In form_view we place only form_id in the request form
renderHiddenField(response_dict, 'form_id', form.id)
......@@ -1194,11 +1238,6 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
# end-if report_section
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:
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
# metadata about the form (its hash and dynamic fields)
renderHiddenField(response_dict, 'extra_param_json', json.dumps(extra_param_json))
......@@ -1480,11 +1519,18 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
# select correct URL template based on action_type and form page template
url_template_key = "traverse_generator"
# Modify Actions on Module - they need access to current form_id and runtime
# information such as `query` in case they operate on selections!
if erp5_action_key not in ("view", "object_view", "object_jio_view"):
url_template_key = "traverse_generator_action"
if traversed_document.getPortalType() in portal.getPortalModuleTypeList():
url_template_key = "traverse_generator_action_module"
erp5_action_list[-1]['templated'] = True
# but when we do not have the last form id we do not pass is of course
if not (current_action.get('view_id', '') or last_form_id):
url_template_key = "traverse_generator"
if 'templated' in erp5_action_list[-1]:
del erp5_action_list[-1]['templated']
# some dialogs need previous form_id when rendering to pass UID to embedded Listbox
extra_param_json['form_id'] = current_action['view_id'] \
......@@ -1651,6 +1697,10 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
# > Method 'search' is used for getting related objects as well which are
# > not backed up by a ListBox thus the value resolution would have to be
# > there anyway. It is better to use one code for all in this case.
#
# How do you deal with old-style Selections?
# > We simply do not use them. All Document selection is handled via passing
# > "query" parameter to Base_callDialogMethod or introspecting list_methods.
#################################################
if REQUEST.other['method'] != "GET":
response.setStatus(405)
......
......@@ -630,7 +630,7 @@ class TestERP5Document_getHateoas_mode_traverse(ERP5HALJSONStyleSkinsMixin):
self.assertEqual(result_dict['_embedded']['_view']['listbox']['editable_column_list'], [['id', 'ID'], ['title', 'Title'], ['quantity', 'quantity'], ['start_date', 'Date']])
self.assertEqual(result_dict['_embedded']['_view']['listbox']['sort_column_list'], [['id', 'ID'], ['title', 'Title'], ['quantity', 'Quantity'], ['start_date', 'Date']])
self.assertEqual(result_dict['_embedded']['_view']['listbox']['list_method_template'],
'%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=search&relative_url=foo_module%%2F%s&form_relative_url=portal_skins/erp5_ui_test/Foo_view/listbox&list_method=objectValues&default_param_json=eyJwb3J0YWxfdHlwZSI6IFsiRm9vIExpbmUiXSwgImlnbm9yZV91bmtub3duX2NvbHVtbnMiOiB0cnVlfQ=={&query,select_list*,limit*,sort_on*,local_roles*,selection_domain*}' % (self.portal.absolute_url(), document.getId()))
'%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=search&relative_url=foo_module%%2F%s&form_relative_url=portal_skins/erp5_ui_test/Foo_view/listbox&list_method=objectValues&extra_param_json=eyJmb3JtX2lkIjogIkZvb192aWV3In0=&default_param_json=eyJwb3J0YWxfdHlwZSI6IFsiRm9vIExpbmUiXSwgImlnbm9yZV91bmtub3duX2NvbHVtbnMiOiB0cnVlfQ=={&query,select_list*,limit*,sort_on*,local_roles*,selection_domain*}' % (self.portal.absolute_url(), document.getId()))
self.assertEqual(result_dict['_embedded']['_view']['listbox']['domain_root_list'], [['foo_category', 'FooCat'], ['foo_domain', 'FooDomain'], ['not_existing_domain', 'NotExisting']])
NBSP_prefix = u'\xA0' * 4
self.assertEqual(result_dict['_embedded']['_view']['listbox']['domain_dict'], {'foo_domain': [['a', 'a'], ['%sa1' % NBSP_prefix, 'a/a1'], ['%sa2' % NBSP_prefix, 'a/a2'], ['b', 'b']], 'foo_category': [['a', 'a'], ['a/a1', 'a/a1'], ['a/a2', 'a/a2'], ['b', 'b']]})
......@@ -910,7 +910,7 @@ class TestERP5Document_getHateoas_mode_traverse(ERP5HALJSONStyleSkinsMixin):
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['editable_column_list'], [['time', 'Time'], ['comment', 'Comment'], ['error_message', 'Error Message']])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['sort_column_list'], [])
self.assertEqual(result_dict['_embedded']['_view']['report_section_list'][1]['listbox']['list_method_template'],
'%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=search&relative_url=foo_module%%2F%s&form_relative_url=portal_skins/erp5_core/Base_viewWorkflowHistory/listbox&list_method=Base_getWorkflowHistoryItemList&default_param_json=eyJ3b3JrZmxvd19pZCI6ICJmb29fd29ya2Zsb3ciLCAiY2hlY2tlZF9wZXJtaXNzaW9uIjogIlZpZXciLCAid29ya2Zsb3dfdGl0bGUiOiAiRm9vIFdvcmtmbG93IiwgImlnbm9yZV91bmtub3duX2NvbHVtbnMiOiB0cnVlfQ=={&query,select_list*,limit*,sort_on*,local_roles*,selection_domain*}' % (self.portal.absolute_url(), document.getId()))
'%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=search&relative_url=foo_module%%2F%s&form_relative_url=portal_skins/erp5_core/Base_viewWorkflowHistory/listbox&list_method=Base_getWorkflowHistoryItemList&extra_param_json=eyJmb3JtX2lkIjogIkJhc2Vfdmlld1dvcmtmbG93SGlzdG9yeSJ9&default_param_json=eyJ3b3JrZmxvd19pZCI6ICJmb29fd29ya2Zsb3ciLCAiY2hlY2tlZF9wZXJtaXNzaW9uIjogIlZpZXciLCAid29ya2Zsb3dfdGl0bGUiOiAiRm9vIFdvcmtmbG93IiwgImlnbm9yZV91bmtub3duX2NvbHVtbnMiOiB0cnVlfQ=={&query,select_list*,limit*,sort_on*,local_roles*,selection_domain*}' % (self.portal.absolute_url(), document.getId()))
@simulate('Base_getRequestUrl', '*args, **kwargs',
......@@ -961,6 +961,25 @@ class TestERP5Document_getHateoas_mode_traverse(ERP5HALJSONStyleSkinsMixin):
self.assertEqual(result_dict['group_list'][0][1][0], ['my_id', {'meta_type': 'ProxyField'}])
self.assertEqual(result_dict['_debug'], "traverse")
@simulate('Base_getRequestUrl', '*args, **kwargs',
'return "http://example.org/bar"')
@simulate('Base_getRequestHeader', '*args, **kwargs',
'return "application/hal+json"')
@changeSkin('Hal')
def test_getHateoasForm_action_module(self):
fake_request = do_fake_request("GET")
result = self.portal.web_site_module.hateoas.ERP5Document_getHateoas(REQUEST=fake_request, mode="traverse", relative_url="foo_module", view='view')
self.assertEquals(fake_request.RESPONSE.status, 200)
self.assertEquals(fake_request.RESPONSE.getHeader('Content-Type'),
"application/hal+json"
)
result_dict = json.loads(result)
for object_jio_action in result_dict['_links']['action_object_jio_action']:
# the link is a template
self.assertTrue(object_jio_action['templated'])
self.assertTrue("{&" in object_jio_action['href'])
class TestERP5Document_getHateoas_mode_search(ERP5HALJSONStyleSkinsMixin):
@simulate('Base_getRequestHeader', '*args, **kwargs',
......
<?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_jio_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_jio_action</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>empty_mass_action</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>10.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Empty Mass Action</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}/Folder_doNothingDialog</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from Products.ERP5Type.Log import log
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."})
<?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>Folder_doNothing</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>
<string>listbox</string>
</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/>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Folder_doNothingDialog</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>Folder_doNothingDialog</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_dialog</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>Do Massively Nothing</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>Folder_doNothingDialog</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/>
</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>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>listbox</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>FooModule_viewFooList</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>request/form_id</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
"""
Used to set properties for Listbox
"""
from Products.ERP5Type.Log import log
field = context
d = dict(
field_title = 'Foos',
field_description = '',
......@@ -55,8 +56,20 @@ d = dict(
d.update(context.REQUEST)
d.update(kw)
# If the listbox is a proxy field, replace it with just a listbox
# before it was always failing on "form_id was not transferred"
if field.meta_type == "ProxyField":
form = field.aq_parent
field_id = field.getId()
field_title = field.getTitle()
log("Replacing ProxyField with a Listbox for {!s}.{!s}".format(form, context))
form.manage_delObjects(field_id)
form.manage_addField(field_id, field_title, 'ListBox')
field = getattr(form, 'listbox')
#context.log('ListBox_setPropertyList', 'kw = %r, d = %r' % (kw, d,))
r = context.form.validate(d)
context.manage_edit_xmlrpc(r)
r = field.form.validate(d)
field.manage_edit_xmlrpc(r)
return 'Set Successfully.'
......@@ -10,6 +10,7 @@ Foo Line | view_contentlist
Foo Line | view_dynamic_matrixbox
Foo Line | view_matrixbox
Foo Module | do_nothing_report_jio
Foo Module | empty_mass_action
Foo Module | list
Foo Module | list_ui
Foo Module | search
......@@ -22,7 +23,6 @@ Foo | dummy_multicheckboxfield_report
Foo | dummy_multilistfield_report
Foo | dummy_radiofield_report
Foo | dummy_report
Foo | fail_dialog_action_jio
Foo | list
Foo | select_bar
Foo | view
......
/*global window, rJS, RSVP, Handlebars, calculatePageTitle, ensureArray */
/*global window, rJS, RSVP, Handlebars, UriTemplate, calculatePageTitle, ensureArray */
/*jslint nomen: true, indent: 2, maxerr: 3 */
(function (window, rJS, RSVP, Handlebars, calculatePageTitle, ensureArray) {
(function (window, rJS, RSVP, Handlebars, UriTemplate, calculatePageTitle, ensureArray) {
"use strict";
/////////////////////////////////////////////////////////////////
......@@ -19,15 +19,15 @@
* @param {Array} command_list - array of links obtained from ERP5 HATEOAS
*/
function renderLinkList(gadget, title, icon, erp5_link_list) {
return new RSVP.Queue()
.push(function () {
return gadget.getUrlParameter("extended_search")
.push(function (query) {
// obtain RJS links from ERP5 links
return RSVP.all(
erp5_link_list.map(function (erp5_link) {
return gadget.getUrlFor({
"command": 'change',
"options": {
"view": erp5_link.href,
"view": UriTemplate.parse(erp5_link.href).expand({query: query}),
"page": undefined
}
});
......@@ -62,6 +62,7 @@
.declareAcquiredMethod("translateHtml", "translateHtml")
.declareAcquiredMethod("getUrlFor", "getUrlFor")
.declareAcquiredMethod("updateHeader", "updateHeader")
.declareAcquiredMethod("getUrlParameter", "getUrlParameter")
/////////////////////////////////////////////////////////////////
// declared methods
......@@ -105,4 +106,4 @@
});
});
}(window, rJS, RSVP, Handlebars, calculatePageTitle, ensureArray));
\ No newline at end of file
}(window, rJS, RSVP, Handlebars, UriTemplate, calculatePageTitle, ensureArray));
\ No newline at end of file
......@@ -216,7 +216,7 @@
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
<value> <string>superkato</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
......@@ -230,7 +230,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>965.57861.34804.9762</string> </value>
<value> <string>966.29724.45106.17220</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -248,7 +248,7 @@
</tuple>
<state>
<tuple>
<float>1519987107.92</float>
<float>1523021357.19</float>
<string>UTC</string>
</tuple>
</state>
......
/*global window, rJS, RSVP, Handlebars, calculatePageTitle, ensureArray */
/*global window, rJS, RSVP, Handlebars, UriTemplate, calculatePageTitle, ensureArray */
/*jslint nomen: true, indent: 2, maxerr: 3 */
(function (window, rJS, RSVP, Handlebars, calculatePageTitle, ensureArray) {
(function (window, rJS, RSVP, Handlebars, UriTemplate, calculatePageTitle, ensureArray) {
"use strict";
/////////////////////////////////////////////////////////////////
......@@ -19,15 +19,15 @@
* @param {Array} command_list - array of links obtained from ERP5 HATEOAS
*/
function renderLinkList(gadget, title, icon, erp5_link_list) {
return new RSVP.Queue()
.push(function () {
return gadget.getUrlParameter("extended_search")
.push(function (query) {
// obtain RJS links from ERP5 links
return RSVP.all(
erp5_link_list.map(function (erp5_link) {
return gadget.getUrlFor({
"command": 'change',
"options": {
"view": erp5_link.href,
"view": UriTemplate.parse(erp5_link.href).expand({query: query}),
"page": undefined
}
});
......@@ -60,6 +60,7 @@
.declareAcquiredMethod("jio_getAttachment", "jio_getAttachment")
.declareAcquiredMethod("translateHtml", "translateHtml")
.declareAcquiredMethod("getUrlFor", "getUrlFor")
.declareAcquiredMethod("getUrlParameter", "getUrlParameter")
.declareAcquiredMethod("updateHeader", "updateHeader")
/////////////////////////////////////////////////////////////////
......@@ -100,4 +101,4 @@
});
});
}(window, rJS, RSVP, Handlebars, calculatePageTitle, ensureArray));
\ No newline at end of file
}(window, rJS, RSVP, Handlebars, UriTemplate, calculatePageTitle, ensureArray));
\ No newline at end of file
......@@ -216,7 +216,7 @@
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
<value> <string>superkato</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
......@@ -230,7 +230,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>965.24987.20289.62754</string> </value>
<value> <string>966.34409.25650.52155</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -248,7 +248,7 @@
</tuple>
<state>
<tuple>
<float>1518184046.23</float>
<float>1522842712.42</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -103,6 +103,7 @@
/////////////////////////////////////////////////////////////////
.declareAcquiredMethod("redirect", "redirect")
.declareAcquiredMethod("getUrlFor", "getUrlFor")
.declareAcquiredMethod("getUrlParameter", "getUrlParameter")
.declareAcquiredMethod("updateHeader", "updateHeader")
.declareAcquiredMethod("translate", "translate")
.declareAcquiredMethod("translateHtml", "translateHtml")
......@@ -135,19 +136,23 @@
.declareMethod('render', function (options) {
var gadget = this;
// copy out wanted items from options and pass it to `changeState`
return gadget.changeState({
jio_key: options.jio_key,
view: options.view,
// ignore options.editable because dialog is always editable
erp5_document: options.erp5_document,
form_definition: options.form_definition,
erp5_form: options.erp5_form || {},
// editable: true, // ignore global editable state (be always editable)
has_update_action: Boolean(options.form_definition.update_action),
// XXX Hack of ERP5 how to express redirect to parent after success
redirect_to_parent: options.erp5_document._embedded._view.field_your_redirect_to_parent !== undefined
});
return gadget.getUrlParameter('extended_search')
.push(function (extended_search) {
return gadget.changeState({
jio_key: options.jio_key,
view: options.view,
// ignore options.editable because dialog is always editable
erp5_document: options.erp5_document,
form_definition: options.form_definition,
erp5_form: options.erp5_form || {},
// editable: true, // ignore global editable state (be always editable)
has_update_action: Boolean(options.form_definition.update_action),
// pass extended_search from previous view in case any gadget is curious
extended_search: extended_search,
// XXX Hack of ERP5 how to express redirect to parent after success
redirect_to_parent: options.erp5_document._embedded._view.field_your_redirect_to_parent !== undefined
});
});
})
.onStateChange(function (modification_dict) {
......@@ -224,7 +229,11 @@
form_options.view = form_gadget.state.view;
form_options.jio_key = form_gadget.state.jio_key;
form_options.editable = true; // dialog is always editable
// this might cause problems if the listbox in the dialog is not curious
// about the previous search
if (form_gadget.state.extended_search) {
form_options.form_definition.extended_search = form_gadget.state.extended_search;
}
return erp5_form.render(form_options);
})
.push(function () {
......
......@@ -136,12 +136,6 @@
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>processing_status_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAU=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
......@@ -163,7 +157,7 @@
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>romain</string> </value>
<value> <string>superkato</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
......@@ -185,8 +179,8 @@
</tuple>
<state>
<tuple>
<float>1406903475.67</float>
<string>GMT</string>
<float>1523969253.3</float>
<string>UTC</string>
</tuple>
</state>
</object>
......@@ -216,7 +210,7 @@
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
<value> <string>superkato</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
......@@ -230,7 +224,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>965.50744.39391.46916</string> </value>
<value> <string>966.62936.1765.16691</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -248,7 +242,7 @@
</tuple>
<state>
<tuple>
<float>1519729953.99</float>
<float>1524144844.96</float>
<string>UTC</string>
</tuple>
</state>
......@@ -260,61 +254,4 @@
</tuple>
</pickle>
</record>
<record id="5" aka="AAAAAAAAAAU=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>detect_converted_file</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>romain</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_processing_state</string> </key>
<value> <string>converted</string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>0.0.0.0</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1406902506.19</float>
<string>GMT</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
<?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>testSelectAll</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 RenderJS UI Module Action Selecting All</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="3">Test RenderJS UI Module Action Selecting All</th></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<!-- Clean Up -->
<tr><td>open</td>
<td>${base_url}/foo_module/ListBoxZuite_reset</td><td></td></tr>
<tr><td>assertTextPresent</td>
<td>Reset Successfully.</td><td></td></tr>
<!-- Shortcut for full renderjs url -->
<tr><td>store</td>
<td>${base_url}/web_site_module/renderjs_runner</td>
<td>renderjs_url</td></tr>
<tr><td>open</td>
<td>${base_url}/foo_module/FooModule_createObjects?start:int=10&amp;num:int=5</td><td></td></tr>
<tr><td>assertTextPresent</td>
<td>Created Successfully.</td><td></td></tr>
<tr><td>open</td>
<td>${base_url}/foo_module/FooModule_createObjects?start:int=20&amp;num:int=5</td><td></td></tr>
<tr><td>assertTextPresent</td>
<td>Created Successfully.</td><td></td></tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" />
<tr><td>open</td>
<td>${renderjs_url}/#/foo_module</td><td></td></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>
<tal:block tal:define="pagination_configuration python: {'header': '(1 - 3 / 10)', 'footer': 'Records 1 - 3 / 10'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/check_listbox_pagination_text" />
</tal:block>
<tr><th colspan="3">Check that sort and pagination work</th></tr>
<tr><td>waitForElementPresent</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/thead/tr/th//a[@data-i18n="ID"]</td><td></td></tr>
<tr><td>click</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/thead/tr/th//a[@data-i18n="ID"]</td><td></td></tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
<tal:block tal:define="pagination_configuration python: {'header': '(1 - 3 / 10)', 'footer': 'Records 1 - 3 / 10'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/check_listbox_pagination_text" />
</tal:block>
<tr><td>waitForText</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tbody/tr[1]/td[1]//p</td>
<td>24</td></tr>
<tr><td>waitForText</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tbody/tr[2]/td[1]//p</td>
<td>23</td></tr>
<tr><td>waitForText</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tbody/tr[3]/td[1]//p</td>
<td>22</td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/a[@data-i18n="Next"]</td><td></td></tr>
<tr><td>click</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/a[@data-i18n="Next"]</td><td></td></tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
<tal:block tal:define="pagination_configuration python: {'header': '(4 - 6 / 10)', 'footer': 'Records 4 - 6 / 10'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/check_listbox_pagination_text" />
</tal:block>
<tr><td>waitForText</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tbody/tr[1]/td[1]//p</td>
<td>21</td></tr>
<tr><td>waitForText</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tbody/tr[2]/td[1]//p</td>
<td>20</td></tr>
<tr><td>waitForText</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tbody/tr[3]/td[1]//p</td>
<td>14</td></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/wait_for_content_loaded" />
<tal:block tal:define="notification_configuration python: {'class': 'success', 'text': 'Data received.'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block>
<tr><th rowspan="1" colspan="3">Submitting, however, must warn the user</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><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/wait_for_content_loaded" />
<tal:block tal:define="notification_configuration python: {'class': 'success', 'text': 'Did nothing.'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block>
<tr><th colspan="3">However search will avoid warning</th></tr>
<tal:block tal:define='search_query string:( title: "Title 1%" )'>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/search_in_form_list" />
</tal:block>
<tal:block tal:define="pagination_configuration python: {'header': '(1 - 3 / 5)', 'footer': 'Records 1 - 3 / 5'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/check_listbox_pagination_text" />
</tal:block>
<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><th colspan="3">Make sure that search stays</th></tr>
<tal:block tal:define="pagination_configuration python: {'header': '(1 - 3 / 5)', 'footer': 'Records 1 - 3 / 5'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/check_listbox_pagination_text" />
</tal:block>
<tr><th colspan="3">Even when paginating</th></tr>
<tr><td>waitForElementPresent</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/a[@data-i18n="Next"]</td><td></td></tr>
<tr><td>click</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/a[@data-i18n="Next"]</td><td></td></tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
<tal:block tal:define="pagination_configuration python: {'header': '(4 - 5 / 5)', 'footer': 'Records 4 - 5 / 5'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/check_listbox_pagination_text" />
</tal:block>
<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/wait_for_content_loaded" />
<tal:block tal:define="notification_configuration python: {'class': 'success', 'text': 'Did nothing.'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block>
</tbody></table>
</body>
</html>
\ No newline at end of file
"""Return an Iterator over database result from `form_id`'s listbox and optional `query`.
This script is intended to be used only internally.
"""
form = getattr(context, form_id)
listbox = form.Form_getListbox()
return context.Base_searchUsingListbox(listbox, query, sort_on, limit)
<?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>form_id, query=\'\', sort_on=(), limit=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_searchUsingFormIdAndQuery</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
"""Extract search settings from a listbox and issue search using `query`.
Listbox is a search-capable component but searching using it is not straightforward. This
script solves exactly that.
Returns an iterable (most likely SearchResult instance depending on list_method definition)
"""
list_method_kwargs = dict(listbox.get_value('default_params')) or {}
# Listbox contraints portal types
portal_types = listbox.get_value('portal_types')
if portal_types:
if "portal_type" in list_method_kwargs:
if isinstance(list_method_kwargs['portal_type'], (str, unicode)):
list_method_kwargs['portal_type'] = [list_method_kwargs['portal_type'], ]
else:
list_method_kwargs['portal_type'] = []
list_method_kwargs['portal_type'].extend(portal_type_name for portal_type_name, _ in portal_types)
# query is provided by the caller because it is a runtime information
if query:
list_method_kwargs.update(full_text=query) # second overwrite the query
if limit:
list_method_kwargs.update(limit=limit)
if sort_on:
list_method_kwargs.update(sort_on=sort_on)
list_method_name = listbox.get_value('list_method').getMethodName()
list_method = getattr(context, list_method_name) # get the list_method with correct context
return list_method(**list_method_kwargs)
<?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>listbox, query=\'\', sort_on=(), limit=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_searchUsingListbox</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
# Return first listbox in a form that is enabled and not hidden
# Christophe Dumez <christophe@nexedi.com>
# This script should be used to detect a listbox without having to name it "listbox"
if form is None:
form=context
if form.meta_type != 'ERP5 Form':
return None
# XXX We should not use meta_type properly,
# XXX We need to discuss this problem.(yusei)
def isListBox(field):
if field.meta_type=='ListBox':
return True
elif field.meta_type=='ProxyField':
template_field = field.getRecursiveTemplateField()
if template_field.meta_type=='ListBox':
return True
return False
# we start with 'bottom' because most of the time
# the listbox is there.
for group in ('bottom', 'center', 'left', 'right'):
for field in form.get_fields_in_group(group):
if isListBox(field) and not(field['hidden']) and field['enabled']:
return field
"""Return first listbox in a form that is enabled and not hidden
This script should be used to detect a listbox without having to name it "listbox".
:param form: {Form} optional Form instance instead of calling this script directly on a Form
:param form_id: {str} if specified the Script must be called on currently traversed document
Christophe Dumez <christophe@nexedi.com>
"""
def isListBox(field):
if field.meta_type == "ListBox":
return True
elif field.meta_type == "ProxyField":
template_field = field.getRecursiveTemplateField()
if template_field.meta_type == "ListBox":
return True
return False
if form_id is not None:
form = getattr(context, form_id)
if form is None:
form = context
if form.meta_type not in ('ERP5 Form', 'Folder', 'ERP5 Folder'):
raise RuntimeError("Cannot get Listbox field from \"{!s}\"! Supported is only ERP5 Form and (ERP5) Folder".format(form.meta_type))
if form.has_field('listbox'):
return form.get_field('listbox')
# we start with 'bottom' because most of the time
# the listbox is there.
for group in ('bottom', 'center', 'left', 'right'):
for field in form.get_fields_in_group(group):
if (isListBox(field) and
not field.get_value('hidden') and
field.get_value('enabled')):
return field
......@@ -50,11 +50,11 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>form=None</string> </value>
<value> <string>form=None, form_id=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_getListbox</string> </value>
<value> <string>Form_getListbox</string> </value>
</item>
</dictionary>
</pickle>
......
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