Add support to domain parser to search on empty relation field

Closes #14106
This commit is contained in:
Cédric Krier
2025-11-06 18:15:38 +01:00
parent 4859bedaaf
commit 66da5bf18a
6 changed files with 59 additions and 17 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add search on empty relation field to domain parser
* Escape completion content with custom format (issue14363)
* Use sandboxed iframe to display document (issue14290)
* Add support for multiple button in the tree view
+14 -9
View File
@@ -1171,8 +1171,7 @@
string_prefix = string_prefix || '';
for (var name in fields) {
var field = fields[name];
if ((field.searchable || (field.searchable === undefined)) &&
(name !== 'rec_name')) {
if (field.searchable || (field.searchable === undefined)) {
field = jQuery.extend({}, field);
var fullname = prefix ? prefix + '.' + name : name;
var string = string_prefix ?
@@ -1227,7 +1226,7 @@
}
var name = clause[0];
var value = clause[2];
if (name.endsWith('.rec_name')) {
if (name.endsWith('.rec_name') && value) {
name = name.slice(0, -9);
}
if (name in this.fields) {
@@ -1276,11 +1275,11 @@
var name = clause[0];
var operator = clause[1];
var value = clause[2];
if (name.endsWith('.rec_name')) {
if (name.endsWith('.rec_name') && value) {
name = name.slice(0, -9);
}
if (!(name in this.fields)) {
if (this.is_full_text(value)) {
if ((value !== null) && this.is_full_text(value)) {
value = value.slice(1, -1);
}
return this.quote(value);
@@ -1434,7 +1433,7 @@
name = clause[0];
operator = clause[1];
value = clause[2];
if (name.endsWith('.rec_name')) {
if (name.endsWith('.rec_name') && value) {
name = name.substring(0, name.length - 9);
}
}
@@ -1854,7 +1853,7 @@
var split = this.split_target_value(field, value);
target = split[0];
value = split[1];
if (target) {
if (target && value) {
field_name += '.rec_name';
}
} else if (field.type == 'multiselection') {
@@ -1901,7 +1900,7 @@
}
}
if (['many2one', 'one2many', 'many2many', 'one2one',
'many2many', 'one2one'].includes(field.type)) {
'many2many', 'one2one'].includes(field.type) && value) {
field_name += '.rec_name';
}
if (value instanceof Array) {
@@ -2116,7 +2115,13 @@
},
'selection': convert_selection,
'multiselection': convert_selection,
'reference': convert_selection,
'reference': function() {
if (value === '') {
return null;
} else {
return convert_selection();
}
},
'datetime': () => Sao.common.parse_datetime(
Sao.common.date_format(context.date_format) + ' ' +
this.time_format(field), value),
+9
View File
@@ -2051,6 +2051,8 @@
[['many2one.rec_name', 'ilike', '%John%']]],
[[c(['Many2One', null, ['John', 'Jane']])],
[['many2one.rec_name', 'in', ['John', 'Jane']]]],
[[c(['Many2One', '=', null])], [['many2one', '=', null]]],
[[c(['Many2One', '=', ''])], [['many2one', '=', null]]],
[[[c(['John'])]], [[['rec_name', 'ilike', '%John%']]]],
[[c(['Relation', null, 'John'])],
[['relation.rec_name', 'ilike', '%John%']]],
@@ -2308,6 +2310,10 @@
'string': "Name",
'type': 'char',
},
'rec_name': {
'string': "Record Name",
'type': 'char',
},
},
},
});
@@ -2371,6 +2377,9 @@
[[['reference', 'in', ['foo', 'bar']]], 'Reference: foo;bar'],
[[['many2one', 'ilike', '%John%']], 'Many2One: John'],
[[['many2one.rec_name', 'in', ['John', 'Jane']]], 'Many2One: John;Jane'],
[[['many2one.rec_name', '=', 'John']], 'Many2One: =John'],
[[['many2one.rec_name', '=', '']], '"Many2One.Record Name": =""'],
[[['many2one.rec_name', '=', null]], '"Many2One.Record Name": ='],
[[['many2one.name', 'ilike', '%Foo%']], 'Many2One.Name: Foo'],
].forEach(function(test) {
var value = test[0];
+1
View File
@@ -1,3 +1,4 @@
* Add search on empty relation field to domain parser
* Add support for multiple button in the tree view
Version 7.6.0 - 2025-04-28
+13 -8
View File
@@ -292,6 +292,11 @@ def convert_value(field, value, context=None):
return None
return value
def convert_reference():
if value == '':
return None
return convert_selection()
converts = {
'boolean': convert_boolean,
'float': convert_float,
@@ -299,7 +304,7 @@ def convert_value(field, value, context=None):
'numeric': convert_numeric,
'selection': convert_selection,
'multiselection': convert_selection,
'reference': convert_selection,
'reference': convert_reference,
'datetime': convert_datetime,
'timestamp': convert_datetime,
'date': convert_date,
@@ -543,7 +548,7 @@ class DomainParser(object):
def update_fields(fields, prefix='', string_prefix=''):
for name, field in fields.items():
if not field.get('searchable', True) or name == 'rec_name':
if not field.get('searchable', True):
continue
field = field.copy()
fullname = '.'.join(filter(None, [prefix, name]))
@@ -584,7 +589,7 @@ class DomainParser(object):
and all(isinstance(c, (list, tuple)) for c in clause[1:])):
return self.stringable(clause)
name, _, value = clause[:3]
if name.endswith('.rec_name'):
if name.endswith('.rec_name') and value:
name = name[:-len('.rec_name')]
if name in self.fields:
field = self.fields[name]
@@ -619,10 +624,10 @@ class DomainParser(object):
or clause[0] in ('AND', 'OR')):
return '(%s)' % self.string(clause)
name, operator, value = clause[:3]
if name.endswith('.rec_name'):
if name.endswith('.rec_name') and value:
name = name[:-9]
if name not in self.fields:
if is_full_text(value):
if value is not None and is_full_text(value):
value = value[1:-1]
return quote(value)
field = self.fields[name]
@@ -724,7 +729,7 @@ class DomainParser(object):
name, operator, value = clause
else:
name, operator, value, target = clause
if name.endswith('.rec_name'):
if name.endswith('.rec_name') and value:
name = name[:-9]
value = target
if name == 'rec_name':
@@ -846,7 +851,7 @@ class DomainParser(object):
target = None
if field['type'] == 'reference':
target, value = split_target_value(field, value)
if target:
if target and value:
field_name += '.rec_name'
elif field['type'] == 'multiselection':
if value is not None and not isinstance(value, list):
@@ -881,7 +886,7 @@ class DomainParser(object):
continue
if field['type'] in {
'many2one', 'one2many', 'many2many', 'one2one',
}:
} and value:
field_name += '.rec_name'
if isinstance(value, list):
value = [convert_value(field, v, self.context)
@@ -663,6 +663,10 @@ class DomainParserTestCase(TestCase):
'string': "Name",
'type': 'char',
},
'rec_name': {
'string': "Record Name",
'type': 'char',
},
},
},
})
@@ -776,6 +780,15 @@ class DomainParserTestCase(TestCase):
self.assertEqual(
dom.string([('many2one.rec_name', 'in', ['John', 'Jane'])]),
'Many2One: John;Jane')
self.assertEqual(
dom.string([('many2one.rec_name', '=', 'John')]),
'Many2One: =John')
self.assertEqual(
dom.string([('many2one.rec_name', '=', '')]),
'"Many2One.Record Name": =""')
self.assertEqual(
dom.string([('many2one.rec_name', '=', None)]),
'"Many2One.Record Name": =')
self.assertEqual(
dom.string([('many2one.name', 'ilike', '%Foo%')]),
"Many2One.Name: Foo")
@@ -1112,6 +1125,14 @@ class DomainParserTestCase(TestCase):
rlist(dom.parse_clause([('Many2One', None, ['John', 'Jane'])])), [
('many2one.rec_name', 'in', ['John', 'Jane']),
])
self.assertEqual(
rlist(dom.parse_clause([('Many2One', '=', None)])), [
('many2one', '=', None),
])
self.assertEqual(
rlist(dom.parse_clause([('Many2One', '=', '')])), [
('many2one', '=', None),
])
self.assertEqual(
rlist(dom.parse_clause(iter([iter([['John']])]))), [
[('rec_name', 'ilike', '%John%')]])