diff --git a/.docker_files/main/__manifest__.py b/.docker_files/main/__manifest__.py index 37ec02b1..eddd1ea4 100644 --- a/.docker_files/main/__manifest__.py +++ b/.docker_files/main/__manifest__.py @@ -26,6 +26,7 @@ "admin_light_filters", "attachment_minio", "base_extended_security", + "base_view_mode_restricted", "crm", # module added for test purpose "currency_rate_update_boc", "database_bi_user", diff --git a/Dockerfile b/Dockerfile index a9719f33..70ec42cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,7 @@ COPY admin_light_user /mnt/extra-addons/admin_light_user COPY admin_light_web /mnt/extra-addons/admin_light_web COPY attachment_minio /mnt/extra-addons/attachment_minio COPY base_extended_security /mnt/extra-addons/base_extended_security +COPY base_view_mode_restricted /mnt/extra-addons/base_view_mode_restricted COPY currency_rate_update_boc /mnt/extra-addons/currency_rate_update_boc COPY database_bi_user /mnt/extra-addons/database_bi_user COPY lang_fr_activated /mnt/extra-addons/lang_fr_activated diff --git a/base_view_mode_restricted/README.rst b/base_view_mode_restricted/README.rst new file mode 100644 index 00000000..4c295594 --- /dev/null +++ b/base_view_mode_restricted/README.rst @@ -0,0 +1,54 @@ +Base View Mode Restricted +========================= +This module allows to restrict the view modes of a given action to a specific group. + +.. contents:: Table of Contents + +Configuration +------------- +I go to the list of ``Window Actions``. + +.. image:: static/description/window_action_list.png + +I select the action view ``All Timesheets``. + +.. image:: static/description/window_action_list_filtered.png + +In the form view of the action, I notice a new section ``View Restrictions``. + +.. image:: static/description/window_action_restrictions_empty.png + +I define a new rule so that only Administrators can see the graph and pivot view types. + +.. image:: static/description/window_action_restrictions_filled.png + +.. + + The column View Modes must contain the technical names of the view modes to restrict, + separated by commas. + + If no user group is specified, then the view modes are disabled for everyone. + +Usage +----- +Connected as a non-administrator, I go to ``Timesheets > Timesheet > All Timesheets``. + +I notice that the pivot and graph views are not available. + +.. image:: static/description/timesheet_view_no_pivot.png + +Connected as an administrator, I go to ``Timesheets > Timesheet > All Timesheets``. + +I notice that the pivot and graph views are available. + +.. image:: static/description/timesheet_view_with_pivot.png + +Security +-------- +This module does not add any real security. + +Users without the access to a view mode can find a workaround to access the data. + +Contributors +------------ +* Numigi (tm) and all its contributors (https://bit.ly/numigiens) diff --git a/base_view_mode_restricted/__init__.py b/base_view_mode_restricted/__init__.py new file mode 100644 index 00000000..0ec9a3c9 --- /dev/null +++ b/base_view_mode_restricted/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2021 - today Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from . import models diff --git a/base_view_mode_restricted/__manifest__.py b/base_view_mode_restricted/__manifest__.py new file mode 100644 index 00000000..546c8bbc --- /dev/null +++ b/base_view_mode_restricted/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2021 - today Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +{ + "name": "Base View Mode Restricted", + "version": "16.0.1.0.0", + "author": "Numigi", + "maintainer": "Numigi", + "license": "LGPL-3", + "category": "Other", + "summary": "Add view mode restrictions on window actions", + "depends": [ + "base", + ], + "data": [ + "security/ir.model.access.csv", + "views/ir_actions_act_window.xml", + ], + "installable": True, +} diff --git a/base_view_mode_restricted/i18n/fr.po b/base_view_mode_restricted/i18n/fr.po new file mode 100644 index 00000000..66bc3591 --- /dev/null +++ b/base_view_mode_restricted/i18n/fr.po @@ -0,0 +1,86 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_view_mode_restricted +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-06-23 13:13+0000\n" +"PO-Revision-Date: 2021-06-23 13:13+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: base_view_mode_restricted +#: model:ir.model.fields,field_description:base_view_mode_restricted.field_ir_actions_view_mode_restriction__action_id +msgid "Action" +msgstr "Action" + +#. module: base_view_mode_restricted +#: model:ir.model,name:base_view_mode_restricted.model_ir_actions_act_window +msgid "Action Window" +msgstr "" + +#. module: base_view_mode_restricted +#: model:ir.model.fields,field_description:base_view_mode_restricted.field_ir_actions_view_mode_restriction__create_uid +msgid "Created by" +msgstr "" + +#. module: base_view_mode_restricted +#: model:ir.model.fields,field_description:base_view_mode_restricted.field_ir_actions_view_mode_restriction__create_date +msgid "Created on" +msgstr "" + +#. module: base_view_mode_restricted +#: model:ir.model.fields,field_description:base_view_mode_restricted.field_ir_actions_view_mode_restriction__display_name +msgid "Display Name" +msgstr "" + +#. module: base_view_mode_restricted +#: model:ir.model.fields,field_description:base_view_mode_restricted.field_ir_actions_view_mode_restriction__group_ids +msgid "Group" +msgstr "Groupe" + +#. module: base_view_mode_restricted +#: model:ir.model.fields,field_description:base_view_mode_restricted.field_ir_actions_view_mode_restriction__id +msgid "ID" +msgstr "" + +#. module: base_view_mode_restricted +#: model:ir.model,name:base_view_mode_restricted.model_ir_actions_view_mode_restriction +msgid "Ir Action View Mode Restriction" +msgstr "" + +#. module: base_view_mode_restricted +#: model:ir.model.fields,field_description:base_view_mode_restricted.field_ir_actions_view_mode_restriction____last_update +msgid "Last Modified on" +msgstr "" + +#. module: base_view_mode_restricted +#: model:ir.model.fields,field_description:base_view_mode_restricted.field_ir_actions_view_mode_restriction__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: base_view_mode_restricted +#: model:ir.model.fields,field_description:base_view_mode_restricted.field_ir_actions_view_mode_restriction__write_date +msgid "Last Updated on" +msgstr "" + +#. module: base_view_mode_restricted +#: model:ir.model.fields,field_description:base_view_mode_restricted.field_ir_actions_act_window__view_mode_restriction_ids +msgid "View Mode Restriction" +msgstr "" + +#. module: base_view_mode_restricted +#: model:ir.model.fields,field_description:base_view_mode_restricted.field_ir_actions_view_mode_restriction__view_modes +msgid "View Modes" +msgstr "Modes de vue" + +#. module: base_view_mode_restricted +#: model_terms:ir.ui.view,arch_db:base_view_mode_restricted.ir_actions_act_window_form +msgid "View Restrictions" +msgstr "Restrictions de vue" diff --git a/base_view_mode_restricted/models/__init__.py b/base_view_mode_restricted/models/__init__.py new file mode 100644 index 00000000..ff1229af --- /dev/null +++ b/base_view_mode_restricted/models/__init__.py @@ -0,0 +1,7 @@ +# Copyright 2021 - today Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from . import ( + ir_actions_act_window, + ir_actions_view_mode_restriction, +) diff --git a/base_view_mode_restricted/models/ir_actions_act_window.py b/base_view_mode_restricted/models/ir_actions_act_window.py new file mode 100644 index 00000000..bad88303 --- /dev/null +++ b/base_view_mode_restricted/models/ir_actions_act_window.py @@ -0,0 +1,36 @@ +# Copyright 2021 - today Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from itertools import chain +from odoo import api, fields, models + + +class IrActionsActWindow(models.Model): + + _inherit = "ir.actions.act_window" + + view_mode_restriction_ids = fields.One2many( + "ir.actions.view.mode.restriction", + "action_id", + ) + + @api.depends("view_ids.view_mode", "view_mode", "view_id.type") + def _compute_views(self): + super()._compute_views() + for action in self: + action.views = action._get_authorized_views() + + def _get_authorized_views(self): + forbiden_modes = self._get_forbiden_view_modes() + return [ + (view_id, mode) + for view_id, mode in self.views + if mode not in forbiden_modes + ] + + def _get_forbiden_view_modes(self): + user_groups = self.env.user.groups_id + restrictions = self.view_mode_restriction_ids.filtered( + lambda r: not (r.group_ids & user_groups) + ) + return set(chain.from_iterable(r._get_view_mode_list() for r in restrictions)) diff --git a/base_view_mode_restricted/models/ir_actions_view_mode_restriction.py b/base_view_mode_restricted/models/ir_actions_view_mode_restriction.py new file mode 100644 index 00000000..02232ba7 --- /dev/null +++ b/base_view_mode_restricted/models/ir_actions_view_mode_restriction.py @@ -0,0 +1,28 @@ +# Copyright 2021 - today Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import fields, models + + +class IrActionsViewModeRestriction(models.Model): + + _name = "ir.actions.view.mode.restriction" + _description = "Ir Action View Mode Restriction" + + action_id = fields.Many2one( + "ir.actions.act_window", + required=True, + ondelete="cascade", + index=True, + ) + view_modes = fields.Char(required=True) + group_ids = fields.Many2many( + "res.groups", + "ir_actions_view_mode_restriction_group_rel", + "restriction_id", + "group_id", + required=True, + ) + + def _get_view_mode_list(self): + return [mode.strip() for mode in self.view_modes.split(",")] diff --git a/base_view_mode_restricted/security/ir.model.access.csv b/base_view_mode_restricted/security/ir.model.access.csv new file mode 100644 index 00000000..2383f739 --- /dev/null +++ b/base_view_mode_restricted/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_ir_actions_view_mode_restriction_manager,ir_actions_view_mode_restriction_manager,model_ir_actions_view_mode_restriction,base.group_erp_manager,1,1,1,1 +access_ir_actions_view_mode_restriction_user,ir_actions_view_mode_restriction_user,model_ir_actions_view_mode_restriction,,1,0,0,0 diff --git a/base_view_mode_restricted/static/description/icon.png b/base_view_mode_restricted/static/description/icon.png new file mode 100644 index 00000000..92a86b10 Binary files /dev/null and b/base_view_mode_restricted/static/description/icon.png differ diff --git a/base_view_mode_restricted/static/description/timesheet_view_no_pivot.png b/base_view_mode_restricted/static/description/timesheet_view_no_pivot.png new file mode 100644 index 00000000..6f2af676 Binary files /dev/null and b/base_view_mode_restricted/static/description/timesheet_view_no_pivot.png differ diff --git a/base_view_mode_restricted/static/description/timesheet_view_with_pivot.png b/base_view_mode_restricted/static/description/timesheet_view_with_pivot.png new file mode 100644 index 00000000..5a4d45f6 Binary files /dev/null and b/base_view_mode_restricted/static/description/timesheet_view_with_pivot.png differ diff --git a/base_view_mode_restricted/static/description/window_action_list.png b/base_view_mode_restricted/static/description/window_action_list.png new file mode 100644 index 00000000..24d68b9c Binary files /dev/null and b/base_view_mode_restricted/static/description/window_action_list.png differ diff --git a/base_view_mode_restricted/static/description/window_action_list_filtered.png b/base_view_mode_restricted/static/description/window_action_list_filtered.png new file mode 100644 index 00000000..77963913 Binary files /dev/null and b/base_view_mode_restricted/static/description/window_action_list_filtered.png differ diff --git a/base_view_mode_restricted/static/description/window_action_restrictions_empty.png b/base_view_mode_restricted/static/description/window_action_restrictions_empty.png new file mode 100644 index 00000000..12fa0675 Binary files /dev/null and b/base_view_mode_restricted/static/description/window_action_restrictions_empty.png differ diff --git a/base_view_mode_restricted/static/description/window_action_restrictions_filled.png b/base_view_mode_restricted/static/description/window_action_restrictions_filled.png new file mode 100644 index 00000000..cb5a9d6c Binary files /dev/null and b/base_view_mode_restricted/static/description/window_action_restrictions_filled.png differ diff --git a/base_view_mode_restricted/tests/__init__.py b/base_view_mode_restricted/tests/__init__.py new file mode 100644 index 00000000..799b9422 --- /dev/null +++ b/base_view_mode_restricted/tests/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2021 - today Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). diff --git a/base_view_mode_restricted/tests/test_actions.py b/base_view_mode_restricted/tests/test_actions.py new file mode 100644 index 00000000..7e03c3e9 --- /dev/null +++ b/base_view_mode_restricted/tests/test_actions.py @@ -0,0 +1,58 @@ +# Copyright 2021 - today Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo.tests import common + + +class TestViewModeRestrictions(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.user = cls.env.ref("base.user_admin") + cls.group_1 = cls.env["res.groups"].create({"name": "Group 1"}) + cls.action = cls.env.ref("base.action_partner_form") + + def test_user_without_group(self): + self.user.groups_id -= self.group_1 + self._setup_restriction("kanban", self.group_1) + modes = self._get_authorized_view_modes() + assert "kanban" not in modes + + def test_user_with_group(self): + self.user.groups_id |= self.group_1 + self._setup_restriction("kanban", self.group_1) + modes = self._get_authorized_view_modes() + assert "kanban" in modes + + def test_multiple_restrictions(self): + self.user.groups_id -= self.group_1 + self._setup_restriction("kanban,list", self.group_1) + modes = self._get_authorized_view_modes() + assert "kanban" not in modes + assert "list" not in modes + assert "form" in modes + + def test_view_mode_with_extra_spaces(self): + self.user.groups_id -= self.group_1 + self._setup_restriction(" kanban ", self.group_1) + modes = self._get_authorized_view_modes() + assert "kanban" not in modes + + def _setup_restriction(self, view_modes, groups): + self.action.write( + { + "view_mode_restriction_ids": [ + ( + 0, + 0, + { + "view_modes": view_modes, + "group_ids": [(6, 0, groups.ids)], + }, + ) + ] + } + ) + + def _get_authorized_view_modes(self): + return {v[1] for v in self.action.with_user(self.user).views} diff --git a/base_view_mode_restricted/views/ir_actions_act_window.xml b/base_view_mode_restricted/views/ir_actions_act_window.xml new file mode 100644 index 00000000..20354e35 --- /dev/null +++ b/base_view_mode_restricted/views/ir_actions_act_window.xml @@ -0,0 +1,22 @@ + + + + + Ir Actions Act Window: add restrictions on view modes + ir.actions.act_window + + + + + + + + + + + + + + + +