details:   https://code.tryton.org/tryton/commit/649dc4f1e511
branch:    default
user:      Cédric Krier <[email protected]>
date:      Mon Feb 09 18:13:34 2026 +0100
description:
        Check button states when testing access

        Closes #14591
diffstat:

 trytond/CHANGELOG                       |   1 +
 trytond/trytond/ir/message.xml          |   3 +++
 trytond/trytond/model/modelview.py      |  13 ++++++++++++-
 trytond/trytond/tests/modelview.py      |  16 ++++++++++++++++
 trytond/trytond/tests/test_modelview.py |  19 +++++++++++++++++++
 5 files changed, 51 insertions(+), 1 deletions(-)

diffs (116 lines):

diff -r a233b038106b -r 649dc4f1e511 trytond/CHANGELOG
--- a/trytond/CHANGELOG Tue Mar 24 19:28:02 2026 +0100
+++ b/trytond/CHANGELOG Mon Feb 09 18:13:34 2026 +0100
@@ -1,3 +1,4 @@
+* Check button states when testing access
 * Remove the files of Binary fields from the Filestore
 * Add delete and delete_many to Filestore
 * Deprecate ``grouped_slice`` without size
diff -r a233b038106b -r 649dc4f1e511 trytond/trytond/ir/message.xml
--- a/trytond/trytond/ir/message.xml    Tue Mar 24 19:28:02 2026 +0100
+++ b/trytond/trytond/ir/message.xml    Mon Feb 09 18:13:34 2026 +0100
@@ -282,6 +282,9 @@
         <record model="ir.message" id="msg_access_button_error">
             <field name="text">Calling button "%(button)s on "%(model)s" is 
not allowed.</field>
         </record>
+        <record model="ir.message" id="msg_button_state_record">
+            <field name="text">You can not call "%(button)s" on "%(record)s" 
of "%(model)s".</field>
+        </record>
 
         <record model="ir.message" id="msg_view_invalid_xml">
             <field name="text">Invalid XML for view "%(name)s".</field>
diff -r a233b038106b -r 649dc4f1e511 trytond/trytond/model/modelview.py
--- a/trytond/trytond/model/modelview.py        Tue Mar 24 19:28:02 2026 +0100
+++ b/trytond/trytond/model/modelview.py        Mon Feb 09 18:13:34 2026 +0100
@@ -19,6 +19,7 @@
 from .fields import on_change_result
 from .fields.field import _set_value
 from .model import Model
+from .modelstorage import _pyson_encoder, _record_eval_pyson
 
 __all__ = ['ModelView']
 
@@ -764,10 +765,20 @@
                         raise AccessButtonError(
                             gettext('ir.msg_access_button_error',
                                 button=func.__name__,
-                                model=cls.__name__))
+                                **cls.__names__()))
                 else:
                     ModelAccess.check(cls.__name__, 'write')
 
+                states = cls._buttons.get(func.__name__, {})
+                for state_name in {'invisible', 'readonly'} & states.keys():
+                    state = _pyson_encoder.encode(states[state_name])
+                    for record in records:
+                        if _record_eval_pyson(record, state, encoded=True):
+                            raise AccessButtonError(
+                                gettext('ir.msg_button_state_record',
+                                    button=func.__name__,
+                                    **cls.__names__(record=record)))
+
             if (transaction.user != 0) and check_access:
                 button_rules = Button.get_rules(
                     cls.__name__, func.__name__)
diff -r a233b038106b -r 649dc4f1e511 trytond/trytond/tests/modelview.py
--- a/trytond/trytond/tests/modelview.py        Tue Mar 24 19:28:02 2026 +0100
+++ b/trytond/trytond/tests/modelview.py        Mon Feb 09 18:13:34 2026 +0100
@@ -68,6 +68,12 @@
         super().__setup__()
         cls._buttons = {
             'test': {},
+            'test_invisible': {
+                'invisible': Eval('value', 0) == 42,
+                },
+            'test_readonly': {
+                'readonly': Eval('value', 0) == 42,
+                },
             }
 
     @classmethod
@@ -79,6 +85,16 @@
     def test_non_decorated(cls, records):
         pass
 
+    @classmethod
+    @ModelView.button
+    def test_invisible(cls, records):
+        pass
+
+    @classmethod
+    @ModelView.button
+    def test_readonly(cls, records):
+        pass
+
 
 class ModelViewButtonDepends(ModelView):
     __name__ = 'test.modelview.button_depends'
diff -r a233b038106b -r 649dc4f1e511 trytond/trytond/tests/test_modelview.py
--- a/trytond/trytond/tests/test_modelview.py   Tue Mar 24 19:28:02 2026 +0100
+++ b/trytond/trytond/tests/test_modelview.py   Mon Feb 09 18:13:34 2026 +0100
@@ -455,6 +455,25 @@
             TestModel.test([test])
 
     @with_transaction(context={'_check_access': True})
+    def test_button_access_state(self):
+        "Test Button Access with states"
+        pool = Pool()
+        Model = pool.get('test.modelview.button')
+
+        record = Model()
+
+        Model.test_invisible([record])
+        Model.test_readonly([record])
+
+        record = Model(value=42)
+
+        with self.assertRaises(AccessButtonError):
+            Model.test_invisible([record])
+
+        with self.assertRaises(AccessButtonError):
+            Model.test_readonly([record])
+
+    @with_transaction(context={'_check_access': True})
     def test_button_no_rule(self):
         "Test no Button Rule"
         pool = Pool()

Reply via email to