Skip to content

Commit

Permalink
[16.0][MIG] - attribute_set
Browse files Browse the repository at this point in the history
  • Loading branch information
sbejaoui committed Feb 28, 2023
1 parent c233fec commit d8c776f
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 124 deletions.
2 changes: 1 addition & 1 deletion attribute_set/__manifest__.py
Original file line number Diff line number Diff line change
@@ -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)",
Expand Down
133 changes: 67 additions & 66 deletions attribute_set/models/attribute_attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion attribute_set/models/attribute_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
23 changes: 11 additions & 12 deletions attribute_set/models/attribute_set_owner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
18 changes: 8 additions & 10 deletions attribute_set/tests/test_build_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
68 changes: 34 additions & 34 deletions attribute_set/wizard/attribute_option_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -56,26 +59,23 @@ 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

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)],
Expand All @@ -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
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# generated from manifests external_dependencies
unidecode
1 change: 1 addition & 0 deletions setup/attribute_set/odoo/addons/attribute_set
6 changes: 6 additions & 0 deletions setup/attribute_set/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import setuptools

setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)
1 change: 1 addition & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mock

0 comments on commit d8c776f

Please sign in to comment.