diff --git a/base_archive_manager/README.rst b/base_archive_manager/README.rst new file mode 100644 index 0000000..f9a361d --- /dev/null +++ b/base_archive_manager/README.rst @@ -0,0 +1,108 @@ +=============== +Archive Manager +=============== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:2346ac753b8e3940aa35c3ea94dbd53152c6d64b6cdb1f10d20d7af3edee9342 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--ux-lightgray.png?logo=github + :target: https://github.com/OCA/server-ux/tree/18.0/base_archive_manager + :alt: OCA/server-ux +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-ux-18-0/server-ux-18-0-base_archive_manager + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-ux&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows administrators to control who can archive and +unarchive records in Odoo. By default, Odoo only provides a ``write`` +permission, which implicitly allows any user with write access to +archive and unarchive records. This module introduces specific +``Archive Access`` and ``Unarchive Access`` permissions on the Access +Rights (``ir.model.access``) level. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, you need to: + +1. Go to *Settings > Technical > Security > Access Rights*. +2. Open any access right record. +3. You will see two new checkboxes: **Archive Access** + (``perm_archive``) and **Unarchive Access** (``perm_unarchive``). +4. Check or uncheck these boxes to grant or deny archive/unarchive + permissions for the corresponding group and model. + +Usage +===== + +Once configured, the module automatically controls the visibility of the +Archive and Unarchive actions across the system: + +- In Form views, the ``Active`` stat button will be hidden if the user + lacks the required permission to toggle it (e.g., if a record is + active and the user lacks archive permission, the button is hidden). +- In List and Kanban views, the ``Archive`` and ``Unarchive`` action + menu items will be hidden depending on the user's permissions. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Solomon Prabu +* CIT Services + +Contributors +------------ + +- `CIT-Services `__ + + - Solomon Prabu s.prabu@cit-services.eu + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-ux `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/base_archive_manager/__init__.py b/base_archive_manager/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/base_archive_manager/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/base_archive_manager/__manifest__.py b/base_archive_manager/__manifest__.py new file mode 100644 index 0000000..d1372d2 --- /dev/null +++ b/base_archive_manager/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright CIT Services 2026-27 +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Archive Manager", + "summary": "Manage model archive/unarchive accesses", + "category": "Personalization", + "version": "18.0.1.0.0", + "depends": ["web"], + "author": "Solomon Prabu, CIT Services, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/server-ux", + "assets": { + "web.assets_backend": [ + "base_archive_manager/static/src/js/archiveAccess.esm.js", + "base_archive_manager/static/src/js/listController.esm.js", + "base_archive_manager/static/src/js/formController.esm.js", + "base_archive_manager/static/src/js/kanbanController.esm.js", + ], + }, + "data": ["views/ir_model_access.xml", "views/res_groups.xml"], + "license": "AGPL-3", + "installable": True, +} diff --git a/base_archive_manager/models/__init__.py b/base_archive_manager/models/__init__.py new file mode 100644 index 0000000..cf35b78 --- /dev/null +++ b/base_archive_manager/models/__init__.py @@ -0,0 +1 @@ +from . import base, ir_model_access diff --git a/base_archive_manager/models/base.py b/base_archive_manager/models/base.py new file mode 100644 index 0000000..ff1b800 --- /dev/null +++ b/base_archive_manager/models/base.py @@ -0,0 +1,68 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from lxml import etree + +from odoo import api, models + + +class Base(models.AbstractModel): + _inherit = "base" + + @api.model + def get_views(self, views, options=None): + """Dynamically modify form view arch to hide the toggle_active button.""" + res = super().get_views(views, options=options) + + active_name = self._active_name + if not active_name: + return res + + can_archive = self.env["ir.model.access"].check( + self._name, "archive", raise_exception=False + ) + can_unarchive = self.env["ir.model.access"].check( + self._name, "unarchive", raise_exception=False + ) + + if can_archive and can_unarchive: + return res + + if "views" in res: + for view_type in ["form", "list"]: + if view_type in res["views"]: + arch = res["views"][view_type].get("arch") + if arch: + doc = etree.fromstring(arch) + # Look for both the toggle_active button and + # the active field itself + elements = doc.xpath( + "//button[@name='toggle_active']" + ) + doc.xpath(f"//field[@name='{active_name}']") + for elem in elements: + invisible_attr = "" + if not can_archive and not can_unarchive: + invisible_attr = "True" + elif can_archive and not can_unarchive: + # Can archive: + # hide if record is already archived (active=False) + invisible_attr = f"not {active_name}" + elif not can_archive and can_unarchive: + # Can unarchive: + # hide if record is already active (active=True) + invisible_attr = active_name + + if invisible_attr: + existing = elem.get("invisible") + if existing: + elem.set( + "invisible", + f"({existing}) or ({invisible_attr})", + ) + else: + elem.set("invisible", invisible_attr) + + res["views"][view_type]["arch"] = etree.tostring( + doc, encoding="unicode" + ) + + return res diff --git a/base_archive_manager/models/ir_model_access.py b/base_archive_manager/models/ir_model_access.py new file mode 100644 index 0000000..94882e5 --- /dev/null +++ b/base_archive_manager/models/ir_model_access.py @@ -0,0 +1,53 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models, tools +from odoo.tools import SQL + + +class IrModelAccess(models.Model): + _inherit = "ir.model.access" + + perm_archive = fields.Boolean("Archive Access", default=True) + perm_unarchive = fields.Boolean("Unarchive Access", default=True) + + @api.model + @tools.ormcache("self.env.uid", "mode") + def _get_allowed_models(self, mode="read"): + """Extend to support archive/unarchive access modes.""" + if mode not in ("archive", "unarchive"): + return super()._get_allowed_models(mode) + + group_ids = self.env.user._get_group_ids() + + self.flush_model() + rows = self.env.execute_query( + SQL( + """ + SELECT m.model + FROM ir_model_access a + JOIN ir_model m ON (m.id = a.model_id) + WHERE a.perm_%s + AND a.active + AND ( + a.group_id IS NULL OR + a.group_id IN %s + ) + GROUP BY m.model + """, + SQL(mode), + tuple(group_ids) or (None,), + ) + ) + return frozenset(v[0] for v in rows) + + @api.model + def get_archive_access(self, model): + """Return archive/unarchive permission for the current user and model. + Called by the JS layer to decide whether to show Archive/Unarchive + action menu items. + """ + return { + "can_archive": self.check(model, "archive", raise_exception=False), + "can_unarchive": self.check(model, "unarchive", raise_exception=False), + } diff --git a/base_archive_manager/pyproject.toml b/base_archive_manager/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/base_archive_manager/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/base_archive_manager/readme/CONFIGURE.md b/base_archive_manager/readme/CONFIGURE.md new file mode 100644 index 0000000..466e9ac --- /dev/null +++ b/base_archive_manager/readme/CONFIGURE.md @@ -0,0 +1,6 @@ +To configure this module, you need to: + +1. Go to *Settings > Technical > Security > Access Rights*. +2. Open any access right record. +3. You will see two new checkboxes: **Archive Access** (`perm_archive`) and **Unarchive Access** (`perm_unarchive`). +4. Check or uncheck these boxes to grant or deny archive/unarchive permissions for the corresponding group and model. diff --git a/base_archive_manager/readme/CONTRIBUTORS.md b/base_archive_manager/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..59b132f --- /dev/null +++ b/base_archive_manager/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [CIT-Services](cit-services.eu) + - Solomon Prabu diff --git a/base_archive_manager/readme/DESCRIPTION.md b/base_archive_manager/readme/DESCRIPTION.md new file mode 100644 index 0000000..7147f66 --- /dev/null +++ b/base_archive_manager/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module allows administrators to control who can archive and unarchive records in Odoo. By default, Odoo only provides a `write` permission, which implicitly allows any user with write access to archive and unarchive records. This module introduces specific `Archive Access` and `Unarchive Access` permissions on the Access Rights (`ir.model.access`) level. diff --git a/base_archive_manager/readme/USAGE.md b/base_archive_manager/readme/USAGE.md new file mode 100644 index 0000000..c638a3e --- /dev/null +++ b/base_archive_manager/readme/USAGE.md @@ -0,0 +1,4 @@ +Once configured, the module automatically controls the visibility of the Archive and Unarchive actions across the system: + +* In Form views, the `Active` stat button will be hidden if the user lacks the required permission to toggle it (e.g., if a record is active and the user lacks archive permission, the button is hidden). +* In List and Kanban views, the `Archive` and `Unarchive` action menu items will be hidden depending on the user's permissions. diff --git a/base_archive_manager/static/description/index.html b/base_archive_manager/static/description/index.html new file mode 100644 index 0000000..76c4bfb --- /dev/null +++ b/base_archive_manager/static/description/index.html @@ -0,0 +1,458 @@ + + + + + +Archive Manager + + + +
+

