[IMP] *: limit in search panel
This commit introduces a new attribute 'limit' for search panel fields that allows to avoid performance issues. That integer attribute (with default 200) allows to fix a maximal number of values to display for the fields. When the number of field values to display reaches the limit, no values will be displayed. Instead, a warning message will be shown in the corresponding search panel section. Note it is possible to have no limit using limit="0" on a field. This commit reintroduces in a better way the principle brought by the fix 8d57153b34c04952a85f6642951cb2697016da84. Task ID: 2154749
This commit is contained in:
committed by
Julien Mougenot
parent
e5585c078e
commit
3306a0f414
@@ -119,7 +119,7 @@
|
||||
<filter string="Account Type" name="accounttype" domain="" context="{'group_by':'user_type_id'}"/>
|
||||
</group>
|
||||
<searchpanel class="account_root">
|
||||
<field name="root_id" icon="fa-filter"/>
|
||||
<field name="root_id" icon="fa-filter" limit="0"/>
|
||||
</searchpanel>
|
||||
</search>
|
||||
</field>
|
||||
|
||||
@@ -368,7 +368,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group" position="after">
|
||||
<searchpanel class="account_root">
|
||||
<field name="account_root_id" icon="fa-filter"/>
|
||||
<field name="account_root_id" icon="fa-filter" limit="0"/>
|
||||
</searchpanel>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
+58
-25
@@ -12,6 +12,7 @@ from odoo.tools.misc import get_lang
|
||||
from odoo.exceptions import UserError
|
||||
from collections import defaultdict
|
||||
|
||||
SEARCH_PANEL_ERROR_MESSAGE = "Too many items to display."
|
||||
|
||||
class lazymapping(defaultdict):
|
||||
def __missing__(self, key):
|
||||
@@ -223,13 +224,14 @@ class Base(models.AbstractModel):
|
||||
|
||||
|
||||
@api.model
|
||||
def _search_panel_domain_image(self, field_name, domain, set_count=False):
|
||||
def _search_panel_domain_image(self, field_name, domain, set_count=False, limit=False):
|
||||
"""
|
||||
Return the values in the image of the provided domain by field_name.
|
||||
|
||||
:param domain: domain whose image is returned
|
||||
:param field_name: the name of a field (type many2one or selection)
|
||||
:param set_count: whether to set the key '__count' in image values. Default is False.
|
||||
:param limit: integer, maximal number of values to fetch. Default is False.
|
||||
:return: a dict of the form
|
||||
{
|
||||
id: { 'id': id, 'display_name': display_name, ('__count': c,) },
|
||||
@@ -253,7 +255,8 @@ class Base(models.AbstractModel):
|
||||
domain,
|
||||
[(field_name, '!=', False)],
|
||||
])
|
||||
groups = self.read_group(domain, [field_name], [field_name])
|
||||
groups = self.read_group(domain, [field_name], [field_name], limit=limit)
|
||||
|
||||
domain_image = {}
|
||||
for group in groups:
|
||||
id, display_name = group_id_name(group[field_name])
|
||||
@@ -312,7 +315,7 @@ class Base(models.AbstractModel):
|
||||
{ 'id': id, parent_name: False or (id, display_name),... }
|
||||
:param parent_name, string, indicates which key determines the parent
|
||||
:param ids: list of record ids
|
||||
:return: the sublist (as a filter object) of records with the above properties
|
||||
:return: the sublist of records with the above properties
|
||||
}
|
||||
"""
|
||||
def get_parent_id(record):
|
||||
@@ -342,7 +345,7 @@ class Base(models.AbstractModel):
|
||||
records_to_keep[id] = chain_is_fully_included
|
||||
|
||||
# we keep initial order
|
||||
return filter(lambda rec: records_to_keep.get(rec['id']), records)
|
||||
return [rec for rec in records if records_to_keep.get(rec['id'])]
|
||||
|
||||
|
||||
@api.model
|
||||
@@ -412,7 +415,9 @@ class Base(models.AbstractModel):
|
||||
(if possible). If set to true and _parent_name is set on the
|
||||
comodel field, the information necessary for the hierarchization will
|
||||
be returned. Default is True.
|
||||
:param limit: integer, maximal number of values to fetch. Default is None.
|
||||
:param search_domain: base domain of search. Default is [].
|
||||
with parents if hierarchize is set)
|
||||
:return: {
|
||||
'parent_field': parent field on the comodel of field, or False
|
||||
'values': array of dictionaries containing some info on the records
|
||||
@@ -420,6 +425,7 @@ class Base(models.AbstractModel):
|
||||
The display name, the __count (how many records with that value)
|
||||
and possibly parent_field are fetched.
|
||||
}
|
||||
or an object with an error message when limit is defined and is reached.
|
||||
"""
|
||||
field = self._fields[field_name]
|
||||
supported_types = ['many2one', 'selection']
|
||||
@@ -439,12 +445,6 @@ class Base(models.AbstractModel):
|
||||
'values': self._search_panel_selection_range(field_name, model_domain=model_domain,**kwargs),
|
||||
}
|
||||
|
||||
enable_counters = kwargs.get('enable_counters')
|
||||
expand = kwargs.get('expand')
|
||||
domain_image = {}
|
||||
if enable_counters or not expand:
|
||||
domain_image = self._search_panel_domain_image(field_name, model_domain, enable_counters)
|
||||
|
||||
Comodel = self.env[field.comodel_name].with_context(hierarchical_naming=False)
|
||||
field_names = ['display_name']
|
||||
hierarchize = kwargs.get('hierarchize', True)
|
||||
@@ -460,10 +460,21 @@ class Base(models.AbstractModel):
|
||||
hierarchize = False
|
||||
|
||||
comodel_domain = kwargs.get('comodel_domain', [])
|
||||
enable_counters = kwargs.get('enable_counters')
|
||||
expand = kwargs.get('expand')
|
||||
limit = kwargs.get('limit')
|
||||
domain_image = {}
|
||||
if enable_counters or not expand:
|
||||
domain_image = self._search_panel_domain_image(field_name, model_domain, enable_counters,
|
||||
limit if not (expand or hierarchize or comodel_domain) else False
|
||||
)
|
||||
if not (expand or hierarchize or comodel_domain):
|
||||
values = list(domain_image.values())
|
||||
if limit and len(values) == limit:
|
||||
return {'error_msg': _(SEARCH_PANEL_ERROR_MESSAGE)}
|
||||
return {
|
||||
'parent_field': parent_name,
|
||||
'values': list(domain_image.values()),
|
||||
'values': values,
|
||||
}
|
||||
|
||||
if not expand:
|
||||
@@ -473,12 +484,15 @@ class Base(models.AbstractModel):
|
||||
else:
|
||||
condition = [('id', 'in', image_element_ids)]
|
||||
comodel_domain = AND([comodel_domain, condition])
|
||||
comodel_records = Comodel.search_read(comodel_domain, field_names)
|
||||
comodel_records = Comodel.search_read(comodel_domain, field_names, limit=limit)
|
||||
|
||||
if hierarchize:
|
||||
ids = [rec['id'] for rec in comodel_records] if expand else image_element_ids
|
||||
comodel_records = self._search_panel_sanitized_parent_hierarchy(comodel_records, parent_name, ids)
|
||||
|
||||
if limit and len(comodel_records) == limit:
|
||||
return {'error_msg': _(SEARCH_PANEL_ERROR_MESSAGE)}
|
||||
|
||||
field_range = {}
|
||||
for record in comodel_records:
|
||||
record_id = record['id']
|
||||
@@ -522,13 +536,17 @@ class Base(models.AbstractModel):
|
||||
for the group_by (if any). Those domains are
|
||||
used to fech accurate counters for values in each group.
|
||||
Default is [] (many2one case) or None.
|
||||
:param limit: integer, maximal number of values to fetch. Default is None.
|
||||
:param search_domain: base domain of search. Default is [].
|
||||
:return: a list of possible values, each being a dict with keys
|
||||
'id' (value),
|
||||
'name' (value label),
|
||||
'__count' (how many records with that value),
|
||||
'group_id' (value of group), set if a group_by has been provided,
|
||||
'group_name' (label of group), set if a group_by has been provided
|
||||
:return: {
|
||||
'values': a list of possible values, each being a dict with keys
|
||||
'id' (value),
|
||||
'name' (value label),
|
||||
'__count' (how many records with that value),
|
||||
'group_id' (value of group), set if a group_by has been provided,
|
||||
'group_name' (label of group), set if a group_by has been provided
|
||||
}
|
||||
or an object with an error message when limit is defined and reached.
|
||||
"""
|
||||
field = self._fields[field_name]
|
||||
supported_types = ['many2one', 'many2many', 'selection']
|
||||
@@ -544,11 +562,14 @@ class Base(models.AbstractModel):
|
||||
])
|
||||
|
||||
if field.type == 'selection':
|
||||
return self._search_panel_selection_range(field_name, model_domain=model_domain, **kwargs)
|
||||
return {
|
||||
'values': self._search_panel_selection_range(field_name, model_domain=model_domain, **kwargs)
|
||||
}
|
||||
|
||||
Comodel = self.env.get(field.comodel_name).with_context(hierarchical_naming=False)
|
||||
field_names = ['display_name']
|
||||
group_by = kwargs.get('group_by')
|
||||
limit = kwargs.get('limit')
|
||||
if group_by:
|
||||
group_by_field = Comodel._fields[group_by]
|
||||
|
||||
@@ -575,7 +596,9 @@ class Base(models.AbstractModel):
|
||||
expand = kwargs.get('expand')
|
||||
|
||||
if field.type == 'many2many':
|
||||
comodel_records = Comodel.search_read(comodel_domain, field_names)
|
||||
comodel_records = Comodel.search_read(comodel_domain, field_names, limit=limit)
|
||||
if expand and limit and len(comodel_records) == limit:
|
||||
return {'error_msg': _(SEARCH_PANEL_ERROR_MESSAGE)}
|
||||
|
||||
group_domain = kwargs.get('group_domain')
|
||||
field_range = []
|
||||
@@ -608,7 +631,10 @@ class Base(models.AbstractModel):
|
||||
values['__count'] = count
|
||||
field_range.append(values)
|
||||
|
||||
return field_range
|
||||
if not expand and limit and len(field_range) == limit:
|
||||
return {'error_msg': _(SEARCH_PANEL_ERROR_MESSAGE)}
|
||||
|
||||
return { 'values': field_range, }
|
||||
|
||||
if field.type == 'many2one':
|
||||
domain_image = {}
|
||||
@@ -617,10 +643,15 @@ class Base(models.AbstractModel):
|
||||
model_domain,
|
||||
kwargs.get('group_domain', []),
|
||||
])
|
||||
domain_image = self._search_panel_domain_image(field_name, model_domain, enable_counters)
|
||||
domain_image = self._search_panel_domain_image(field_name, model_domain, enable_counters,
|
||||
limit if not (expand or group_by or comodel_domain) else False
|
||||
)
|
||||
|
||||
if not (expand or group_by or comodel_domain):
|
||||
return list(domain_image.values())
|
||||
values = list(domain_image.values())
|
||||
if limit and len(values) == limit:
|
||||
return {'error_msg': _(SEARCH_PANEL_ERROR_MESSAGE)}
|
||||
return {'values': values, }
|
||||
|
||||
if not expand:
|
||||
image_element_ids = list(domain_image.keys())
|
||||
@@ -628,7 +659,9 @@ class Base(models.AbstractModel):
|
||||
comodel_domain,
|
||||
[('id', 'in', image_element_ids)],
|
||||
])
|
||||
comodel_records = Comodel.search_read(comodel_domain, field_names)
|
||||
comodel_records = Comodel.search_read(comodel_domain, field_names, limit=limit)
|
||||
if limit and len(comodel_records) == limit:
|
||||
return {'error_msg': _(SEARCH_PANEL_ERROR_MESSAGE)}
|
||||
|
||||
field_range = []
|
||||
for record in comodel_records:
|
||||
@@ -649,7 +682,7 @@ class Base(models.AbstractModel):
|
||||
|
||||
field_range.append(values)
|
||||
|
||||
return field_range
|
||||
return { 'values': field_range, }
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
|
||||
@@ -21,6 +21,8 @@ const qweb = core.qweb;
|
||||
const defaultViewTypes = ['kanban', 'tree'];
|
||||
let nextSectionId = 1;
|
||||
|
||||
const SEARCH_PANEL_DEFAULT_LIMIT = 200;
|
||||
|
||||
/**
|
||||
* Given a <searchpanel> arch node, iterate over its children to generate the
|
||||
* description of each section (being either a category or a filter).
|
||||
@@ -31,31 +33,34 @@ let nextSectionId = 1;
|
||||
*/
|
||||
function _processSearchPanelNode(node, fields) {
|
||||
const sections = {};
|
||||
node.children.forEach((childNode, index) => {
|
||||
if (childNode.tag !== 'field' || childNode.attrs.invisible === "1") {
|
||||
node.children.forEach(({ attrs, tag }, index) => {
|
||||
if (tag !== 'field' || attrs.invisible === "1") {
|
||||
return;
|
||||
}
|
||||
const fieldName = childNode.attrs.name;
|
||||
const type = childNode.attrs.select === 'multi' ? 'filter' : 'category';
|
||||
const fieldName = attrs.name;
|
||||
const type = attrs.select === 'multi' ? 'filter' : 'category';
|
||||
|
||||
const sectionId = `section_${nextSectionId++}`;
|
||||
const section = {
|
||||
color: childNode.attrs.color,
|
||||
description: childNode.attrs.string || fields[fieldName].string,
|
||||
enableCounters: !!pyUtils.py_eval(childNode.attrs.enable_counters || '0'),
|
||||
expand: !!pyUtils.py_eval(childNode.attrs.expand || '0'),
|
||||
color: attrs.color,
|
||||
description: attrs.string || fields[fieldName].string,
|
||||
enableCounters: !!pyUtils.py_eval(attrs.enable_counters || '0'),
|
||||
expand: !!pyUtils.py_eval(attrs.expand || '0'),
|
||||
fieldName,
|
||||
icon: childNode.attrs.icon,
|
||||
icon: attrs.icon,
|
||||
id: sectionId,
|
||||
index,
|
||||
limit: attrs.limit ?
|
||||
pyUtils.py_eval(attrs.limit) :
|
||||
SEARCH_PANEL_DEFAULT_LIMIT,
|
||||
type,
|
||||
};
|
||||
if (section.type === 'category') {
|
||||
section.icon = section.icon || 'fa-folder';
|
||||
section.hierarchize = !!pyUtils.py_eval(childNode.attrs.hierarchize || '1');
|
||||
section.hierarchize = !!pyUtils.py_eval(attrs.hierarchize || '1');
|
||||
} else if (section.type === 'filter') {
|
||||
section.domain = childNode.attrs.domain || '[]';
|
||||
section.groupBy = childNode.attrs.groupby;
|
||||
section.domain = attrs.domain || '[]';
|
||||
section.groupBy = attrs.groupby;
|
||||
section.icon = section.icon || 'fa-filter';
|
||||
}
|
||||
sections[sectionId] = section;
|
||||
@@ -267,11 +272,18 @@ const SearchPanel = Widget.extend({
|
||||
/**
|
||||
* @private
|
||||
* @param {string} categoryId
|
||||
* @param {Object[]} values
|
||||
* @param {Object} result
|
||||
*/
|
||||
_createCategoryTree(categoryId, values) {
|
||||
_createCategoryTree(categoryId, result) {
|
||||
const category = this.categories[categoryId];
|
||||
const parentField = category.parentField;
|
||||
let { error_msg, parent_field: parentField, values } = result;
|
||||
if (error_msg) {
|
||||
category.errorMsg = error_msg;
|
||||
values = [];
|
||||
}
|
||||
if (category.hierarchize) {
|
||||
category.parentField = parentField;
|
||||
}
|
||||
|
||||
const unfoldedIds = Object.values(category.values || {})
|
||||
.filter(c => c.folded === false)
|
||||
@@ -315,10 +327,15 @@ const SearchPanel = Widget.extend({
|
||||
/**
|
||||
* @private
|
||||
* @param {string} filterId
|
||||
* @param {Object[]} values
|
||||
* @param {Object} result
|
||||
*/
|
||||
_createFilterTree(filterId, values) {
|
||||
_createFilterTree(filterId, result) {
|
||||
const filter = this.filters[filterId];
|
||||
let { error_msg, values } = result;
|
||||
if (error_msg) {
|
||||
filter.errorMsg = error_msg;
|
||||
values = [];
|
||||
}
|
||||
|
||||
// restore checked property
|
||||
values.forEach(value => {
|
||||
@@ -379,8 +396,7 @@ const SearchPanel = Widget.extend({
|
||||
_fetchCategories(force) {
|
||||
const proms = [];
|
||||
for (const category of Object.values(this.categories)) {
|
||||
const { enableCounters, expand, hierarchize } = category;
|
||||
const field = this.fields[category.fieldName];
|
||||
const { enableCounters, expand, hierarchize, limit } = category;
|
||||
if (force || enableCounters || !expand) {
|
||||
const prom = this._rpc({
|
||||
method: 'search_panel_select_range',
|
||||
@@ -391,14 +407,12 @@ const SearchPanel = Widget.extend({
|
||||
enable_counters: enableCounters,
|
||||
expand,
|
||||
hierarchize,
|
||||
limit,
|
||||
search_domain: this.searchDomain,
|
||||
},
|
||||
}).then(({ parent_field, values }) => {
|
||||
if (field.type === 'many2one' && hierarchize) {
|
||||
category.parentField = parent_field;
|
||||
}
|
||||
this._createCategoryTree(category.id, values);
|
||||
});
|
||||
}).then(
|
||||
(result) => this._createCategoryTree(category.id, result)
|
||||
);
|
||||
proms.push(prom);
|
||||
}
|
||||
}
|
||||
@@ -419,7 +433,7 @@ const SearchPanel = Widget.extend({
|
||||
const categoryDomain = this._getCategoryDomain();
|
||||
const proms = [];
|
||||
for (const filter of Object.values(this.filters)) {
|
||||
const { enableCounters, expand, groupBy } = filter;
|
||||
const { enableCounters, expand, groupBy, limit } = filter;
|
||||
const prom = this._rpc({
|
||||
method: 'search_panel_select_multi_range',
|
||||
model: this.model,
|
||||
@@ -432,9 +446,12 @@ const SearchPanel = Widget.extend({
|
||||
expand,
|
||||
group_by: groupBy || false,
|
||||
group_domain: this._getGroupDomain(filter),
|
||||
limit,
|
||||
search_domain: [...this.searchDomain, ...this.viewDomain],
|
||||
},
|
||||
}).then(values => { this._createFilterTree(filter.id, values); });
|
||||
}).then(
|
||||
(result) => this._createFilterTree(filter.id, result)
|
||||
);
|
||||
proms.push(prom);
|
||||
}
|
||||
return Promise.all(proms);
|
||||
@@ -646,7 +663,7 @@ const SearchPanel = Widget.extend({
|
||||
const sections = categories.concat(filters).sort((s1, s2) => s1.index - s2.index);
|
||||
|
||||
sections.forEach(section => {
|
||||
if (Object.keys(section.values).length) {
|
||||
if (Object.keys(section.values).length || section.errorMsg) {
|
||||
if (section.type === 'category') {
|
||||
this.$el.append(this._renderCategory(section));
|
||||
} else {
|
||||
|
||||
@@ -222,7 +222,10 @@
|
||||
<t t-call="SearchPanel.SectionHeader">
|
||||
<t t-set="section" t-value="category"/>
|
||||
</t>
|
||||
<ul class="list-group d-block o_search_panel_field" t-att-data-field-name="category.fieldName">
|
||||
<div t-if="category.errorMsg" class="alert alert-warning">
|
||||
<span><t t-esc="category.errorMsg"/></span>
|
||||
</div>
|
||||
<ul t-else="" class="list-group d-block o_search_panel_field" t-att-data-field-name="category.fieldName">
|
||||
<li class="o_search_panel_category_value border-0 list-group-item" t-att-data-category-id="category.id">
|
||||
<header t-att-class="'list-group-item-action' + (!category.activeValueId ? ' active' : '')">
|
||||
<label class="o_search_panel_label mb0">
|
||||
@@ -267,7 +270,10 @@
|
||||
<t t-call="SearchPanel.SectionHeader">
|
||||
<t t-set="section" t-value="filter"/>
|
||||
</t>
|
||||
<ul class="list-group d-block o_search_panel_field" t-att-data-field-name="filter.fieldName">
|
||||
<div t-if="filter.errorMsg" class="alert alert-warning">
|
||||
<span><t t-esc="filter.errorMsg"/></span>
|
||||
</div>
|
||||
<ul t-else="" class="list-group d-block o_search_panel_field" t-att-data-field-name="filter.fieldName">
|
||||
<t t-if="filter.groups" t-call="SearchPanel.FilterGroups">
|
||||
<t t-set="groups" t-value="filter.groups"/>
|
||||
</t>
|
||||
|
||||
@@ -539,15 +539,16 @@ var MockServer = Class.extend({
|
||||
return modelFields;
|
||||
},
|
||||
/**
|
||||
* Simulate a call to the server '_search_panel_domain_image' method.
|
||||
* Simulates a call to the server '_search_panel_domain_image' method.
|
||||
*
|
||||
* @private
|
||||
* @param {string} model
|
||||
* @param {Array[]} domain
|
||||
* @param {string} fieldName
|
||||
* @param {boolean} setCount
|
||||
* @returns {Map}
|
||||
*/
|
||||
_mockSearchPanelDomainImage: function (model, fieldName, domain, setCount=false) {
|
||||
_mockSearchPanelDomainImage: function (model, fieldName, domain, setCount=false, limit=false) {
|
||||
const field = this.data[model].fields[fieldName];
|
||||
let groupIdName;
|
||||
if (field.type === 'many2one') {
|
||||
@@ -569,6 +570,7 @@ var MockServer = Class.extend({
|
||||
domain,
|
||||
fields: [fieldName],
|
||||
groupby: [fieldName],
|
||||
limit,
|
||||
});
|
||||
const domainImage = new Map();
|
||||
for (const group of groups) {
|
||||
@@ -582,8 +584,9 @@ var MockServer = Class.extend({
|
||||
return domainImage;
|
||||
},
|
||||
/**
|
||||
* Simulate a call to the server '_search_panel_global_counters' method.
|
||||
* Simulates a call to the server '_search_panel_global_counters' method.
|
||||
*
|
||||
* @private
|
||||
* @param {Map} valuesRange
|
||||
* @param {(string|boolean)} parentName 'parent_id' or false
|
||||
*/
|
||||
@@ -602,8 +605,9 @@ var MockServer = Class.extend({
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Simulate a call to the server '_search_panel_sanitized_parent_hierarchy' method.
|
||||
* Simulates a call to the server '_search_panel_sanitized_parent_hierarchy' method.
|
||||
*
|
||||
* @private
|
||||
* @param {Object[]} records
|
||||
* @param {(string|boolean)} parentName 'parent_id' or false
|
||||
* @param {number[]} ids
|
||||
@@ -641,8 +645,9 @@ var MockServer = Class.extend({
|
||||
return records.filter(rec => recordsToKeep[rec.id]);
|
||||
},
|
||||
/**
|
||||
* Simulate a call to the server 'search_panel_selection_range' method.
|
||||
* Simulates a call to the server 'search_panel_selection_range' method.
|
||||
*
|
||||
* @private
|
||||
* @param {string} model
|
||||
* @param {string} fieldName
|
||||
* @param {Object} kwargs
|
||||
@@ -677,8 +682,9 @@ var MockServer = Class.extend({
|
||||
return selectionRange;
|
||||
},
|
||||
/**
|
||||
* Simulate a call to the server 'search_panel_select_range' method.
|
||||
* Simulates a call to the server 'search_panel_select_range' method.
|
||||
*
|
||||
* @private
|
||||
* @param {string} model
|
||||
* @param {string[]} args
|
||||
* @param {string} args[fieldName]
|
||||
@@ -688,6 +694,7 @@ var MockServer = Class.extend({
|
||||
* @param {Array[]} [kwargs.comodel_domain] domain of field values (if relational)
|
||||
* (this parameter is used in _search_panel_range)
|
||||
* @param {boolean} [kwargs.enable_counters] whether to count records by value
|
||||
* @param {integer} [kwargs.limit] maximal number of values to fetch
|
||||
* @param {Array[]} [kwargs.search_domain] base domain of search (this parameter
|
||||
* is used in _search_panel_range)
|
||||
* @returns {Object}
|
||||
@@ -710,12 +717,7 @@ var MockServer = Class.extend({
|
||||
values: this._mockSearchPanelSelectionRange(model, fieldName, kwargs),
|
||||
};
|
||||
}
|
||||
const enableCounters = kwargs.enable_counters;
|
||||
const expand = kwargs.expand;
|
||||
let domainImage = new Map();
|
||||
if (enableCounters || !expand) {
|
||||
domainImage = this._mockSearchPanelDomainImage(model, fieldName, modelDomain, enableCounters);
|
||||
}
|
||||
|
||||
const fieldNames = ['display_name'];
|
||||
let hierarchize = 'hierarchize' in kwargs ? kwargs.hierarchize : true;
|
||||
let getParentId;
|
||||
@@ -728,7 +730,20 @@ var MockServer = Class.extend({
|
||||
hierarchize = false;
|
||||
}
|
||||
let comodelDomain = kwargs.comodel_domain || [];
|
||||
const enableCounters = kwargs.enable_counters;
|
||||
const expand = kwargs.expand;
|
||||
const limit = kwargs.limit;
|
||||
let domainImage = new Map();
|
||||
if (enableCounters || !expand) {
|
||||
domainImage = this._mockSearchPanelDomainImage(
|
||||
model, fieldName, modelDomain, enableCounters,
|
||||
!expand && !hierarchize && !comodelDomain.length ? limit : false
|
||||
);
|
||||
}
|
||||
if (!expand && !hierarchize && !comodelDomain.length) {
|
||||
if (limit && domainImage.size === limit) {
|
||||
return { error_msg: "Too many items to display." };
|
||||
}
|
||||
return {
|
||||
parent_field: parentName,
|
||||
values: [...domainImage.values()],
|
||||
@@ -759,11 +774,16 @@ var MockServer = Class.extend({
|
||||
condition,
|
||||
]);
|
||||
}
|
||||
let comodelRecords = this._mockSearchRead(field.relation, [comodelDomain, fieldNames], {});
|
||||
let comodelRecords = this._mockSearchRead(field.relation, [comodelDomain, fieldNames], { limit });
|
||||
|
||||
if (hierarchize) {
|
||||
const ids = expand ? comodelRecords.map(rec => rec.id) : imageElementIds;
|
||||
comodelRecords = this._mockSearchPanelSanitizedParentHierarchy(comodelRecords, parentName, ids);
|
||||
}
|
||||
|
||||
if (limit && comodelRecords.length === limit) {
|
||||
return { error_msg: "Too many items to display." };
|
||||
}
|
||||
// A map is used to keep the initial order.
|
||||
const fieldRange = new Map();
|
||||
for (const record of comodelRecords) {
|
||||
@@ -790,8 +810,9 @@ var MockServer = Class.extend({
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Simulate a call to the server 'search_panel_select_multi_range' method.
|
||||
* Simulates a call to the server 'search_panel_select_multi_range' method.
|
||||
*
|
||||
* @private
|
||||
* @param {string} model
|
||||
* @param {string[]} args
|
||||
* @param {string} args[fieldName]
|
||||
@@ -806,8 +827,9 @@ var MockServer = Class.extend({
|
||||
* @param {Array[]} [kwargs.group_domain] dict, one domain for each activated
|
||||
* group for the group_by (if any). Those domains are used to fech accurate
|
||||
* counters for values in each group
|
||||
* @param {integer} [kwargs.limit] maximal number of values to fetch
|
||||
* @param {Array[]} [kwargs.search_domain] base domain of search
|
||||
* @returns {Object[]}
|
||||
* @returns {Object}
|
||||
*/
|
||||
_mockSearchPanelSelectMultiRange: function (model, [fieldName], kwargs) {
|
||||
const field = this.data[model].fields[fieldName];
|
||||
@@ -823,7 +845,9 @@ var MockServer = Class.extend({
|
||||
]);
|
||||
if (field.type === 'selection') {
|
||||
kwargs.model_domain = modelDomain;
|
||||
return this._mockSearchPanelSelectionRange(model, fieldName, kwargs);
|
||||
return {
|
||||
values: this._mockSearchPanelSelectionRange(model, fieldName, kwargs),
|
||||
};
|
||||
}
|
||||
const fieldNames = ['display_name'];
|
||||
const groupBy = kwargs.group_by;
|
||||
@@ -844,8 +868,13 @@ var MockServer = Class.extend({
|
||||
let comodelDomain = kwargs.comodel_domain || [];
|
||||
const enableCounters = kwargs.enable_counters;
|
||||
const expand = kwargs.expand;
|
||||
const limit = kwargs.limit;
|
||||
if (field.type === 'many2many') {
|
||||
const comodelRecords = this._mockSearchRead(field.relation, [comodelDomain, fieldNames], {});
|
||||
const comodelRecords = this._mockSearchRead(field.relation, [comodelDomain, fieldNames], { limit });
|
||||
if (expand && limit && comodelRecords.length === limit) {
|
||||
return { error_msg: "Too many items to display." };
|
||||
}
|
||||
|
||||
const groupDomain = kwargs.group_domain;
|
||||
const fieldRange = [];
|
||||
for (const record of comodelRecords) {
|
||||
@@ -881,7 +910,12 @@ var MockServer = Class.extend({
|
||||
fieldRange.push(values);
|
||||
}
|
||||
}
|
||||
return fieldRange;
|
||||
|
||||
if (!expand && limit && fieldRange.length === limit) {
|
||||
return { error_msg: "Too many items to display." };
|
||||
}
|
||||
|
||||
return { values: fieldRange };
|
||||
}
|
||||
|
||||
if (field.type === 'many2one') {
|
||||
@@ -891,10 +925,16 @@ var MockServer = Class.extend({
|
||||
...modelDomain,
|
||||
...(kwargs.group_domain || []),
|
||||
]);
|
||||
domainImage = this._mockSearchPanelDomainImage(model, fieldName, modelDomain, enableCounters);
|
||||
domainImage = this._mockSearchPanelDomainImage(
|
||||
model, fieldName, modelDomain, enableCounters,
|
||||
!expand && !groupBy && !comodelDomain.length ? limit : false,
|
||||
);
|
||||
}
|
||||
if (!expand && !groupBy && !comodelDomain.length) {
|
||||
return [...domainImage.values()];
|
||||
if (limit && domainImage.size === limit) {
|
||||
return { error_msg: "Too many items to display." };
|
||||
}
|
||||
return { values: [...domainImage.values()] };
|
||||
}
|
||||
if (!expand) {
|
||||
const imageElementIds = [...domainImage.keys()].map(Number);
|
||||
@@ -903,7 +943,11 @@ var MockServer = Class.extend({
|
||||
['id', 'in', imageElementIds],
|
||||
]);
|
||||
}
|
||||
const comodelRecords = this._mockSearchRead(field.relation, [comodelDomain, fieldNames], {});
|
||||
const comodelRecords = this._mockSearchRead(field.relation, [comodelDomain, fieldNames], { limit });
|
||||
if (limit && comodelRecords.length === limit) {
|
||||
return { error_msg: "Too many items to display." };
|
||||
}
|
||||
|
||||
const fieldRange = [];
|
||||
for (const record of comodelRecords) {
|
||||
const values= {
|
||||
@@ -920,7 +964,7 @@ var MockServer = Class.extend({
|
||||
}
|
||||
fieldRange.push(values);
|
||||
}
|
||||
return fieldRange;
|
||||
return { values: fieldRange };
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -1948,6 +1948,7 @@ QUnit.module('Views', {
|
||||
comodel_domain: [['parent_id', '=', false]],
|
||||
group_domain: [],
|
||||
enable_counters: true,
|
||||
limit: 200,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
@@ -3834,5 +3835,81 @@ QUnit.module('Views', {
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("reached limit for a category", async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const kanban = await createView({
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="foo"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
archs: {
|
||||
'partner,false,search': `
|
||||
<search>
|
||||
<searchpanel>
|
||||
<field name="company_id" limit="2"/>
|
||||
</searchpanel>
|
||||
</search>`,
|
||||
},
|
||||
data: this.data,
|
||||
model: 'partner',
|
||||
services: this.services,
|
||||
View: KanbanView,
|
||||
});
|
||||
|
||||
assert.containsOnce(kanban, '.o_search_panel_section');
|
||||
assert.containsOnce(kanban, '.o_search_panel_section_header');
|
||||
assert.strictEqual(kanban.el.querySelector('.o_search_panel_section_header').innerText, " COMPANY");
|
||||
assert.containsOnce(kanban, 'section div.alert.alert-warning');
|
||||
assert.strictEqual(kanban.el.querySelector('section div.alert.alert-warning').innerText, "Too many items to display.");
|
||||
assert.containsNone(kanban, '.o_search_panel_category_value');
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("reached limit for a filter", async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const kanban = await createView({
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="foo"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
archs: {
|
||||
'partner,false,search': `
|
||||
<search>
|
||||
<searchpanel>
|
||||
<field name="company_id" select="multi" limit="2"/>
|
||||
</searchpanel>
|
||||
</search>`,
|
||||
},
|
||||
data: this.data,
|
||||
model: 'partner',
|
||||
services: this.services,
|
||||
View: KanbanView,
|
||||
});
|
||||
|
||||
assert.containsOnce(kanban, '.o_search_panel_section');
|
||||
assert.containsOnce(kanban, '.o_search_panel_section_header');
|
||||
assert.strictEqual(kanban.el.querySelector('.o_search_panel_section_header').innerText, " COMPANY");
|
||||
assert.containsOnce(kanban, 'section div.alert.alert-warning');
|
||||
assert.strictEqual(kanban.el.querySelector('section div.alert.alert-warning').innerText, "Too many items to display.");
|
||||
assert.containsNone(kanban, '.o_search_panel_filter_value');
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2005,6 +2005,11 @@ Possible children elements of the search view are:
|
||||
|
||||
* ``expand``: default is false. If set to false categories or filters with 0 records will be hidden.
|
||||
|
||||
* ``limit``: default is 200. Integer determining the maximal number of values to fetch for the field.
|
||||
If the limit is reached, no values will be displayed in the search panel and an error message will
|
||||
appear instead because we consider that is useless / bad performance-wise. All values will be
|
||||
fetched if set to 0.
|
||||
|
||||
Additional optional attributes are available according to the chosen case:
|
||||
|
||||
For the ``one`` case:
|
||||
|
||||
@@ -237,6 +237,7 @@
|
||||
<rng:optional><rng:attribute name="color"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="groupby"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="enable_counters"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="limit"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="operator"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="colspan"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="nolabel"/></rng:optional>
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
import odoo.tests
|
||||
import json
|
||||
|
||||
SEARCH_PANEL_ERROR = {'error_msg': "Too many items to display.", }
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
|
||||
@@ -16,7 +19,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
def test_many2one_empty(self):
|
||||
result = self.SourceModel.search_panel_select_multi_range('tag_id')
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[],
|
||||
)
|
||||
|
||||
@@ -55,7 +58,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
group_by='folder_id'
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': f2_id, 'group_name': 'Folder 2', },
|
||||
@@ -74,7 +77,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
group_by='status'
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': 'cool', 'group_name': 'Cool', },
|
||||
@@ -93,7 +96,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
group_by='color'
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': 'Red', 'group_name': 'Red', },
|
||||
@@ -111,7 +114,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
expand=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id, },
|
||||
{'__count': 1, 'display_name': 'Tag 2', 'id': t2_id, },
|
||||
@@ -127,7 +130,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', 'in', [r1_id, r2_id]]],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id, },
|
||||
{'__count': 0, 'display_name': 'Tag 2', 'id': t2_id, },
|
||||
@@ -135,6 +138,15 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
],
|
||||
)
|
||||
|
||||
# counters, expand, no group_by, and limit
|
||||
result = self.SourceModel.search_panel_select_multi_range(
|
||||
'tag_id',
|
||||
enable_counters=True,
|
||||
expand=True,
|
||||
limit=2,
|
||||
)
|
||||
self.assertEqual(result, SEARCH_PANEL_ERROR, )
|
||||
|
||||
# no counters, expand, and group_by (many2one case)
|
||||
result = self.SourceModel.search_panel_select_multi_range(
|
||||
'tag_id',
|
||||
@@ -142,7 +154,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
group_by='folder_id',
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': f2_id, 'group_name': 'Folder 2', },
|
||||
@@ -159,7 +171,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
expand=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'Tag 1', 'id': t1_id, },
|
||||
{'display_name': 'Tag 2', 'id': t2_id, },
|
||||
@@ -174,7 +186,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', 'in', [r1_id, r2_id]]],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'Tag 1', 'id': t1_id, },
|
||||
{'display_name': 'Tag 2', 'id': t2_id, },
|
||||
@@ -189,7 +201,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
group_by='folder_id',
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': f2_id, 'group_name': 'Folder 2', },
|
||||
@@ -205,19 +217,27 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', 'in', [r1_id, r2_id]]],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id, },
|
||||
]
|
||||
)
|
||||
|
||||
# counters, no expand, no group_by, and limit
|
||||
result = self.SourceModel.search_panel_select_multi_range(
|
||||
'tag_id',
|
||||
enable_counters=True,
|
||||
limit=2,
|
||||
)
|
||||
self.assertEqual(result, SEARCH_PANEL_ERROR, )
|
||||
|
||||
# no counters, no expand, group_by (many2one case), and search domain
|
||||
result = self.SourceModel.search_panel_select_multi_range(
|
||||
'tag_id',
|
||||
group_by='folder_id',
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': f2_id, 'group_name': 'Folder 2', },
|
||||
@@ -232,7 +252,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', 'in', [r1_id, r2_id]]],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'Tag 1', 'id': t1_id, },
|
||||
]
|
||||
@@ -244,7 +264,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
comodel_domain=[['id', 'in', [t2_id, t3_id]]],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'Tag 2', 'id': t2_id, },
|
||||
]
|
||||
@@ -255,7 +275,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
def test_many2many_empty(self):
|
||||
result = self.SourceModel.search_panel_select_multi_range('tag_ids')
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[],
|
||||
)
|
||||
|
||||
@@ -294,7 +314,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
group_by='folder_id'
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': f2_id, 'group_name': 'Folder 2', },
|
||||
@@ -313,7 +333,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
group_by='status'
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': 'cool', 'group_name': 'Cool', },
|
||||
@@ -332,7 +352,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
group_by='color'
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': 'Red', 'group_name': 'Red', },
|
||||
@@ -356,7 +376,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 1, 'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': f2_id, 'group_name': 'Folder 2', },
|
||||
@@ -379,7 +399,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': 'Red', 'group_name': 'Red', },
|
||||
@@ -397,7 +417,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
expand=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id, },
|
||||
{'__count': 2, 'display_name': 'Tag 2', 'id': t2_id, },
|
||||
@@ -413,7 +433,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', 'in', [r1_id, r2_id]]],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'Tag 1', 'id': t1_id, },
|
||||
{'__count': 1, 'display_name': 'Tag 2', 'id': t2_id, },
|
||||
@@ -428,7 +448,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
group_by='folder_id',
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': f2_id, 'group_name': 'Folder 2', },
|
||||
@@ -445,7 +465,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
expand=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'Tag 1', 'id': t1_id, },
|
||||
{'display_name': 'Tag 2', 'id': t2_id, },
|
||||
@@ -460,7 +480,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', 'in', [r1_id, r2_id]]],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'Tag 1', 'id': t1_id, },
|
||||
{'display_name': 'Tag 2', 'id': t2_id, },
|
||||
@@ -476,7 +496,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', '=', r2_id]],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 1, 'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': f2_id, 'group_name': 'Folder 2', },
|
||||
@@ -490,7 +510,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', '=', r2_id]],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 1, 'display_name': 'Tag 1', 'id': t1_id, },
|
||||
]
|
||||
@@ -503,7 +523,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', '=', r2_id]],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'Tag 1', 'id': t1_id,
|
||||
'group_id': f2_id, 'group_name': 'Folder 2', },
|
||||
@@ -516,12 +536,20 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', '=', r2_id]],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'Tag 1', 'id': t1_id, },
|
||||
]
|
||||
)
|
||||
|
||||
# no counters, no expand, no group_by, and search_domain
|
||||
result = self.SourceModel.search_panel_select_multi_range(
|
||||
'tag_ids',
|
||||
limit=2,
|
||||
)
|
||||
self.assertEqual(result, SEARCH_PANEL_ERROR, )
|
||||
|
||||
|
||||
# Selection case
|
||||
|
||||
def test_selection_empty(self):
|
||||
@@ -530,7 +558,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
expand=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'A', 'id': 'a', },
|
||||
{'display_name': 'B', 'id': 'b', },
|
||||
@@ -553,7 +581,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
group_by='not_possible_to_group', # no impact expected
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'A', 'id': 'a', '__count': 2, },
|
||||
{'display_name': 'B', 'id': 'b', '__count': 0, },
|
||||
@@ -567,7 +595,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
expand=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'A', 'id': 'a', '__count': 2, },
|
||||
{'display_name': 'B', 'id': 'b', '__count': 0, },
|
||||
@@ -582,7 +610,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', '=', r1_id]], # impact expected
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'A', 'id': 'a', '__count': 1, },
|
||||
{'display_name': 'B', 'id': 'b', '__count': 0, },
|
||||
@@ -595,7 +623,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
expand=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'A', 'id': 'a', },
|
||||
{'display_name': 'B', 'id': 'b', },
|
||||
@@ -609,7 +637,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
search_domain=[['id', '=', r1_id]], # no impact expected
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'A', 'id': 'a', },
|
||||
{'display_name': 'B', 'id': 'b', },
|
||||
@@ -622,7 +650,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
enable_counters=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'__count': 2, 'display_name': 'A', 'id': 'a', },
|
||||
]
|
||||
@@ -633,7 +661,7 @@ class TestSelectRangeMulti(odoo.tests.TransactionCase):
|
||||
'state',
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
result['values'],
|
||||
[
|
||||
{'display_name': 'A', 'id': 'a', },
|
||||
]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import odoo.tests
|
||||
|
||||
SEARCH_PANEL_ERROR = {'error_msg': "Too many items to display.", }
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestSelectRange(odoo.tests.TransactionCase):
|
||||
@@ -88,6 +90,39 @@ class TestSelectRange(odoo.tests.TransactionCase):
|
||||
]
|
||||
)
|
||||
|
||||
# counters, expand, hierarchization, and reached limit
|
||||
result = self.SourceModel.search_panel_select_range(
|
||||
'folder_id',
|
||||
enable_counters=True,
|
||||
expand=True,
|
||||
limit=2,
|
||||
)
|
||||
self.assertEqual(result, SEARCH_PANEL_ERROR, )
|
||||
|
||||
# counters, expand, hierarchization, and unreached limit
|
||||
result = self.SourceModel.search_panel_select_range(
|
||||
'folder_id',
|
||||
enable_counters=True,
|
||||
expand=True,
|
||||
limit=200,
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
{
|
||||
'parent_field': 'parent_name_id',
|
||||
'values': [
|
||||
{'__count': 2, 'display_name': 'Folder 1',
|
||||
'id': f1_id, 'parent_name_id': False, },
|
||||
{'__count': 1, 'display_name': 'Folder 2',
|
||||
'id': f2_id, 'parent_name_id': False, },
|
||||
{'__count': 1, 'display_name': 'Folder 3',
|
||||
'id': f3_id, 'parent_name_id': f1_id, },
|
||||
{'__count': 1, 'display_name': 'Folder 4',
|
||||
'id': f4_id, 'parent_name_id': f2_id, },
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
# counters, expand, and no hierarchization
|
||||
result = self.SourceModel.search_panel_select_range(
|
||||
'folder_id',
|
||||
@@ -210,6 +245,15 @@ class TestSelectRange(odoo.tests.TransactionCase):
|
||||
False
|
||||
)
|
||||
|
||||
# counters, no expand, no hierarchization, and limit
|
||||
result = self.SourceModel.search_panel_select_range(
|
||||
'folder_id',
|
||||
enable_counters=True,
|
||||
hierarchize=False,
|
||||
limit=2,
|
||||
)
|
||||
self.assertEqual(result, SEARCH_PANEL_ERROR, )
|
||||
|
||||
# no counters, no expand, and hierarchization
|
||||
result = self.SourceModel.search_panel_select_range(
|
||||
'folder_id',
|
||||
@@ -233,6 +277,16 @@ class TestSelectRange(odoo.tests.TransactionCase):
|
||||
'parent_name_id'
|
||||
)
|
||||
|
||||
# no counters, no expand, and hierarchization
|
||||
result = self.SourceModel.search_panel_select_range(
|
||||
'folder_id',
|
||||
search_domain=[(0, '=', 1)],
|
||||
)
|
||||
self.assertEqual(
|
||||
result,
|
||||
{'parent_field': 'parent_name_id', 'values': [], } # should not be a SEARCH_PANEL_ERROR
|
||||
)
|
||||
|
||||
# no counters, no expand, and no hierarchization
|
||||
result = self.SourceModel.search_panel_select_range(
|
||||
'folder_id',
|
||||
|
||||
Reference in New Issue
Block a user