Commit 2119056f authored by Tomáš Peterka's avatar Tomáš Peterka

Finish?

parent d2f8c300
......@@ -140,6 +140,9 @@ def kwargsForCallable(func, initial_kwargs, kwargs_dict):
In case the function cannot state required arguments it throws an AttributeError.
"""
if not hasattr(func, 'params'):
return initial_kwargs
func_param_list = [func_param.strip() for func_param in func.params().split(",")]
func_param_name_list = [func_param if '=' not in func_param else func_param.split('=')[0]
for func_param in func_param_list if '*' not in func_param]
......@@ -180,7 +183,6 @@ def anythingUidAndAccessor(search_result, result_index, traversed_document):
result[uid] = {'url': portal.abolute_url() + url}
value = getter(random_object, "value")
"""
context.log("anythingUidAndAccessor({!s}#type:{!s}, {:d}, {!s}".format(search_result, type(search_result), result_index, traversed_document))
if hasattr(search_result, "getObject"):
# "Brain" object - which simulates DB Cursor thus result must have UID
contents_uid = search_result.uid
......@@ -387,6 +389,10 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
if selection_params is None:
selection_params = {}
# some TALES expressions are using Base_getRelatedObjectParameter which requires that
previous_request_field = REQUEST.other.pop('field_id', None)
REQUEST.other['field_id'] = field.id
if meta_type is None:
meta_type = field.meta_type
if key is None:
......@@ -396,9 +402,6 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
# resolve the base meta_type
meta_type = field.getRecursiveTemplateField().meta_type
# some TALES expressions are using Base_getRelatedObjectParameter which requires that
previous_request_field = REQUEST.other.pop('field_id', None)
REQUEST.other['field_id'] = field.id
result = {
"type": meta_type,
"title": Base_translateString(field.get_value("title")),
......@@ -416,7 +419,7 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
"default": getFieldDefault(form, field, key, value),
})
# start the actuall "switch" on field's meta_type here
# start the actual "switch" on field's meta_type here
if meta_type in ("ListField", "RadioField", "ParallelListField", "MultiListField"):
result.update({
# XXX Message can not be converted to json as is
......@@ -591,6 +594,11 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
"url": field.get_value("gadget_url"),
"sandbox": field.get_value("js_sandbox")
})
try:
result["renderjs_extra"] = json.dumps(dict(field.get_value("renderjs_extra")))
except KeyError:
# Ensure compatibility if the products are not yet up to date
result["renderjs_extra"] = json.dumps({})
elif meta_type == "ListBox":
"""Display list of objects with optional search/sort capabilities on columns from catalog.
......@@ -629,7 +637,6 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
# see https://lab.nexedi.com/nexedi/erp5/blob/HEAD/product/ERP5Form/ListBox.py#L1004
# implemented in javascript in the end
# see https://lab.nexedi.com/nexedi/erp5/blob/master/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.js#L163
default_params = dict(field.get_value('default_params')) # default_params is a list of tuples
default_params['ignore_unknown_columns'] = True
# we abandoned Selections in RJS thus we mix selection query parameters into
......@@ -659,6 +666,7 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
field, list_method_name, error), level=100)
else:
list_method = None
# Put all ListBox's search method params from REQUEST to `default_param_json`
# because old code expects synchronous render thus having all form's values
# still in the request which is not our case because we do asynchronous rendering
......@@ -666,7 +674,7 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
kwargsForCallable(list_method, list_method_query_dict, REQUEST)
# Now if the list_method does not specify **kwargs we need to remove
# unwanted parameters like "portal_type" which is everywhere
if "**" not in list_method.params():
if hasattr(list_method, 'params') and "**" not in list_method.params():
_param_key_list = tuple(list_method_query_dict.keys()) # copy the keys
for param_key in _param_key_list:
if param_key not in list_method.params(): # we search in raw string
......@@ -901,7 +909,6 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
if report_item.selection_name:
selection_name = report_prefix + "_" + report_item.selection_name
context.log('Report {} defines selection_name {}'.format(report_title, selection_name))
report_form_params.update(selection_name=selection_name)
# this should load selections with correct values - since it is modifying
# global state in the backend we have nothing more to do here
......@@ -1464,7 +1471,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
#
# for k, v in catalog_kw.items():
# REQUEST.set(k, v)
context.log('list_method >>> {}({!s})'.format(list_method, catalog_kw))
search_result_iterable = callable_list_method(**catalog_kw)
# Cast to list if only one element is provided
......@@ -1533,6 +1540,10 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
contents_uid, contents_relative_url, property_getter, property_hasser = \
anythingUidAndAccessor(search_result, result_index, traversed_document)
# Check if this object provides a specific URL method.
# if getattr(search_result, 'getListItemUrl', None) is not None:
# search_result.getListItemUrl(contents_uid, result_index, selection_name)
# _links.self.href is mandatory for JIO so it can create reference to the
# (listbox) item alone
contents_item['_links'] = {
......@@ -1565,14 +1576,6 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
# if there is no tales expr (or is empty) we extract the value from search result
default_field_value = getAttrFromAnything(search_result, select, property_getter, property_hasser, {})
context.log('renderField!for"{}"({!s}, field={!s}, form={!s}, value={!s}, key={}'.format(
select,
traversed_document,
editable_field_dict[select],
listbox_form,
default_field_value,
'field_%s_%s' % (editable_field_dict[select].id, contents_uid)))
contents_item[select] = renderField(
traversed_document,
editable_field_dict[select],
......@@ -1588,7 +1591,6 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
contents_item[select] = getAttrFromAnything(search_result, select, property_getter, property_hasser, {'brain': search_result})
# endfor select
contents_list.append(contents_item)
result_dict['_embedded']['contents'] = ensureSerializable(contents_list)
# Compute statistics if the search issuer was ListBox
......@@ -1604,13 +1606,11 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
# XXX: we should check whether they asked for it
stat_method = source_field.get_value('stat_method')
stat_columns = source_field.get_value('stat_columns')
context.log('stat_method "{!s}", stat_columns {!s}'.format(stat_method, stat_columns))
# support only selection_name for stat methods because any `selection` is deprecated
# and should be removed
selection_name = source_field.get_value('selection_name')
if selection_name and 'selection_name' not in catalog_kw:
catalog_kw['selection_name'] = selection_name
context.log('stat_method will receive selection_name "{}"'.format(catalog_kw['selection_name']))
contents_stat = {}
if len(stat_columns) > 0:
# prefer stat per column (follow original ListBox.py implementation)
......@@ -1621,6 +1621,9 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
# general stat_method is second in priority list - should return dictionary or list of dictionaries
# where all "fields" should be accessible by their "select" name (no "listbox_" prefix)
stat_method_result = getattr(traversed_document, stat_method.getMethodName())(**catalog_kw)
# stat method can return simple dictionary or subscriptable object thus we put it into one-item list
if stat_method_result is not None and not isinstance(stat_method_result, (list, tuple)):
stat_method_result = [stat_method_result, ]
contents_stat_list = toBasicTypes(stat_method_result) or []
for contents_stat in contents_stat_list:
......@@ -1628,9 +1631,7 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
if key in editable_field_dict:
contents_stat[key] = renderField(
traversed_document, editable_field_dict[key], listbox_form, value, key=editable_field_dict[key].id + '__sum')
for contents_stat in contents_stat_list:
for key, value in contents_stat.items():
context.log('contents_stat["{}"] = type {!s}, value {!s}'.format(key, type(value), value))
if len(contents_stat_list) > 0:
result_dict['_embedded']['sum'] = ensureSerializable(contents_stat_list)
......
"""Compute stats from actual Foo Lines on a Foo object"""
column_list = ['getQuantity', 'id']
result = {c: 0.0 for c in column_list}
for line in context.contentValues(portal_type="Foo"):
for column in column_list:
value = getattr(line, column)
if callable(value):
value = value()
result[column] = result[column] + float(value)
return [result, ]
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Python Script" module="erp5.portal_type"/>
</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>**kwargs</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>FooModule_statMethod</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Python Script</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
counter = 0
for value in context.contentValues():
counter = counter + int(value.getQuantity())
return counter
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Python Script" module="erp5.portal_type"/>
</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>**kwargs</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>FooModule_statQuantity</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Python Script</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -103,7 +103,7 @@
</a>
{{/if}}
{{else}}
<a href="{{href}}" class="ui-link">{{text}}</a>
<a href="{{href}}" class="ui-link">{{default}}</a>
{{/if}}
</td>
{{/each}}
......@@ -131,7 +131,13 @@
{{#if type}}
<div class="editable_div" data-column="{{column}}" data-line="{{line}}"></div>
{{else}}
{{text}}
{{#if default}}
{{default}}
{{else}}
{{#if @first}}
Total
{{/if}}
{{/if}}
{{/if}}
</td>
{{/each}}
......
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>963.52015.15055.51592</string> </value>
<value> <string>963.63278.21548.22971</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1512039069.26</float>
<float>1512617871.97</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -495,9 +495,9 @@
cell_list = [];
for (j = 0; j < column_list.length; j += 1) {
value = allDocs_result.data.rows[i].value[column_list[j][0]] || "";
// value can be simple string with value in case of non-editable field
// thus we construct basic "field_json" manually and insert the value in "default"
if (typeof value === "string") {
// value can be simply just a value in case of non-editable field
// thus we construct "field_json" manually and insert the value in "default"
if (value.constructor !== Object) {
value = {
'editable': 0,
'default': value
......@@ -564,8 +564,8 @@
"uid": 'summary' + row_index,
"cell_list": column_list.map(function (col_name, col_index) {
var field_json = row.value[col_name[0]] || "";
if (typeof field_json === "string") {
field_json = {'default': 'value', 'editable': 0};
if (field_json.constructor !== Object) {
field_json = {'default': field_json, 'editable': 0};
}
field_json.editable = field_json.editable && row_editability;
field_json.column = col_index;
......
......@@ -236,7 +236,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>963.60288.35957.62805</string> </value>
<value> <string>963.61737.34616.6621</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -254,7 +254,7 @@
</tuple>
<state>
<tuple>
<float>1512439237.91</float>
<float>1512556744.93</float>
<string>UTC</string>
</tuple>
</state>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>testStatColumns</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">
<!--
Ensure Stat Column methods are executed correctly and result displayed in tfoot element of the listbox table.
- if anchor, then text "Total" is present
- columns which are not present in Stat Columns do not display any data
-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test RenderJS ListBox Stat Columns</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">Test RenderJS ListBox Stat Columns</td></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>
<!-- Create Foo objects with IDs 0-9 -->
<tr><td>open</td>
<td>${base_url}/foo_module/FooModule_createObjects?start:int=1&amp;num:int=2</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" />
<!-- Let's set up stat column property on listbox -->
<tr><td>open</td>
<td>${base_url}/FooModule_viewFooList/listbox/ListBox_setPropertyList?field_stat_columns=getQuantity+%7C+FooModule_statQuantity+%0A+title+%7C+FooModule_statTitle</td><td></td></tr>
<tr><td>assertTextPresent</td>
<td>Set Successfully.</td><td></td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/foo_module</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/span[@data-i18n="2 Records"]</td><td></td></tr>
<tr><td>store</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table</td>
<td>listbox_table</td></tr>
<!-- Default sort on ID column has to be ASCENDING -->
<tr><td>assertFloat</td>
<td>${listbox_table}/tbody/tr[1]/td[3]/a</td>
<td>9</td></tr>
<tr><td>assertFloat</td>
<td>${listbox_table}/tbody/tr[2]/td[3]/a</td>
<td>8</td></tr>
<tr><td>assertText</td><!-- This tests that "Total" appears when first column has no stat defined -->
<td>${listbox_table}/tfoot/tr[1]/td[1]</td>
<td>Total</td></tr>
<tr><td>assertText</td><!-- Test multiple Stat Columns -->
<td>${listbox_table}/tfoot/tr[1]/td[2]</td>
<td>Foos</td></tr>
<tr><td>assertFloat</td>
<td>${listbox_table}/tfoot/tr[1]/td[3]</td>
<td>17</td></tr>
</tbody></table>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="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>testStatMethod</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">
<!--
Ensure stat_method gets executed and result displayed in tfoot element of the listbox table.
- if anchor, then text "Total" is present
- columns for which stat_method does not return any data remain empty
-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test RenderJS UI ListBox Stat Method</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">Test RenderJS UI ListBox Stat Method</td></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>
<!-- Create Foo objects with IDs 0-9 -->
<tr><td>open</td>
<td>${base_url}/foo_module/FooModule_createObjects?start:int=1&amp;num:int=3</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" />
<!-- Let's set up the default sort correctly: id | ASC -->
<tr><td>open</td>
<td>${base_url}/FooModule_viewFooList/listbox/ListBox_setPropertyList?field_stat_method=FooModule_statMethod</td><td></td></tr>
<tr><td>assertTextPresent</td>
<td>Set Successfully.</td><td></td></tr>
<tr><td>open</td>
<td>${renderjs_url}/#/foo_module</td><td></td></tr>
<tr><td>waitForElementPresent</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/span[@data-i18n="3 Records"]</td><td></td></tr>
<tr><td>store</td>
<td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table</td>
<td>listbox_table</td></tr>
<!-- Default sort on ID column has to be ASCENDING -->
<tr><td>assertFloat</td>
<td>${listbox_table}/tbody/tr[1]/td[3]/a</td>
<td>9</td></tr>
<tr><td>assertFloat</td>
<td>${listbox_table}/tbody/tr[2]/td[3]/a</td>
<td>8</td></tr>
<tr><td>assertFloat</td>
<td>${listbox_table}/tbody/tr[3]/td[3]/a</td>
<td>7</td></tr>
<tr><td>assertFloat</td><!-- This tests that "Total" does not appear when first column has stat defined -->
<td>${listbox_table}/tfoot/tr[1]/td[1]</td>
<td>6</td></tr>
<tr><td>assertFloat</td>
<td>${listbox_table}/tfoot/tr[1]/td[3]</td>
<td>24</td></tr>
</tbody></table>
</body>
</html>
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment