Use tables and Model as arguments for sql_column of the Field

This commit is contained in:
Cédric Krier
2026-01-29 12:42:56 +01:00
parent 4435474f3e
commit dfe58aa125
18 changed files with 62 additions and 60 deletions
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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]
+1
View File
@@ -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
+3 -2
View File
@@ -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 <ref-tables>` could be updated to add new joins.
.. method:: Field.set_rpc(model)
+4 -4
View File
@@ -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)
+2 -4
View File
@@ -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):
+7 -7
View File
@@ -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)
+7 -5
View File
@@ -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)
+9 -9
View File
@@ -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 = {
@@ -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):
+4 -4
View File
@@ -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)
+1 -2
View File
@@ -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:
+1 -2
View File
@@ -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))
+2 -3
View File
@@ -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:
+14 -10
View File
@@ -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)