Ensure that all buttons are registered in ir.model.button

issue6389
review36621002
This commit is contained in:
Sergi Almacellas Abellana
2018-03-01 11:59:23 +01:00
parent 4a909f94a2
commit ff2bded417
24 changed files with 177 additions and 62 deletions
+1
View File
@@ -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
+10 -2
View File
@@ -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
+6
View File
@@ -35,5 +35,11 @@ this repository contains the full copyright notices and license terms. -->
</record>
<menuitem parent="ir.menu_scheduler"
action="act_cron_form" id="menu_cron_form"/>
<record model="ir.model.button" id="cron_run_once_button">
<field name="name">run_once</field>
<field name="string">Run Once</field>
<field name="model" search="[('model', '=', 'ir.cron')]"/>
</record>
</data>
</tryton>
+32
View File
@@ -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"
+5
View File
@@ -297,6 +297,11 @@ this repository contains the full copyright notices and license terms. -->
</record>
<menuitem parent="menu_model_form" action="act_model_data_form"
id="menu_model_data_form"/>
<record model="ir.model.button" id="model_data_sync_button">
<field name="name">sync</field>
<field name="string">Sync</field>
<field name="model" search="[('model', '=', 'ir.model.data')]"/>
</record>
</data>
</tryton>
+31
View File
@@ -35,6 +35,37 @@ this repository contains the full copyright notices and license terms. -->
<menuitem parent="menu_modules"
action="act_module_form" id="menu_module_form"/>
<record model="ir.model.button" id="module_activate_button">
<field name="name">activate</field>
<field name="string">Mark for Activation</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.model.button" id="module_activate_cancel_button">
<field name="name">activate_cancel</field>
<field name="string">Cancel Activation</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.model.button" id="module_deactivate_button">
<field name="name">deactivate</field>
<field name="string">Mark for Deactivation (beta)</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.model.button" id="module_deactivate_cancel_button">
<field name="name">deactivate_cancel</field>
<field name="string">Cancel Deactivation</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.model.button" id="module_upgrade_button">
<field name="name">upgrade</field>
<field name="string">Mark for Upgrade</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.model.button" id="module_upgrade_cancel_button">
<field name="name">upgrade_cancel</field>
<field name="string">Cancel Upgrade</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.ui.view" id="module_dependency_view_form">
<field name="model">ir.module.dependency</field>
<field name="type">form</field>
+1 -2
View File
@@ -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 }
+1 -1
View File
@@ -541,7 +541,7 @@
</define>
<define name="attlist.button" combine="interleave">
<optional>
<attribute name="string" a:defaultValue="Unknown"/>
<attribute name="string"/>
</optional>
</define>
<define name="attlist.button" combine="interleave">
+1 -2
View File
@@ -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 }?
+1 -1
View File
@@ -234,7 +234,7 @@
</define>
<define name="attlist.button" combine="interleave">
<optional>
<attribute name="string" a:defaultValue="Unknown"/>
<attribute name="string"/>
</optional>
</define>
<define name="attlist.button" combine="interleave">
+5
View File
@@ -38,6 +38,11 @@ this repository contains the full copyright notices and license terms. -->
</record>
<menuitem parent="menu_ui"
action="act_view_form" id="menu_view"/>
<record model="ir.model.button" id="view_show_button">
<field name="name">show</field>
<field name="string">Show</field>
<field name="model" search="[('model', '=', 'ir.ui.view')]"/>
</record>
<record model="ir.ui.view" id="view_tree_width_view_form">
<field name="model">ir.ui.view_tree_width</field>
+1 -1
View File
@@ -20,7 +20,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="next_call"/>
<label name="repeat_missed"/>
<field name="repeat_missed"/>
<button name="run_once" colspan="2" string="Run Once"/>
<button name="run_once" colspan="2"/>
<separator string="Action to trigger" colspan="4"
id="action_trigger"/>
<label name="model"/>
+7
View File
@@ -6,6 +6,13 @@ this repository contains the full copyright notices and license terms. -->
<field name="model"/>
<label name="name"/>
<field name="name"/>
<label name="string"/>
<field name="string"/>
<newline/>
<separator name="help" colspan="4"/>
<field name="help" colspan="4"/>
<separator name="confirm" colspan="4"/>
<field name="confirm" colspan="4"/>
<field name="groups" colspan="4"/>
<field name="rules" colspan="2"/>
<field name="reset_by" colspan="2"/>
+1 -1
View File
@@ -15,7 +15,7 @@ this repository contains the full copyright notices and license terms. -->
<newline/>
<label name="out_of_sync"/>
<field name="out_of_sync"/>
<button string="Sync" name="sync" colspan="2"/>
<button name="sync" colspan="2"/>
<group id="values" colspan="2" yexpand="1" yfill="1" col="1">
<separator name="values"/>
<field name="values"/>
+1 -1
View File
@@ -8,6 +8,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="db_id"/>
<field name="noupdate"/>
<field name="out_of_sync"/>
<button string="Sync" name="sync"/>
<button name="sync"/>
</tree>
+1 -1
View File
@@ -20,5 +20,5 @@ this repository contains the full copyright notices and license terms. -->
<field name="name"/>
<separator name="arch" colspan="4"/>
<field name="arch" colspan="4"/>
<button name="show" string="Show" colspan="4"/>
<button name="show" colspan="4"/>
</form>
+5
View File
@@ -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
+16 -40
View File
@@ -700,73 +700,49 @@ this repository contains the full copyright notices and license terms. -->
<field name="group" ref="group_admin"/>
</record>
<record model="ir.model.button" id="module_activate_button">
<field name="name">activate</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.model.button-res.group"
id="module_activate_button_group_admin">
<field name="button" ref="module_activate_button"/>
<field name="button" ref="ir.module_activate_button"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.model.button" id="module_activate_cancel_button">
<field name="name">activate_cancel</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.model.button-res.group"
id="module_activate_cancel_button_group_admin">
<field name="button" ref="module_activate_cancel_button"/>
<field name="button" ref="ir.module_activate_cancel_button"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.model.button" id="module_deactivate_button">
<field name="name">deactivate</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.model.button-res.group"
id="module_deactivate_button_group_admin">
<field name="button" ref="module_deactivate_button"/>
<field name="button" ref="ir.module_deactivate_button"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.model.button" id="module_deactivate_cancel_button">
<field name="name">deactivate_cancel</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.model.button-res.group"
id="module_deactivate_cancel_button_group_admin">
<field name="button" ref="module_deactivate_cancel_button"/>
<field name="button" ref="ir.module_deactivate_cancel_button"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.model.button" id="module_upgrade_button">
<field name="name">upgrade</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.model.button-res.group"
id="module_upgrade_button_group_admin">
<field name="button" ref="module_upgrade_button"/>
<field name="button" ref="ir.module_upgrade_button"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.model.button" id="module_upgrade_cancel_button">
<field name="name">upgrade_cancel</field>
<field name="model" search="[('model', '=', 'ir.module')]"/>
</record>
<record model="ir.model.button-res.group"
id="module_upgrade_cancel_button_group_admin">
<field name="button" ref="module_upgrade_cancel_button"/>
<field name="button" ref="ir.module_upgrade_cancel_button"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.model.button" id="model_data_sync_button">
<field name="name">sync</field>
<field name="model" search="[('model', '=', 'ir.model.data')]"/>
<record model="ir.model.button-res.group"
id="cron_run_once_button_group_admin">
<field name="button" ref="ir.cron_run_once_button"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.model.button-res.group"
id="model_data_sync_button_group_admin">
<field name="button" ref="model_data_sync_button"/>
<field name="button" ref="ir.model_data_sync_button"/>
<field name="group" ref="group_admin"/>
</record>
<record model="ir.model.button-res.group"
id="view_show_button_group_admin">
<field name="button" ref="ir.view_show_button"/>
<field name="group" ref="group_admin"/>
</record>
+21
View File
@@ -68,6 +68,14 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.button" id="user_reset_password_button">
<field name="name">reset_password</field>
<field name="string">Reset Password</field>
<field name="help">Send by email a new temporary password to the user</field>
<field name="model"
search="[('model', '=', 'res.user')]"/>
</record>
<record model="ir.ui.view" id="user_config_start_view_form">
<field name="model">res.user.config.start</field>
<field name="type">form</field>
@@ -154,5 +162,18 @@ this repository contains the full copyright notices and license terms. -->
<field name="report">res/email_reset_password.html</field>
<field name="template_extension">html</field>
</record>
<record model="ir.model.button" id="user_application_validate_button">
<field name="name">validate_</field>
<field name="string">Validate</field>
<field name="model"
search="[('model', '=', 'res.user.application')]"/>
</record>
<record model="ir.model.button" id="user_application_cancel_button">
<field name="name">cancel</field>
<field name="string">Cancel</field>
<field name="model"
search="[('model', '=', 'res.user.application')]"/>
</record>
</data>
</tryton>
+2 -2
View File
@@ -12,7 +12,7 @@ this repository contains the full copyright notices and license terms. -->
<label name="state"/>
<field name="state"/>
<group id="buttons" colspan="2" col="-1">
<button name="cancel" string="Cancel" icon="tryton-cancel"/>
<button name="validate_" string="Validate" icon="tryton-ok"/>
<button name="cancel" icon="tryton-cancel"/>
<button name="validate_" icon="tryton-ok"/>
</group>
</form>
+2 -2
View File
@@ -6,6 +6,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="key" expand="1"/>
<field name="application" expand="1"/>
<field name="state"/>
<button name="cancel" string="Cancel"/>
<button name="validate_" string="Validate"/>
<button name="cancel"/>
<button name="validate_"/>
</tree>
+1 -5
View File
@@ -14,11 +14,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="email" widget="email"/>
<label name="password"/>
<field name="password" widget="password"/>
<button
name="reset_password"
colspan="2"
string="Reset Password"
help="Send by email a new temporary password to the user"/>
<button name="reset_password" colspan="2" />
<separator name="signature" colspan="4"/>
<field name="signature" colspan="4"/>
</page>
+4 -1
View File
@@ -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])
+21
View File
@@ -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')