changeset 8bebc87164e0 in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=8bebc87164e0
description:
Support other methods for button_change decorator
issue9871
review314871002
diffstat:
CHANGELOG | 1 +
doc/ref/models/models.rst | 5 ++++-
trytond/model/modelview.py | 30 ++++++++++++++++++++++++------
trytond/tests/modelview.py | 24 ++++++++++++++++++++++++
trytond/tests/test_field_depends.py | 4 ++++
trytond/tests/test_modelview.py | 17 ++++++++++++++++-
6 files changed, 73 insertions(+), 8 deletions(-)
diffs (214 lines):
diff -r 718739cf087d -r 8bebc87164e0 CHANGELOG
--- a/CHANGELOG Sat Dec 05 11:58:41 2020 +0100
+++ b/CHANGELOG Sat Dec 05 12:09:27 2020 +0100
@@ -1,3 +1,4 @@
+* Support other methods for button_change decorator
* Allow copying Python instances of Model
* Add all buttons to default form view
* Unify PYSON string format
diff -r 718739cf087d -r 8bebc87164e0 doc/ref/models/models.rst
--- a/doc/ref/models/models.rst Sat Dec 05 11:58:41 2020 +0100
+++ b/doc/ref/models/models.rst Sat Dec 05 12:09:27 2020 +0100
@@ -124,11 +124,14 @@
Same as :meth:`ModelView.button` but return the action id of the XML ``id``
action or the action value updated by the returned value of the method.
-.. staticmethod:: ModelView.button_change([\*fields])
+.. staticmethod:: ModelView.button_change([\*fields[, methods]])
Same as :meth:`ModelView.button` but for button that change values of the
fields on client side (similar to :ref:`on_change
<ref-models-fields-on_change>`).
+ The `methods` argument can be used to duplicate the field names from other
+ decorated methods. This is useful if the decorated method calls another
+ method.
.. warning::
Only on instance methods.
diff -r 718739cf087d -r 8bebc87164e0 trytond/model/modelview.py
--- a/trytond/model/modelview.py Sat Dec 05 11:58:41 2020 +0100
+++ b/trytond/model/modelview.py Sat Dec 05 12:09:27 2020 +0100
@@ -132,6 +132,10 @@
if isinstance(attr, fields.Field):
fields_[name] = attr
+ @classmethod
+ def __post_setup__(cls):
+ super(ModelView, cls).__post_setup__()
+
methods = {
'_done': set(),
'depends': collections.defaultdict(set),
@@ -192,7 +196,7 @@
# Decorate on_change to always return self
setattr(cls, function_name, on_change(function))
- for name, field in fields_.items():
+ for name, field in cls._fields.items():
for attribute in [
'on_change',
'on_change_with',
@@ -201,10 +205,6 @@
]:
setup_field(name, field, attribute)
- @classmethod
- def __post_setup__(cls):
- super(ModelView, cls).__post_setup__()
-
# Update __rpc__
for field_name, field in cls._fields.items():
field.set_rpc(cls)
@@ -217,12 +217,25 @@
cls.__rpc__.setdefault(button,
RPC(instantiate=0, result=on_change_result))
+ meth_names = set()
+ meth_done = set()
for parent_cls in cls.__mro__:
parent_meth = getattr(parent_cls, button, None)
if not parent_meth:
continue
cls.__change_buttons[button] |= getattr(
parent_meth, 'change', set())
+ meth_names |= getattr(parent_meth, 'change_methods', set())
+ while meth_names:
+ meth_name = meth_names.pop()
+ method = getattr(cls, meth_name)
+ assert callable(method) or isinstance(method, property), \
+ "%s.%s not callable or property" % (cls, meth_name)
+ set_methods(meth_name)
+ cls.__change_buttons[button] |= methods['depends'][meth_name]
+ meth_names |= (
+ methods['depend_methods'][meth_name] - meth_done)
+ meth_done.add(meth_name)
@classmethod
def fields_view_get(cls, view_id=None, view_type='form', level=None):
@@ -771,10 +784,15 @@
return decorator
@staticmethod
- def button_change(*fields):
+ def button_change(*fields, **kwargs):
+ methods = kwargs.pop('methods', None)
+ assert not kwargs
+
def decorator(func):
func = on_change(func)
func.change = set(fields)
+ if methods:
+ func.change_methods = set(methods)
return func
return decorator
diff -r 718739cf087d -r 8bebc87164e0 trytond/tests/modelview.py
--- a/trytond/tests/modelview.py Sat Dec 05 11:58:41 2020 +0100
+++ b/trytond/tests/modelview.py Sat Dec 05 12:09:27 2020 +0100
@@ -113,6 +113,29 @@
return {'url': 'http://www.tryton.org/'}
+class ModelViewButtonChange(ModelView):
+ "ModelView Button Change"
+ __name__ = 'test.modelview.button_change'
+
+ name = fields.Char("Name")
+ extra = fields.Char("Extra")
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls._buttons = {
+ 'test': {}
+ }
+
+ @ModelView.button_change('name', methods=['extra_method'])
+ def test(self):
+ self.extra_method()
+
+ @fields.depends('extra')
+ def extra_method(self):
+ pass
+
+
class ModelViewLink(ModelView):
"ModelView Link"
__name__ = 'test.modelview.link'
@@ -237,6 +260,7 @@
ModelViewButton,
ModelViewButtonDepends,
ModelViewButtonAction,
+ ModelViewButtonChange,
ModelViewLink,
ModelViewLinkTarget,
ModelViewRPC,
diff -r 718739cf087d -r 8bebc87164e0 trytond/tests/test_field_depends.py
--- a/trytond/tests/test_field_depends.py Sat Dec 05 11:58:41 2020 +0100
+++ b/trytond/tests/test_field_depends.py Sat Dec 05 12:09:27 2020 +0100
@@ -120,6 +120,7 @@
super(Model, self).on_change_name()
Model.__setup__()
+ Model.__post_setup__()
self.assertEqual(Model.name.on_change, {'foo', 'bar'})
@@ -140,6 +141,7 @@
pass
Model.__setup__()
+ Model.__post_setup__()
self.assertEqual(Model.name.on_change, {'foo', 'bar'})
@@ -164,6 +166,7 @@
pass
Model.__setup__()
+ Model.__post_setup__()
self.assertEqual(Model.name.on_change, {'foo', 'bar'})
@@ -213,6 +216,7 @@
pass
Model.__setup__()
+ Model.__post_setup__()
self.assertEqual(Model.bar.on_change, {'foo', 'bar'})
diff -r 718739cf087d -r 8bebc87164e0 trytond/tests/test_modelview.py
--- a/trytond/tests/test_modelview.py Sat Dec 05 11:58:41 2020 +0100
+++ b/trytond/tests/test_modelview.py Sat Dec 05 12:09:27 2020 +0100
@@ -9,7 +9,7 @@
from trytond.model.exceptions import AccessError, AccessButtonError
from trytond.tests.test_tryton import activate_module, with_transaction
from trytond.pool import Pool
-from trytond.pyson import PYSONEncoder, Eval
+from trytond.pyson import PYSONEncoder, PYSONDecoder, Eval
class ModelView(unittest.TestCase):
@@ -409,6 +409,21 @@
self.assertEqual(action['url'], 'http://www.tryton.org/')
@with_transaction()
+ def test_button_change(self):
+ "Test button change"
+ pool = Pool()
+ Model = pool.get('test.modelview.button_change')
+
+ decoder = PYSONDecoder()
+ view = Model.fields_view_get()
+ tree = etree.fromstring(view['arch'])
+ button = tree.xpath('//button[@name="test"]')[0]
+
+ self.assertEqual(
+ set(decoder.decode(button.attrib['change'])),
+ {'name', 'extra'})
+
+ @with_transaction()
def test_link(self):
"Test link in view"
pool = Pool()