diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG
index 12db347f84..aa3e6f7b01 100644
--- a/trytond/CHANGELOG
+++ b/trytond/CHANGELOG
@@ -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 --git a/trytond/trytond/ir/message.xml b/trytond/trytond/ir/message.xml
index b968d16f68..6b79de940d 100644
--- a/trytond/trytond/ir/message.xml
+++ b/trytond/trytond/ir/message.xml
@@ -282,6 +282,9 @@ this repository contains the full copyright notices and license terms. -->
Calling button "%(button)s on "%(model)s" is not allowed.
+
+ You can not call "%(button)s" on "%(record)s" of "%(model)s".
+
Invalid XML for view "%(name)s".
diff --git a/trytond/trytond/model/modelview.py b/trytond/trytond/model/modelview.py
index c99384f5e9..f2f53ae3c9 100644
--- a/trytond/trytond/model/modelview.py
+++ b/trytond/trytond/model/modelview.py
@@ -19,6 +19,7 @@ from . import fields
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 @@ class ModelView(Model):
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 --git a/trytond/trytond/tests/modelview.py b/trytond/trytond/tests/modelview.py
index 50c7dc8a0b..38f81dd1b4 100644
--- a/trytond/trytond/tests/modelview.py
+++ b/trytond/trytond/tests/modelview.py
@@ -68,6 +68,12 @@ class ModelViewButton(ModelView):
super().__setup__()
cls._buttons = {
'test': {},
+ 'test_invisible': {
+ 'invisible': Eval('value', 0) == 42,
+ },
+ 'test_readonly': {
+ 'readonly': Eval('value', 0) == 42,
+ },
}
@classmethod
@@ -79,6 +85,16 @@ class ModelViewButton(ModelView):
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 --git a/trytond/trytond/tests/test_modelview.py b/trytond/trytond/tests/test_modelview.py
index d17c5e2255..5fb454e9fe 100644
--- a/trytond/trytond/tests/test_modelview.py
+++ b/trytond/trytond/tests/test_modelview.py
@@ -454,6 +454,25 @@ class ModelView(TestCase):
with self.assertRaises(AccessButtonError):
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"