diff --git a/sao/src/common.js b/sao/src/common.js index 0ff060c6bb..9accc69fac 100644 --- a/sao/src/common.js +++ b/sao/src/common.js @@ -1204,11 +1204,12 @@ var name = clause[0]; var operator = clause[1]; var value = clause[2]; - if (name.endsWith('.rec_name')) { + if (name.endsWith('.rec_name') + && (value || (clause.length > 3))) { 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); @@ -2149,7 +2150,7 @@ break; } } - return target + ',' + value; + return target + ',' + (value || ''); }; var converts = { diff --git a/sao/tests/sao.js b/sao/tests/sao.js index ec3c5b3fa5..8ad1265bea 100644 --- a/sao/tests/sao.js +++ b/sao/tests/sao.js @@ -1930,6 +1930,9 @@ [[c(['Reference', null, 'Spam,bar'])], [ c(['reference.rec_name', 'ilike', '%bar%', 'spam']) ]], + [[c(['Reference', null, 'Spam,'])], [ + c(['reference.rec_name', 'ilike', '%', 'spam']) + ]], [[c(['Reference', null, ['foo', 'bar']])], [ c(['reference', 'in', ['foo', 'bar']]) ]], @@ -2250,6 +2253,7 @@ [[['multiselection', 'not in', ['foo', 'bar']]], "MultiSelection: !Foo;Bar"], [[['reference', 'ilike', '%foo%']], 'Reference: foo'], [[['reference', 'ilike', '%bar%', 'spam']], 'Reference: Spam,bar'], + [[['reference.rec_name', '=', null, 'spam']], 'Reference: =Spam,'], [[['reference', 'in', ['foo', 'bar']]], 'Reference: foo;bar'], [[['many2one', 'ilike', '%John%']], 'Many2One: John'], [[['many2one.rec_name', 'in', ['John', 'Jane']]], 'Many2One: John;Jane'], diff --git a/tryton/tryton/common/domain_parser.py b/tryton/tryton/common/domain_parser.py index df46a1c3a8..030066282c 100644 --- a/tryton/tryton/common/domain_parser.py +++ b/tryton/tryton/common/domain_parser.py @@ -361,7 +361,7 @@ def format_value(field, value, target=None, context=None, _quote_empty=False): if not target: return format_selection() selections = dict(field['selection']) - return '%s,%s' % (selections.get(target, target), value) + return '%s,%s' % (selections.get(target, target), value or '') def format_datetime(): if not value: @@ -617,10 +617,11 @@ 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 or len(clause) > 3)): 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] diff --git a/tryton/tryton/tests/test_common_domain_parser.py b/tryton/tryton/tests/test_common_domain_parser.py index 26e98d2ac2..5b21c2d6f3 100644 --- a/tryton/tryton/tests/test_common_domain_parser.py +++ b/tryton/tryton/tests/test_common_domain_parser.py @@ -768,6 +768,9 @@ class DomainParserTestCase(TestCase): self.assertEqual( dom.string([('reference.rec_name', 'ilike', '%bar%', 'spam')]), 'Reference: Spam,bar') + self.assertEqual( + dom.string([('reference.rec_name', '=', None, 'spam')]), + "Reference: =Spam,") self.assertEqual( dom.string([('reference', 'in', ['foo', 'bar'])]), 'Reference: foo;bar') @@ -1081,6 +1084,10 @@ class DomainParserTestCase(TestCase): rlist(dom.parse_clause([('Reference', None, 'Spam,bar')])), [ ('reference.rec_name', 'ilike', '%bar%', 'spam'), ]) + self.assertEqual( + rlist(dom.parse_clause([('Reference', None, 'Spam,')])), [ + ('reference.rec_name', 'ilike', '%', 'spam'), + ]) self.assertEqual( rlist(dom.parse_clause([('Reference', None, ['foo', 'bar'])])), [ ('reference', 'in', ['foo', 'bar']),