Include NULL value for negative search on nested Many2One and Reference

This unify the search behavior on relational fields with NULL value started
with c14f51b77b30.

Closes #13585
(grafted from 87456218dcc9b075658f9e2338b120fce9b9a43d)

--HG--
branch : 7.0
This commit is contained in:
Cédric Krier committed 2024-10-11 11:24:06 +02:00
1 parent 82d30e56ef
commit 1dcbb0255c
5 files changed
+54 -5

No files matched your search

+4
View File
@@ -37,6 +37,10 @@ by this pattern::
The number of *dots* in a clause is not limited.
.. note::
Negative operators match records for which any of the field before the
last dot is empty.
.. warning::
For :class:`trytond.model.fields.Reference`, an extra ending clause is
needed to define the target model to join, for example::
+5 -3
View File
@@ -1,6 +1,6 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from sql import As, Column, Expression, Literal, Query, With
from sql import As, Column, Expression, Literal, Null, Query, With
from sql.aggregate import Max
from sql.conditionals import Coalesce
from sql.operators import Or
@@ -266,7 +266,7 @@ class Many2One(Field):
target_domain = [(target_name,) + tuple(domain[1:])]
if Target.estimated_count() < _subquery_threshold:
query = Target.search(target_domain, order=[], query=True)
return column.in_(query)
expression = column.in_(query)
else:
rule_domain = Rule.domain_get(Target.__name__, mode='read')
if rule_domain:
@@ -279,7 +279,9 @@ class Many2One(Field):
target_table, _ = target_tables[None]
_, expression = Target.search_domain(
target_domain, tables=target_tables)
return expression
if operator.startswith('not') or operator == '!=':
expression |= column == Null
return expression
@order_method
def convert_order(self, name, tables, Model):
+5 -2
View File
@@ -2,7 +2,7 @@
# this repository contains the full copyright notices and license terms.
import warnings
from sql import Cast, Expression, Literal, Query
from sql import Cast, Expression, Literal, Null, Query
from sql.functions import Position, Substring
from trytond.pool import Pool
@@ -214,9 +214,12 @@ class Reference(SelectionMixin, Field):
if 'active' in Target._fields:
target_domain.append(('active', 'in', [True, False]))
query = Target.search(target_domain, order=[], query=True)
return (
expression = (
self.sql_id(column, Model).in_(query)
& column.like(target + ',%'))
if operator.startswith('not') or operator == '!=':
expression |= column == Null
return expression
def definition(self, model, language):
encoder = PYSONEncoder()
@@ -200,6 +200,24 @@ class FieldMany2OneTestCase(unittest.TestCase):
query = self._test_search_join()
self.assertNotIsInstance(query.from_[0], Join)
@with_transaction()
def test_search_nested_null(self):
"Test search by null many2one"
pool = Pool()
Many2One = pool.get('test.many2one')
record, = Many2One.create([{'many2one': None}])
result = Many2One.search([
('many2one.value', '=', 1),
])
self.assertListEqual(result, [])
result = Many2One.search([
('many2one.value', '!=', 1),
])
self.assertListEqual(result, [record])
@with_transaction()
def test_context_attribute(self):
"Test context on many2one attribute"
@@ -352,6 +352,28 @@ class FieldReferenceTestCase(unittest.TestCase):
self.assertListEqual(references, [reference])
@with_transaction()
def test_search_join_null(self):
"Test search by null reference join"
pool = Pool()
Target = pool.get('test.reference.target')
Reference = pool.get('test.reference')
reference, = Reference.create([{
'reference': None,
}])
result = Reference.search([
('reference.name', '=', "Target", Target.__name__),
])
self.assertListEqual(result, [])
result = Reference.search([
('reference.name', '!=', "Target", Target.__name__),
])
self.assertListEqual(result, [reference])
@with_transaction()
def test_write_string(self):
"Test write reference string"