diff --git a/trytond/doc/topics/domain.rst b/trytond/doc/topics/domain.rst index f7c9be42cf..f51b09a94e 100644 --- a/trytond/doc/topics/domain.rst +++ b/trytond/doc/topics/domain.rst @@ -40,6 +40,7 @@ by this pattern:: .. note:: Negative operators match records for which any of the field before the last dot is empty. + Except if the operand is or contains ``None``. .. warning:: For :class:`trytond.model.fields.Reference`, an extra ending clause is diff --git a/trytond/trytond/model/fields/many2one.py b/trytond/trytond/model/fields/many2one.py index 1c31118e0c..42708901d4 100644 --- a/trytond/trytond/model/fields/many2one.py +++ b/trytond/trytond/model/fields/many2one.py @@ -236,7 +236,10 @@ class Many2One(Field): target_domain = [target_domain, rule_domain] _, expression = Target.search_domain( target_domain, tables=target_tables) - if operator.startswith('not') or operator == '!=': + if ((operator.startswith('not') and not ( + operator == 'not in' + and None in value)) + or (operator == '!=' and value is not None)): expression |= column == Null return expression diff --git a/trytond/trytond/model/fields/reference.py b/trytond/trytond/model/fields/reference.py index 33d47d7350..4fdd2707ba 100644 --- a/trytond/trytond/model/fields/reference.py +++ b/trytond/trytond/model/fields/reference.py @@ -189,7 +189,10 @@ class Reference(SelectionMixin, Field): Position(',', column) + Literal(1)), Model.id.sql_type().base).in_(query) & column.ilike(target + ',%')) - if operator.startswith('not') or operator == '!=': + if ((operator.startswith('not') and not ( + operator == 'not in' + and None in value)) + or (operator == '!=' and value is not None)): expression |= column == Null return expression diff --git a/trytond/trytond/tests/test_field_many2one.py b/trytond/trytond/tests/test_field_many2one.py index 6b7ca8d118..ce71e7e3c6 100644 --- a/trytond/trytond/tests/test_field_many2one.py +++ b/trytond/trytond/tests/test_field_many2one.py @@ -485,6 +485,21 @@ class FieldMany2OneTestCase(unittest.TestCase): ]) self.assertListEqual(result, [record]) + result = Many2One.search([ + ('many2one.value', '!=', None), + ]) + self.assertListEqual(result, []) + + result = Many2One.search([ + ('many2one.value', 'not in', [1]), + ]) + self.assertListEqual(result, [record]) + + result = Many2One.search([ + ('many2one.value', 'not in', [1, None]), + ]) + self.assertListEqual(result, []) + @with_transaction() def test_context_attribute(self): "Test context on many2one attribute" diff --git a/trytond/trytond/tests/test_field_reference.py b/trytond/trytond/tests/test_field_reference.py index ae36dc8c31..96d9bc0432 100644 --- a/trytond/trytond/tests/test_field_reference.py +++ b/trytond/trytond/tests/test_field_reference.py @@ -373,6 +373,25 @@ class FieldReferenceTestCase(unittest.TestCase): self.assertListEqual(result, [reference]) + result = Reference.search([ + ('reference.name', '!=', None, Target.__name__), + ]) + + self.assertListEqual(result, []) + + result = Reference.search([ + ('reference.name', 'not in', ["Target"], Target.__name__), + ]) + + self.assertListEqual(result, [reference]) + + result = Reference.search([ + ('reference.name', 'not in', ["Target", None], + Target.__name__), + ]) + + self.assertListEqual(result, []) + @with_transaction() def test_write_string(self): "Test write reference string"