From c879535e01f15ccd4e5a2d01f618657b5207252c Mon Sep 17 00:00:00 2001 From: Ilyas Date: Fri, 12 May 2023 13:48:02 +0200 Subject: [PATCH] [ADD] web_field_required_invisible_manager --- .../web_field_required_invisible_manager | 1 + .../setup.py | 6 + .../README.rst | 0 .../__init__.py | 1 + .../__manifest__.py | 15 +++ .../models/__init__.py | 2 + .../models/custom_field_restriction.py | 58 +++++++++ .../models/models.py | 79 +++++++++++++ .../readme/CONTRIBUTORS.rst | 3 + .../readme/DESCRIPTION.rst | 3 + .../readme/USAGE.rst | 7 ++ .../security/ir.model.access.csv | 3 + .../tests/__init__.py | 3 + ...st_web_field_required_invisible_manager.py | 68 +++++++++++ .../views/views.xml | 110 ++++++++++++++++++ 15 files changed, 359 insertions(+) create mode 120000 setup/web_field_required_invisible_manager/odoo/addons/web_field_required_invisible_manager create mode 100644 setup/web_field_required_invisible_manager/setup.py create mode 100644 web_field_required_invisible_manager/README.rst create mode 100644 web_field_required_invisible_manager/__init__.py create mode 100644 web_field_required_invisible_manager/__manifest__.py create mode 100644 web_field_required_invisible_manager/models/__init__.py create mode 100644 web_field_required_invisible_manager/models/custom_field_restriction.py create mode 100644 web_field_required_invisible_manager/models/models.py create mode 100644 web_field_required_invisible_manager/readme/CONTRIBUTORS.rst create mode 100644 web_field_required_invisible_manager/readme/DESCRIPTION.rst create mode 100644 web_field_required_invisible_manager/readme/USAGE.rst create mode 100644 web_field_required_invisible_manager/security/ir.model.access.csv create mode 100644 web_field_required_invisible_manager/tests/__init__.py create mode 100644 web_field_required_invisible_manager/tests/test_web_field_required_invisible_manager.py create mode 100644 web_field_required_invisible_manager/views/views.xml diff --git a/setup/web_field_required_invisible_manager/odoo/addons/web_field_required_invisible_manager b/setup/web_field_required_invisible_manager/odoo/addons/web_field_required_invisible_manager new file mode 120000 index 000000000..f9629c884 --- /dev/null +++ b/setup/web_field_required_invisible_manager/odoo/addons/web_field_required_invisible_manager @@ -0,0 +1 @@ +../../../../web_field_required_invisible_manager \ No newline at end of file diff --git a/setup/web_field_required_invisible_manager/setup.py b/setup/web_field_required_invisible_manager/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/web_field_required_invisible_manager/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_field_required_invisible_manager/README.rst b/web_field_required_invisible_manager/README.rst new file mode 100644 index 000000000..e69de29bb diff --git a/web_field_required_invisible_manager/__init__.py b/web_field_required_invisible_manager/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/web_field_required_invisible_manager/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/web_field_required_invisible_manager/__manifest__.py b/web_field_required_invisible_manager/__manifest__.py new file mode 100644 index 000000000..9c4e99b67 --- /dev/null +++ b/web_field_required_invisible_manager/__manifest__.py @@ -0,0 +1,15 @@ +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) +{ + "name": "Web Field Required Invisible Manager", + "category": "Web", + "version": "14.0.1.0.0", + "license": "AGPL-3", + "author": "Ilyas, ooops404, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/web", + "maintainers": ["ilyasProgrammer"], + "depends": ["base"], + "data": [ + "security/ir.model.access.csv", + "views/views.xml", + ], +} diff --git a/web_field_required_invisible_manager/models/__init__.py b/web_field_required_invisible_manager/models/__init__.py new file mode 100644 index 000000000..379b396c7 --- /dev/null +++ b/web_field_required_invisible_manager/models/__init__.py @@ -0,0 +1,2 @@ +from . import custom_field_restriction +from . import models diff --git a/web_field_required_invisible_manager/models/custom_field_restriction.py b/web_field_required_invisible_manager/models/custom_field_restriction.py new file mode 100644 index 000000000..ba543e9c3 --- /dev/null +++ b/web_field_required_invisible_manager/models/custom_field_restriction.py @@ -0,0 +1,58 @@ +# Copyright 2020 ooops404 +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) +from odoo import api, fields, models + + +class CustomFieldRestriction(models.Model): + _name = "custom.field.restriction" + _description = "Make field invisible or required" + + field_id = fields.Many2one( + "ir.model.fields", + ondelete="cascade", + required=True, + string="Field", + ) + + field_name = fields.Char( + related="field_id.name", + store=True, + string="Field Name", + ) + + required_model_id = fields.Many2one( + "ir.model", + ondelete="cascade", + string="required_model_id", + index=True, + ) + invisible_model_id = fields.Many2one( + "ir.model", + ondelete="cascade", + string="invisible_model_id", + index=True, + ) + + model_name = fields.Char( + compute="_compute_model_name", + store=True, + string="Model Name", + index=True, + ) + condition_domain = fields.Char() + group_ids = fields.Many2many("res.groups", required=True) + required = fields.Boolean() + default_required = fields.Boolean(related="field_id.required") + field_invisible = fields.Boolean() + + @api.onchange("field_id") + def onchange_field_id(self): + self.required = self.field_id.required + + @api.depends("required_model_id", "invisible_model_id") + def _compute_model_name(self): + for rec in self: + if rec.required_model_id: + rec.model_name = rec.required_model_id.model + elif rec.invisible_model_id: + rec.model_name = rec.invisible_model_id.model diff --git a/web_field_required_invisible_manager/models/models.py b/web_field_required_invisible_manager/models/models.py new file mode 100644 index 000000000..3b1975f2a --- /dev/null +++ b/web_field_required_invisible_manager/models/models.py @@ -0,0 +1,79 @@ +# Copyright 2020 ooops404 +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) +import json as simplejson + +from lxml import etree + +from odoo import api, fields, models +from odoo.tools.safe_eval import safe_eval + + +class IrModel(models.Model): + _inherit = "ir.model" + + custom_required_restriction_ids = fields.One2many( + "custom.field.restriction", + "required_model_id", + ) + custom_invisible_restriction_ids = fields.One2many( + "custom.field.restriction", + "invisible_model_id", + ) + + +class Base(models.AbstractModel): + _inherit = "base" + + @api.model + def fields_view_get( + self, view_id=None, view_type=False, toolbar=False, submenu=False + ): + res = super().fields_view_get( + view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu + ) + if view_type not in ["form", "tree", "kanban"]: + return res + # TODO speed up somehow + restrictions = self.env["custom.field.restriction"].search( + [ + "|", + ("model_name", "=", self._name), + ("group_ids", "in", self.env.user.groups_id.ids), + ] + ) + if not restrictions: + return res + doc = etree.XML(res["arch"]) + for node in doc.xpath("//field"): + name = node.attrib.get("name") + restrictions_filtered = restrictions.filtered( + lambda x: x.field_id.name == name + ) + for r in restrictions_filtered: + if ( + view_type == "form" + and self.env.context.get("params") + and self.env.context["params"].get("id") + ): + rec_id = self.env[r.model_name].browse( + self.env.context["params"]["id"] + ) + if r.condition_domain: + filtered_rec_id = rec_id.filtered_domain( + safe_eval(r.condition_domain) + ) + if not filtered_rec_id: + continue + if r.required: + node.set("required", "1") + modifiers = simplejson.loads(node.get("modifiers")) + modifiers["required"] = True + node.set("modifiers", simplejson.dumps(modifiers)) + res["arch"] = etree.tostring(doc) + if r.field_invisible: + node.set("invisible", "1") + modifiers = simplejson.loads(node.get("modifiers")) + modifiers["invisible"] = True + node.set("modifiers", simplejson.dumps(modifiers)) + res["arch"] = etree.tostring(doc) + return res diff --git a/web_field_required_invisible_manager/readme/CONTRIBUTORS.rst b/web_field_required_invisible_manager/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..c29a9c72e --- /dev/null +++ b/web_field_required_invisible_manager/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Ooops404 `__: + + * Ilyas diff --git a/web_field_required_invisible_manager/readme/DESCRIPTION.rst b/web_field_required_invisible_manager/readme/DESCRIPTION.rst new file mode 100644 index 000000000..865933451 --- /dev/null +++ b/web_field_required_invisible_manager/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module allows to set a field required or invisible for users belonging to a specific group. + +The field can be required or invisible in any case, or according to specific conditions. diff --git a/web_field_required_invisible_manager/readme/USAGE.rst b/web_field_required_invisible_manager/readme/USAGE.rst new file mode 100644 index 000000000..9bf077834 --- /dev/null +++ b/web_field_required_invisible_manager/readme/USAGE.rst @@ -0,0 +1,7 @@ +Go to Settings > Technical > Models + +Select a model > in tab "Custom required fields" or "Custom invisible fields" add a line + +Select a field, add one or more group and enable flag "Required" or "Invisible" + +If needed, set a condition for which the field should be required or invisible for users of those groups. diff --git a/web_field_required_invisible_manager/security/ir.model.access.csv b/web_field_required_invisible_manager/security/ir.model.access.csv new file mode 100644 index 000000000..28c2198ec --- /dev/null +++ b/web_field_required_invisible_manager/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 +cfr1,cfr1,model_custom_field_restriction,base.group_system,1,1,1,1 +cfr2,cfr2,model_custom_field_restriction,base.group_user,1,0,0,0 diff --git a/web_field_required_invisible_manager/tests/__init__.py b/web_field_required_invisible_manager/tests/__init__.py new file mode 100644 index 000000000..487f25a27 --- /dev/null +++ b/web_field_required_invisible_manager/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_web_field_required_invisible_manager diff --git a/web_field_required_invisible_manager/tests/test_web_field_required_invisible_manager.py b/web_field_required_invisible_manager/tests/test_web_field_required_invisible_manager.py new file mode 100644 index 000000000..6dac9b59b --- /dev/null +++ b/web_field_required_invisible_manager/tests/test_web_field_required_invisible_manager.py @@ -0,0 +1,68 @@ +# Copyright 2023 ooops404 +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests import common + + +class TestFieldRequiredIvisibleManager(common.SavepointCase): + @classmethod + def setUpClass(cls): + super(TestFieldRequiredIvisibleManager, cls).setUpClass() + cls.partner_name_field_id = cls.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "name")] + ) + cls.partner_id_field_id = cls.env["ir.model.fields"].search( + [("model", "=", "res.partner"), ("name", "=", "id")] + ) + cls.partner_model_id = cls.partner_name_field_id.model_id + cls.partner_title_model_id = cls.env["ir.model"].search( + [("name", "=", "res.partner.title")] + ) + cls.partner_title_name_field_id = cls.env["ir.model.fields"].search( + [("model", "=", "res.partner.title"), ("name", "=", "name")] + ) + cls.partner_title_shortcut_field_id = cls.env["ir.model.fields"].search( + [("model", "=", "res.partner.title"), ("name", "=", "shortcut")] + ) + cls.invisible_title_rec_id = cls.env["custom.field.restriction"].create( + { + "invisible_model_id": cls.partner_title_model_id.id, + "field_id": cls.partner_title_shortcut_field_id.id, + "group_ids": [(6, 0, cls.env.ref("base.group_user").ids)], + "field_invisible": True, + } + ) + cls.invisible_rec_id = cls.env["custom.field.restriction"].create( + { + "invisible_model_id": cls.partner_model_id.id, + "field_id": cls.partner_id_field_id.id, + "group_ids": [(6, 0, cls.env.ref("base.group_user").ids)], + "field_invisible": True, + "condition_domain": "[('id', '<', 0)]", + } + ) + cls.required_rec_id = cls.env["custom.field.restriction"].create( + { + "required_model_id": cls.partner_model_id.id, + "field_id": cls.partner_id_field_id.id, + "group_ids": [(6, 0, cls.env.ref("base.group_user").ids)], + "required": True, + "condition_domain": "[('id', '>', 0)]", + } + ) + cls.deco_addict = cls.env.ref("base.res_partner_2") + cls.partner_view = cls.env.ref("base.view_partner_simple_form") + + def test_all_web_field_required_invisible_manager(self): + # onchange_field_id() + self.assertFalse(self.invisible_title_rec_id.required) + self.invisible_title_rec_id.field_id = self.partner_title_name_field_id + self.invisible_title_rec_id.onchange_field_id() + self.assertTrue(self.invisible_title_rec_id.required) + # _compute_model_name() + self.invisible_rec_id._compute_model_name() + self.assertEqual(self.invisible_rec_id.model_name, "res.partner") + self.required_rec_id._compute_model_name() + self.assertEqual(self.required_rec_id.model_name, "res.partner") + # fields_view_get() + self.deco_addict.fields_view_get(view_id=self.partner_view.id, view_type="form") diff --git a/web_field_required_invisible_manager/views/views.xml b/web_field_required_invisible_manager/views/views.xml new file mode 100644 index 000000000..636bb65b5 --- /dev/null +++ b/web_field_required_invisible_manager/views/views.xml @@ -0,0 +1,110 @@ + + + + + view.model.form.inherit + ir.model + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + custom.field.restriction.form.view + custom.field.restriction + +
+ + + + + + + + + + + + + + +
+ +
+
+ +