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:
1 parent
37cd24210a
commit
39c12cf496
11 files changed
+141
-22
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
=============
|
||||
|
||||
|
||||
@@ -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))
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -124,6 +124,7 @@ def register():
|
||||
ModelViewChangedValues,
|
||||
ModelViewChangedValuesTarget,
|
||||
ModelViewButton,
|
||||
ModelViewRPC,
|
||||
MPTT,
|
||||
ImportDataBoolean,
|
||||
ImportDataInteger,
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user