@@ -1,3 +1,4 @@
|
||||
* Support Function field without getter but SQL expression
|
||||
* Add support for column_<field name> method
|
||||
* Use tables and Model as arguments for sql_column of the Field
|
||||
* Add support for basic authentication for user application
|
||||
|
||||
@@ -1081,7 +1081,7 @@ Instance methods:
|
||||
Function
|
||||
--------
|
||||
|
||||
.. class:: Function(field, getter[, setter[, searcher[, getter_with_context]]])
|
||||
.. class:: Function(field, [getter[, setter[, searcher[, getter_with_context]]]])
|
||||
|
||||
A function field can emulate any other given :class:`field <Field>`.
|
||||
|
||||
|
||||
@@ -270,6 +270,9 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<record model="ir.message" id="msg_search_function_missing">
|
||||
<field name="text">Missing search function for field "%(field)s" in "%(model)s".</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_order_function_missing">
|
||||
<field name="text">Missing order function for field "%(field)s" in "%(model)s".</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_setter_function_missing">
|
||||
<field name="text">Missing setter function for field "%(field)s" in "%(model)s".</field>
|
||||
</record>
|
||||
|
||||
@@ -30,7 +30,7 @@ def getter_context(func):
|
||||
|
||||
class Function(Field):
|
||||
|
||||
def __init__(self, field, getter, setter=None, searcher=None,
|
||||
def __init__(self, field, getter=None, setter=None, searcher=None,
|
||||
getter_with_context=True, loading='lazy'):
|
||||
'''
|
||||
:param field: The field of the function.
|
||||
@@ -88,19 +88,27 @@ class Function(Field):
|
||||
return self._field.sql_format(value)
|
||||
|
||||
def sql_type(self):
|
||||
if not self.getter:
|
||||
return self._field.sql_type()
|
||||
return None
|
||||
|
||||
@domain_method
|
||||
def convert_domain(self, domain, tables, Model):
|
||||
if self.searcher:
|
||||
return getattr(Model, self.searcher)(self.name, domain)
|
||||
elif not self.getter:
|
||||
return self._field.convert_domain(domain, tables, Model)
|
||||
raise NotImplementedError(gettext(
|
||||
'ir.msg_search_function_missing',
|
||||
**Model.__names__(self.name)))
|
||||
|
||||
@order_method
|
||||
def convert_order(self, name, tables, Model):
|
||||
raise NotImplementedError
|
||||
if not self.getter:
|
||||
return self._field.convert_order(name, tables, Model)
|
||||
raise NotImplementedError(gettext(
|
||||
'ir.msg_order_function_missing',
|
||||
**Model.__names__(self.name)))
|
||||
|
||||
@getter_context
|
||||
@without_check_access
|
||||
@@ -110,6 +118,15 @@ class Function(Field):
|
||||
If the function has ``names`` in the function definition then
|
||||
it will call it with a list of name.
|
||||
'''
|
||||
if not self.getter:
|
||||
def get_values(name):
|
||||
return {r['id']: r[name] for r in values}
|
||||
if isinstance(name, list):
|
||||
names = name
|
||||
return {name: get_values(name) for name in names}
|
||||
else:
|
||||
return get_values(name)
|
||||
|
||||
method = getattr(Model, self.getter)
|
||||
instance_method = is_instance_method(Model, self.getter)
|
||||
multiple = self.getter_multiple(method)
|
||||
|
||||
@@ -493,7 +493,7 @@ class ModelSQL(ModelStorage):
|
||||
if field_name == 'id':
|
||||
continue
|
||||
sql_type = field.sql_type()
|
||||
if not sql_type:
|
||||
if not sql_type or isinstance(field, fields.Function):
|
||||
continue
|
||||
|
||||
if field_name in cls._defaults:
|
||||
@@ -610,7 +610,7 @@ class ModelSQL(ModelStorage):
|
||||
if cls._history:
|
||||
history_table = cls.__table_handler__(history=True)
|
||||
for field_name, field in cls._fields.items():
|
||||
if not field.sql_type():
|
||||
if not field.sql_type() or isinstance(field, fields.Function):
|
||||
continue
|
||||
history_table.add_column(field_name, field._sql_type)
|
||||
|
||||
@@ -782,15 +782,15 @@ class ModelSQL(ModelStorage):
|
||||
columns = []
|
||||
hcolumns = []
|
||||
if not deleted:
|
||||
fields = cls._fields
|
||||
fields_ = cls._fields
|
||||
else:
|
||||
fields = {
|
||||
fields_ = {
|
||||
'id': cls.id,
|
||||
'write_uid': cls.write_uid,
|
||||
'write_date': cls.write_date,
|
||||
}
|
||||
for fname, field in sorted(fields.items()):
|
||||
if not field.sql_type():
|
||||
for fname, field in sorted(fields_.items()):
|
||||
if not field.sql_type() or isinstance(field, fields.Function):
|
||||
continue
|
||||
columns.append(Column(table, fname))
|
||||
hcolumns.append(Column(history, fname))
|
||||
@@ -834,7 +834,7 @@ class ModelSQL(ModelStorage):
|
||||
hcolumns = []
|
||||
history_columns = []
|
||||
fnames = sorted(n for n, f in cls._fields.items()
|
||||
if f.sql_type())
|
||||
if f.sql_type() and not isinstance(f, fields.Function))
|
||||
id_idx = fnames.index('id')
|
||||
for fname in fnames:
|
||||
columns.append(Column(table, fname))
|
||||
|
||||
@@ -1885,6 +1885,7 @@ class ModelStorage(Model):
|
||||
multiple_getter = None
|
||||
if (field.loading == 'lazy'
|
||||
and isinstance(field, fields.Function)
|
||||
and field.getter
|
||||
and field.getter_multiple(
|
||||
getattr(self.__class__, field.getter))):
|
||||
multiple_getter = field.getter
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# 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 Literal
|
||||
|
||||
from trytond.model import ModelSQL, ModelStorage, fields
|
||||
from trytond.pool import Pool
|
||||
from trytond.transaction import Transaction
|
||||
@@ -107,6 +109,63 @@ class FunctionGetterLocalCache(ModelSQL):
|
||||
return index
|
||||
|
||||
|
||||
class FunctionNoGetter(ModelSQL):
|
||||
__name__ = 'test.function.no_getter'
|
||||
|
||||
value = fields.Integer("Value")
|
||||
value_inc = fields.Function(fields.Integer("Value Inc"))
|
||||
|
||||
@classmethod
|
||||
def column_value_inc(cls, tables):
|
||||
table, _ = tables[None]
|
||||
return table.value + Literal(1)
|
||||
|
||||
|
||||
class FunctionNoGetterRelation(ModelSQL):
|
||||
__name__ = 'test.function.no_getter.relation'
|
||||
|
||||
target = fields.Many2One(
|
||||
'test.function.no_getter.target',
|
||||
"Target")
|
||||
target_name = fields.Function(fields.Char("Target Name"))
|
||||
target_target = fields.Function(
|
||||
fields.Many2One('test.function.no_getter.target', "Target Target"))
|
||||
|
||||
@classmethod
|
||||
def column_target_name(cls, tables):
|
||||
pool = Pool()
|
||||
Target = pool.get('test.function.no_getter.target')
|
||||
table, _ = tables[None]
|
||||
if 'target' not in tables:
|
||||
target = Target.__table__()
|
||||
tables['target'] = {
|
||||
None: (target, table.target == target.id),
|
||||
}
|
||||
else:
|
||||
target, _ = tables['target'][None]
|
||||
return target.name
|
||||
|
||||
@classmethod
|
||||
def column_target_target(cls, tables):
|
||||
pool = Pool()
|
||||
Target = pool.get('test.function.no_getter.target')
|
||||
table, _ = tables[None]
|
||||
if 'target' not in tables:
|
||||
target = Target.__table__()
|
||||
tables['target'] = {
|
||||
None: (target, table.target == target.id),
|
||||
}
|
||||
else:
|
||||
target, _ = tables['target'][None]
|
||||
return target.id
|
||||
|
||||
|
||||
class FunctionNoGetterTarget(ModelSQL):
|
||||
__name__ = 'test.function.no_getter.target'
|
||||
|
||||
name = fields.Char("Name")
|
||||
|
||||
|
||||
def register(module):
|
||||
Pool.register(
|
||||
FunctionDefinition,
|
||||
@@ -115,4 +174,7 @@ def register(module):
|
||||
FunctonGetter,
|
||||
FunctionGetterContext,
|
||||
FunctionGetterLocalCache,
|
||||
FunctionNoGetter,
|
||||
FunctionNoGetterRelation,
|
||||
FunctionNoGetterTarget,
|
||||
module=module, type_='model')
|
||||
|
||||
@@ -125,3 +125,141 @@ class FieldFunctionTestCase(TestCase):
|
||||
Model.read([record.id], ['function1', 'function2'])
|
||||
|
||||
self.assertEqual(getter.call_count, 1)
|
||||
|
||||
@with_transaction()
|
||||
def test_no_getter(self):
|
||||
"Test no getter"
|
||||
pool = Pool()
|
||||
Model = pool.get('test.function.no_getter')
|
||||
|
||||
record = Model(value=42)
|
||||
record.save()
|
||||
|
||||
self.assertEqual(record.value_inc, 43)
|
||||
|
||||
@with_transaction()
|
||||
def test_no_getter_no_column(self):
|
||||
"Test no column without getter"
|
||||
pool = Pool()
|
||||
Model = pool.get('test.function.no_getter')
|
||||
|
||||
table_handler = Model.__table_handler__()
|
||||
|
||||
self.assertFalse(table_handler.column_exist('value_inc'))
|
||||
|
||||
@with_transaction()
|
||||
def test_no_getter_search(self):
|
||||
"Test search without getter"
|
||||
pool = Pool()
|
||||
Model = pool.get('test.function.no_getter')
|
||||
|
||||
record = Model(value=42)
|
||||
record.save()
|
||||
|
||||
result = Model.search([('value_inc', '=', 43)])
|
||||
self.assertEqual(result, [record])
|
||||
|
||||
@with_transaction()
|
||||
def test_no_getter_order(self):
|
||||
"Test order without getter"
|
||||
pool = Pool()
|
||||
Model = pool.get('test.function.no_getter')
|
||||
|
||||
Model.create([{'value': i} for i in range(10)])
|
||||
|
||||
asc = Model.search([], order=[('value_inc', 'ASC')])
|
||||
asc = [r.value_inc for r in asc]
|
||||
desc = Model.search([], order=[('value_inc', 'DESC')])
|
||||
desc = [r.value_inc for r in desc]
|
||||
|
||||
self.assertEqual(asc, sorted(asc))
|
||||
self.assertEqual(desc, sorted(asc, reverse=True))
|
||||
|
||||
@with_transaction()
|
||||
def test_no_getter_relation(self):
|
||||
"Test no getter with relation"
|
||||
pool = Pool()
|
||||
Model = pool.get('test.function.no_getter.relation')
|
||||
Target = pool.get('test.function.no_getter.target')
|
||||
|
||||
target = Target(name="Test")
|
||||
target.save()
|
||||
record = Model(target=target)
|
||||
record.save()
|
||||
|
||||
self.assertEqual(record.target_name, "Test")
|
||||
self.assertEqual(record.target_target, target)
|
||||
|
||||
@with_transaction()
|
||||
def test_no_getter_search_relation(self):
|
||||
"Test search on relation without getter"
|
||||
pool = Pool()
|
||||
Model = pool.get('test.function.no_getter.relation')
|
||||
Target = pool.get('test.function.no_getter.target')
|
||||
|
||||
target = Target(name="Test")
|
||||
target.save()
|
||||
record = Model(target=target)
|
||||
record.save()
|
||||
|
||||
result = Model.search([('target_name', '=', "Test")])
|
||||
|
||||
self.assertEqual(result, [record])
|
||||
|
||||
@with_transaction()
|
||||
def test_no_getter_search_relation_dotted(self):
|
||||
"Test search on dotted relation without getter"
|
||||
pool = Pool()
|
||||
Model = pool.get('test.function.no_getter.relation')
|
||||
Target = pool.get('test.function.no_getter.target')
|
||||
|
||||
target = Target(name="Test")
|
||||
target.save()
|
||||
record = Model(target=target)
|
||||
record.save()
|
||||
|
||||
result = Model.search([('target_target.name', '=', "Test")])
|
||||
|
||||
self.assertEqual(result, [record])
|
||||
|
||||
@with_transaction()
|
||||
def test_no_getter_order_relation(self):
|
||||
"Test order on relation without getter"
|
||||
pool = Pool()
|
||||
Model = pool.get('test.function.no_getter.relation')
|
||||
Target = pool.get('test.function.no_getter.target')
|
||||
|
||||
for i in range(10):
|
||||
target = Target(name=str(i))
|
||||
target.save()
|
||||
record = Model(target=target)
|
||||
record.save()
|
||||
|
||||
asc = Model.search([], order=[('target_name', 'ASC')])
|
||||
asc = [r.target_name for r in asc]
|
||||
desc = Model.search([], order=[('target_name', 'DESC')])
|
||||
desc = [r.target_name for r in desc]
|
||||
|
||||
self.assertEqual(asc, sorted(asc))
|
||||
self.assertEqual(desc, sorted(asc, reverse=True))
|
||||
|
||||
@with_transaction()
|
||||
def test_no_getter_order_relation_dotted(self):
|
||||
"Test order on dotted relation without getter"
|
||||
pool = Pool()
|
||||
Model = pool.get('test.function.no_getter.relation')
|
||||
Target = pool.get('test.function.no_getter.target')
|
||||
|
||||
for i in range(10):
|
||||
target = Target(name=str(i))
|
||||
target.save()
|
||||
record = Model(target=target)
|
||||
record.save()
|
||||
|
||||
asc = Model.search([], order=[('target_target.name', 'ASC')])
|
||||
asc = [r.target_target.name for r in asc]
|
||||
desc = Model.search([], order=[('target_target.name', 'DESC')])
|
||||
desc = [r.target_target.name for r in desc]
|
||||
|
||||
self.assertEqual(asc, sorted(asc))
|
||||
self.assertEqual(desc, sorted(asc, reverse=True))
|
||||
|
||||
@@ -972,6 +972,12 @@ class ModuleTestCase(_DBTestCase):
|
||||
'field': field_name,
|
||||
'type': field._type,
|
||||
})
|
||||
if not field.getter and isinstance(model, ModelSQL):
|
||||
func_name = f'column_{field_name}'
|
||||
self.assertTrue(
|
||||
getattr(model, func_name, None),
|
||||
msg=f"Missing method {func_name!r} "
|
||||
f"on model {mname!r} for field {field_name!r}")
|
||||
for func_name in [field.getter, field.setter, field.searcher]:
|
||||
if not func_name:
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user