Make depends on methods generic to any method
issue5191 review50381002
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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'):
|
||||
'''
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user