From 34686ae187fca0d11496903808c84bb617568631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sun, 9 Mar 2025 11:23:43 +0100 Subject: [PATCH] Do not include record with NULL field in nested negative clause on None Since 87456218dcc9, record with NULL field is include in nested negative clause but this should not applied if the operand is or contains None. Closes #13911 --- trytond/doc/topics/domain.rst | 1 + trytond/trytond/model/fields/many2one.py | 5 ++++- trytond/trytond/model/fields/reference.py | 5 ++++- trytond/trytond/tests/test_field_many2one.py | 15 +++++++++++++++ trytond/trytond/tests/test_field_reference.py | 19 +++++++++++++++++++ 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/trytond/doc/topics/domain.rst b/trytond/doc/topics/domain.rst index e65a166f18..e61456ff47 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 d7714a12b4..3c6ec233e6 100644 --- a/trytond/trytond/model/fields/many2one.py +++ b/trytond/trytond/model/fields/many2one.py @@ -280,7 +280,10 @@ class Many2One(Field): target_table, _ = target_tables[None] _, 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 6b30e9e884..232e3a0b88 100644 --- a/trytond/trytond/model/fields/reference.py +++ b/trytond/trytond/model/fields/reference.py @@ -216,7 +216,10 @@ class Reference(SelectionMixin, Field): expression = ( self.sql_id(column, Model).in_(query) & column.like(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 7993d5e282..b992904cea 100644 --- a/trytond/trytond/tests/test_field_many2one.py +++ b/trytond/trytond/tests/test_field_many2one.py @@ -220,6 +220,21 @@ class FieldMany2OneTestCase(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_set_instance(self): "Test set instance" diff --git a/trytond/trytond/tests/test_field_reference.py b/trytond/trytond/tests/test_field_reference.py index 4559ab15a9..76a48860e5 100644 --- a/trytond/trytond/tests/test_field_reference.py +++ b/trytond/trytond/tests/test_field_reference.py @@ -375,6 +375,25 @@ class FieldReferenceTestCase(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"