Use & and | as boolean operator in search widget

Closes #11962, #8714
This commit is contained in:
Cédric Krier
2022-12-27 17:36:16 +01:00
parent 3aadada047
commit 26b9f97577
7 changed files with 70 additions and 60 deletions
+1
View File
@@ -1,3 +1,4 @@
* Use ``&`` and ``|`` as boolean operator in search widget
* Use toggle button and label on translate dialog
* Support border type of images
* Manage domain on id in single value list as unique
+4 -3
View File
@@ -1306,7 +1306,7 @@
var nary = ' ';
if ((domain[0] == 'AND') || (domain[0] == 'OR')) {
if (domain[0] == 'OR') {
nary = ' or ';
nary = ' | ';
}
domain = domain.slice(1);
}
@@ -1729,11 +1729,12 @@
var result = [];
operator = operator || 'or';
tokens = jQuery.extend([], tokens);
var notation = {'or': '|', 'and': '&'}[operator];
var test = function(value) {
if (value instanceof Array) {
return Sao.common.compare(value, [operator]);
return Sao.common.compare(value, [notation]);
} else {
return value == operator;
return value == notation;
}
};
var cur = tokens.shift();
+23 -23
View File
@@ -1578,31 +1578,31 @@
[
[['a'], ['a']],
[['a', 'or', 'b'], [['OR', 'a', 'b']]],
[['a', 'or', 'b', 'or', 'c'], [['OR', ['OR', 'a', 'b'], 'c']]],
[['a', 'b', 'or', 'c'], ['a', ['OR', 'b', 'c']]],
[['a', 'or', 'b', 'c'], [['OR', 'a', 'b'], 'c']],
[['a', '|', 'b'], [['OR', 'a', 'b']]],
[['a', '|', 'b', '|', 'c'], [['OR', ['OR', 'a', 'b'], 'c']]],
[['a', 'b', '|', 'c'], ['a', ['OR', 'b', 'c']]],
[['a', '|', 'b', 'c'], [['OR', 'a', 'b'], 'c']],
[['a', ['b', 'c']], ['a', ['b', 'c']]],
[['a', ['b', 'c'], 'd'], ['a', ['b', 'c'], 'd']],
[['a', 'or', ['b', 'c']], [['OR', 'a', ['b', 'c']]]],
[['a', 'or', ['b', 'c'], 'd'],
[['a', '|', ['b', 'c']], [['OR', 'a', ['b', 'c']]]],
[['a', '|', ['b', 'c'], 'd'],
[['OR', 'a', ['b', 'c']], 'd']],
[['a', ['b', 'c'], 'or', 'd'],
[['a', ['b', 'c'], '|', 'd'],
['a', ['OR', ['b', 'c'], 'd']]],
[['a', 'or', ['b', 'or', 'c']],
[['a', '|', ['b', '|', 'c']],
[['OR', 'a', [['OR', 'b', 'c']]]]],
[['or'], []],
[['or', 'a'], ['a']],
[['a', ['or', 'b']], ['a', ['b']]],
[['a', 'or', 'or', 'b'], [['OR', 'a', 'b']]],
[['or', 'or', 'a'], ['a']],
[['or', 'or', 'a', 'b'], ['a', 'b']],
[['or', 'or', 'a', 'or', 'b'], [['OR', 'a', 'b']]],
[['a', ['b', 'or', 'c']], ['a', [['OR', 'b', 'c']]]],
[[a, [b, ['or'], c]], [a, [['OR', b, c]]]],
[['a', ['b', 'or']], ['a', [['OR', 'b']]]],
[['|'], []],
[['|', 'a'], ['a']],
[['a', ['|', 'b']], ['a', ['b']]],
[['a', '|', '|', 'b'], [['OR', 'a', 'b']]],
[['|', '|', 'a'], ['a']],
[['|', '|', 'a', 'b'], ['a', 'b']],
[['|', '|', 'a', '|', 'b'], [['OR', 'a', 'b']]],
[['a', ['b', '|', 'c']], ['a', [['OR', 'b', 'c']]]],
[[a, [b, ['|'], c]], [a, [['OR', b, c]]]],
[['a', ['b', '|']], ['a', [['OR', 'b']]]],
[[null_], [null_]],
[[null_, 'or', double_null_], [['OR', null_, double_null_]]]
[[null_, '|', double_null_], [['OR', null_, double_null_]]]
].forEach(function(test) {
var value = test[0];
var result = test[1];
@@ -2233,14 +2233,14 @@
[['OR',
['name', 'ilike', '%Doe%'],
['name', 'ilike', '%Jane%']
], 'Name: Doe or Name: Jane'],
], 'Name: Doe | Name: Jane'],
[[
['name', 'ilike', '%Doe%'],
['OR',
['name', 'ilike', '%John%'],
['name', 'ilike', '%Jane%']
]
], 'Name: Doe (Name: John or Name: Jane)'],
], 'Name: Doe (Name: John | Name: Jane)'],
[[], ''],
[[['surname', 'ilike', '%Doe%']], '"(Sur)Name": Doe'],
//[[['date', '>=', new Date(2012, 10, 24)]], 'Date: >=10/24/2012'],
@@ -3112,8 +3112,8 @@
['Name: !=foo', []],
['', ["Name: "]],
[' ', ["", "Name: "]],
["Name: foo or", ["Name: foo"]],
['Name: foo (Name: foo or N', ["Name: foo (Name: foo or Name: "]],
["Name: foo |", ["Name: foo"]],
['Name: foo (Name: foo | N', ["Name: foo (Name: foo | Name: "]],
].forEach(function(test) {
var value = test[0];
var expected = test[1];
+1
View File
@@ -1,3 +1,4 @@
* Use ``&`` and ``|`` as boolean operator in search widget
* Use toggle button and label on translate dialog
* Manage domain on id in single value list as unique
* Do not validate domain and enforce uniqueness for empty fields unless they're required
+12 -8
View File
@@ -512,22 +512,26 @@ It is possible to escape special characters in values by using double quotes.
Clause composition
++++++++++++++++++
The clauses can be composed using the two boolean operators ``and`` and ``or``.
By default, there is an implicit ``and`` between each clause if no operator is
The clauses can be composed using the two boolean operators ``&`` (for `logical
conjunction`_) and ``|`` (for `logical disjunction`_).
By default, there is an implicit ``&`` between each clause if no operator is
specified.
For example: ``Name: Michael Amount: 100``
is the same as ``Name: Michael and Amount: 100``
is the same as ``Name: Michael & Amount: 100``
The ``and`` operator has a highest precedence than ``or`` but you can change it
by using parenthesis.
The ``&`` operator has a highest precedence than ``|`` but you can change it by
using parenthesis.
For example: ``(Name: Michael or Name: Pam) and Amount: 100``
For example: ``(Name: Michael | Name: Pam) & Amount: 100``
is different than ``Name: Michael or Name: Pam and Amount: 100``
is different than ``Name: Michael | Name: Pam & Amount: 100``
which is evaluated as ``Name: Michael or (Name: Pam and Amount: 100)``
which is evaluated as ``Name: Michael | (Name: Pam & Amount: 100)``
.. _logical conjunction: https://en.wikipedia.org/wiki/Logical_conjunction
.. _logical disjunction: https://en.wikipedia.org/wiki/Logical_disjunction
RichText Editor
^^^^^^^^^^^^^^^
+5 -2
View File
@@ -484,7 +484,10 @@ def parenthesize(tokens):
def operatorize(tokens, operator='or'):
"Convert operators"
test = (operator, (operator,))
test = {
'or': ('|', ('|',)),
'and': ('&', ('&',)),
}[operator]
try:
cur = next(tokens)
while cur in test:
@@ -656,7 +659,7 @@ class DomainParser(object):
if not domain:
return ''
if domain[0] in ('AND', 'OR'):
nary = ' ' if domain[0] == 'AND' else ' or '
nary = ' ' if domain[0] == 'AND' else ' | '
domain = domain[1:]
else:
nary = ' '
@@ -529,31 +529,31 @@ class DomainParserTestCase(TestCase):
double_null_ = ('e', None, None)
for value, result in (
(['a'], ['a']),
(['a', 'or', 'b'], [['OR', 'a', 'b']]),
(['a', 'or', 'b', 'or', 'c'], [['OR', ['OR', 'a', 'b'], 'c']]),
(['a', 'b', 'or', 'c'], ['a', ['OR', 'b', 'c']]),
(['a', 'or', 'b', 'c'], [['OR', 'a', 'b'], 'c']),
(['a', '|', 'b'], [['OR', 'a', 'b']]),
(['a', '|', 'b', '|', 'c'], [['OR', ['OR', 'a', 'b'], 'c']]),
(['a', 'b', '|', 'c'], ['a', ['OR', 'b', 'c']]),
(['a', '|', 'b', 'c'], [['OR', 'a', 'b'], 'c']),
(['a', iter(['b', 'c'])], ['a', ['b', 'c']]),
(['a', iter(['b', 'c']), 'd'], ['a', ['b', 'c'], 'd']),
(['a', 'or', iter(['b', 'c'])], [['OR', 'a', ['b', 'c']]]),
(['a', 'or', iter(['b', 'c']), 'd'],
(['a', '|', iter(['b', 'c'])], [['OR', 'a', ['b', 'c']]]),
(['a', '|', iter(['b', 'c']), 'd'],
[['OR', 'a', ['b', 'c']], 'd']),
(['a', iter(['b', 'c']), 'or', 'd'],
(['a', iter(['b', 'c']), '|', 'd'],
['a', ['OR', ['b', 'c'], 'd']]),
(['a', 'or', iter(['b', 'or', 'c'])],
(['a', '|', iter(['b', '|', 'c'])],
[['OR', 'a', [['OR', 'b', 'c']]]]),
(['or'], []),
(['or', 'a'], ['a']),
(['a', iter(['or', 'b'])], ['a', ['b']]),
(['a', 'or', 'or', 'b'], [['OR', 'a', 'b']]),
(['or', 'or', 'a'], ['a']),
(['or', 'or', 'a', 'b'], ['a', 'b']),
(['or', 'or', 'a', 'or', 'b'], [['OR', 'a', 'b']]),
(['a', iter(['b', 'or', 'c'])], ['a', [['OR', 'b', 'c']]]),
([a, iter([b, ('or',), c])], [a, [['OR', b, c]]]),
(['a', iter(['b', 'or'])], ['a', [['OR', 'b']]]),
(['|'], []),
(['|', 'a'], ['a']),
(['a', iter(['|', 'b'])], ['a', ['b']]),
(['a', '|', '|', 'b'], [['OR', 'a', 'b']]),
(['|', '|', 'a'], ['a']),
(['|', '|', 'a', 'b'], ['a', 'b']),
(['|', '|', 'a', '|', 'b'], [['OR', 'a', 'b']]),
(['a', iter(['b', '|', 'c'])], ['a', [['OR', 'b', 'c']]]),
([a, iter([b, ('|',), c])], [a, [['OR', b, c]]]),
(['a', iter(['b', '|'])], ['a', [['OR', 'b']]]),
([null_], [null_]),
([null_, 'or', double_null_], [['OR', null_, double_null_]]),
([null_, '|', double_null_], [['OR', null_, double_null_]]),
):
self.assertEqual(
rlist(operatorize(iter(value))), result,
@@ -696,14 +696,14 @@ class DomainParserTestCase(TestCase):
dom.string(['OR',
('name', 'ilike', '%Doe%'),
('name', 'ilike', '%Jane%')]),
'Name: Doe or Name: Jane')
'Name: Doe | Name: Jane')
self.assertEqual(
dom.string([
('name', 'ilike', '%Doe%'),
['OR',
('name', 'ilike', '%John%'),
('name', 'ilike', '%Jane%')]]),
'Name: Doe (Name: John or Name: Jane)')
'Name: Doe (Name: John | Name: Jane)')
self.assertEqual(dom.string([]), '')
self.assertEqual(
dom.string([('surname', 'ilike', '%Doe%')]), '"(Sur)Name": Doe')
@@ -1121,10 +1121,10 @@ class DomainParserTestCase(TestCase):
self.assertEqual(list(dom.completion('Name: !=foo')), [])
self.assertEqual(list(dom.completion('')), ['Name: '])
self.assertEqual(list(dom.completion(' ')), ['', 'Name: '])
self.assertEqual(list(dom.completion('Name: foo or')), ['Name: foo'])
self.assertEqual(list(dom.completion('Name: foo |')), ['Name: foo'])
self.assertEqual(
list(dom.completion('Name: foo (Name: foo or N')),
['Name: foo (Name: foo or Name: '])
list(dom.completion('Name: foo (Name: foo | N')),
['Name: foo (Name: foo | Name: '])
def test_completion_many2one(self):
"Test completion many2one"