diff --git a/CHANGELOG b/CHANGELOG
index 1aecaa2353..2c478dcc82 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,4 @@
+* Ensure that all buttons are registered in ir.model.button
* Make trytond-admin ask for admin email
* Add option to set admin email with trytond-admin
* Add option to reset admin password with trytond-admin
diff --git a/doc/topics/views/index.rst b/doc/topics/views/index.rst
index 28b0218897..5eddb7e61e 100644
--- a/doc/topics/views/index.rst
+++ b/doc/topics/views/index.rst
@@ -333,8 +333,6 @@ button
Display a button.
- * ``string``: The string that will be displayed inside the button.
-
* ``name``: The name of the function that will be called. The function must
have this syntax:
@@ -343,6 +341,16 @@ Display a button.
The function may return an `ir.action` id or one of those client side
action keywords:
+ * ``string``: The string that will be displayed inside the button.
+
+ * ``confirm``: A string that will be shown in order to request
+ confirmation when clicking the button.
+
+ * ``help``: see in common-attributes-help_.
+
+The button should be registered on ``ir.model.button`` where the default value
+of the ``string``, ``confirm`` and ``help`` attributes can be can be defined.
+
.. _topics-views-client-actions:
* ``new``: to create a new record
diff --git a/trytond/ir/cron.xml b/trytond/ir/cron.xml
index cc1ca464f5..3f81eca94e 100644
--- a/trytond/ir/cron.xml
+++ b/trytond/ir/cron.xml
@@ -35,5 +35,11 @@ this repository contains the full copyright notices and license terms. -->
+
+
+ run_once
+ Run Once
+
+
diff --git a/trytond/ir/model.py b/trytond/ir/model.py
index 05d0fc9f86..266b1308ff 100644
--- a/trytond/ir/model.py
+++ b/trytond/ir/model.py
@@ -708,6 +708,10 @@ class ModelButton(ModelSQL, ModelView):
"Model Button"
__name__ = 'ir.model.button'
name = fields.Char('Name', required=True, readonly=True)
+ string = fields.Char("Label", translate=True)
+ help = fields.Text("Help", translate=True)
+ confirm = fields.Text("Confirm", translate=True,
+ help="Text to ask user confirmation when clicking the button.")
model = fields.Many2One('ir.model', 'Model', required=True, readonly=True,
ondelete='CASCADE', select=True)
groups = fields.Many2Many('ir.model.button-res.group', 'button', 'group',
@@ -732,6 +736,8 @@ class ModelButton(ModelSQL, ModelView):
],
depends=['model', 'id'])
_reset_cache = Cache('ir.model.button.reset')
+ _view_attributes_cache = Cache(
+ 'ir.model.button.view_attributes', context=False)
@classmethod
def __setup__(cls):
@@ -750,6 +756,7 @@ class ModelButton(ModelSQL, ModelView):
cls._groups_cache.clear()
cls._rules_cache.clear()
cls._reset_cache.clear()
+ cls._view_attributes_cache.clear()
return result
@classmethod
@@ -759,6 +766,7 @@ class ModelButton(ModelSQL, ModelView):
cls._groups_cache.clear()
cls._rules_cache.clear()
cls._reset_cache.clear()
+ cls._view_attributes_cache.clear()
@classmethod
def delete(cls, buttons):
@@ -767,6 +775,7 @@ class ModelButton(ModelSQL, ModelView):
cls._groups_cache.clear()
cls._rules_cache.clear()
cls._reset_cache.clear()
+ cls._view_attributes_cache.clear()
@classmethod
def copy(cls, buttons, default=None):
@@ -838,6 +847,29 @@ class ModelButton(ModelSQL, ModelView):
cls._reset_cache.set(key, reset)
return reset
+ @classmethod
+ def get_view_attributes(cls, model, name):
+ "Return the view attributes of the named button of the model"
+ key = (model, name, Transaction().language)
+ attributes = cls._view_attributes_cache.get(key)
+ if attributes is not None:
+ return attributes
+ buttons = cls.search([
+ ('model.model', '=', model),
+ ('name', '=', name),
+ ])
+ if not buttons:
+ attributes = {}
+ else:
+ button, = buttons
+ attributes = {
+ 'string': button.string,
+ 'help': button.help,
+ 'confirm': button.confirm,
+ }
+ cls._view_attributes_cache.set(key, attributes)
+ return attributes
+
class ModelButtonRule(ModelSQL, ModelView):
"Model Button Rule"
diff --git a/trytond/ir/model.xml b/trytond/ir/model.xml
index bb03999fcb..b90704a794 100644
--- a/trytond/ir/model.xml
+++ b/trytond/ir/model.xml
@@ -297,6 +297,11 @@ this repository contains the full copyright notices and license terms. -->
+
+ sync
+ Sync
+
+
diff --git a/trytond/ir/module.xml b/trytond/ir/module.xml
index 037d02a300..6ae517afb3 100644
--- a/trytond/ir/module.xml
+++ b/trytond/ir/module.xml
@@ -35,6 +35,37 @@ this repository contains the full copyright notices and license terms. -->
+
+ activate
+ Mark for Activation
+
+
+
+ activate_cancel
+ Cancel Activation
+
+
+
+ deactivate
+ Mark for Deactivation (beta)
+
+
+
+ deactivate_cancel
+ Cancel Deactivation
+
+
+
+ upgrade
+ Mark for Upgrade
+
+
+
+ upgrade_cancel
+ Cancel Upgrade
+
+
+
ir.module.dependency
form
diff --git a/trytond/ir/ui/form.rnc b/trytond/ir/ui/form.rnc
index 9151c60ca1..f8ca912e12 100644
--- a/trytond/ir/ui/form.rnc
+++ b/trytond/ir/ui/form.rnc
@@ -138,8 +138,7 @@ attlist.newline &= empty
button = element button { attlist.button, empty }
attlist.button &= [ a:defaultValue = "1" ] attribute colspan { text }?
attlist.button &= attribute help { text }?
-attlist.button &=
- [ a:defaultValue = "Unknown" ] attribute string { text }?
+attlist.button &= attribute string { text }?
attlist.button &= attribute icon { text }?
attlist.button &= attribute confirm { text }?
attlist.button &= attribute name { text }
diff --git a/trytond/ir/ui/form.rng b/trytond/ir/ui/form.rng
index 87fb51e195..ca6ba63046 100644
--- a/trytond/ir/ui/form.rng
+++ b/trytond/ir/ui/form.rng
@@ -541,7 +541,7 @@
-
+
diff --git a/trytond/ir/ui/tree.rnc b/trytond/ir/ui/tree.rnc
index 4b3ece8835..f85383b545 100644
--- a/trytond/ir/ui/tree.rnc
+++ b/trytond/ir/ui/tree.rnc
@@ -68,8 +68,7 @@ attlist.affix &= ( attribute name { text } | attribute id { text } )
attlist.affix &= attribute icon { text }?
button = element button { attlist.button, empty }
attlist.button &= attribute help { text }?
-attlist.button &=
- [ a:defaultValue = "Unknown" ] attribute string { text }?
+attlist.button &= attribute string { text }?
attlist.button &= attribute confirm { text }?
attlist.button &= attribute name { text }
attlist.button &= attribute states { text }?
diff --git a/trytond/ir/ui/tree.rng b/trytond/ir/ui/tree.rng
index 028ffa820f..edd4fc7b74 100644
--- a/trytond/ir/ui/tree.rng
+++ b/trytond/ir/ui/tree.rng
@@ -234,7 +234,7 @@
-
+
diff --git a/trytond/ir/ui/view.xml b/trytond/ir/ui/view.xml
index e3d3f97753..e4c61ea84e 100644
--- a/trytond/ir/ui/view.xml
+++ b/trytond/ir/ui/view.xml
@@ -38,6 +38,11 @@ this repository contains the full copyright notices and license terms. -->
+
+ show
+ Show
+
+
ir.ui.view_tree_width
diff --git a/trytond/ir/view/cron_form.xml b/trytond/ir/view/cron_form.xml
index d9a49c1c8d..7fab5e1f8d 100644
--- a/trytond/ir/view/cron_form.xml
+++ b/trytond/ir/view/cron_form.xml
@@ -20,7 +20,7 @@ this repository contains the full copyright notices and license terms. -->
-
+
diff --git a/trytond/ir/view/model_button_form.xml b/trytond/ir/view/model_button_form.xml
index 9ba036757c..a2d9b4582c 100644
--- a/trytond/ir/view/model_button_form.xml
+++ b/trytond/ir/view/model_button_form.xml
@@ -6,6 +6,13 @@ this repository contains the full copyright notices and license terms. -->
+
+
+
+
+
+
+
diff --git a/trytond/ir/view/model_data_form.xml b/trytond/ir/view/model_data_form.xml
index 1284dca79e..370fd36ac5 100644
--- a/trytond/ir/view/model_data_form.xml
+++ b/trytond/ir/view/model_data_form.xml
@@ -15,7 +15,7 @@ this repository contains the full copyright notices and license terms. -->
-
+
diff --git a/trytond/ir/view/model_data_list.xml b/trytond/ir/view/model_data_list.xml
index c2b80c55b1..c4838c4fc8 100644
--- a/trytond/ir/view/model_data_list.xml
+++ b/trytond/ir/view/model_data_list.xml
@@ -8,6 +8,6 @@ this repository contains the full copyright notices and license terms. -->
-
+
diff --git a/trytond/ir/view/ui_view_form.xml b/trytond/ir/view/ui_view_form.xml
index b6e932a5d8..cc75e1a596 100644
--- a/trytond/ir/view/ui_view_form.xml
+++ b/trytond/ir/view/ui_view_form.xml
@@ -20,5 +20,5 @@ this repository contains the full copyright notices and license terms. -->
-
+
diff --git a/trytond/model/modelview.py b/trytond/model/modelview.py
index 7a791efe53..0455247644 100644
--- a/trytond/model/modelview.py
+++ b/trytond/model/modelview.py
@@ -539,6 +539,11 @@ class ModelView(Model):
else:
states = {}
groups = set(User.get_groups())
+ button_attr = Button.get_view_attributes(
+ cls.__name__, button_name)
+ for attr, value in button_attr.iteritems():
+ if not element.get(attr):
+ element.set(attr, value or '')
button_groups = Button.get_groups(cls.__name__, button_name)
if ((button_groups and not groups & button_groups)
or (not button_groups
diff --git a/trytond/res/ir.xml b/trytond/res/ir.xml
index dabe6bdc60..063791ec7d 100644
--- a/trytond/res/ir.xml
+++ b/trytond/res/ir.xml
@@ -700,73 +700,49 @@ this repository contains the full copyright notices and license terms. -->
-
- activate
-
-
-
+
-
-
- activate_cancel
-
-
-
+
-
-
- deactivate
-
-
-
+
-
-
- deactivate_cancel
-
-
-
+
-
-
- upgrade
-
-
-
+
-
-
- upgrade_cancel
-
-
-
+
-
-
- sync
-
+
+
+
-
+
+
+
+
+
diff --git a/trytond/res/user.xml b/trytond/res/user.xml
index ecea8ae6ee..f8c483ba3a 100644
--- a/trytond/res/user.xml
+++ b/trytond/res/user.xml
@@ -68,6 +68,14 @@ this repository contains the full copyright notices and license terms. -->
+
+ reset_password
+ Reset Password
+ Send by email a new temporary password to the user
+
+
+
res.user.config.start
form
@@ -154,5 +162,18 @@ this repository contains the full copyright notices and license terms. -->
res/email_reset_password.html
html
+
+
+ validate_
+ Validate
+
+
+
+ cancel
+ Cancel
+
+
diff --git a/trytond/res/view/user_application_form.xml b/trytond/res/view/user_application_form.xml
index f28b163eea..2379498a1c 100644
--- a/trytond/res/view/user_application_form.xml
+++ b/trytond/res/view/user_application_form.xml
@@ -12,7 +12,7 @@ this repository contains the full copyright notices and license terms. -->
-
-
+
+
diff --git a/trytond/res/view/user_application_list.xml b/trytond/res/view/user_application_list.xml
index 94aa27c686..f4cdc9e404 100644
--- a/trytond/res/view/user_application_list.xml
+++ b/trytond/res/view/user_application_list.xml
@@ -6,6 +6,6 @@ this repository contains the full copyright notices and license terms. -->
-
-
+
+
diff --git a/trytond/res/view/user_form.xml b/trytond/res/view/user_form.xml
index 5797bf00b7..2139d6f82c 100644
--- a/trytond/res/view/user_form.xml
+++ b/trytond/res/view/user_form.xml
@@ -14,11 +14,7 @@ this repository contains the full copyright notices and license terms. -->
-
+
diff --git a/trytond/tests/test_modelview.py b/trytond/tests/test_modelview.py
index a108da9845..979048f382 100644
--- a/trytond/tests/test_modelview.py
+++ b/trytond/tests/test_modelview.py
@@ -90,6 +90,9 @@ class ModelView(unittest.TestCase):
admin, = Group.search([('name', '=', 'Administration')])
test = TestModel()
+ button = Button(name='test', model=model)
+ button.save()
+
# Without model/button access
TestModel.test([test])
@@ -105,7 +108,7 @@ class ModelView(unittest.TestCase):
self.assertRaises(UserError, TestModel.test, [test])
# Without write access but with button access
- button = Button(name='test', model=model, groups=[admin])
+ button.groups = [admin]
button.save()
TestModel.test([test])
diff --git a/trytond/tests/test_tryton.py b/trytond/tests/test_tryton.py
index c47389e182..feaec077e5 100644
--- a/trytond/tests/test_tryton.py
+++ b/trytond/tests/test_tryton.py
@@ -562,6 +562,27 @@ class ModuleTestCase(unittest.TestCase):
"ModelSingleton must appear before ModelSQL in the parent "
"classes of '%s'." % mname)
+ @with_transaction()
+ def test_buttons_registered(self):
+ 'Test all buttons are registered in ir.model.button'
+ pool = Pool()
+ Button = pool.get('ir.model.button')
+ for mname, model in Pool().iterobject():
+ if not isregisteredby(model, self.module):
+ continue
+ if not issubclass(model, ModelView):
+ continue
+ ir_buttons = {b.name for b in Button.search([
+ ('model.model', '=', model.__name__),
+ ])}
+ buttons = set(model._buttons)
+ assert ir_buttons >= buttons, (
+ 'The buttons "%(buttons)s" of Model "%(model)s" are not '
+ 'registered in ir.model.button.' % {
+ 'buttons': list(buttons - ir_buttons),
+ 'model': model.__name__,
+ })
+
def db_exist(name=DB_NAME):
Database = backend.get('Database')