Archive Manager

+ + +

Beta License: AGPL-3 OCA/server-ux Translate me on Weblate Try me on Runboat

+

This module allows administrators to control who can archive and +unarchive records in Odoo. By default, Odoo only provides a write +permission, which implicitly allows any user with write access to +archive and unarchive records. This module introduces specific +Archive Access and Unarchive Access permissions on the Access +Rights (ir.model.access) level.

+

Table of contents

+ +
+

Configuration

+

To configure this module, you need to:

+
    +
  1. Go to Settings > Technical > Security > Access Rights.
  2. +
  3. Open any access right record.
  4. +
  5. You will see two new checkboxes: Archive Access +(perm_archive) and Unarchive Access (perm_unarchive).
  6. +
  7. Check or uncheck these boxes to grant or deny archive/unarchive +permissions for the corresponding group and model.
  8. +
+
+
+

Usage

+

Once configured, the module automatically controls the visibility of the +Archive and Unarchive actions across the system:

+
    +
  • In Form views, the Active stat button will be hidden if the user +lacks the required permission to toggle it (e.g., if a record is +active and the user lacks archive permission, the button is hidden).
  • +
  • In List and Kanban views, the Archive and Unarchive action +menu items will be hidden depending on the user’s permissions.
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Solomon Prabu
  • +
  • CIT Services
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-ux project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/base_archive_manager/static/src/js/archiveAccess.esm.js b/base_archive_manager/static/src/js/archiveAccess.esm.js new file mode 100644 index 0000000..af46a7e --- /dev/null +++ b/base_archive_manager/static/src/js/archiveAccess.esm.js @@ -0,0 +1,18 @@ +/** + * Copyright 2026 CIT Services + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + */ + +import {Cache} from "@web/core/utils/cache"; +import {rpc} from "@web/core/network/rpc"; + +async function fetchArchiveAccess(resModel) { + const result = await rpc("/web/dataset/call_kw", { + model: "ir.model.access", + method: "get_archive_access", + args: [resModel], + kwargs: {}, + }); + return result; +} +export const archiveAccessCache = new Cache(fetchArchiveAccess, (model) => model); diff --git a/base_archive_manager/static/src/js/formController.esm.js b/base_archive_manager/static/src/js/formController.esm.js new file mode 100644 index 0000000..3106497 --- /dev/null +++ b/base_archive_manager/static/src/js/formController.esm.js @@ -0,0 +1,51 @@ +/** + * Copyright 2026 CIT Services + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + */ + +import {archiveAccessCache} from "@base_archive_manager/js/archiveAccess.esm"; +import {FormController} from "@web/views/form/form_controller"; +import {onWillStart} from "@odoo/owl"; +import {patch} from "@web/core/utils/patch"; + +export const getArchiveAccessPatch = () => ({ + setup() { + super.setup(...arguments); + this.hasArchiveAccess = false; + this.hasUnarchiveAccess = false; + + onWillStart(async () => { + // Only fetch access rights if the model supports archiving + const hasActiveField = + "active" in this.props.fields || "x_active" in this.props.fields; + if (hasActiveField) { + const access = await archiveAccessCache.read(this.props.resModel); + this.hasArchiveAccess = access.can_archive; + this.hasUnarchiveAccess = access.can_unarchive; + } + }); + }, + + getStaticActionMenuItems() { + const menuItems = super.getStaticActionMenuItems(...arguments); + + const applyAccessCheck = (menuItem, hasAccess) => { + if (!menuItem) return; + const originalIsAvailable = menuItem.isAvailable; + menuItem.isAvailable = () => { + const isAvailable = + typeof originalIsAvailable === "function" + ? originalIsAvailable() + : (originalIsAvailable ?? true); + return isAvailable && hasAccess; + }; + }; + + applyAccessCheck(menuItems.archive, this.hasArchiveAccess); + applyAccessCheck(menuItems.unarchive, this.hasUnarchiveAccess); + + return menuItems; + }, +}); + +patch(FormController.prototype, getArchiveAccessPatch()); diff --git a/base_archive_manager/static/src/js/kanbanController.esm.js b/base_archive_manager/static/src/js/kanbanController.esm.js new file mode 100644 index 0000000..b94ef03 --- /dev/null +++ b/base_archive_manager/static/src/js/kanbanController.esm.js @@ -0,0 +1,73 @@ +/** + * Copyright 2026 CIT Services + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + */ + +import {KanbanCompiler} from "@web/views/kanban/kanban_compiler"; +import {KanbanController} from "@web/views/kanban/kanban_controller"; +import {KanbanHeader} from "@web/views/kanban/kanban_header"; +import {KanbanRecord} from "@web/views/kanban/kanban_record"; +import {onWillStart, useSubEnv} from "@odoo/owl"; +import {patch} from "@web/core/utils/patch"; +import {archiveAccessCache} from "@base_archive_manager/js/archiveAccess.esm"; + +patch(KanbanController.prototype, { + setup() { + super.setup(...arguments); + this.archivePermissions = {archive: false, unarchive: false}; + useSubEnv({archivePermissions: this.archivePermissions}); + + onWillStart(async () => { + const access = await archiveAccessCache.read(this.props.resModel); + this.archivePermissions.archive = access.can_archive; + this.archivePermissions.unarchive = access.can_unarchive; + }); + }, +}); + +patch(KanbanRecord.prototype, { + createWidget(props) { + super.createWidget(props); + this.dataState.widget.hasArchiveAccess = this.env.archivePermissions + ? this.env.archivePermissions.archive + : true; + this.dataState.widget.hasUnarchiveAccess = this.env.archivePermissions + ? this.env.archivePermissions.unarchive + : true; + }, +}); + +patch(KanbanHeader.prototype, { + canArchiveGroup() { + const canArchive = super.canArchiveGroup(); + return ( + canArchive && + (this.env.archivePermissions ? this.env.archivePermissions.archive : true) + ); + }, +}); + +patch(KanbanCompiler.prototype, { + compileButton(el, params) { + const type = el.getAttribute("type"); + if (type === "archive") { + const existingIf = el.getAttribute("t-if"); + el.setAttribute( + "t-if", + existingIf + ? `(${existingIf}) and widget.hasArchiveAccess` + : "widget.hasArchiveAccess" + ); + } + if (type === "unarchive") { + const existingIf = el.getAttribute("t-if"); + el.setAttribute( + "t-if", + existingIf + ? `(${existingIf}) and widget.hasUnarchiveAccess` + : "widget.hasUnarchiveAccess" + ); + } + return super.compileButton(el, params); + }, +}); diff --git a/base_archive_manager/static/src/js/listController.esm.js b/base_archive_manager/static/src/js/listController.esm.js new file mode 100644 index 0000000..94c0fee --- /dev/null +++ b/base_archive_manager/static/src/js/listController.esm.js @@ -0,0 +1,10 @@ +/** + * Copyright 2026 CIT Services + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + */ + +import {ListController} from "@web/views/list/list_controller"; +import {patch} from "@web/core/utils/patch"; +import {getArchiveAccessPatch} from "@base_archive_manager/js/formController.esm"; + +patch(ListController.prototype, getArchiveAccessPatch()); diff --git a/base_archive_manager/tests/__init__.py b/base_archive_manager/tests/__init__.py new file mode 100644 index 0000000..f3359eb --- /dev/null +++ b/base_archive_manager/tests/__init__.py @@ -0,0 +1 @@ +from . import test_base_archive_manager diff --git a/base_archive_manager/tests/test_base_archive_manager.py b/base_archive_manager/tests/test_base_archive_manager.py new file mode 100644 index 0000000..607cbc0 --- /dev/null +++ b/base_archive_manager/tests/test_base_archive_manager.py @@ -0,0 +1,206 @@ +# Copyright 2026 CIT Services +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from lxml import etree + +from odoo import Command +from odoo.tests.common import TransactionCase + + +class TestBaseArchiveManager(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.ModelAccess = cls.env["ir.model.access"] + cls.Partner = cls.env["res.partner"] + + # Create groups + cls.group_no_access = cls.env["res.groups"].create( + {"name": "No Archive Access"} + ) + cls.group_archive_only = cls.env["res.groups"].create( + {"name": "Archive Only Access"} + ) + cls.group_unarchive_only = cls.env["res.groups"].create( + {"name": "Unarchive Only Access"} + ) + cls.group_full_access = cls.env["res.groups"].create( + {"name": "Full Archive Access"} + ) + + partner_model = cls.env["ir.model"]._get("res.partner") + + # Clear existing access rights to avoid interference from default permissions + existing_access = cls.ModelAccess.search([("model_id", "=", partner_model.id)]) + existing_access.write( + { + "perm_archive": False, + "perm_unarchive": False, + } + ) + + # Create access rights for res.partner + cls.ModelAccess.create( + { + "name": "No Access", + "model_id": partner_model.id, + "group_id": cls.group_no_access.id, + "perm_read": True, + "perm_write": True, + "perm_archive": False, + "perm_unarchive": False, + } + ) + + cls.ModelAccess.create( + { + "name": "Archive Only", + "model_id": partner_model.id, + "group_id": cls.group_archive_only.id, + "perm_read": True, + "perm_write": True, + "perm_archive": True, + "perm_unarchive": False, + } + ) + + cls.ModelAccess.create( + { + "name": "Unarchive Only", + "model_id": partner_model.id, + "group_id": cls.group_unarchive_only.id, + "perm_read": True, + "perm_write": True, + "perm_archive": False, + "perm_unarchive": True, + } + ) + + cls.ModelAccess.create( + { + "name": "Full Access", + "model_id": partner_model.id, + "group_id": cls.group_full_access.id, + "perm_read": True, + "perm_write": True, + "perm_archive": True, + "perm_unarchive": True, + } + ) + + # Create users + cls.user_no_access = cls.env["res.users"].create( + { + "name": "User No Access", + "login": "user_no_access", + "groups_id": [ + Command.set( + [cls.group_no_access.id, cls.env.ref("base.group_user").id] + ) + ], + } + ) + cls.user_archive_only = cls.env["res.users"].create( + { + "name": "User Archive Only", + "login": "user_archive_only", + "groups_id": [ + Command.set( + [cls.group_archive_only.id, cls.env.ref("base.group_user").id] + ) + ], + } + ) + cls.user_unarchive_only = cls.env["res.users"].create( + { + "name": "User Unarchive Only", + "login": "user_unarchive_only", + "groups_id": [ + Command.set( + [cls.group_unarchive_only.id, cls.env.ref("base.group_user").id] + ) + ], + } + ) + cls.user_full_access = cls.env["res.users"].create( + { + "name": "User Full Access", + "login": "user_full_access", + "groups_id": [ + Command.set( + [cls.group_full_access.id, cls.env.ref("base.group_user").id] + ) + ], + } + ) + + def test_get_archive_access(self): + """Test the get_archive_access RPC method for all users.""" + res = self.ModelAccess.with_user(self.user_no_access).get_archive_access( + "res.partner" + ) + self.assertFalse(res["can_archive"]) + self.assertFalse(res["can_unarchive"]) + + res = self.ModelAccess.with_user(self.user_archive_only).get_archive_access( + "res.partner" + ) + self.assertTrue(res["can_archive"]) + self.assertFalse(res["can_unarchive"]) + + res = self.ModelAccess.with_user(self.user_unarchive_only).get_archive_access( + "res.partner" + ) + self.assertFalse(res["can_archive"]) + self.assertTrue(res["can_unarchive"]) + + res = self.ModelAccess.with_user(self.user_full_access).get_archive_access( + "res.partner" + ) + self.assertTrue(res["can_archive"]) + self.assertTrue(res["can_unarchive"]) + + def test_get_views(self): + """Test get_views injection of invisible attributes.""" + views = [[False, "form"]] + + # Helper to get targeted elements in the arch + def get_elements(user): + res = self.Partner.with_user(user).get_views(views=views) + arch = res["views"]["form"]["arch"] + doc = etree.fromstring(arch) + elements = doc.xpath("//button[@name='toggle_active']") + doc.xpath( + "//field[@name='active']" + ) + return elements, arch + + # 1. No access -> Should inject True + elements_no, arch_no = get_elements(self.user_no_access) + if elements_no: + self.assertTrue( + any("True" in el.get("invisible", "") for el in elements_no) + ) + + # 2. Archive only -> Should inject not active + elements_ao, arch_ao = get_elements(self.user_archive_only) + if elements_ao: + self.assertTrue( + any("not active" in el.get("invisible", "") for el in elements_ao) + ) + + # 3. Unarchive only -> Should inject active + elements_uo, arch_uo = get_elements(self.user_unarchive_only) + if elements_uo: + for el in elements_uo: + inv = el.get("invisible", "") + if "active" in inv and "not active" not in inv: + break + else: + self.fail("Expected 'active' (and not 'not active') in invisible attr") + + # 4. Full access -> No injection + elements_fa, arch_fa = get_elements(self.user_full_access) + if elements_no: + self.assertNotEqual(arch_fa, arch_no) + self.assertNotEqual(arch_fa, arch_ao) + self.assertNotEqual(arch_fa, arch_uo) diff --git a/base_archive_manager/views/ir_model_access.xml b/base_archive_manager/views/ir_model_access.xml new file mode 100644 index 0000000..dde116a --- /dev/null +++ b/base_archive_manager/views/ir_model_access.xml @@ -0,0 +1,36 @@ + + + + ir.model.access.list.base_archive_manager + ir.model.access + + + + + + + + + + ir.model.access.list.edition.base_archive_manager + ir.model.access + + + + + + + + + + ir.model.access.form.base_archive_manager + ir.model.access + + + + + + + + + diff --git a/base_archive_manager/views/res_groups.xml b/base_archive_manager/views/res_groups.xml new file mode 100644 index 0000000..46ecdc8 --- /dev/null +++ b/base_archive_manager/views/res_groups.xml @@ -0,0 +1,17 @@ + + + + res.groups.form.base_archive_manager + res.groups + + + + + + + + +