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()

Reply via email to