diff --git a/attribute_set/models/attribute_attribute.py b/attribute_set/models/attribute_attribute.py index 7c26c8e00..79ac1c7db 100644 --- a/attribute_set/models/attribute_attribute.py +++ b/attribute_set/models/attribute_attribute.py @@ -10,9 +10,8 @@ from lxml import etree -from odoo import Command, _, api, fields, models +from odoo import _, api, fields, models from odoo.exceptions import ValidationError -from odoo.tools.safe_eval import safe_eval from ..utils.orm import setup_modifiers @@ -83,10 +82,6 @@ class AttributeAttribute(models.Model): "ir.model", "Relational Model", ondelete="cascade" ) - relation_model_name = fields.Char( - "Relational Model Name", related="relation_model_id.model" - ) - widget = fields.Char(help="Specify widget to add to the field on the views.") required_on_views = fields.Boolean( @@ -102,6 +97,10 @@ class AttributeAttribute(models.Model): column1="attribute_id", column2="attribute_set_id", ) + allowed_attribute_set_ids = fields.Many2many( + comodel_name="attribute.set", + compute="_compute_allowed_attribute_set_ids", + ) attribute_group_id = fields.Many2one( "attribute.group", "Attribute Group", required=True, ondelete="cascade" @@ -135,7 +134,7 @@ def _build_attribute_field(self, attribute_egroup): Conditional invisibility based on its attribute sets. """ self.ensure_one() - kwargs = {"name": "%s" % self.name} + kwargs = {"name": f"{self.name}"} kwargs["attrs"] = str(self._get_attrs()) if self.widget: kwargs["widget"] = self.widget @@ -159,7 +158,7 @@ def _build_attribute_field(self, attribute_egroup): else: # Display only options linked to an existing object ids = [op.value_ref.id for op in self.option_ids if op.value_ref] - kwargs["domain"] = "[('id', 'in', %s)]" % ids + kwargs["domain"] = f"[('id', 'in', {ids})]" # Add color options if the attribute's Relational Model # has a color field relation_model_obj = self.env[self.relation_model_id.model] @@ -168,8 +167,8 @@ def _build_attribute_field(self, attribute_egroup): elif self.nature == "custom": # Define field's domain and context with attribute's id to go along with # Attribute Options search and creation - kwargs["domain"] = "[('attribute_id', '=', %s)]" % (self.id) - kwargs["context"] = "{'default_attribute_id': %s}" % (self.id) + kwargs["domain"] = f"[('attribute_id', '=', {self.id})]" + kwargs["context"] = f"{{'default_attribute_id': {self.id}}}" elif self.nature != "custom": kwargs["context"] = self._get_native_field_context() @@ -229,6 +228,18 @@ def _build_attribute_eview(self): return attribute_eview + def _get_attribute_set_allowed_model(self): + return self.model_id + + @api.depends("model_id") + def _compute_allowed_attribute_set_ids(self): + AttributeSet = self.env["attribute.set"] + for record in self: + allowed_models = record._get_attribute_set_allowed_model() + record.allowed_attribute_set_ids = AttributeSet.search( + [("model_id", "in", allowed_models.ids)] + ) + @api.onchange("model_id") def onchange_model_id(self): return {"domain": {"field_id": [("model_id", "=", self.model_id.id)]}} @@ -242,7 +253,7 @@ def onchange_field_description(self): def onchange_name(self): name = self.name if not name.startswith("x_"): - self.name = "x_%s" % name + self.name = f"x_{name}" @api.onchange("attribute_type") def onchange_attribute_type(self): @@ -274,18 +285,22 @@ def _onchange_domain(self): def button_add_options(self): self.ensure_one() - values = self.env[self.relation_model_id.model].search(safe_eval(self.domain)) - options = [] - for value in values: - options.append( - Command.create( - { - "name": value.display_name, - "value_ref": f"{value._name},{value.id}", - } - ) - ) - self.option_ids = options + # Before adding another option delete the ones which are linked + # to a deleted object + for option in self.option_ids: + if not option.value_ref: + option.unlink() + # Then open the Options Wizard which will display an 'opt_ids' m2m field related + # to the 'relation_model_id' model + return { + "context": dict(self.env.context, attribute_id=self.id), + "name": _("Options Wizard"), + "view_type": "form", + "view_mode": "form", + "res_model": "attribute.option.wizard", + "type": "ir.actions.act_window", + "target": "new", + } @api.model_create_multi def create(self, vals_list): @@ -325,8 +340,9 @@ def create(self, vals_list): elif attr_type == "multiselect": vals["ttype"] = "many2many" vals["relation"] = relation - # Specify the relation_table's name in case of m2m not serialized to - # avoid creating the same default relation_table name for any attribute + # Specify the relation_table's name in case of m2m not serialized + # to avoid creating the same default + # relation_table name for any attribute # linked to the same attribute.option or relation_model_id's model. if not vals.get("serialized"): att_model_id = self.env["ir.model"].browse(vals["model_id"]) diff --git a/attribute_set/models/attribute_option.py b/attribute_set/models/attribute_option.py index 90ad0e668..3b3ba4a9b 100644 --- a/attribute_set/models/attribute_option.py +++ b/attribute_set/models/attribute_option.py @@ -22,7 +22,6 @@ def _selection_model_list(self): value_ref = fields.Reference( selection="_selection_model_list", string="Reference", - groups="base.group_erp_manager", ) attribute_id = fields.Many2one( diff --git a/attribute_set/tests/test_build_view.py b/attribute_set/tests/test_build_view.py index 7f9747cf7..16d21cdcc 100644 --- a/attribute_set/tests/test_build_view.py +++ b/attribute_set/tests/test_build_view.py @@ -34,7 +34,8 @@ def setUpClass(cls): cls.demo = cls.env.ref("base.user_demo") # This user will have access to - cls.attribute_manager_user = cls.env["res.users"].create( + cls.attribute_manager_user = cls.env.ref("base.user_admin") + cls.attribute_manager_user.write( { "name": "Attribute Manager", "login": "attribute_manager", @@ -344,18 +345,20 @@ def test_unlink_native_attribute(self): ) # TEST form views rendering - @users("demo") + @users("attribute_manager") def test_model_form(self): # Test attributes modifications through form self.assertFalse(self.partner.x_attr_3) with Form( - self.partner.with_user(self.demo).with_context(load_all_views=True) + self.partner.with_user(self.attribute_manager_user).with_context( + load_all_views=True + ) ) as partner_form: partner_form.attribute_set_id = self.set_1 partner_form.x_attr_3 = True partner_form.x_attr_select = self.attr_select_option partner_form.x_multi_attribute.add(self.multi_attribute.option_ids[0]) - partner = partner_form.save().with_user(self.demo) + partner = partner_form.save().with_user(self.attribute_manager_user) self.assertTrue(partner.x_attr_3) self.assertTrue(partner.x_attr_select) # As options are Many2many, Form() is not able to render the sub form diff --git a/attribute_set/tests/test_custom_attribute.py b/attribute_set/tests/test_custom_attribute.py index e21dbd222..1af8bb0f7 100644 --- a/attribute_set/tests/test_custom_attribute.py +++ b/attribute_set/tests/test_custom_attribute.py @@ -25,8 +25,10 @@ def _create_attribute(self, vals): { "nature": "custom", "model_id": self.model_id, - "field_description": "Attribute %s" % vals["attribute_type"], - "name": "x_%s" % vals["attribute_type"], + "field_description": "Attribute {key}".format( + key=vals["attribute_type"] + ), + "name": "x_{key}".format(key=vals["attribute_type"]), "attribute_group_id": self.group.id, } ) diff --git a/attribute_set/views/attribute_attribute_view.xml b/attribute_set/views/attribute_attribute_view.xml index 16550293c..97c2e8482 100644 --- a/attribute_set/views/attribute_attribute_view.xml +++ b/attribute_set/views/attribute_attribute_view.xml @@ -14,14 +14,19 @@ nolabel="1" required="1" /> - + - + + - + + @@ -63,7 +69,7 @@ @@ -74,25 +80,21 @@ > - + - - + +