diff --git a/modules/account_payment/payment.py b/modules/account_payment/payment.py index aa3b6ba3d3..4c5d4904f0 100644 --- a/modules/account_payment/payment.py +++ b/modules/account_payment/payment.py @@ -547,7 +547,7 @@ class Payment(Workflow, ModelSQL, ModelView, ChatMixin): def order_amount(cls, tables): table, _ = tables[None] context = Transaction().context - column = cls.amount.sql_column(table) + column = cls.amount.sql_column(tables, cls) if isinstance(context.get('amount_order'), Decimal): return [Abs(column - abs(context['amount_order']))] else: diff --git a/modules/bank/bank.py b/modules/bank/bank.py index 1d42b943d9..48c130ada3 100644 --- a/modules/bank/bank.py +++ b/modules/bank/bank.py @@ -245,7 +245,7 @@ class AccountNumber(DeactivableMixin, sequence_ordered(), ModelSQL, ModelView): Operator = fields.SQL_OPERATORS[operator] result = None for field in (cls.number, cls.number_compact): - column = field.sql_column(table) + column = field.sql_column(tables, cls) expression = Operator(column, field._domain_value(operator, value)) if isinstance(expression, operators.In) and not expression.right: expression = Literal(False) diff --git a/modules/country/country.py b/modules/country/country.py index e2719c6d66..e0001cdc95 100644 --- a/modules/country/country.py +++ b/modules/country/country.py @@ -437,7 +437,7 @@ class Subdivision(DeactivableMixin, ModelSQL, ModelView): _, op, value = domain Operator = fields.SQL_OPERATORS[op] - column = cls.code.sql_column(table) + column = cls.code.sql_column(tables, cls) if op.endswith('like'): if op.endswith('ilike') and cls.code.search_unaccented: database = Transaction().database diff --git a/modules/product_cost_fifo/move.py b/modules/product_cost_fifo/move.py index 0c86144600..7ad7b3b1f4 100644 --- a/modules/product_cost_fifo/move.py +++ b/modules/product_cost_fifo/move.py @@ -61,8 +61,8 @@ class Move(metaclass=PoolMeta): field = cls.fifo_quantity_available._field Operator = fields.SQL_OPERATORS[operator] column = ( - cls.quantity.sql_column(table) - - cls.fifo_quantity.sql_column(table)) + cls.quantity.sql_column(tables, cls) + - cls.fifo_quantity.sql_column(tables, cls)) expression = Operator(column, field._domain_value(operator, value)) if isinstance(expression, operators.In) and not expression.right: expression = Literal(False) diff --git a/trytond-gis/trytond_gis/fields.py b/trytond-gis/trytond_gis/fields.py index c0dc234dcd..1af990428d 100644 --- a/trytond-gis/trytond_gis/fields.py +++ b/trytond-gis/trytond_gis/fields.py @@ -42,7 +42,7 @@ class Geometry(fields.Field): assert operator in GEOGRAPHIC_OPERATORS - column = self.sql_column(table) + column = self.sql_column(tables, Model) if operator in {'=', '!='} and value is Null: Operator = SQL_OPERATORS[operator] diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG index b70ddcd17e..2463d4bff2 100644 --- a/trytond/CHANGELOG +++ b/trytond/CHANGELOG @@ -1,3 +1,4 @@ +* Use tables and Model as arguments for sql_column of the Field * Add support for basic authentication for user application * Support the conversion of MJML reports to HTML * Allow filtering users to be notified by cron tasks diff --git a/trytond/doc/ref/fields.rst b/trytond/doc/ref/fields.rst index 9bb8318950..556153f580 100644 --- a/trytond/doc/ref/fields.rst +++ b/trytond/doc/ref/fields.rst @@ -191,9 +191,10 @@ Instance methods: Return the SQL expression with cast with the type of the field. -.. method:: Field.sql_column(table) +.. method:: Field.sql_column(tables, Model) - Return the Column instance based on table. + Return the Column or SQL expression instance for the field. + :ref:`tables ` could be updated to add new joins. .. method:: Field.set_rpc(model) diff --git a/trytond/trytond/model/fields/char.py b/trytond/trytond/model/fields/char.py index 698cb9de65..4a9c654e59 100644 --- a/trytond/trytond/model/fields/char.py +++ b/trytond/trytond/model/fields/char.py @@ -3,7 +3,7 @@ import string import warnings -from sql import Expression, Query +from sql import Column, Expression, Query from sql.conditionals import Coalesce, NullIf from sql.functions import Trim from sql.operators import Not @@ -143,10 +143,10 @@ class Char(FieldTranslate): language = transaction.language model, join, column = self._get_translation_column( Model, name) - column = Coalesce(NullIf(column, ''), self.sql_column(model)) + column = Coalesce(NullIf(column, ''), Column(model, self.name)) else: language = None - column = self.sql_column(table) + column = self.sql_column(tables, Model) column = self._domain_column(operator, column) threshold = context.get( @@ -207,7 +207,7 @@ class Char(FieldTranslate): column = self._get_translation_order(tables, Model, name) else: language = None - column = self.sql_column(table) + column = self.sql_column(tables, Model) column = self._domain_column('ilike', column) if database.has_similarity(): sim_value = unescape_wildcard(value) diff --git a/trytond/trytond/model/fields/dict.py b/trytond/trytond/model/fields/dict.py index b24a2c4e65..99fcc05bc9 100644 --- a/trytond/trytond/model/fields/dict.py +++ b/trytond/trytond/model/fields/dict.py @@ -119,10 +119,9 @@ class Dict(Field): if '.' not in name: return super().convert_domain(domain, tables, Model) database = Transaction().database - table, _ = tables[None] name, key = name.split('.', 1) Operator = SQL_OPERATORS[operator] - raw_column = self.sql_column(table) + raw_column = self.sql_column(tables, Model) column = self._domain_column(operator, raw_column, key) expression = Operator(column, self._domain_value(operator, value)) if operator in {'=', '!='}: @@ -171,8 +170,7 @@ class Dict(Field): if not key: return super().convert_order(fname, tables, Model) database = Transaction().database - table, _ = tables[None] - column = self.sql_column(table) + column = self.sql_column(tables, Model) return [database.json_get(column, key)] def definition(self, model, language): diff --git a/trytond/trytond/model/fields/field.py b/trytond/trytond/model/fields/field.py index b8aea5cda9..1eb4f6e1fe 100644 --- a/trytond/trytond/model/fields/field.py +++ b/trytond/trytond/model/fields/field.py @@ -421,7 +421,8 @@ class Field(object): def sql_cast(self, expression): return Cast(expression, self.sql_type().base) - def sql_column(self, table): + def sql_column(self, tables, Model): + table, _ = tables[None] return Column(table, self.name) def _domain_column(self, operator, column): @@ -451,7 +452,7 @@ class Field(object): table, _ = tables[None] name, operator, value = domain Operator = SQL_OPERATORS[operator] - column = self.sql_column(table) + column = self.sql_column(tables, Model) column = self._domain_column(operator, column) expression = Operator(column, self._domain_value(operator, value)) if isinstance(expression, operators.In) and not expression.right: @@ -464,8 +465,7 @@ class Field(object): @order_method def convert_order(self, name, tables, Model): "Return a SQL expression to order" - table, _ = tables[None] - return [self.sql_column(table)] + return [self.sql_column(tables, Model)] def set_rpc(self, model): for attribute, decorator, result in ( @@ -680,7 +680,7 @@ class FieldTranslate(Field): table, _ = tables[None] name, operator, value = domain model, join, column = self._get_translation_column(Model, name) - column = Coalesce(NullIf(column, ''), self.sql_column(model)) + column = Coalesce(NullIf(column, ''), Column(model, self.name)) column = self._domain_column(operator, column) Operator = SQL_OPERATORS[operator] assert name == self.name @@ -731,9 +731,9 @@ class FieldTranslate(Field): if not self.translate: return super().convert_order(name, tables, Model) assert name == self.name - table, _ = tables[None] column = self._get_translation_order(tables, Model, name) - return [Coalesce(NullIf(column, ''), self.sql_column(table))] + return [ + Coalesce(NullIf(column, ''), self.sql_column(tables, Model))] def definition(self, model, language): definition = super().definition(model, language) diff --git a/trytond/trytond/model/fields/many2many.py b/trytond/trytond/model/fields/many2many.py index 1647ba2503..90c7723e35 100644 --- a/trytond/trytond/model/fields/many2many.py +++ b/trytond/trytond/model/fields/many2many.py @@ -384,14 +384,19 @@ class Many2Many(Field): else: relation = Relation.__table__() history_where = None + relation_tables = { + None: (relation, None), + } origin_field = Relation._fields[self.origin] - origin = getattr(Relation, self.origin).sql_column(relation) + origin = getattr(Relation, self.origin).sql_column( + relation_tables, Relation) origin_where = None if origin_field._type == 'reference': origin_where = origin.like(Model.__name__ + ',%') origin = origin_field.sql_id(origin, Relation) - target = getattr(Relation, self.target).sql_column(relation) + target = getattr(Relation, self.target).sql_column( + relation_tables, Relation) if '.' not in name: if operator.endswith('child_of') or operator.endswith('parent_of'): if Target != Model: @@ -481,9 +486,6 @@ class Many2Many(Field): relation_domain, (self.target, 'where', self.filter), ] - relation_tables = { - None: (relation, None), - } tables, expression = Relation.search_domain( relation_domain, tables=relation_tables) query_table = convert_from(None, relation_tables) diff --git a/trytond/trytond/model/fields/many2one.py b/trytond/trytond/model/fields/many2one.py index aa0d3234aa..497089bf21 100644 --- a/trytond/trytond/model/fields/many2one.py +++ b/trytond/trytond/model/fields/many2one.py @@ -133,7 +133,7 @@ class Many2One(Field): name, operator, ids = domain red_sql = reduce_ids(table.id, (i for i in ids if i is not None)) Target = self.get_target() - path_column = getattr(Target, self.path).sql_column(table) + path_column = getattr(Target, self.path).sql_column(tables, Target) path_column = Coalesce(path_column, '') cursor.execute(*table.select( path_column, where=red_sql, @@ -165,8 +165,8 @@ class Many2One(Field): name, operator, ids = domain red_sql = reduce_ids(table.id, (i for i in ids if i is not None)) Target = self.get_target() - left = getattr(Target, self.left).sql_column(table) - right = getattr(Target, self.right).sql_column(table) + left = getattr(Target, self.left).sql_column(tables, Target) + right = getattr(Target, self.right).sql_column(tables, Target) cursor.execute(*table.select( left, right, where=red_sql, order_by=[(right - left).asc, left.asc])) @@ -228,7 +228,7 @@ class Many2One(Field): table, _ = tables[None] name, operator, value = domain[:3] - column = self.sql_column(table) + column = self.sql_column(tables, Model) if '.' not in name: if operator.endswith('child_of') or operator.endswith('parent_of'): if Target != Model: @@ -307,7 +307,7 @@ class Many2One(Field): expression = column.in_(query) else: target_domain = [target_domain, rule_domain] - target_tables = self._get_target_tables(tables) + target_tables = self._get_target_tables(tables, Model) target_table, _ = target_tables[None] _, expression = Target.search_domain( target_domain, tables=target_tables) @@ -338,13 +338,13 @@ class Many2One(Field): table, _ = tables[None] if oname == 'id': - return [self.sql_column(table)] + return [self.sql_column(tables, Model)] ofield = Target._fields[oname] - target_tables = self._get_target_tables(tables) + target_tables = self._get_target_tables(tables, Model) return ofield.convert_order(oexpr, target_tables, Target) - def _get_target_tables(self, tables): + def _get_target_tables(self, tables, Model): Target = self.get_target() table, _ = tables[None] target_tables = tables.get(self.name) @@ -364,7 +364,7 @@ class Many2One(Field): else: target = Target.__table__() history_condition = None - condition = target.id == self.sql_column(table) + condition = target.id == self.sql_column(tables, Model) if history_condition: condition &= history_condition target_tables = { diff --git a/trytond/trytond/model/fields/multiselection.py b/trytond/trytond/model/fields/multiselection.py index 9955a0baa7..2a999990f4 100644 --- a/trytond/trytond/model/fields/multiselection.py +++ b/trytond/trytond/model/fields/multiselection.py @@ -90,8 +90,7 @@ class MultiSelection(SelectionMixin, Field): if operator not in {'in', 'not in'}: return super().convert_domain(domain, tables, Model) database = Transaction().database - table, _ = tables[None] - raw_column = self.sql_column(table) + raw_column = self.sql_column(tables, Model) if value is None: expression = Literal(False) elif isinstance(value, str): diff --git a/trytond/trytond/model/fields/one2many.py b/trytond/trytond/model/fields/one2many.py index 1690eee2af..0b16c18a9b 100644 --- a/trytond/trytond/model/fields/one2many.py +++ b/trytond/trytond/model/fields/one2many.py @@ -338,8 +338,11 @@ class One2Many(Field): else: target = Target.__table__() history_where = None + target_tables = { + None: (target, None), + } origin_field = Target._fields[self.field] - origin = getattr(Target, self.field).sql_column(target) + origin = getattr(Target, self.field).sql_column(target_tables, Target) origin_where = None if origin_field._type == 'reference': origin_where = origin.like(Model.__name__ + ',%') @@ -408,9 +411,6 @@ class One2Many(Field): target_domain = [target_domain, rule_domain] if self.filter: target_domain = [target_domain, self.filter] - target_tables = { - None: (target, None), - } tables, expression = Target.search_domain( target_domain, tables=target_tables) query_table = convert_from(None, target_tables) diff --git a/trytond/trytond/model/fields/reference.py b/trytond/trytond/model/fields/reference.py index d4f8b370ec..20d86d397a 100644 --- a/trytond/trytond/model/fields/reference.py +++ b/trytond/trytond/model/fields/reference.py @@ -204,10 +204,9 @@ class Reference(SelectionMixin, Field): pool = Pool() name, operator, value, target = domain[:4] Target = pool.get(target) - table, _ = tables[None] name, target_name = name.split('.', 1) assert name == self.name - column = self.sql_column(table) + column = self.sql_column(tables, Model) target_domain = [(target_name,) + tuple(domain[1:3]) + tuple(domain[4:])] if 'active' in Target._fields: diff --git a/trytond/trytond/model/fields/selection.py b/trytond/trytond/model/fields/selection.py index 69a156ae13..5dae1d38fc 100644 --- a/trytond/trytond/model/fields/selection.py +++ b/trytond/trytond/model/fields/selection.py @@ -146,14 +146,13 @@ class Selection(SelectionMixin, Field): @order_method def convert_order(self, name, tables, Model): - table, _ = tables[None] selections = Model.fields_get([name])[name]['selection'] if not isinstance(selections, (tuple, list)): if not is_instance_method(Model, selections): selections = getattr(Model, selections)() else: selections = [] - column = self.sql_column(table) + column = self.sql_column(tables, Model) if not self.sort: else_ = len(selections) + 1 selections = ((k, i) for i, (k, v) in enumerate(selections)) diff --git a/trytond/trytond/model/fields/text.py b/trytond/trytond/model/fields/text.py index cf9aecd1a6..72449d10a6 100644 --- a/trytond/trytond/model/fields/text.py +++ b/trytond/trytond/model/fields/text.py @@ -54,7 +54,7 @@ class FullText(Field): table, _ = tables[None] name, operator, value = domain assert name == self.name - column = self.sql_column(table) + column = self.sql_column(tables, Model) column = self._domain_column(operator, column) if operator.endswith('like'): if database.has_search_full_text(): @@ -81,8 +81,7 @@ class FullText(Field): @order_method def convert_order(self, name, tables, Model): - table, _ = tables[None] - column = self.sql_column(table) + column = self.sql_column(tables, Model) column = self._domain_column('ilike', column) column = self._rank_column(column, name, Model) if column: diff --git a/trytond/trytond/model/modelsql.py b/trytond/trytond/model/modelsql.py index 47241d3cab..754fb6f65c 100644 --- a/trytond/trytond/model/modelsql.py +++ b/trytond/trytond/model/modelsql.py @@ -1192,6 +1192,7 @@ class ModelSQL(ModelStorage): history_clause = (column <= Transaction().context['_datetime']) history_order = (column.desc, Column(table, '__id').desc) history_limit = 1 + tables = {None: (table, None)} columns = {} for f in all_fields: @@ -1200,7 +1201,7 @@ class ModelSQL(ModelStorage): if f in _TABLE_QUERY_COLUMNS and cls._is_table_query(): column = _TABLE_QUERY_COLUMNS[f] else: - column = field.sql_column(table) + column = field.sql_column(tables, cls) columns[f] = column.as_(f) if backend.name == 'sqlite': columns[f].output_name += ' [%s]' % field.sql_type().base @@ -1241,7 +1242,6 @@ class ModelSQL(ModelStorage): if 'id' not in fields_names: columns['id'] = table.id.as_('id') - tables = {None: (table, None)} if domain: tables, dom_exp = cls.search_domain( domain, active_test=False, tables=tables) @@ -1581,10 +1581,9 @@ class ModelSQL(ModelStorage): tree_ids = {} for fname in cls._mptt_fields: - field = cls._fields[fname] tree_ids[fname] = [] for sub_ids in grouped_slice(ids): - where = reduce_ids(field.sql_column(table), sub_ids) + where = reduce_ids(Column(table, fname), sub_ids) cursor.execute(*table.select(table.id, where=where)) tree_ids[fname] += [x[0] for x in cursor] @@ -1841,9 +1840,8 @@ class ModelSQL(ModelStorage): rule_domain, active_test=False, tables=tables) expression &= domain_exp main_table, _ = tables[None] - table = convert_from(None, tables) columns = cls.__searched_columns( - main_table, eager=not count and not query) + tables, eager=not count and not query) o_idx = 0 for oexpr, otype in order: @@ -1858,6 +1856,7 @@ class ModelSQL(ModelStorage): orderings.extend([otype] * len(forder)) done_orderings = True + table = convert_from(None, tables) union_tables.append(table.select( *columns, where=expression)) expression = None @@ -1874,7 +1873,8 @@ class ModelSQL(ModelStorage): return tables, expression, orderings @classmethod - def __searched_columns(cls, table, *, eager=False, history=False): + def __searched_columns(cls, tables, *, eager=False, history=False): + table, _ = tables[None] columns = [table.id.as_('id')] if (cls._history and Transaction().context.get('_datetime') and (eager or history)): @@ -1884,7 +1884,7 @@ class ModelSQL(ModelStorage): if eager: table_query = cls._is_table_query() - columns += [f.sql_column(table).as_(n) + columns += [f.sql_column(tables, cls).as_(n) for n, f in sorted(cls._fields.items()) if not hasattr(f, 'get') and n != 'id' @@ -1955,16 +1955,20 @@ class ModelSQL(ModelStorage): order_by = cls.__search_order(order, tables) # compute it here because __search_order might modify tables - table = convert_from(None, tables) if query: columns = [main_table.id.as_('id')] else: - columns = cls.__searched_columns(main_table, eager=True) + columns = cls.__searched_columns(tables, eager=True) + if union_orderings: + columns = [ + Column(main_table, c.output_name).as_(c.output_name) + for c in columns] if backend.name == 'sqlite': for column in columns: field = cls._fields.get(column.output_name) if field: column.output_name += ' [%s]' % field.sql_type().base + table = convert_from(None, tables) select = table.select( *columns, where=expression, limit=limit, offset=offset, order_by=order_by)