Commit 2fdd5a01 authored by Tomáš Peterka's avatar Tomáš Peterka

[hal_json_style + renderjs_ui] Support Actions on multiple documents

-  [product] allow instrospecion of External Methods
-  [hal json] introspect form dialog methods for `uids` parameter to inject list of selected UIDS from the previous view
-  [hal_json] introspect listbox's list_method for `uid` parameter to inject list of selected UIDS from the previous view
-  [hal_json] add meta field `extra_param_json` into every dialog form to hold script's constant elsewhere than in fields
-  [hal_json] add dynamic field's definition into form definition so they do not need to be hardcoded in javascript
-  [product] add ability of Form to hash its data using `hash_validated_data`
-  [renderjs_ui] move form submit&response handling into one place - Page Form
-  [hal_json] Warn user about performing an action on unrestricted set of documents
-  [hal_json] Form Action failure returns full form with a message instead of just a message
-  [hal_json] Supports keep_items in Base_renderForm for Form Dialogs (where it matters)
parent 3b9d23bf
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_button</string>
<string>action_type/object_jio_button</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_button</string> </value>
<value> <string>object_jio_button</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_exchange</string>
<string>action_type/object_jio_exchange</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_exchange</string> </value>
<value> <string>object_jio_exchange</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_exchange</string>
<string>action_type/object_jio_exchange</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_exchange</string> </value>
<value> <string>object_jio_exchange</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_exchange</string>
<string>action_type/object_jio_exchange</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_exchange</string> </value>
<value> <string>object_jio_exchange</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
<string>action_type/object_jio_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
<value> <string>object_jio_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
<string>action_type/object_jio_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
<value> <string>object_jio_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_exchange</string>
<string>action_type/object_jio_exchange</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_exchange</string> </value>
<value> <string>object_jio_exchange</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_button</string>
<string>action_type/object_jio_button</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_button</string> </value>
<value> <string>object_jio_button</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_exchange</string>
<string>action_type/object_jio_exchange</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_exchange</string> </value>
<value> <string>object_jio_exchange</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_exchange</string>
<string>action_type/object_jio_exchange</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_exchange</string> </value>
<value> <string>object_jio_exchange</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_exchange</string>
<string>action_type/object_jio_exchange</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_exchange</string> </value>
<value> <string>object_jio_exchange</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_report</string>
<string>action_type/object_jio_report</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_report</string> </value>
<value> <string>object_jio_report</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_exchange</string>
<string>action_type/object_jio_exchange</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_exchange</string> </value>
<value> <string>object_jio_exchange</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_report</string>
<string>action_type/object_jio_report</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_report</string> </value>
<value> <string>object_jio_report</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_button</string>
<string>action_type/object_jio_button</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_button</string> </value>
<value> <string>object_jio_button</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_exchange</string>
<string>action_type/object_jio_exchange</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_exchange</string> </value>
<value> <string>object_jio_exchange</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_report</string>
<string>action_type/object_jio_report</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_report</string> </value>
<value> <string>object_jio_report</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_exchange</string>
<string>action_type/object_jio_exchange</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_exchange</string> </value>
<value> <string>object_jio_exchange</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_report</string>
<string>action_type/object_jio_report</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_report</string> </value>
<value> <string>object_jio_report</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_button</string>
<string>action_type/object_jio_button</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_button</string> </value>
<value> <string>object_jio_button</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
<string>action_type/object_jio_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
<value> <string>object_jio_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -16,13 +16,13 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
<string>action_type/object_jio_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
<value> <string>object_jio_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
......
......@@ -295,7 +295,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>context/AccountingTransactionModule_getLedgerItemList</string> </value>
<value> <string>python: context.AccountingTransactionModule_getLedgerItemList()</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -280,7 +280,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>context/AccountingTransactionModule_getTranslatedPortalTypeItemList</string> </value>
<value> <string>python: context.AccountingTransactionModule_getTranslatedPortalTypeItemList()</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -175,7 +175,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: list(context.getPortalItemTypeList()) + (\'Payment Transaction Group\', )</string> </value>
<value> <string>python: list(context.getPortalItemTypeList()) + [\'Payment Transaction Group\', ]</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -4,9 +4,13 @@ portal = context.getPortalObject()
countMessage = portal.portal_activities.countMessage
invoice_type_list = portal.getPortalInvoiceTypeList()
portal.portal_selections.updateSelectionCheckedUidList(selection_name, listbox_uid, uids)
selection_uid_list = portal.portal_selections.getSelectionCheckedUidsFor(
if selection_name:
portal.portal_selections.updateSelectionCheckedUidList(selection_name, listbox_uid, uids)
selection_uid_list = portal.portal_selections.getSelectionCheckedUidsFor(
selection_name)
else:
selection_uid_list = uids
if selection_uid_list:
object_list = [brain.getObject() for brain in portal.portal_catalog(uid=selection_uid_list)]
else:
......@@ -20,10 +24,9 @@ portal.portal_selections.setSelectionParamsFor('accounting_create_related_paymen
# XXX prevent to call this on the whole module:
if len(object_list) >= 1000:
return context.Base_redirect(
form_id,
keep_items={'portal_status_message': translateString(
'Refusing to process more than 1000 objects, check your selection.')})
return context.Base_renderMessage(
translateString('Refusing to process more than 1000 objects, check your selection.'),
'warning')
tag = 'payment_creation_%s' % random.randint(0, 1000)
activated = 0
......@@ -51,6 +54,7 @@ if not activated:
'No invoice in your selection.')})
# activate something on the folder
# Kato: Why?
context.activate(after_tag=tag).getTitle()
return context.Base_redirect(
......
from Products.ERP5Type.Message import translateString
from zExceptions import Redirect
portal = context.getPortalObject()
countMessage = portal.portal_activities.countMessage
portal.portal_selections.updateSelectionCheckedUidList(selection_name, listbox_uid, uids)
selection_uid_list = portal.portal_selections.getSelectionCheckedUidsFor(
if selection_name:
portal.portal_selections.updateSelectionCheckedUidList(selection_name, listbox_uid, uids)
selection_uid_list = portal.portal_selections.getSelectionCheckedUidsFor(
selection_name)
else:
selection_uid_list = uids
if selection_uid_list:
object_list = [brain.getObject() for brain in portal.portal_catalog(uid=selection_uid_list)]
else:
......@@ -24,9 +28,12 @@ for obj in object_list:
obj = obj.getObject()
if countMessage(path=obj.getPath(),
method_id='AccountingTransaction_createReversalTransaction'):
raise Redirect, "%s/view?portal_status_message=%s" % (
context.absolute_url(), translateString(
'Reversal creation already in progress, abandon.'))
return context.Base_redirect(form_id,
abort_transaction=True,
keep_items={
"portal_status_message": translateString('Reversal creation already in progress, abandon.'),
"portal_status_level": 'error'
})
obj.activate(tag=tag).AccountingTransaction_createReversalTransaction(
cancellation_amount=cancellation_amount,
date=date,
......@@ -34,11 +41,11 @@ for obj in object_list:
activated += 1
if not activated:
return context.Base_redirect(form_id,
keep_items=dict(portal_status_message=
translateString('No valid transaction in your selection.')))
return context.Base_renderMessage(
translateString('No valid transaction in your selection.'), 'error')
# activate something on the folder
# Kato: ehm ... why?
context.activate(after_tag=tag).getTitle()
return context.Base_redirect(form_id,
......
......@@ -114,6 +114,7 @@ params['select_dict'] = select_dict
if not params.get('accounting_transaction.section_uid'):
params.setdefault('group_by', ('uid',))
# Kato: Be explicit! You have no idea what is in the **params!
if stat:
return context.countFolder(**params)
return context.searchFolder(**params)
......@@ -8,11 +8,13 @@ psm = Base_translateString('Nothing matches.')
request = container.REQUEST
# update selected uids
portal.portal_selections.updateSelectionCheckedUidList(
if list_selection_name:
portal.portal_selections.updateSelectionCheckedUidList(
list_selection_name, uids=uids, listbox_uid=listbox_uid, REQUEST=request)
uids = portal.portal_selections.getSelectionCheckedUidsFor(list_selection_name)
uids = portal.portal_selections.getSelectionCheckedUidsFor(list_selection_name)
# XXX when should it be validated ?
# Kato: It is already validated because this is Form Dialog script
if node == '':
node = context.REQUEST.get('field_your_node', node)
if mirror_section == '':
......@@ -22,9 +24,9 @@ if grouping == '':
grouping = request.get('your_grouping',
request.get('field_your_grouping',
grouping))
# edit selection for dialog parameters
portal.portal_selections.setSelectionParamsFor(
if list_selection_name:
# edit selection for dialog parameters
portal.portal_selections.setSelectionParamsFor(
'grouping_reference_fast_input_selection',
params=dict(node=node,
grouping=grouping,
......@@ -42,8 +44,8 @@ if uids:
request.set('total_selected_amount', total_selected_amount)
if update:
request.set('portal_status_message', Base_translateString('Updated'))
return context.AccountingTransactionModule_viewGroupingFastInputDialog(request)
return context.Base_renderForm('AccountingTransactionModule_viewGroupingFastInputDialog',
keep_items={'portal_status_message': Base_translateString('Updated')})
# otherwise, try to group...
......@@ -53,7 +55,7 @@ if grouping == 'grouping':
if grouped_line_list:
psm = Base_translateString('${grouped_line_count} lines grouped.',
mapping=dict(grouped_line_count=len(grouped_line_list)))
if list_selection_name:
# make sure nothing will be checked next time
portal.portal_selections.setSelectionCheckedUidsFor(list_selection_name, [])
......@@ -119,8 +121,9 @@ else:
psm = Base_translateString('${ungrouped_line_count} lines ungrouped.',
mapping=dict(ungrouped_line_count=len(ungrouped_line_list)))
if list_selection_name:
# make sure nothing will be checked next time
portal.portal_selections.setSelectionCheckedUidsFor(list_selection_name, [])
request.set('portal_status_message', psm)
return context.AccountingTransactionModule_viewGroupingFastInputDialog(request)
return context.Base_renderForm('AccountingTransactionModule_viewGroupingFastInputDialog',
keep_items={'portal_status_message': psm})
......@@ -223,7 +223,7 @@
</item>
<item>
<key> <string>first_item</string> </key>
<value> <int>0</int> </value>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>hidden</string> </key>
......
......@@ -4,8 +4,9 @@
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L894">testAccountingReports.py:TestAccountingReports.testAccountStatement</a>
Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L894">testAccountingReports.py:TestAccountingReports.testAccountStatement</a> (expected failure)
</th></tr>
<!-- This report is not using template fields and overwrites columns dynamicaly in Selection. This report will not be tested -->
</thead>
<tbody
......@@ -43,17 +44,6 @@ Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product
<tr><td>click</td>
<td>//a[@data-i18n="Account Statement"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_node"]/div/div[1]//select</td><td></td></tr>
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_node"]/div/div[1]//select</td>
<td>value=account_module/receivable</td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/demo_group/sub1</td></tr>
<tr><td>waitForElementPresent</td>
<td>//input[@name="field_your_at_date"]</td><td></td></tr>
<tr><td>type</td>
......@@ -77,14 +67,57 @@ Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=delivered</td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_node"]/div/div[1]//select</td><td></td></tr>
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_node"]/div/div[1]//select</td>
<td>value=account_module/receivable</td></tr>
<tr><td colspan="3">Test update button on a valid form</td></tr>
<!-- group/client does not have any Bank account assigned so we test on empty "Bank Account" field -->
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/client</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//button[@type="submit" and @name="action_update"]</td><td></td></tr>
<tr><td>waitForElementNotPresent</td>
<td>//select[@name="field_your_payment"]/option[2]</td><td></td></tr>
<tr><td>assertElementNotPresent</td>
<td>//select[@name="field_your_payment"]/option[2]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
<tr><td>select</td>
<td>//select[@name="field_your_section_category"]</td>
<td>value=group/demo_group/sub1</td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_portal_skin"]</td>
<td>Hal</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//button[@type="submit" and @name="action_update"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_payment"]/option[2]</td><td></td></tr>
<tr><td>assertElementPresent</td>
<td>//select[@name="field_your_payment"]/option[@data-i18n="Bank1"]</td><td></td></tr>
<tr><td>assertElementPresent</td>
<td>//select[@name="field_your_payment"]/option[@data-i18n="Bank2"]</td><td></td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit" and @name="action_confirm"]</td><td></td></tr>
<tr><td>store</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}//../nav//span[text()="6 Records"]</td><td></td></tr>
<td>${table}/../nav//span[text()="6 Records"]</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[@data-gadget-scope="field_your_currency"]//p[text()="EUR"]</td><td></td></tr>
......
<html>
<head><title>Test Aged Balance Report</title></head>
<head><title>Test Aged Balance Report (expected failure)</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><th rowspan="1" colspan="4">
Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L4904">testAccountingReports.py:test_simple_aged_creditor_report_detailed.testOtherPartiesReport</a>
</th></tr>
<tr><td rowspan="1" colspan="3">
Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tests/testAccountingReports.py#L4904">test_simple_aged_creditor_report_detailed.testOtherPartiesReport</a> (expected failure)
</td></tr>
</thead>
<tbody
......@@ -32,11 +32,17 @@ Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Export"]</td><td></td></tr>
<!-- Remove when the test is fixed -->
<tr><td>setTimeout</td><td>2000</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n="Aged Balance"]</td><td></td></tr>
<!-- Remove when the test is fixed -->
<tr><td>assertElementPresent</td>
<td>//a[@data-i18n="Aged Balance"]</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n="Aged Balance"]</td><td></td></tr>
<!-- Remove when the test is fixed -->
<tr><td>setTimeout</td><td>36000</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//select[@name="field_your_section_category"]</td><td></td></tr>
......@@ -74,6 +80,11 @@ Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=delivered</td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_portal_skin"]</td>
<td>Hal</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
......@@ -81,6 +92,7 @@ Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product
<tr><td>store</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_field_listbox.html"]//table</td>
<td>table</td></tr>
<tr><td>waitForElementPresent</td>
<td>${table}/../nav//span[text()="1 Records"]</td><td></td></tr>
......
......@@ -47,7 +47,7 @@ Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product
<tr><td>waitForElementPresent</td><td>//input[@name="field_your_from_date"]</td><td></td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_from_date"]</td>
<td>${year}-1-1</td></tr>
<td>${year}-01-01</td></tr>
<tr><td>waitForElementPresent</td><td>//input[@name="field_your_at_date"]</td><td></td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_at_date"]</td>
......@@ -75,6 +75,15 @@ Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=delivered</td></tr>
<tr><th colspan="3">Override fields set by Action URL parameters to out values</th></tr>
<!-- No Titles are displayed if "export" is checked. Should we remove the check for title (<h3>) instead? -->
<tr><td>click</td>
<td>//input[@name="field_your_export"]</td><td></td></tr>
<!-- Force rendering inplace by replacinf ODS style with Hal -->
<tr><td>type</td>
<td>//input[@name="field_your_portal_skin"]</td>
<td>Hal</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
......
......@@ -69,6 +69,11 @@ Copy of <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product/ERP5/tes
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=delivered</td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_portal_skin"]</td>
<td>Hal</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
......
......@@ -71,6 +71,11 @@ Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product
<tr><td>select</td>
<td>//div[@data-gadget-scope="field_your_simulation_state"]/div/div[1]//select</td>
<td>value=delivered</td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_portal_skin"]</td>
<td>Hal</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
......
......@@ -71,6 +71,10 @@ Copy of unittest <a href="https://lab.nexedi.com/nexedi/erp5/blob/master/product
<td>${multi_select}/div[3]//select</td>
<td></td></tr>
<tr><td>type</td>
<td>//input[@name="field_your_portal_skin"]</td>
<td>Hal</td></tr>
<tr><td>click</td>
<td>//div[@data-gadget-url="${renderjs_url}/gadget_erp5_page_form.html"]//input[@type="submit"]</td><td></td></tr>
......
erp5_full_text_mroonga_catalog
erp5_dummy_movement
erp5_ods_style
\ No newline at end of file
......@@ -142,7 +142,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>context/Event_getPreferredResponseEventSource</string> </value>
<value> <string>python:context.Event_getPreferredResponseEventSource()</string> </value>
</item>
</dictionary>
</pickle>
......@@ -155,7 +155,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>context/Event_getPreferredResponseEventSourceItemList</string> </value>
<value> <string>python:context.Event_getPreferredResponseEventSourceItemList()</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -138,7 +138,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>context/Event_getPreferredResponseEventPortalType</string> </value>
<value> <string>python:context.Event_getPreferredResponseEventPortalType()</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -129,7 +129,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>here/getResource</string> </value>
<value> <string>python:context.getResource()</string> </value>
</item>
</dictionary>
</pickle>
......@@ -142,7 +142,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>here/Event_getResponseResourceItemList</string> </value>
<value> <string>python:context.Event_getResponseResourceItemList()</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -39,7 +39,7 @@
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/update_dialog" />
<tal:block tal:define="notification_configuration python: {'class': 'success',
'text': 'Data received'}">
'text': 'Data received.'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
......
......@@ -39,7 +39,7 @@
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/update_dialog" />
<tal:block tal:define="notification_configuration python: {'class': 'success',
'text': 'Data received'}">
'text': 'Data received.'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_content_loaded" />
......
......@@ -78,7 +78,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: object.isDeletable(check_relation=False)</string> </value>
<value> <string>python: object.isDeletable(check_relation=False) and object.getPortalType() not in portal.getPortalModuleTypeList()</string> </value>
</item>
</dictionary>
</pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_delete_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>delete_document_list</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>103.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Delete Multiple Documents</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_viewDeleteDialog</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: object.getPortalType() in portal.getPortalModuleTypeList()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -16,6 +16,7 @@ return context.ERP5Document_getHateoas(
select_list=select_list,
limit=limit,
form=form,
form_data=form_data,
relative_url=relative_url,
list_method=list_method,
default_param_json=default_param_json,
......@@ -24,6 +25,8 @@ return context.ERP5Document_getHateoas(
sort_on=sort_on,
local_roles=local_roles,
selection_domain=selection_domain,
restricted=1,
extra_param_json=extra_param_json,
restricted=1
portal_status_message=portal_status_message,
portal_status_level=portal_status_level
)
......@@ -50,7 +50,7 @@
</item>
<item>
<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</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>
<key> <string>id</string> </key>
......
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>dialog_method, dialog_id, dialog_category=\'\', update_method=None, **kw</string> </value>
<value> <string>dialog_method, dialog_id, form_id, dialog_category=\'\', update_method=None, extra_param_json="{}", **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
......@@ -19,7 +19,7 @@ if context.isDeletable(check_relation=True):
else:
parent.manage_delObjects(ids= [context.getId()])
# redirect back to the container since the context was deleted
return container.Base_redirect(
return parent.Base_redirect(
keep_items={
"portal_status_message": translate("Document deleted")
})
......
"""
This script factorises code required to redirect to the appropriate
page from a script. It should probably be extended, reviewed and documented
so that less code is copied and pasted in dialog scripts.
"""UI Script to redirect the user to `context` with optional custom view `form_id`.
TODO: improve API and extensively document. ERP5Site_redirect may
be redundant.
:param keep_items: is used mainly to pass "portal_status_message" to be showed to the user
the new UI supports "portal_status_level" with values "success" or "error"
"""
from ZTUtils import make_query
import json
......@@ -40,8 +37,15 @@ response.setHeader("X-Location", "urn:jio:get:%s" % context.getRelativeUrl())
# therefor we don't need to be afraid of clashes
response.setHeader("Content-type", "application/json; charset=utf-8")
portal_status_level = keep_items.pop("portal_status_level", "success")
if portal_status_level in ("warning", "error", "fatal"):
portal_status_level = "error"
if portal_status_level in ("info", "debug", "success"):
portal_status_level = "success"
result_dict = {
'portal_status_message': "%s" % keep_items.pop("portal_status_message", ""),
'portal_status_level': "%s" % portal_status_level,
'_links': {
"self": {
# XXX Include query parameters
......
"""Render form while keeping its values back to user.
This script differs from Base_redirect that it keeps the form values in place.
:param message: {str} message to be displayed at the user
:param level: {str|int} severity of the message using ERP5Type.Log levels or their names like 'info', 'warn', 'error'
:param keep_items: {dict} items to be available in the next call. They will be either added as hidden fields to the
rendered form or in case of "portal_status_message" just displayed to the user
:param REQUEST: request
:param **kwargs: should contain parameters to ERP5Document_getHateoas such as 'query' to replace Selections
"""
keep_items = keep_items or {}
form = getattr(context, form_id)
return context.ERP5Document_getHateoas(form=form, mode='form')
if not message and "portal_status_message" in keep_items:
message = keep_items.pop("portal_status_message")
if not level and "portal_status_level" in keep_items:
level = keep_items.pop("portal_status_level")
return context.ERP5Document_getHateoas(form=form, mode='form', REQUEST=REQUEST, extra_param_json=keep_items,
portal_status_message=message, portal_status_level=level, **kwargs)
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>form_id</string> </value>
<value> <string>form_id, message=\'\', level=None, keep_items=None, REQUEST=None, **kwargs</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
......@@ -56,7 +56,7 @@
</item>
<item>
<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</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>
<key> <string>id</string> </key>
......
......@@ -567,7 +567,7 @@ class TestERP5Document_getHateoas_mode_traverse(ERP5HALJSONStyleSkinsMixin):
self.assertEqual(result_dict['_links']['action_object_view'][0]['name'], "view")
self.assertEqual(result_dict['_links']['action_workflow'][0]['href'],
"%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=traverse&relative_url=%s&view=custom_action_no_dialog&form_id=Foo_view" % (
"%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=traverse&relative_url=%s&view=custom_action_no_dialog&extra_param_json=eyJmb3JtX2lkIjogIkZvb192aWV3In0=" % (
self.portal.absolute_url(),
urllib.quote_plus(document.getRelativeUrl())))
self.assertEqual(result_dict['_links']['action_workflow'][0]['title'], "Custom Action No Dialog")
......@@ -586,7 +586,7 @@ class TestERP5Document_getHateoas_mode_traverse(ERP5HALJSONStyleSkinsMixin):
self.assertEqual(result_dict['_links']['site_root']['name'], self.portal.web_site_module.hateoas.getTitle())
self.assertEqual(result_dict['_links']['action_object_new_content_action']['href'],
"%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=traverse&relative_url=%s&view=create_a_document&form_id=Foo_view" % (
"%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=traverse&relative_url=%s&view=create_a_document&extra_param_json=eyJmb3JtX2lkIjogIkZvb192aWV3In0=" % (
self.portal.absolute_url(),
urllib.quote_plus(document.getRelativeUrl())))
self.assertEqual(result_dict['_links']['action_object_new_content_action']['title'], "Create a Document")
......@@ -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']]})
......@@ -888,7 +888,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',
......@@ -1643,9 +1643,9 @@ class TestERP5Document_getHateoas_mode_bulk(ERP5HALJSONStyleSkinsMixin):
urllib.quote_plus(document.getRelativeUrl())))
self.assertEqual(result_dict['result_list'][0]['_links']['action_object_view'][0]['title'], "View")
self.assertEqual(result_dict['result_list'][0]['_links']['action_object_view'][0]['name'], "view")
# extra_param_json contains information necessary for the getHateoas script itself (currently it is {'form_id': "Foo_view"})
self.assertEqual(result_dict['result_list'][0]['_links']['action_workflow'][0]['href'],
"%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=traverse&relative_url=%s&view=custom_action_no_dialog&form_id=Foo_view" % (
"%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=traverse&relative_url=%s&view=custom_action_no_dialog&extra_param_json=eyJmb3JtX2lkIjogIkZvb192aWV3In0=" % (
self.portal.absolute_url(),
urllib.quote_plus(document.getRelativeUrl())))
self.assertEqual(result_dict['result_list'][0]['_links']['action_workflow'][0]['title'], "Custom Action No Dialog")
......@@ -1658,7 +1658,7 @@ class TestERP5Document_getHateoas_mode_bulk(ERP5HALJSONStyleSkinsMixin):
self.assertEqual(result_dict['result_list'][0]['_links']['site_root']['name'], self.portal.web_site_module.hateoas.getTitle())
self.assertEqual(result_dict['result_list'][0]['_links']['action_object_new_content_action']['href'],
"%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=traverse&relative_url=%s&view=create_a_document&form_id=Foo_view" % (
"%s/web_site_module/hateoas/ERP5Document_getHateoas?mode=traverse&relative_url=%s&view=create_a_document&extra_param_json=eyJmb3JtX2lkIjogIkZvb192aWV3In0=" % (
self.portal.absolute_url(),
urllib.quote_plus(document.getRelativeUrl())))
self.assertEqual(result_dict['result_list'][0]['_links']['action_object_new_content_action']['title'], "Create a Document")
......@@ -1889,6 +1889,7 @@ class TestERP5Action_getHateoas(ERP5HALJSONStyleSkinsMixin):
REQUEST=fake_request,
dialog_method='Foo_doNothing', # 'Workflow_statusModify' would lead us by a different path in the code
dialog_id='Foo_viewCustomWorkflowRequiredActionDialog',
form_id='FooModule_viewFooList'
)
self.assertEqual(fake_request.RESPONSE.status, 400)
......
portal_actions | clone_document
portal_actions | create_a_document
portal_actions | delete_document
portal_actions | delete_document_list
\ No newline at end of file
......@@ -26,7 +26,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
<tal:block tal:define="form_action string:Base_doSelect;
form_id form/id;
list_mode python: True;
listmode_default_listbox python: here.ERP5Site_getListbox(form=form);
listmode_default_listbox python: form.Form_getListbox();
selection_name listmode_default_listbox/selection_name;
selection_index request/selection_index | python:0;
dummy python: selection_name and request.set('selection_name', selection_name);
......
......@@ -110,7 +110,7 @@ It contains the same columns
<td></td>
</tr>
<tr>
<td>verifyTextPresent</td>
<td>assertTextPresent</td>
<td>Deleted.</td>
<td></td>
</tr>
......@@ -186,8 +186,8 @@ It contains the same columns
<td></td>
</tr>
<tr>
<td>verifyTextPresent</td>
<td>Sorry, you can not delete 1 item.</td>
<td>assertTextPresent</td>
<td>Deleted.</td>
<td></td>
</tr>
<tr>
......@@ -217,7 +217,7 @@ It contains the same columns
</tr>
<tr>
<td>open</td>
<td>${base_url}/foo_module/view</td>
<td>${base_url}/foo_module/view?reset=1</td>
<td></td>
</tr>
<tr>
......@@ -289,7 +289,7 @@ It contains the same columns
</tr>
<tr>
<td>open</td>
<td>${base_url}/foo_module/view</td>
<td>${base_url}/foo_module/view?reset=1</td>
<td></td>
</tr>
<tr>
......
/*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
......@@ -72,7 +73,10 @@
// Get the whole view as attachment because actions can change based on
// what view we are at. If no view available than fallback to "links".
return gadget.jio_getAttachment(options.jio_key, options.view || "links")
return new RSVP.Queue()
.push(function () {
return gadget.jio_getAttachment(options.jio_key, options.view || "links");
})
.push(function (jio_attachment) {
var transition_list = ensureArray(jio_attachment._links.action_workflow),
action_list = ensureArray(jio_attachment._links.action_object_jio_action)
......@@ -105,4 +109,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.28693.12915.8550</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -248,7 +248,7 @@
</tuple>
<state>
<tuple>
<float>1519987107.92</float>
<float>1522141241.23</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>
......
<!DOCTYPE html>
<!--
data-i18n=Encountered an unknown error. Try to resubmit.
data-i18n=Input data has errors.
data-i18n=You do not have the permissions to edit the object.
data-i18n=You are offline.
data-i18n=Action succeeded.
data-i18n=Data received.
-->
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
......
......@@ -220,7 +220,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>
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>961.19210.8471.60620</string> </value>
<value> <string>966.41656.42235.61815</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1518685706.64</float>
<float>1523287594.73</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -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.50744.39391.46916</string> </value>
<value> <string>966.53063.4535.13141</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -248,7 +248,7 @@
</tuple>
<state>
<tuple>
<float>1519729953.99</float>
<float>1523541554.47</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -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>966.32967.51097.665</string> </value>
<value> <string>966.48607.41615.32324</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -248,7 +248,7 @@
</tuple>
<state>
<tuple>
<float>1522335846.96</float>
<float>1523351312.53</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -65,7 +65,7 @@
var form_options = gadget.state.erp5_form,
embedded_form = gadget.state.erp5_document._embedded._view,
rendered_form = {},
key, field;
key;
/* Remove empty non-editable fields to prevent them from displaying (business requirement).
Deleting objects inplace was not a good idea.
......
......@@ -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>966.32979.9603.63197</string> </value>
<value> <string>966.39861.30579.11912</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -248,7 +248,7 @@
</tuple>
<state>
<tuple>
<float>1522336529.93</float>
<float>1522749475.57</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -67,7 +67,7 @@
return this.changeState({
erp5_document: options.erp5_document,
form_definition: options.form_definition,
form_gadget_url: form_gadget_url,
form_gadget_url: form_gadget_url
});
})
.onStateChange(function (modification_dict) {
......
......@@ -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>962.56167.53905.31470</string> </value>
<value> <string>964.45882.29366.36147</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -248,7 +248,7 @@
</tuple>
<state>
<tuple>
<float>1508400391.84</float>
<float>1522842618.99</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -57,7 +57,7 @@
<tal:block tal:define="notification_configuration python: {'class': 'error',
'text': 'Workflow script raised.'}">
'text': 'Workflow script raised'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block>
......
......@@ -46,24 +46,18 @@
<tr>
<td>open</td>
<td>${base_url}/web_site_module/renderjs_runner/#/bar_module/1</td>
<td>${base_url}/web_site_module/renderjs_runner/#/bar_module</td>
<td></td>
</tr>
<!-- Wait for gadget to be loaded -->
<tr>
<td>waitForElementPresent</td>
<td>//div[@data-gadget-url='${base_url}/web_site_module/renderjs_runner/gadget_erp5_pt_form_view.html']</td>
<td></td>
</tr>
<tr>
<td>waitForTextPresent</td>
<td>Title 1</td>
<td>//div[@data-gadget-url='${base_url}/web_site_module/renderjs_runner/gadget_erp5_field_listbox.html']</td>
<td></td>
</tr>
<!-- Header has a link to the export page
Here, we assume that portal_types/Bar has no Object JIO Report Action -->
<!-- There is a Print action for every Document except Modules so we test on modules -->
<tr>
<td>waitForElementPresent</td>
<td>//div[@data-gadget-scope='header']//a[text()='Export' and contains(@class, 'ui-disabled')]</td>
......
<?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>testDeleteDocumentList</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</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">Test RenderJS UI</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tr><th colspan="3">Set ListBox to show the State of Foo documents (and 10 documents at once)</th></tr>
<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>
<tr><td>open</td>
<td>${base_url}/foo_module/FooModule_viewFooList/listbox/ListBox_setPropertyList?field_columns=id%7CID%0Atitle%7CTitle%0Adelivery.quantity%7CQuantity%0Asimulation_state%7CState&amp;field_lines=10</td><td></td></tr>
<tr><td>assertTextPresent</td>
<td>Set Successfully.</td><td></td></tr>
<!-- Create 3 Foo objects relation, 2 with embedded documents and one empty -->
<tr><td>open</td>
<td>${base_url}/foo_module/FooModule_createObjects?start:int=1&amp;num:int=3&amp;big_category_related:bool=True</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=4&amp;num:int=2&amp;create_line:bool=True</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=6&amp;num:int=1</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>store</td>
<td>${base_url}/web_site_module/renderjs_runner</td>
<td>renderjs_url</td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/foo_module</td><td></td></tr>
<tal:block tal:define="search_query string:( title: &quot;Title 3&quot; OR id: &quot;5&quot; )">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/search_in_form_list" />
</tal:block>
<tal:block tal:define="pagination_configuration python: {'header': '(2)', 'footer': '2 Records'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/check_listbox_pagination_text" />
</tal:block>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n='Actions']</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n='Actions']</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n='Delete Multiple Documents']</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n='Delete Multiple Documents']</td><td></td></tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/submit_dialog" />
<tr><td>waitForElementNotPresent</td>
<td>//input[@name="action_confirm"]</td><td></td></tr>
<tal:block tal:define="notification_configuration python: {'class': 'success',
'text': 'Deleted.'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block>
<tal:block tal:define="pagination_configuration python: {'header': '(2)', 'footer': '2 Records'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/check_listbox_pagination_text" />
</tal:block>
<tr><td>assertText</td>
<td>//div[@data-gadget-scope="field_listbox"]//table/tbody/tr[1]/td[4]//a</td>
<td>deleted</td></tr>
<tr><td>assertText</td>
<td>//div[@data-gadget-scope="field_listbox"]//table/tbody/tr[2]/td[4]//a</td>
<td>deleted</td></tr>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/clear_query" />
<tal:block tal:define="pagination_configuration python: {'header': '(6)', 'footer': '6 Records'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/check_listbox_pagination_text" />
</tal:block>
<tr><td>assertText</td>
<td>//div[@data-gadget-scope="field_listbox"]//table/tbody/tr[1]/td[4]//a</td>
<td>draft</td></tr>
<tr><td>assertText</td>
<td>//div[@data-gadget-scope="field_listbox"]//table/tbody/tr[3]/td[4]//a</td>
<td>deleted</td></tr>
<tr><td>assertText</td>
<td>//div[@data-gadget-scope="field_listbox"]//table/tbody/tr[5]/td[4]//a</td>
<td>deleted</td></tr>
<tr><td>assertText</td>
<td>//div[@data-gadget-scope="field_listbox"]//table/tbody/tr[6]/td[4]//a</td>
<td>draft</td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n='Actions']</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n='Actions']</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//a[@data-i18n='Delete Multiple Documents']</td><td></td></tr>
<tr><td>click</td>
<td>//a[@data-i18n='Delete Multiple Documents']</td><td></td></tr>
<tal:block tal:define="pagination_configuration python: {'header': '(6)', 'footer': '6 Records'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/check_listbox_pagination_text" />
</tal:block>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/submit_dialog" />
<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>
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/submit_dialog" />
<tr><td>waitForElementNotPresent</td>
<td>//input[@name="action_confirm"]</td><td></td></tr>
<tal:block tal:define="notification_configuration python: {'class': 'success',
'text': 'Deleted.'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/wait_for_notification" />
</tal:block>
<tal:block tal:define="pagination_configuration python: {'header': '(6)', 'footer': '6 Records'}">
<tal:block metal:use-macro="here/Zuite_CommonTemplateForRenderjsUi/macros/check_listbox_pagination_text" />
</tal:block>
<tr><td>assertText</td>
<td>//div[@data-gadget-scope="field_listbox"]//table/tbody/tr[1]/td[4]//a</td>
<td>deleted</td></tr>
<tr><td>assertText</td>
<td>//div[@data-gadget-scope="field_listbox"]//table/tbody/tr[4]/td[4]//a</td>
<td>deleted</td></tr>
<tr><td>assertText</td>
<td>//div[@data-gadget-scope="field_listbox"]//table/tbody/tr[6]/td[4]//a</td>
<td>deleted</td></tr>
</tbody></table>
</body>
</html>
\ No newline at end of file
......@@ -57,8 +57,10 @@ def getDocumentGroupByWorkflowStateList(self, form_id='', **kw):
Base_translateString = portal.Base_translateString
wf_tool = portal.portal_workflow
selection_tool = portal.portal_selections
last_form = getattr(self, form_id)
selection_name = request['selection_name']
last_listbox = last_form.Form_getListbox()
selection_name = last_listbox.get_value('selection_name')
# guess all column name from catalog schema
possible_state_list = [column_name.split('.')[1] for column_name in
......
......@@ -45,7 +45,9 @@
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
<tuple>
<string>W: 55, 2: Unused variable \'request\' (unused-variable)</string>
</tuple>
</value>
</item>
<item>
......
"""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
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=\'\'</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
"""Script to remove Documents inside a Folder.
The new UI does not make any exceptions and treat this script as a generic Dialog Form Method.
Thus it receives form_id of previous form, dialog_id of current dialog and uid of objects from
previous Listbox.
:param fixit: {int} set to 1 if this action displayed warning/error and the user resubmits
"""
from ZODB.POSException import ConflictError
from Products.CMFCore.WorkflowCore import WorkflowException
portal = context.getPortalObject()
Base_translateString = portal.Base_translateString
translate = Base_translateString
REQUEST = portal.REQUEST
try:
fixit = int(fixit)
except ValueError:
fixit = 0
uids = portal.portal_selections.getSelectionCheckedUidsFor(selection_name)
if portal.portal_selections.selectionHasChanged(md5_object_uid_list, uids):
message = Base_translateString("Sorry, your selection has changed.")
elif uids:
# Check if there is some related objets.
object_list = [x.getObject() for x in context.Folder_getDeleteObjectList(uid=uids)]
object_used = sum([x.getRelationCountForDeletion() and 1 for x in object_list])
if selection_name:
uids = portal.portal_selections.getSelectionCheckedUidsFor(selection_name)
if portal.portal_selections.selectionHasChanged(md5_object_uid_list, uids):
return context.Base_redirect(keep_items={'portal_status_message': translate("Sorry, your selection has changed.")})
if object_used > 0:
if object_used == 1:
message = Base_translateString("Sorry, 1 item is in use.")
else:
message = Base_translateString("Sorry, ${count} items are in use.",
mapping={'count': repr(object_used)})
if not uids:
return context.Base_redirect(keep_items={
'portal_status_message': translate("Please select one or more items first."),
'portal_status_level': "warning"})
if True: # useless indentation
# check if selected documents contain related objects because we
# cannot delete those
search_result = context.Folder_getDeleteObjectList(uid=uids)
object_list = [x.getObject() for x in context.Folder_getDeleteObjectList(uid=uids)]
object_list_len = len(object_list)
object_used_list = [x for x in object_list if x.getRelationCountForDeletion() > 0]
object_used_list_len = len(object_used_list)
if not fixit and object_used_list_len > 0:
if selection_name:
# if we have selection_name then we work with old-style Selections
for x in object_used_list:
uids.remove(x.getUid())
portal.portal_selections.setSelectionCheckedUidsFor(selection_name, uids)
return context.Base_renderForm(dialog_id,
Base_translateString("Unselecting ${count} out of ${total} documents with relations because they cannot be deleted.",
mapping={"count": object_used_list_len, 'total': object_list_len}),
level='warning'
)
else:
# No selection_name means we are in non-XHTML interface thus notify user that re-submission
# will trigger delete and omit the undeletable documents
# if user re-confirms we receive fixit=1 flag
return context.Base_renderForm(dialog_id,
Base_translateString('Cannot delete ${count} out of ${total} documents because of related documents. Click "Delete" again to omit them and delete the rest.',
mapping={"count": object_used_list_len, 'total': object_list_len}),
level='warning',
keep_items={'fixit': 1}
)
if fixit and object_used_list_len > 0:
# the user re-submitted the dialog after seeing an error thus remove objects with relations and delete the rest
for x in object_used_list:
uids.remove(x.getUid())
object_list = [x.getObject() for x in context.Folder_getDeleteObjectList(uid=uids)]
if True: # useless indentation adding cyclomatic complexity
# Do not delete objects which have a workflow history
object_to_remove_list = []
......@@ -54,9 +100,9 @@ elif uids:
REQUEST=REQUEST)
except ConflictError:
raise
except Exception, message:
pass
else:
except Exception as error:
return context.Base_renderMessage(str(error), "error")
else: # in the case of no exception raised report sucess
object_ids = [x.getId() for x in object_to_remove_list]
comment = Base_translateString('Deleted objects: ${object_ids}',
mapping={'object_ids': object_ids})
......@@ -70,7 +116,8 @@ elif uids:
message = Base_translateString("Deleted.")
# Change workflow state of others objects
# Try to call "delete_action" workflow transition on documents which defined it
# Failure of such a call is not a failure globally. The document was deleted anyway
not_deleted_count = 0
for object in object_to_delete_list:
# Hidden transition (without a message displayed)
......@@ -82,18 +129,8 @@ elif uids:
except:
not_deleted_count += 1
# Generate message
if not_deleted_count == 1:
message = Base_translateString("Sorry, you can not delete ${count} item.",
mapping={'count': not_deleted_count})
elif not_deleted_count > 1:
message = Base_translateString("Sorry, you can not delete ${count} items.",
mapping={'count': not_deleted_count})
qs = '?portal_status_message=%s' % message
# make sure nothing is checked after
if selection_name:
portal.portal_selections.setSelectionCheckedUidsFor(selection_name, ())
else:
message = Base_translateString("Please select one or more items first.")
return context.Base_redirect(form_id, keep_items={"portal_status_message":message})
return context.Base_redirect(form_id, keep_items={"portal_status_message": str(message)})
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>form_id=\'\',selection_index=None,object_uid=None,selection_name=None,field_id=None,cancel_url=\'\',md5_object_uid_list=\'\'</string> </value>
<value> <string>form_id=\'\',dialog_id=\'\',selection_index=None,object_uid=None,selection_name=None,field_id=None,cancel_url=\'\',md5_object_uid_list=\'\', uids=(), fixit=0, **kwargs</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
# XXX This is a hack which allow to delete non indexed Template
# Never call listFolderContents in a place where there could be million of
# documents!
if context.getPortalType() == 'Preference':
result = []
uid_list = kw.get('uid', [])
for i in context.listFolderContents():
if i.getUid() in uid_list:
if i.getUid() in uid:
result.append(i)
return result
else:
return context.portal_catalog(**kw)
# it is enough to search with uids because it is the most specific attribute
return context.portal_catalog(uid=uid, **kw)
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>**kw</string> </value>
<value> <string>uid=(), **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
......@@ -272,7 +272,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>request/proxy_field_id | python: here.portal_selections.getSelectionParamsFor(\'folder_delete_selection\').get(\'field_id\', \'listbox\')</string> </value>
<value> <string>python: here.Form_getListbox(form_id=request.get(\'form_id\')).getId()</string> </value>
</item>
</dictionary>
</pickle>
......@@ -285,7 +285,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>request/proxy_form_id | python: here.portal_selections.getSelectionParamsFor(\'folder_delete_selection\')[\'form_id\']</string> </value>
<value> <string>request/form_id</string> </value>
</item>
</dictionary>
</pickle>
......
"""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>
......
"""Render form while keeping its values back to user.
This script differs from Base_redirect that it keeps the form values in place.
:param message: {str} message to be displayed at the user
:param level: {str|int} is ignored in XHTML style - no support for message level distinction
:param keep_items: {dict} items to be available in the next call. They will be either added as hidden fields to the
rendered form or in case of "portal_status_message" just displayed to the user
:param REQUEST: request
:param **kwargs: is used to pass necessary parameters to overcome backend-held state (aka Selections)
"""
keep_items = keep_items or {}
if message and "portal_status_message" not in keep_items:
keep_items["portal_status_message"] = message
keep_items.pop("portal_status_level", None)
if REQUEST is None:
REQUEST = context.REQUEST
for key, value in keep_items.items():
REQUEST.set(key, value)
return getattr(context, form_id)()
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>form_id</string> </value>
<value> <string>form_id, message=\'\', level=None, keep_items=None, REQUEST=None, **kwargs</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
"""
Return HTTP Redirect with message to be displayed.
Unfortunately XHTML UI cannot distinguish between error levels - everything is a message so we ignore
most of the parameters here.
:param message: {str}
:param level: {str | int} use ERP5Type.Log levels or simply strings like "info", "warning", or "error"
"""
return context.Base_redirect(
keep_items={"portal_status_message": str(message)})
<?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>message, level="error", request=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_renderMessage</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -491,7 +491,13 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
portal_selections.setSelectionCheckedUidsFor(selection_name,
[x.getUid() for x in object_list])
md5_string = portal_selections.getSelectionChecksum(selection_name)
return object_list[0].getParentValue().Folder_delete(
object_parent = object_list[0].getParentValue()
# get default form from default view for given context
default_view_url = str(self.portal.portal_actions.listFilteredActionsFor(object_parent)['object_view'][0]['url'])
form_id = default_view_url.split('?', 1)[0].split("/")[-1]
return object_parent.Folder_delete(form_id=form_id, dialog_id="Folder_viewDeleteDialog",
selection_name=selection_name, md5_object_uid_list=md5_string)
def test_Folder_delete(self):
......@@ -499,8 +505,7 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
document_1 = module.newContent(portal_type='Folder', id='1')
document_2 = module.newContent(portal_type='Folder', id='2')
self.tic()
redirect = self._Folder_delete(document_1, document_2)
self.assert_('Deleted.' in redirect, redirect)
self._Folder_delete(document_1, document_2)
self.assertEqual(module.objectCount(), 0)
def test_Folder_delete_related_object(self):
......@@ -508,6 +513,7 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
organisation_module_len = len(self.portal.organisation_module)
person_module_len = len(self.portal.person_module)
organisation = self.portal.organisation_module.newContent()
initial_organisation_state = organisation.getValidationState()
person = self.portal.person_module.newContent(
default_career_subordination_value=organisation)
for obj in person, organisation:
......@@ -517,15 +523,20 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
self.tic()
self.assertEqual(2, organisation.getRelationCountForDeletion())
self.assertEqual(0, person.getRelationCountForDeletion())
def delete(assert_deleted, obj):
redirect = self._Folder_delete(obj)
self.assertTrue((urllib.quote('Sorry, 1 item is in use.'), 'Deleted.')[assert_deleted]
in redirect, redirect)
self._Folder_delete(organisation)
self.tic()
# here we check that nothing was done because the organisation has relations
refreshed_organisation = self.portal.organisation_module[organisation.id]
assert initial_organisation_state == refreshed_organisation.getValidationState()
self._Folder_delete(person)
self.tic()
delete(0, organisation)
delete(1, person)
self.assertEqual(0, organisation.getRelationCountForDeletion())
delete(1, organisation)
self._Folder_delete(organisation)
self.tic()
self.assertEqual(organisation_module_len + 1,
len(self.portal.organisation_module))
self.assertEqual(person_module_len + 1,
......@@ -547,8 +558,7 @@ class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
document_1.manage_permission('View', [], acquire=0)
document_1.manage_permission('Access contents information', [], acquire=0)
redirect = self._Folder_delete(document_2)
self.assert_(urllib.quote('Sorry, 1 item is in use.') in redirect, redirect)
self._Folder_delete(document_2)
self.assertEqual(module.objectCount(), 2)
def test_getPropertyForUid(self):
......
......@@ -26,6 +26,7 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import hashlib
from copy import deepcopy
......@@ -766,6 +767,15 @@ class ERP5Form(Base, ZMIForm, ZopePageTemplate):
raise FormValidationError(errors, 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
manage_FTPget = ZMIForm.get_xml
......
......@@ -25,12 +25,29 @@ class _(PatchClass(ExternalMethod)):
@property
def func_defaults(self):
"""Return a tuple of default values.
The first value is for the "second" parameter (self is ommited)
Example:
componentFunction(self, form_id='', **kw)
will have func_defaults = ('', )
"""
return self._getFunction()[1]
@property
def func_code(self):
return self._getFunction()[2]
@property
def func_args(self):
"""Return list of parameter names.
Example:
componentFunction(self, form_id='', **kw)
will have func_args = ['self', 'form_id']
"""
return self._getFunction()[4]
def getFunction(self, reload=False):
return self._getFunction(reload)[0]
......@@ -74,14 +91,21 @@ class _(PatchClass(ExternalMethod)):
except AttributeError:
pass
code = f.func_code
args = getargs(code)[0]
argument_object = getargs(code)
# reconstruct back the original names
arg_list = argument_object.args[:]
if argument_object.varargs:
arg_list.append('*' + argument_object.varargs)
if argument_object.keywords:
arg_list.append('**' + argument_object.keywords)
i = isinstance(f, MethodType)
ff = f.__func__ if i else f
has_self = len(args) > i and args[i] == 'self'
has_self = len(arg_list) > i and arg_list[i] == 'self'
i += has_self
if i:
code = FuncCode(ff, i)
self._v_f = _f = (f, f.func_defaults, code, has_self)
self._v_f = _f = (f, f.func_defaults, code, has_self, arg_list)
return _f
def __call__(self, *args, **kw):
......
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