From 39c12cf496fc7bd5a820a27d1bd8dc7413677fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=C3=89vrard?= Date: Wed, 1 Feb 2017 16:11:00 +0100 Subject: [PATCH] Add set_rpc on Field The __rpc__ attribute is currently filled by method in ModelView. Allowing fields to define themselves what endpoints should be remotely callable make this process more flexible. issue4044 review30991002 --- CHANGELOG | 1 + doc/ref/models/fields.rst | 4 +++ trytond/model/fields/char.py | 9 ++++++ trytond/model/fields/field.py | 19 +++++++++++ trytond/model/fields/function.py | 3 ++ trytond/model/fields/reference.py | 10 ++++++ trytond/model/fields/selection.py | 10 ++++++ trytond/model/modelview.py | 25 ++------------- trytond/tests/__init__.py | 1 + trytond/tests/modelview.py | 53 +++++++++++++++++++++++++++++++ trytond/tests/test_modelview.py | 28 ++++++++++++++++ 11 files changed, 141 insertions(+), 22 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1e657e5939..c6a619edcc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add set_rpc on Field * Add has_select_for on Database * Store custom report translation in separate module * Add form action keyword for set/synchronize translation on report and view diff --git a/doc/ref/models/fields.rst b/doc/ref/models/fields.rst index 1c6046ffac..aceade0c41 100644 --- a/doc/ref/models/fields.rst +++ b/doc/ref/models/fields.rst @@ -178,6 +178,10 @@ Instance methods: Return the Column instance based on table. +.. method:: Field.set_rpc(model) + + Adds to `model` the default RPC instances required by the field. + Default value ============= diff --git a/trytond/model/fields/char.py b/trytond/model/fields/char.py index 83ba32370b..c3bef949dd 100644 --- a/trytond/model/fields/char.py +++ b/trytond/model/fields/char.py @@ -7,6 +7,7 @@ from sql import Query, Expression from ... import backend from .field import Field, FieldTranslate, size_validate, SQLType +from ...rpc import RPC class Char(FieldTranslate): @@ -66,3 +67,11 @@ class Char(FieldTranslate): elif db_type == 'mysql': return SQLType('CHAR', 'VARCHAR(255)') return SQLType('VARCHAR', 'VARCHAR') + + def set_rpc(self, model): + super(Char, self).set_rpc(model) + if self.autocomplete: + func_name = 'autocomplete_%s' % self.name + assert hasattr(model, func_name), \ + 'Missing %s on model %s' % (func_name, model.__name__) + model.__rpc__.setdefault(func_name, RPC(instantiate=0)) diff --git a/trytond/model/fields/field.py b/trytond/model/fields/field.py index 0f07729859..6926e47c68 100644 --- a/trytond/model/fields/field.py +++ b/trytond/model/fields/field.py @@ -14,6 +14,8 @@ from trytond.transaction import Transaction from trytond.pool import Pool from trytond.cache import LRUDictTransaction +from ...rpc import RPC + def domain_validate(value): assert isinstance(value, list), 'domain must be a list' @@ -137,6 +139,10 @@ def instanciate_values(Target, value): return tuple(instance(x) for x in (value or [])) +def on_change_result(record): + return record._changed_values + + SQL_OPERATORS = { '=': operators.Equal, '!=': operators.NotEqual, @@ -322,6 +328,19 @@ class Field(object): else: return [self.sql_column(table)] + def set_rpc(self, model): + for attribute, result in ( + ('on_change', on_change_result), + ('on_change_with', None), + ): + if not getattr(self, attribute): + continue + func_name = '%s_%s' % (attribute, self.name) + assert hasattr(model, func_name), \ + 'Missing %s on model %s' % (func_name, model.__name__) + model.__rpc__.setdefault( + func_name, RPC(instantiate=0, result=result)) + class FieldTranslate(Field): diff --git a/trytond/model/fields/function.py b/trytond/model/fields/function.py index 73fb0d9d66..c0834bb517 100644 --- a/trytond/model/fields/function.py +++ b/trytond/model/fields/function.py @@ -58,6 +58,9 @@ class Function(Field): return setattr(self._field, name, value) + def set_rpc(self, model): + self._field.set_rpc(model) + def sql_type(self): return None diff --git a/trytond/model/fields/reference.py b/trytond/model/fields/reference.py index 76433c4e22..cd94911314 100644 --- a/trytond/model/fields/reference.py +++ b/trytond/model/fields/reference.py @@ -10,6 +10,7 @@ from .char import Char from ...transaction import Transaction from ...pool import Pool from ... import backend +from ...rpc import RPC class Reference(Field): @@ -48,6 +49,15 @@ class Reference(Field): self.selection_change_with |= set(selection_change_with) __init__.__doc__ += Field.__init__.__doc__ + def set_rpc(self, model): + super(Reference, self).set_rpc(model) + if not isinstance(self.selection, (list, tuple)): + assert hasattr(model, self.selection), \ + 'Missing %s on model %s' % (self.selection, model.__name__) + instantiate = 0 if self.selection_change_with else None + model.__rpc__.setdefault( + self.selection, RPC(instantiate=instantiate)) + def get(self, ids, model, name, values=None): ''' Replace removed reference id by None. diff --git a/trytond/model/fields/selection.py b/trytond/model/fields/selection.py index 976dbf2998..a1bc083dcd 100644 --- a/trytond/model/fields/selection.py +++ b/trytond/model/fields/selection.py @@ -8,6 +8,7 @@ from ... import backend from ...transaction import Transaction from ...tools import is_instance_method from .field import Field, SQLType +from ...rpc import RPC class Selection(Field): @@ -45,6 +46,15 @@ class Selection(Field): self.translate_selection = translate __init__.__doc__ += Field.__init__.__doc__ + def set_rpc(self, model): + super(Selection, self).set_rpc(model) + if not isinstance(self.selection, (list, tuple)): + assert hasattr(model, self.selection), \ + 'Missing %s on model %s' % (self.selection, model.__name__) + instantiate = 0 if self.selection_change_with else None + model.__rpc__.setdefault( + self.selection, RPC(instantiate=instantiate)) + def sql_type(self): db_type = backend.name() if db_type == 'mysql': diff --git a/trytond/model/modelview.py b/trytond/model/modelview.py index 8a2c2b4ca7..3c8be21359 100644 --- a/trytond/model/modelview.py +++ b/trytond/model/modelview.py @@ -14,6 +14,8 @@ from trytond.pool import Pool from trytond.exceptions import UserError from trytond.rpc import RPC +from .fields import on_change_result + __all__ = ['ModelView'] @@ -86,10 +88,6 @@ def on_change(func): return wrapper -def on_change_result(record): - return record._changed_values - - class ModelView(Model): """ Define a model with views in Tryton. @@ -179,24 +177,7 @@ class ModelView(Model): # Update __rpc__ for field_name, field in cls._fields.iteritems(): - if (isinstance(field, (fields.Selection, fields.Reference)) - or (isinstance(field, fields.Function) - and isinstance(field._field, - (fields.Selection, fields.Reference)))) \ - and not isinstance(field.selection, (list, tuple)) \ - and field.selection not in cls.__rpc__: - instantiate = 0 if field.selection_change_with else None - cls.__rpc__.setdefault(field.selection, - RPC(instantiate=instantiate)) - - for attribute in ('on_change', 'on_change_with', 'autocomplete'): - function_name = '%s_%s' % (attribute, field_name) - if getattr(cls, function_name, None): - result = None - if attribute == 'on_change': - result = on_change_result - cls.__rpc__.setdefault(function_name, - RPC(instantiate=0, result=result)) + field.set_rpc(cls) for button in cls._buttons: if not is_instance_method(cls, button): diff --git a/trytond/tests/__init__.py b/trytond/tests/__init__.py index cf8e2f8d68..bd06d5ce64 100644 --- a/trytond/tests/__init__.py +++ b/trytond/tests/__init__.py @@ -124,6 +124,7 @@ def register(): ModelViewChangedValues, ModelViewChangedValuesTarget, ModelViewButton, + ModelViewRPC, MPTT, ImportDataBoolean, ImportDataInteger, diff --git a/trytond/tests/modelview.py b/trytond/tests/modelview.py index 86e02fd6c4..b7c53d70be 100644 --- a/trytond/tests/modelview.py +++ b/trytond/tests/modelview.py @@ -9,6 +9,7 @@ __all__ = [ 'ModelViewChangedValues', 'ModelViewChangedValuesTarget', 'ModelViewButton', + 'ModelViewRPC', ] @@ -53,3 +54,55 @@ class ModelViewButton(ModelView): @classmethod def test_non_decorated(cls, records): pass + + +class ModelViewRPC(ModelView): + 'ModelView RPC' + __name__ = 'test.modelview.rpc' + + selection = fields.Selection([('a', 'A')], 'Selection') + computed_selection = fields.Selection( + 'get_selection', 'Computed Selection') + function_selection = fields.Function( + fields.Selection('get_function_selection', 'Function Selection'), + 'function_selection_getter') + + reference = fields.Reference('Reference', selection=[('a', 'A')]) + computed_reference = fields.Reference( + 'Computed reference', selection='get_reference') + function_reference = fields.Function( + fields.Reference('Function Reference', + selection='get_function_reference'), + 'function_reference_getter') + + integer = fields.Integer('Integer') + float = fields.Float('Float') + char = fields.Char('Char') + + @fields.depends('selection') + def on_change_with_integer(self): + pass + + @fields.depends('reference') + def on_change_float(self): + pass + + @fields.depends('integer') + def autocomplete_char(self): + pass + + @classmethod + def get_selection(cls): + pass + + @classmethod + def get_function_selection(cls): + pass + + @classmethod + def get_reference(cls): + pass + + @classmethod + def get_function_reference(cls): + pass diff --git a/trytond/tests/test_modelview.py b/trytond/tests/test_modelview.py index 91e224a1d0..4a7ac0a115 100644 --- a/trytond/tests/test_modelview.py +++ b/trytond/tests/test_modelview.py @@ -269,6 +269,34 @@ class ModelView(unittest.TestCase): clicks = [ButtonClick(user=user1), ButtonClick(user=user2)] self.assertTrue(rule.test(record, clicks)) + @with_transaction() + def test_rpc_setup(self): + "Testing the computation of the RPC methods" + pool = Pool() + TestModel = pool.get('test.modelview.rpc') + + def check_rpc(rpc, attributes): + for key, value in attributes.items(): + self.assertEqual(getattr(rpc, key), value) + + NO_INSTANTIATION = { + 'instantiate': None, + } + INSTANTIATE_FIRST = { + 'instantiate': 0, + } + for rpc_name, rpc_attrs in [ + ('get_selection', NO_INSTANTIATION), + ('get_function_selection', NO_INSTANTIATION), + ('get_reference', NO_INSTANTIATION), + ('get_function_reference', NO_INSTANTIATION), + ('on_change_with_integer', INSTANTIATE_FIRST), + ('on_change_float', INSTANTIATE_FIRST), + ('autocomplete_char', INSTANTIATE_FIRST), + ]: + self.assertIn(rpc_name, TestModel.__rpc__) + check_rpc(TestModel.__rpc__[rpc_name], rpc_attrs) + def suite(): func = unittest.TestLoader().loadTestsFromTestCase