Add target_search option on Many2One

The option allow to select between the subquery or the join format for target
search clause.

issue4798
review16341002
This commit is contained in:
Cédric Krier
2015-06-15 01:10:59 +02:00
parent 375cde4422
commit 0c4725b891
8 changed files with 104 additions and 34 deletions
+2
View File
@@ -1,3 +1,5 @@
* Add target_search option to Many2One
* Add tables argument to ModelSQL.search_domain
* Rename ir.module.module* into ir.module*
Version 3.6.0 - 2015-04-20
+20 -15
View File
@@ -155,20 +155,8 @@ Instance methods:
.. method:: Field.convert_domain(domain, tables, Model)
Convert the simple :ref:`domain <topics-domain>` clause into a SQL
expression or a new domain.
Where ``tables`` is a nested dictionary containing the existing joins (and it
could be updated to add new joins)::
{
None: (<Table invoice>, None),
'party': {
None: (<Table party>, <join_on sql expression>),
'addresses': {
None: (<Table address>, <join_on sql expression>),
},
},
}
expression or a new domain. :ref:`tables <ref-tables>` could be updated to
add new joins.
.. method:: Field.sql_format(value)
@@ -478,7 +466,7 @@ Same like :attr:`Selection.selection_change_with`.
Many2One
--------
.. class:: Many2One(model_name, string[, left[, right[, ondelete[, datetime_field[, \**options]]]]])
.. class:: Many2One(model_name, string[, left[, right[, ondelete[, datetime_field[, target_search[, \**options]]]]])
A many-to-one relation field.
@@ -529,6 +517,23 @@ A many-to-one relation field.
:attr:`trytond.model.ModelSQL._history` to request a value for a given date
and time on a historicized model.
.. attribute:: Many2One.target_search
Define the kind of SQL query to use when searching on related target.
Allowed values are:
- ``subquery``: it will use a subquery based on the ids.
- ``join``: it will add a join on the main query.
``join`` is the default value.
.. note::
``join`` could improve the performance if the target has a huge amount
of records.
..
One2Many
--------
+14 -7
View File
@@ -452,17 +452,24 @@ Class methods:
Return a list of records that match the :ref:`domain <topics-domain>` or
the sql query if query is True.
.. classmethod:: ModelSQL.search_domain(domain[, active_test])
.. classmethod:: ModelSQL.search_domain(domain[, active_test[, tables]])
Convert a :ref:`domain <topics-domain>` into a tuple containing:
Convert a :ref:`domain <topics-domain>` into a SQL expression by returning
the updated tables dictionary and a SQL expression.
- a SQL clause string
.. _ref-tables:
- a list of arguments for the SQL clause
Where ``tables`` is a nested dictionary containing the existing joins::
- a list of tables used in the SQL clause
- a list of arguments for the tables
{
None: (<Table invoice>, None),
'party': {
None: (<Table party>, <join_on sql expression>),
'addresses': {
None: (<Table address>, <join_on sql expression>),
},
},
}
========
Workflow
+30 -8
View File
@@ -18,9 +18,9 @@ class Many2One(Field):
_type = 'many2one'
def __init__(self, model_name, string='', left=None, right=None,
ondelete='SET NULL', datetime_field=None, help='', required=False,
readonly=False, domain=None, states=None, select=False,
on_change=None, on_change_with=None, depends=None,
ondelete='SET NULL', datetime_field=None, target_search='join',
help='', required=False, readonly=False, domain=None, states=None,
select=False, on_change=None, on_change_with=None, depends=None,
context=None, loading='eager'):
'''
:param model_name: The name of the target model.
@@ -33,6 +33,7 @@ class Many2One(Field):
``SET NULL`` will be changed into ``RESTRICT`` if required is set.
:param datetime_field: The name of the field that contains the datetime
value to read the target record.
:param target_search: The kind of target search 'subquery' or 'join'
'''
self.__required = required
if ondelete not in ('CASCADE', 'RESTRICT', 'SET NULL'):
@@ -51,6 +52,8 @@ class Many2One(Field):
self.left = left
self.right = right
self.datetime_field = datetime_field
assert target_search in ['subquery', 'join']
self.target_search = target_search
__init__.__doc__ += Field.__init__.__doc__
def __get_required(self):
@@ -132,7 +135,10 @@ class Many2One(Field):
return expression
def convert_domain(self, domain, tables, Model):
pool = Pool()
Rule = pool.get('ir.rule')
Target = self.get_target()
table, _ = tables[None]
name, operator, value = domain[:3]
column = self.sql_column(table)
@@ -176,8 +182,18 @@ class Many2One(Field):
target_domain = [(target_name,) + tuple(domain[1:])]
if 'active' in Target._fields:
target_domain.append(('active', 'in', [True, False]))
query = Target.search(target_domain, order=[], query=True)
return column.in_(query)
if self.target_search == 'subquery':
query = Target.search(target_domain, order=[], query=True)
return column.in_(query)
else:
target_tables = self._get_target_tables(tables)
target_table, _ = target_tables[None]
_, expression = Target.search_domain(
target_domain, tables=target_tables)
rule_domain = Rule.domain_get(Target.__name__, mode='read')
if rule_domain:
expression &= target_table.id.in_(rule_domain)
return expression
def convert_order(self, name, tables, Model):
fname, _, oexpr = name.partition('.')
@@ -202,11 +218,17 @@ class Many2One(Field):
return [self.sql_column(table)]
ofield = Target._fields[oname]
target_tables = tables.get(fname)
target_tables = self._get_target_tables(tables)
return ofield.convert_order(oexpr, target_tables, Target)
def _get_target_tables(self, tables):
Target = self.get_target()
table, _ = tables[None]
target_tables = tables.get(self.name)
if target_tables is None:
target = Target.__table__()
target_tables = {
None: (target, target.id == self.sql_column(table)),
}
tables[fname] = target_tables
return ofield.convert_order(oexpr, target_tables, Target)
tables[self.name] = target_tables
return target_tables
+5 -4
View File
@@ -1140,7 +1140,7 @@ class ModelSQL(ModelStorage):
return cls.browse([x['id'] for x in rows])
@classmethod
def search_domain(cls, domain, active_test=True):
def search_domain(cls, domain, active_test=True, tables=None):
'''
Return SQL tables and expression
Set active_test to add it.
@@ -1148,9 +1148,10 @@ class ModelSQL(ModelStorage):
transaction = Transaction()
domain = cls._search_domain_active(domain, active_test=active_test)
tables = {
None: (cls.__table__(), None)
}
if tables is None:
tables = {}
if None not in tables:
tables[None] = (cls.__table__(), None)
if cls._history and transaction.context.get('_datetime'):
tables[None] = (cls.__table_history__(), None)
+1
View File
@@ -159,6 +159,7 @@ def register():
Many2OneTarget,
Many2OneDomainValidation,
Many2OneOrderBy,
Many2OneSearch,
TestHistory,
TestHistoryLine,
FieldContextChild,
+7
View File
@@ -36,6 +36,7 @@ __all__ = [
'DictSchema', 'Dict', 'DictDefault', 'DictRequired',
'Binary', 'BinaryDefault', 'BinaryRequired',
'Many2OneDomainValidation', 'Many2OneTarget', 'Many2OneOrderBy',
'Many2OneSearch',
]
@@ -734,3 +735,9 @@ class Many2OneOrderBy(ModelSQL):
"Many2One OrderBy"
__name__ = 'test.many2one_orderby'
many2one = fields.Many2One('test.many2one_target', 'many2one')
class Many2OneSearch(ModelSQL):
"Many2One Search"
__name__ = 'test.many2one_search'
many2one = fields.Many2One('test.many2one_target', 'many2one')
+25
View File
@@ -118,6 +118,7 @@ class FieldsTestCase(unittest.TestCase):
self.m2o_domain_validation = POOL.get('test.many2one_domainvalidation')
self.m2o_orderby = POOL.get('test.many2one_orderby')
self.m2o_target = POOL.get('test.many2one_target')
self.m2o_search = POOL.get('test.many2one_search')
def test0010boolean(self):
'Test Boolean'
@@ -3265,6 +3266,30 @@ class FieldsTestCase(unittest.TestCase):
transaction.cursor.rollback()
with Transaction().start(DB_NAME, USER, context=CONTEXT):
target1, target2 = self.m2o_target.create([
{'value': 1},
{'value': 2},
])
search1, search2 = self.m2o_search.create([
{'many2one': target1.id},
{'many2one': target2.id},
])
# Search join
self.m2o_search.target_search = 'join'
self.assertEqual(self.m2o_search.search([
('many2one.value', '=', 1),
]), [search1])
# Search subquery
self.m2o_search.target_search = 'subquery'
self.assertEqual(self.m2o_search.search([
('many2one.value', '=', 1),
]), [search1])
def test0200timedelta(self):
'Test timedelta'
with Transaction().start(DB_NAME, USER,