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