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
This commit is contained in:
Nicolas Évrard committed 2017-02-01 16:11:00 +01:00
1 parent 37cd24210a
commit 39c12cf496
11 files changed
+141 -22

No files matched your search

+1
View File
@@ -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
+4
View File
@@ -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
=============
+9
View File
@@ -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))
+19
View File
@@ -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):
+3
View File
@@ -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
+10
View File
@@ -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.
+10
View File
@@ -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':
+3 -22
View File
@@ -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):
+1
View File
@@ -124,6 +124,7 @@ def register():
ModelViewChangedValues,
ModelViewChangedValuesTarget,
ModelViewButton,
ModelViewRPC,
MPTT,
ImportDataBoolean,
ImportDataInteger,
+53
View File
@@ -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
+28
View File
@@ -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