From d8c776f5f49254fa9c8997d7791fd34c067be12a Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Tue, 28 Feb 2023 11:20:26 +0100 Subject: [PATCH] [16.0][MIG] - attribute_set --- attribute_set/__manifest__.py | 2 +- attribute_set/models/attribute_attribute.py | 133 +++++++++--------- attribute_set/models/attribute_group.py | 2 +- attribute_set/models/attribute_set_owner.py | 23 ++- attribute_set/tests/test_build_view.py | 18 ++- .../wizard/attribute_option_wizard.py | 68 ++++----- requirements.txt | 2 + setup/attribute_set/odoo/addons/attribute_set | 1 + setup/attribute_set/setup.py | 6 + test-requirements.txt | 1 + 10 files changed, 132 insertions(+), 124 deletions(-) create mode 100644 requirements.txt create mode 120000 setup/attribute_set/odoo/addons/attribute_set create mode 100644 setup/attribute_set/setup.py create mode 100644 test-requirements.txt diff --git a/attribute_set/__manifest__.py b/attribute_set/__manifest__.py index 84af4c1b..abc1a2e9 100644 --- a/attribute_set/__manifest__.py +++ b/attribute_set/__manifest__.py @@ -1,6 +1,6 @@ { "name": "Attribute Set", - "version": "15.0.1.0.0", + "version": "16.0.1.0.0", "category": "Generic Modules/Others", "license": "AGPL-3", "author": "Akretion,Odoo Community Association (OCA)", diff --git a/attribute_set/models/attribute_attribute.py b/attribute_set/models/attribute_attribute.py index d40a7dac..cc199e5c 100644 --- a/attribute_set/models/attribute_attribute.py +++ b/attribute_set/models/attribute_attribute.py @@ -274,8 +274,8 @@ def button_add_options(self): "target": "new", } - @api.model - def create(self, vals): + @api.model_create_multi + def create(self, vals_list): """Create an attribute.attribute - In case of a new "custom" attribute, a new field object 'ir.model.fields' will @@ -289,76 +289,77 @@ def create(self, vals): from `vals` before creating our new 'attribute.attribute'. """ - if vals.get("nature") == "native": - # Remove all the values that can modify the related native field - # before creating the new 'attribute.attribute' - for key in set(vals).intersection(self.env["ir.model.fields"]._fields): - del vals[key] - return super().create(vals) - - if vals.get("relation_model_id"): - model = self.env["ir.model"].browse(vals["relation_model_id"]) - relation = model.model - else: - relation = "attribute.option" - - attr_type = vals.get("attribute_type") - - if attr_type == "select": - vals["ttype"] = "many2one" - vals["relation"] = relation - - 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 - # 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"]) - table_name = ( - "x_" - + att_model_id.model.replace(".", "_") - + "_" - + vals["name"] - + "_" - + relation.replace(".", "_") - + "_rel" - ) - # avoid too long relation_table names - vals["relation_table"] = table_name[0:60] + for vals in vals_list: + if vals.get("nature") == "native": + # Remove all the values that can modify the related native field + # before creating the new 'attribute.attribute' + for key in set(vals).intersection(self.env["ir.model.fields"]._fields): + del vals[key] + continue + + if vals.get("relation_model_id"): + model = self.env["ir.model"].browse(vals["relation_model_id"]) + relation = model.model + else: + relation = "attribute.option" + + attr_type = vals.get("attribute_type") + + if attr_type == "select": + vals["ttype"] = "many2one" + vals["relation"] = relation + + 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 + # 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"]) + table_name = ( + "x_" + + att_model_id.model.replace(".", "_") + + "_" + + vals["name"] + + "_" + + relation.replace(".", "_") + + "_rel" + ) + # avoid too long relation_table names + vals["relation_table"] = table_name[0:60] - else: - vals["ttype"] = attr_type + else: + vals["ttype"] = attr_type - if vals.get("serialized"): - field_obj = self.env["ir.model.fields"] + if vals.get("serialized"): + field_obj = self.env["ir.model.fields"] - serialized_fields = field_obj.search( - [ - ("ttype", "=", "serialized"), - ("model_id", "=", vals["model_id"]), - ("name", "=", "x_custom_json_attrs"), - ] - ) + serialized_fields = field_obj.search( + [ + ("ttype", "=", "serialized"), + ("model_id", "=", vals["model_id"]), + ("name", "=", "x_custom_json_attrs"), + ] + ) - if serialized_fields: - vals["serialization_field_id"] = serialized_fields[0].id + if serialized_fields: + vals["serialization_field_id"] = serialized_fields[0].id - else: - f_vals = { - "name": "x_custom_json_attrs", - "field_description": "Serialized JSON Attributes", - "ttype": "serialized", - "model_id": vals["model_id"], - } - - vals["serialization_field_id"] = ( - field_obj.with_context(manual=True).create(f_vals).id - ) + else: + f_vals = { + "name": "x_custom_json_attrs", + "field_description": "Serialized JSON Attributes", + "ttype": "serialized", + "model_id": vals["model_id"], + } + + vals["serialization_field_id"] = ( + field_obj.with_context(manual=True).create(f_vals).id + ) - vals["state"] = "manual" - return super().create(vals) + vals["state"] = "manual" + return super().create(vals_list) def _delete_related_option_wizard(self, option_vals): """Delete the attribute's options wizards related to the attribute's options diff --git a/attribute_set/models/attribute_group.py b/attribute_set/models/attribute_group.py index 37d6887e..b079a2a1 100644 --- a/attribute_set/models/attribute_group.py +++ b/attribute_set/models/attribute_group.py @@ -12,7 +12,7 @@ class AttributeGroup(models.Model): _description = "Attribute Group" _order = "sequence" - name = fields.Char(size=128, required=True, translate=True) + name = fields.Char(required=True, translate=True) sequence = fields.Integer( "Sequence in Set", help="The Group order in his attribute's Set" diff --git a/attribute_set/models/attribute_set_owner.py b/attribute_set/models/attribute_set_owner.py index 902aef94..7127dcc2 100644 --- a/attribute_set/models/attribute_set_owner.py +++ b/attribute_set/models/attribute_set_owner.py @@ -10,7 +10,7 @@ class AttributeSetOwnerMixin(models.AbstractModel): - """Override the '_inheriting' model's fields_view_get() and replace + """Override the '_inheriting' model's get_views() and replace the 'attributes_placeholder' by the fields related to the '_inheriting' model's Attributes. Each Attribute's field will have a conditional invisibility depending on its @@ -77,15 +77,14 @@ def _insert_attribute(self, arch): return etree.tostring(eview, pretty_print=True) @api.model - def fields_view_get( - self, view_id=None, view_type="form", toolbar=False, submenu=False - ): - result = super().fields_view_get( - view_id=view_id, - view_type=view_type, - toolbar=toolbar, - submenu=submenu, - ) - if view_type == "form": - result["arch"] = self._insert_attribute(result["arch"]) + def get_views(self, views, options=None): + result = super().get_views(views, options=options) + if ( + "views" in result + and "form" in result["views"] + and "arch" in result["views"]["form"] + ): + result["views"]["form"]["arch"] = self._insert_attribute( + result["views"]["form"]["arch"] + ) return result diff --git a/attribute_set/tests/test_build_view.py b/attribute_set/tests/test_build_view.py index 1cc9a87f..425063d5 100644 --- a/attribute_set/tests/test_build_view.py +++ b/attribute_set/tests/test_build_view.py @@ -250,18 +250,16 @@ def test_render_all_field_type(self): self.assertFalse(attr.get("nolabel", False)) # TEST on NATIVE ATTRIBUTES - def _get_eview_from_fields_view_get(self, include_native_attribute=True): - fields_view = ( + def _get_eview_from_get_views(self, include_native_attribute=True): + result = ( self.env["res.partner"] .with_context(include_native_attribute=include_native_attribute) - .fields_view_get( - view_id=self.view.id, view_type="form", toolbar=False, submenu=False - ) + .get_views([(self.view.id, "form")]) ) - return etree.fromstring(fields_view["arch"]) + return etree.fromstring(result["views"]["form"]["arch"]) def test_include_native_attr(self): - eview = self._get_eview_from_fields_view_get() + eview = self._get_eview_from_get_views() attr = eview.xpath("//field[@name='{}']".format(self.attr_native.name)) # Only one field with this name @@ -274,13 +272,13 @@ def test_include_native_attr(self): ) def test_native_readonly(self): - eview = self._get_eview_from_fields_view_get() + eview = self._get_eview_from_get_views() attr = eview.xpath("//field[@name='{}']".format(self.attr_native_readonly.name)) self.assertTrue(attr[0].get("readonly")) def test_no_include_native_attr(self): - # Run fields_view_get on the test view with no "include_native_attribute" - eview = self._get_eview_from_fields_view_get(include_native_attribute=False) + # Run get_views on the test view with no "include_native_attribute" + eview = self._get_eview_from_get_views(include_native_attribute=False) attr = eview.xpath("//field[@name='{}']".format(self.attr_native.name)) # Only one field with this name diff --git a/attribute_set/wizard/attribute_option_wizard.py b/attribute_set/wizard/attribute_option_wizard.py index facedc8d..e95599ab 100644 --- a/attribute_set/wizard/attribute_option_wizard.py +++ b/attribute_set/wizard/attribute_option_wizard.py @@ -26,27 +26,30 @@ class AttributeOptionWizard(models.TransientModel): def validate(self): return True - @api.model - def create(self, vals): + @api.model_create_multi + def create(self, vals_list): attr_obj = self.env["attribute.attribute"] - attr = attr_obj.browse(vals["attribute_id"]) - - opt_obj = self.env["attribute.option"] - - for op_id in vals.get("option_ids") and vals["option_ids"][0][2] or []: - model = attr.relation_model_id.model - - name = self.env[model].browse(op_id).name_get()[0][1] - opt_obj.create( - { - "attribute_id": vals["attribute_id"], - "name": name, - "value_ref": "{},{}".format(attr.relation_model_id.model, op_id), - } - ) - if vals.get("option_ids"): - del vals["option_ids"] - return super().create(vals) + for vals in vals_list: + attr = attr_obj.browse(vals["attribute_id"]) + + opt_obj = self.env["attribute.option"] + + for op_id in vals.get("option_ids") and vals["option_ids"][0][2] or []: + model = attr.relation_model_id.model + + name = self.env[model].browse(op_id).name_get()[0][1] + opt_obj.create( + { + "attribute_id": vals["attribute_id"], + "name": name, + "value_ref": "{},{}".format( + attr.relation_model_id.model, op_id + ), + } + ) + if vals.get("option_ids"): + del vals["option_ids"] + return super().create(vals_list) # Hack to circumvent the fact that option_ids never actually exists in the DB, # thus crashing when read is called after create @@ -56,18 +59,15 @@ def read(self, fields=None, load="_classic_read"): return super().read(fields, load) @api.model - def fields_view_get( - self, view_id=None, view_type="form", toolbar=False, submenu=False - ): + def get_views(self, views, options=None): context = self.env.context - res = super().fields_view_get( - view_id=view_id, - view_type=view_type, - toolbar=toolbar, - submenu=submenu, - ) - - if view_type == "form" and context and context.get("attribute_id"): + res = super().get_views(views, options=options) + if ( + "views" in res + and "form" in res["views"] + and context + and context.get("attribute_id") + ): attr_obj = self.env["attribute.attribute"] attr = attr_obj.browse(context.get("attribute_id")) model = attr.relation_model_id @@ -75,7 +75,7 @@ def fields_view_get( relation = model.model domain_ids = [op.value_ref.id for op in attr.option_ids if op.value_ref] - res["fields"].update( + res["models"][self._name].update( { "option_ids": { "domain": [("id", "not in", domain_ids)], @@ -87,10 +87,10 @@ def fields_view_get( } ) - eview = etree.fromstring(res["arch"]) + eview = etree.fromstring(res["views"]["form"]["arch"]) options = etree.Element("field", name="option_ids", nolabel="1") placeholder = eview.xpath("//separator[@string='options_placeholder']")[0] placeholder.getparent().replace(placeholder, options) - res["arch"] = etree.tostring(eview, pretty_print=True) + res["views"]["form"]["arch"] = etree.tostring(eview, pretty_print=True) return res diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..6f3f1c6f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# generated from manifests external_dependencies +unidecode diff --git a/setup/attribute_set/odoo/addons/attribute_set b/setup/attribute_set/odoo/addons/attribute_set new file mode 120000 index 00000000..8c9148d6 --- /dev/null +++ b/setup/attribute_set/odoo/addons/attribute_set @@ -0,0 +1 @@ +../../../../attribute_set \ No newline at end of file diff --git a/setup/attribute_set/setup.py b/setup/attribute_set/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/attribute_set/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..932a8957 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +mock