Make depends on methods generic to any method

issue5191
review50381002
This commit is contained in:
Cédric Krier
2018-05-31 13:23:55 +02:00
parent 6a50848201
commit 35bba61748
4 changed files with 138 additions and 59 deletions
+1
View File
@@ -1,3 +1,4 @@
* Make depends on methods generic to any method
* Add width/height attribute to notebook
* Allow to call set_lang with None and language instance
* Add hostname configuration to list database
+1 -1
View File
@@ -227,7 +227,7 @@ Depends
A decorator to define the field names on which the decorated method depends.
The `methods` argument can be used to duplicate the field names from other
fields. This is usefull if the decorated method calls another method.
decorated methods. This is useful if the decorated method calls another method.
Field types
===========
+69 -57
View File
@@ -119,57 +119,79 @@ class ModelView(Model):
cls.__rpc__['on_change_with'] = RPC(instantiate=0)
cls._buttons = {}
if hasattr(cls, '__depend_methods'):
cls.__depend_methods = cls.__depend_methods.copy()
else:
cls.__depend_methods = collections.defaultdict(set)
if hasattr(cls, '__change_buttons'):
cls.__change_buttons = cls.__change_buttons.copy()
else:
cls.__change_buttons = collections.defaultdict(set)
def setup_field(field, field_name):
for attribute in ('on_change', 'on_change_with', 'autocomplete',
'selection_change_with'):
if attribute == 'selection_change_with':
if isinstance(
getattr(field, 'selection', None), basestring):
function_name = field.selection
else:
continue
else:
function_name = '%s_%s' % (attribute, field_name)
if not getattr(cls, function_name, None):
continue
# Search depends on all parent class because field has been
# copied with the original definition
for parent_cls in cls.__mro__:
function = getattr(parent_cls, function_name, None)
if not function:
continue
if getattr(function, 'depends', None):
setattr(field, attribute,
getattr(field, attribute) | function.depends)
if getattr(function, 'depend_methods', None):
cls.__depend_methods[(field_name, attribute)] |= \
function.depend_methods
function = getattr(cls, function_name, None)
if (attribute == 'on_change'
and not getattr(function, 'on_change', None)):
# Decorate on_change to always return self
setattr(cls, function_name, on_change(function))
def setup_callable(function, name):
if hasattr(function, 'change'):
cls.__change_buttons[name] |= function.change
fields_ = {}
callables = {}
for name in dir(cls):
if name.startswith('__'):
continue
attr = getattr(cls, name)
if isinstance(attr, fields.Field):
setup_field(attr, name)
fields_[name] = attr
elif isinstance(attr, collections.Callable):
setup_callable(attr, name)
callables[name] = attr
methods = {
'depends': collections.defaultdict(set),
'depend_methods': collections.defaultdict(set),
'change': collections.defaultdict(set),
}
cls.__change_buttons = methods['change']
def get_callable_attributes(name, method):
for parent_cls in cls.__mro__:
parent_meth = getattr(parent_cls, name, None)
if not parent_meth:
continue
for attr in ['depends', 'depend_methods', 'change']:
parent_value = getattr(parent_meth, attr, None)
if parent_value:
methods[attr][name] |= parent_value
for name, method in callables.iteritems():
get_callable_attributes(name, method)
def setup_field(field_name, field, attribute):
if attribute == 'selection_change_with':
if isinstance(
getattr(field, 'selection', None), basestring):
function_name = field.selection
else:
return
else:
function_name = '%s_%s' % (attribute, field_name)
if not getattr(cls, function_name, None):
return
function = getattr(cls, function_name, None)
setattr(field, attribute,
getattr(field, attribute) | methods['depends'][function_name])
meth_names = list(methods['depend_methods'][function_name])
meth_done = set()
while meth_names:
meth_name = meth_names.pop()
assert isinstance(
getattr(cls, meth_name), collections.Callable), \
"%s.%s not callable" % (cls, meth_name)
setattr(field, attribute,
getattr(field, attribute) | methods['depends'][meth_name])
meth_names += list(
methods['depend_methods'][meth_name] - meth_done)
meth_done.add(meth_name)
if (attribute == 'on_change'
and not getattr(function, 'on_change', None)):
# Decorate on_change to always return self
setattr(cls, function_name, on_change(function))
for name, field in fields_.iteritems():
for attribute in [
'on_change',
'on_change_with',
'autocomplete',
'selection_change_with',
]:
setup_field(name, field, attribute)
@classmethod
def __post_setup__(cls):
@@ -187,16 +209,6 @@ class ModelView(Model):
cls.__rpc__.setdefault(button,
RPC(instantiate=0, result=on_change_result))
# Update depend on methods
for (field_name, attribute), others in (
cls.__depend_methods.iteritems()):
field = getattr(cls, field_name)
for other in others:
other_field = getattr(cls, other)
setattr(field, attribute,
getattr(field, attribute)
| getattr(other_field, attribute))
@classmethod
def fields_view_get(cls, view_id=None, view_type='form'):
'''
+67 -1
View File
@@ -2,7 +2,7 @@
# repository contains the full copyright notices and license terms.
import unittest
from trytond.model import fields
from trytond.model import ModelView, fields
class FieldDependsTestCase(unittest.TestCase):
@@ -77,6 +77,72 @@ class FieldDependsTestCase(unittest.TestCase):
self.assertIsNone(record.parent.parent.name)
self.assertEqual(record.parent.parent.description, "Description")
def test_inherit(self):
"Tests inherited depends"
class Parent(ModelView):
name = fields.Char("Name")
foo = fields.Char("Foo")
@fields.depends('foo')
def on_change_name(self):
pass
class Model(Parent):
bar = fields.Char("Bar")
@fields.depends('bar')
def on_change_name(self):
super(Model, self).on_change_name()
Model.__setup__()
self.assertEqual(Model.name.on_change, {'foo', 'bar'})
def test_methods(self):
"Tests depends on method"
class Model(ModelView):
name = fields.Char("Name")
foo = fields.Char("Foo")
bar = fields.Char("Bar")
@fields.depends('foo', methods=['other_method'])
def on_change_name(self):
self.other_method()
@fields.depends('bar')
def other_method(self):
pass
Model.__setup__()
self.assertEqual(Model.name.on_change, {'foo', 'bar'})
def test_methods_2(self):
"Tests depends on method on method"
class Model(ModelView):
name = fields.Char("Name")
foo = fields.Char("Foo")
bar = fields.Char("Bar")
@fields.depends('foo', methods=['other_method'])
def on_change_name(self):
self.other_method()
@fields.depends(methods=['another_method'])
def other_method(self):
self.another_method()
@fields.depends('bar')
def another_method(self):
pass
Model.__setup__()
self.assertEqual(Model.name.on_change, {'foo', 'bar'})
def suite():
return unittest.TestLoader().loadTestsFromTestCase(FieldDependsTestCase)