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/odoo-pim project on GitHub.
+
This module is part of the OCA/odoo-pim project on GitHub.
diff --git a/attribute_set/tests/test_build_view.py b/attribute_set/tests/test_build_view.py
index 6b2141408..1cc9a87f7 100644
--- a/attribute_set/tests/test_build_view.py
+++ b/attribute_set/tests/test_build_view.py
@@ -8,10 +8,10 @@
from lxml import etree
from odoo_test_helper import FakeModelLoader
-from odoo.tests import SavepointCase
+from odoo.tests import TransactionCase
-class BuildViewCase(SavepointCase):
+class BuildViewCase(TransactionCase):
@classmethod
def _create_set(cls, name):
return cls.env["attribute.set"].create({"name": name, "model_id": cls.model_id})
@@ -129,7 +129,7 @@ def setUpClass(cls):
@classmethod
def tearDownClass(cls):
cls.loader.restore_registry()
- super(BuildViewCase, cls).tearDownClass()
+ return super(BuildViewCase, cls).tearDownClass()
# TEST write on attributes
def test_write_attribute_values_text(self):
@@ -253,7 +253,7 @@ def test_render_all_field_type(self):
def _get_eview_from_fields_view_get(self, include_native_attribute=True):
fields_view = (
self.env["res.partner"]
- .with_context({"include_native_attribute": include_native_attribute})
+ .with_context(include_native_attribute=include_native_attribute)
.fields_view_get(
view_id=self.view.id, view_type="form", toolbar=False, submenu=False
)
diff --git a/attribute_set/views/attribute_attribute_view.xml b/attribute_set/views/attribute_attribute_view.xml
index 5c2768c75..6f2512b25 100644
--- a/attribute_set/views/attribute_attribute_view.xml
+++ b/attribute_set/views/attribute_attribute_view.xml
@@ -139,7 +139,7 @@
attribute.attribute.treeattribute.attribute
-
+ attribute.group
-
+
diff --git a/attribute_set/views/attribute_option_view.xml b/attribute_set/views/attribute_option_view.xml
index 630967e07..5b56d88c9 100644
--- a/attribute_set/views/attribute_option_view.xml
+++ b/attribute_set/views/attribute_option_view.xml
@@ -45,7 +45,7 @@
attribute.option
-
+
-
+
@@ -38,7 +38,7 @@
attribute.set.treeattribute.set
-
+
From b766b607e7c92b30514a9bc775a8fd5a518e9796 Mon Sep 17 00:00:00 2001
From: Kevin Khao
Date: Fri, 2 Dec 2022 11:26:38 +0200
Subject: [PATCH 26/47] [FIX] attribute_set: fix create options w/ rel model
When you define an attribute as a select field and add a related model,
you have the option to "Load attribute options".
On this wizard, an "option_ids" dummy field is created via
fields_view_get.
This change deletes option_ids from create (once the options are created) and read
---
attribute_set/wizard/attribute_option_wizard.py | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/attribute_set/wizard/attribute_option_wizard.py b/attribute_set/wizard/attribute_option_wizard.py
index 36b8a9108..facedc8d9 100644
--- a/attribute_set/wizard/attribute_option_wizard.py
+++ b/attribute_set/wizard/attribute_option_wizard.py
@@ -44,10 +44,16 @@ def create(self, vals):
"value_ref": "{},{}".format(attr.relation_model_id.model, op_id),
}
)
-
- res = super().create(vals)
-
- return res
+ if vals.get("option_ids"):
+ del vals["option_ids"]
+ return super().create(vals)
+
+ # Hack to circumvent the fact that option_ids never actually exists in the DB,
+ # thus crashing when read is called after create
+ def read(self, fields=None, load="_classic_read"):
+ if "option_ids" in fields:
+ fields.remove("option_ids")
+ return super().read(fields, load)
@api.model
def fields_view_get(
From b4ac47bd292f84090720497e1b7e44b08e886493 Mon Sep 17 00:00:00 2001
From: sbejaoui
Date: Tue, 28 Feb 2023 11:20:26 +0100
Subject: [PATCH 27/47] [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 ++++-----
6 files changed, 122 insertions(+), 124 deletions(-)
diff --git a/attribute_set/__manifest__.py b/attribute_set/__manifest__.py
index 84af4c1bb..abc1a2e9f 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 d40a7dacf..cc199e5c5 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 37d6887ef..b079a2a10 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 902aef94c..7127dcc26 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 1cc9a87f7..425063d54 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 facedc8d9..e95599aba 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
From 3237b0ca3e0466fa713e77fbae0fd376914ca52b Mon Sep 17 00:00:00 2001
From: sbejaoui
Date: Tue, 28 Feb 2023 19:05:31 +0100
Subject: [PATCH 28/47] [IMP] attribute_set: set ctx for native fields
---
attribute_set/models/attribute_attribute.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/attribute_set/models/attribute_attribute.py b/attribute_set/models/attribute_attribute.py
index cc199e5c5..0fbdbcae7 100644
--- a/attribute_set/models/attribute_attribute.py
+++ b/attribute_set/models/attribute_attribute.py
@@ -147,6 +147,7 @@ def _build_attribute_field(self, attribute_egroup):
domain = ast.literal_eval(self.domain)
except ValueError:
domain = None
+
if domain:
kwargs["domain"] = self.domain
else:
@@ -163,6 +164,8 @@ def _build_attribute_field(self, attribute_egroup):
# Attribute Options search and creation
kwargs["domain"] = "[('attribute_id', '=', %s)]" % (self.id)
kwargs["context"] = "{'default_attribute_id': %s}" % (self.id)
+ elif self.nature != "custom":
+ kwargs["context"] = self._get_native_field_context()
if self.ttype == "text":
# Display field label above his value
@@ -176,6 +179,9 @@ def _build_attribute_field(self, attribute_egroup):
efield = etree.SubElement(attribute_egroup, "field", **kwargs)
setup_modifiers(efield)
+ def _get_native_field_context(self):
+ return str(self.env[self.field_id.model]._fields[self.field_id.name].context)
+
def _build_attribute_eview(self):
"""Return an 'attribute_eview' including all the Attributes (in the current
recorset 'self') distributed in different 'attribute_egroup' for each
From b885dc69332867c49cdc7dc77c05542346e9b053 Mon Sep 17 00:00:00 2001
From: sbejaoui
Date: Thu, 2 Mar 2023 20:37:35 +0100
Subject: [PATCH 29/47] [FIX] attribute_set: fix menu position
menu items are visible only for two levels
---
attribute_set/views/menu_view.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/attribute_set/views/menu_view.xml b/attribute_set/views/menu_view.xml
index 221a41f81..a30834203 100644
--- a/attribute_set/views/menu_view.xml
+++ b/attribute_set/views/menu_view.xml
@@ -3,7 +3,7 @@
From eb6425188cf02ae06a5ae5a47174a31c205abec0 Mon Sep 17 00:00:00 2001
From: sbejaoui
Date: Fri, 3 Mar 2023 09:23:32 +0100
Subject: [PATCH 30/47] [IMP] attribute_set: add group menu item
---
attribute_set/views/attribute_attribute_view.xml | 2 +-
attribute_set/views/attribute_group_view.xml | 14 ++++++++++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/attribute_set/views/attribute_attribute_view.xml b/attribute_set/views/attribute_attribute_view.xml
index 6f2512b25..c1bb7042a 100644
--- a/attribute_set/views/attribute_attribute_view.xml
+++ b/attribute_set/views/attribute_attribute_view.xml
@@ -174,7 +174,7 @@
Attributesattribute.attribute
- tree
+ tree,form
{"search_default_attribute_group_id": active_id, "active_attribute_sort":
diff --git a/attribute_set/views/attribute_group_view.xml b/attribute_set/views/attribute_group_view.xml
index bd8f60bb7..993d381f9 100644
--- a/attribute_set/views/attribute_group_view.xml
+++ b/attribute_set/views/attribute_group_view.xml
@@ -35,4 +35,18 @@
+
+ Attribute Groups
+ attribute.group
+ tree,form
+
+ {}
+
+
+
From 9f0894655e3dd7ce223207c63cd59e7ab518aca2 Mon Sep 17 00:00:00 2001
From: sbejaoui
Date: Tue, 15 Aug 2023 14:36:06 +0200
Subject: [PATCH 31/47] [IMP] attribute_set: misc improvement
---
attribute_set/models/attribute_attribute.py | 44 ++++++++++----------
attribute_set/models/attribute_option.py | 13 +++---
attribute_set/models/attribute_set_owner.py | 24 ++++-------
attribute_set/readme/DESCRIPTION.rst | 2 +-
attribute_set/tests/test_custom_attribute.py | 15 +++----
attribute_set/utils/orm.py | 5 +--
6 files changed, 50 insertions(+), 53 deletions(-)
diff --git a/attribute_set/models/attribute_attribute.py b/attribute_set/models/attribute_attribute.py
index 0fbdbcae7..55ba7446a 100644
--- a/attribute_set/models/attribute_attribute.py
+++ b/attribute_set/models/attribute_attribute.py
@@ -125,9 +125,10 @@ def _get_attrs(self):
@api.model
def _build_attribute_field(self, attribute_egroup):
- """Add an etree 'field' subelement (related to the current attribute 'self')
- to attribute_egroup, with a conditional invisibility based on its
- attribute sets."""
+ """Add field into given attribute group.
+
+ Conditional invisibility based on its attribute sets.
+ """
self.ensure_one()
kwargs = {"name": "%s" % self.name}
kwargs["attrs"] = str(self._get_attrs())
@@ -183,7 +184,9 @@ def _get_native_field_context(self):
return str(self.env[self.field_id.model]._fields[self.field_id.name].context)
def _build_attribute_eview(self):
- """Return an 'attribute_eview' including all the Attributes (in the current
+ """Generate group element for all attributes in the current recordset.
+
+ Return an 'attribute_eview' including all the Attributes (in the current
recorset 'self') distributed in different 'attribute_egroup' for each
Attribute's group.
"""
@@ -240,22 +243,23 @@ def onchange_attribute_type(self):
self.widget = "many2many_tags"
@api.onchange("relation_model_id")
- def relation_model_id_change(self):
- "Remove selected options as they would be inconsistent"
+ def _onchange_relation_model_id(self):
+ """Remove selected options as they would be inconsistent"""
self.option_ids = [(5, 0)]
@api.onchange("domain")
- def domain_change(self):
+ def _onchange_domain(self):
if self.domain not in ["", False]:
try:
ast.literal_eval(self.domain)
except ValueError:
raise ValidationError(
_(
- """ "{}" is an unvalid Domain name.\n
- Specify a Python expression defining a list of triplets.\
- For example : "[('color', '=', 'red')]" """
- ).format(self.domain)
+ "`%(domain)s` is an invalid Domain name.\n"
+ "Specify a Python expression defining a list of triplets.\n"
+ "For example : `[('color', '=', 'red')]`",
+ domain=self.domain,
+ )
) from ValueError
# Remove selected options as the domain will predominate on actual options
if self.domain != "[]":
@@ -271,7 +275,7 @@ def button_add_options(self):
# Then open the Options Wizard which will display an 'opt_ids' m2m field related
# to the 'relation_model_id' model
return {
- "context": "{'attribute_id': %s}" % (self.id),
+ "context": dict(self.env.context, attribute_id=self.id),
"name": _("Options Wizard"),
"view_type": "form",
"view_mode": "form",
@@ -368,19 +372,17 @@ def create(self, vals_list):
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
- deleted after the write"""
+ """Delete related attribute's options wizards."""
self.ensure_one()
for option_change in option_vals:
if option_change[0] == 2:
self.env["attribute.option.wizard"].search(
[("attribute_id", "=", self.id)]
).unlink()
+ break
def _delete_old_fields_options(self, options):
- """Delete attribute's field values in the objects using our attribute
- as a field, if these values are not in the new Domain or Options list
- """
+ """Delete outdated attribute's field values on existing records."""
self.ensure_one()
custom_field = self.name
for obj in self.env[self.model].search([]):
@@ -395,7 +397,7 @@ def _delete_old_fields_options(self, options):
def write(self, vals):
# Prevent from changing Attribute's type
if "attribute_type" in list(vals.keys()):
- if self.search(
+ if self.search_count(
[
("attribute_type", "!=", vals["attribute_type"]),
("id", "in", self.ids),
@@ -413,7 +415,7 @@ def write(self, vals):
# as the values of the existing many2many Attribute fields won't be
# deleted if changing relation_model_id
if "relation_model_id" in list(vals.keys()):
- if self.search(
+ if self.search_count(
[
("relation_model_id", "!=", vals["relation_model_id"]),
("id", "in", self.ids),
@@ -428,7 +430,7 @@ def write(self, vals):
)
# Prevent from changing 'Serialized'
if "serialized" in list(vals.keys()):
- if self.search(
+ if self.search_count(
[("serialized", "!=", vals["serialized"]), ("id", "in", self.ids)]
):
raise ValidationError(
@@ -443,7 +445,7 @@ def write(self, vals):
for att in self:
options = att.option_ids
- if self.relation_model_id:
+ if att.relation_model_id:
options = self.env[att.relation_model_id.model]
if "option_ids" in list(vals.keys()):
# Delete related attribute.option.wizard if an attribute.option
diff --git a/attribute_set/models/attribute_option.py b/attribute_set/models/attribute_option.py
index cb4850d39..ae1e4bb7d 100644
--- a/attribute_set/models/attribute_option.py
+++ b/attribute_set/models/attribute_option.py
@@ -13,13 +13,13 @@ class AttributeOption(models.Model):
_order = "sequence"
@api.model
- def _get_model_list(self):
+ def _selection_model_list(self):
models = self.env["ir.model"].search([])
return [(m.model, m.name) for m in models]
name = fields.Char(translate=True, required=True)
- value_ref = fields.Reference(_get_model_list, "Reference")
+ value_ref = fields.Reference(selection="_selection_model_list", string="Reference")
attribute_id = fields.Many2one(
"attribute.attribute",
@@ -38,9 +38,12 @@ def _get_model_list(self):
sequence = fields.Integer()
@api.onchange("name")
- def name_change(self):
- """Prevent the user from adding manually an option to m2o or m2m Attributes
- linked to another model (through 'relation_model_id')"""
+ def _onchange_name(self):
+ """Prevent improper linking of attributes.
+
+ The user could add manually an option to m2o or m2m Attributes
+ linked to another model (through 'relation_model_id').
+ """
if self.attribute_id.relation_model_id:
warning = {
"title": _("Error!"),
diff --git a/attribute_set/models/attribute_set_owner.py b/attribute_set/models/attribute_set_owner.py
index 7127dcc26..c016d296c 100644
--- a/attribute_set/models/attribute_set_owner.py
+++ b/attribute_set/models/attribute_set_owner.py
@@ -10,12 +10,7 @@
class AttributeSetOwnerMixin(models.AbstractModel):
- """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
- Attribute Sets.
- """
+ """Mixin for consumers of attribute sets."""
_name = "attribute.set.owner.mixin"
_description = "Attribute set owner mixin"
@@ -52,8 +47,7 @@ def remove_native_fields(self, eview):
efield[0].getparent().remove(efield[0])
def _insert_attribute(self, arch):
- """Insert the model's Attributes related fields into the arch's view form
- at the placeholder's place."""
+ """Replace attributes' placeholders with real fields in form view arch."""
eview = etree.fromstring(arch)
form_name = eview.get("string")
placeholder = eview.xpath("//separator[@name='attributes_placeholder']")
@@ -61,11 +55,12 @@ def _insert_attribute(self, arch):
if len(placeholder) != 1:
raise ValidationError(
_(
- """It is impossible to add Attributes on "{}" xml
+ """It is impossible to add Attributes on "%(name)s" xml
view as there is
not one "" in it.
- """
- ).format(form_name)
+ """,
+ name=form_name,
+ )
)
if self._context.get("include_native_attribute"):
@@ -79,11 +74,8 @@ def _insert_attribute(self, arch):
@api.model
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"]
- ):
+ form_arch = result.get("views", {}).get("form", {}).get("arch")
+ if form_arch:
result["views"]["form"]["arch"] = self._insert_attribute(
result["views"]["form"]["arch"]
)
diff --git a/attribute_set/readme/DESCRIPTION.rst b/attribute_set/readme/DESCRIPTION.rst
index 23109421e..046d326e1 100644
--- a/attribute_set/readme/DESCRIPTION.rst
+++ b/attribute_set/readme/DESCRIPTION.rst
@@ -7,5 +7,5 @@ A *"custom"* Attribute can be of any type : Char, Text, Boolean, Date, Binary...
In case of m2o or m2m, these attributes can be related to **custom options** created for the Attribute, or to **existing Odoo objects** from other models.
-Last but not least an Attribute can be **serialized** using the Odoo SA module `base_sparse_field `_ .
+Last but not least an Attribute can be **serialized** using the Odoo SA module `base_sparse_field `_ .
It means that all the serialized attributes will be stored in a single "JSON serialization field" and will not create new columns in the database (and better, it will not create new SQL tables in case of Many2many Attributes), **increasing significantly the requests speed** when dealing with thousands of Attributes.
diff --git a/attribute_set/tests/test_custom_attribute.py b/attribute_set/tests/test_custom_attribute.py
index 55adcb52f..e21dbd222 100644
--- a/attribute_set/tests/test_custom_attribute.py
+++ b/attribute_set/tests/test_custom_attribute.py
@@ -4,20 +4,21 @@
# Copyright 2015 Savoir-faire Linux
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-import mock
+from unittest import mock
from odoo.tests import common
class TestAttributeSet(common.TransactionCase):
- def setUp(self):
- super(TestAttributeSet, self).setUp()
- self.model_id = self.env.ref("base.model_res_partner").id
- self.group = self.env["attribute.group"].create(
- {"name": "My Group", "model_id": self.model_id}
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.model_id = cls.env.ref("base.model_res_partner").id
+ cls.group = cls.env["attribute.group"].create(
+ {"name": "My Group", "model_id": cls.model_id}
)
# Do not commit
- self.env.cr.commit = mock.Mock()
+ cls.env.cr.commit = mock.Mock()
def _create_attribute(self, vals):
vals.update(
diff --git a/attribute_set/utils/orm.py b/attribute_set/utils/orm.py
index f630178bd..1b5b8dabc 100644
--- a/attribute_set/utils/orm.py
+++ b/attribute_set/utils/orm.py
@@ -69,8 +69,7 @@ def transfer_modifiers_to_node(modifiers, node):
def setup_modifiers(node, field=None, context=None, in_tree_view=False):
- """Processes node attributes and field descriptors to generate
- the ``modifiers`` node attribute and set it on the provided node.
+ """Generate ``modifiers`` from node attributes and fields descriptors.
Alters its first argument in-place.
:param node: ``field`` node from an OpenERP view
:type node: lxml.etree._Element
@@ -84,7 +83,7 @@ def setup_modifiers(node, field=None, context=None, in_tree_view=False):
displayed) with ``invisible`` and column
invisibility (the whole column is
hidden) with ``column_invisible``.
- :returns: nothing
+ :returns: None
"""
modifiers = {}
if field is not None:
From 518f4cdc0f94b4af7c0234eddacabfc07276f400 Mon Sep 17 00:00:00 2001
From: oca-ci
Date: Fri, 18 Aug 2023 10:00:03 +0000
Subject: [PATCH 32/47] [UPD] Update attribute_set.pot
---
attribute_set/i18n/attribute_set.pot | 36 +++++++++++++++++++---------
1 file changed, 25 insertions(+), 11 deletions(-)
diff --git a/attribute_set/i18n/attribute_set.pot b/attribute_set/i18n/attribute_set.pot
index 06046b85f..5447a392f 100644
--- a/attribute_set/i18n/attribute_set.pot
+++ b/attribute_set/i18n/attribute_set.pot
@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 15.0\n"
+"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -13,15 +13,6 @@ msgstr ""
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
-#. module: attribute_set
-#: code:addons/attribute_set/models/attribute_attribute.py:0
-#, python-format
-msgid ""
-" \"{}\" is an unvalid Domain name.\n"
-"\n"
-" Specify a Python expression defining a list of triplets. For example : \"[('color', '=', 'red')]\" "
-msgstr ""
-
#. module: attribute_set
#: model:res.groups,name:attribute_set.group_advanced_attribute_set
msgid "Advanced Attribute Set settings"
@@ -40,6 +31,12 @@ msgstr ""
msgid "Attribute Group"
msgstr ""
+#. module: attribute_set
+#: model:ir.actions.act_window,name:attribute_set.attribute_group_action
+#: model:ir.ui.menu,name:attribute_set.menu_attribute_group_action
+msgid "Attribute Groups"
+msgstr ""
+
#. module: attribute_set
#: model:ir.model.fields,field_description:attribute_set.field_attribute_attribute__nature
msgid "Attribute Nature"
@@ -106,6 +103,7 @@ msgid "Boolean"
msgstr ""
#. module: attribute_set
+#. odoo-python
#: code:addons/attribute_set/models/attribute_attribute.py:0
#, python-format
msgid ""
@@ -115,6 +113,7 @@ msgid ""
msgstr ""
#. module: attribute_set
+#. odoo-python
#: code:addons/attribute_set/models/attribute_attribute.py:0
#, python-format
msgid "Can't change the type of an attribute. Please create a new one."
@@ -247,6 +246,7 @@ msgid "Domain"
msgstr ""
#. module: attribute_set
+#. odoo-python
#: code:addons/attribute_set/models/attribute_option.py:0
#, python-format
msgid "Error!"
@@ -369,16 +369,18 @@ msgid "Ir Model Fields"
msgstr ""
#. module: attribute_set
+#. odoo-python
#: code:addons/attribute_set/models/attribute_set_owner.py:0
#, python-format
msgid ""
-"It is impossible to add Attributes on \"{}\" xml\n"
+"It is impossible to add Attributes on \"%(name)s\" xml\n"
" view as there is\n"
" not one \"\" in it.\n"
" "
msgstr ""
#. module: attribute_set
+#. odoo-python
#: code:addons/attribute_set/models/attribute_attribute.py:0
#, python-format
msgid ""
@@ -463,6 +465,7 @@ msgid "On delete property for many2one fields"
msgstr ""
#. module: attribute_set
+#. odoo-python
#: code:addons/attribute_set/models/attribute_attribute.py:0
#: model_terms:ir.ui.view,arch_db:attribute_set.attribute_option_wizard_form_view
#, python-format
@@ -678,6 +681,7 @@ msgid "Type"
msgstr ""
#. module: attribute_set
+#. odoo-python
#: code:addons/attribute_set/models/attribute_option.py:0
#, python-format
msgid ""
@@ -720,6 +724,16 @@ msgstr ""
msgid "Widget"
msgstr ""
+#. module: attribute_set
+#. odoo-python
+#: code:addons/attribute_set/models/attribute_attribute.py:0
+#, python-format
+msgid ""
+"`%(domain)s` is an invalid Domain name.\n"
+"Specify a Python expression defining a list of triplets.\n"
+"For example : `[('color', '=', 'red')]`"
+msgstr ""
+
#. module: attribute_set
#: model_terms:ir.ui.view,arch_db:attribute_set.attribute_option_wizard_form_view
msgid "options_placeholder"
From 9ebe3683299274101ff737839e586cb870a75e87 Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Fri, 18 Aug 2023 10:01:49 +0000
Subject: [PATCH 33/47] [UPD] README.rst
---
attribute_set/README.rst | 12 ++++++------
attribute_set/static/description/index.html | 8 ++++----
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/attribute_set/README.rst b/attribute_set/README.rst
index 9683020a8..d714b2d84 100644
--- a/attribute_set/README.rst
+++ b/attribute_set/README.rst
@@ -14,13 +14,13 @@ Attribute Set
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fodoo--pim-lightgray.png?logo=github
- :target: https://github.com/OCA/odoo-pim/tree/15.0/attribute_set
+ :target: https://github.com/OCA/odoo-pim/tree/16.0/attribute_set
:alt: OCA/odoo-pim
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/odoo-pim-15-0/odoo-pim-15-0-attribute_set
+ :target: https://translation.odoo-community.org/projects/odoo-pim-16-0/odoo-pim-16-0-attribute_set
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/295/15.0
+ :target: https://runbot.odoo-community.org/runbot/295/16.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -34,7 +34,7 @@ A *"custom"* Attribute can be of any type : Char, Text, Boolean, Date, Binary...
In case of m2o or m2m, these attributes can be related to **custom options** created for the Attribute, or to **existing Odoo objects** from other models.
-Last but not least an Attribute can be **serialized** using the Odoo SA module `base_sparse_field `_ .
+Last but not least an Attribute can be **serialized** using the Odoo SA module `base_sparse_field `_ .
It means that all the serialized attributes will be stored in a single "JSON serialization field" and will not create new columns in the database (and better, it will not create new SQL tables in case of Many2many Attributes), **increasing significantly the requests speed** when dealing with thousands of Attributes.
**Table of contents**
@@ -69,7 +69,7 @@ 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 smashing it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -104,6 +104,6 @@ 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/odoo-pim `_ project on GitHub.
+This module is part of the `OCA/odoo-pim `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/attribute_set/static/description/index.html b/attribute_set/static/description/index.html
index 96440d914..f0c45fe2e 100644
--- a/attribute_set/static/description/index.html
+++ b/attribute_set/static/description/index.html
@@ -367,13 +367,13 @@
Attribute Set
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-
+
This module allows the user to create Attributes to any model.
This is a basic module in the way that it does not provide views to display these new Attributes.
Each Attribute created will be related to an existing field (in case of a “native” Attribute) or to a newly created field (in case of a “custom” Attribute).
A “custom” Attribute can be of any type : Char, Text, Boolean, Date, Binary… but also Many2one or Many2many.
In case of m2o or m2m, these attributes can be related to custom options created for the Attribute, or to existing Odoo objects from other models.
-
Last but not least an Attribute can be serialized using the Odoo SA module base_sparse_field .
+
Last but not least an Attribute can be serialized using the Odoo SA module base_sparse_field .
It means that all the serialized attributes will be stored in a single “JSON serialization field” and will not create new columns in the database (and better, it will not create new SQL tables in case of Many2many Attributes), increasing significantly the requests speed when dealing with thousands of Attributes.
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 smashing it by providing a detailed and welcomed
-feedback.
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/odoo-pim project on GitHub.
+
This module is part of the OCA/odoo-pim project on GitHub.
From 1d03015e12faf9cf2af831aa0613a4f81e873087 Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Sun, 3 Sep 2023 14:45:47 +0000
Subject: [PATCH 34/47] [UPD] README.rst
---
attribute_set/README.rst | 15 ++++----
attribute_set/static/description/index.html | 38 +++++++++++----------
2 files changed, 29 insertions(+), 24 deletions(-)
diff --git a/attribute_set/README.rst b/attribute_set/README.rst
index d714b2d84..3bd31d0e3 100644
--- a/attribute_set/README.rst
+++ b/attribute_set/README.rst
@@ -2,10 +2,13 @@
Attribute Set
=============
-.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:67202b0685d728fec56872c7b67b81352d3441d5e9e3bfebdb21c21d40b05f1b
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
@@ -19,11 +22,11 @@ Attribute Set
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/odoo-pim-16-0/odoo-pim-16-0-attribute_set
:alt: Translate me on Weblate
-.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/295/16.0
- :alt: Try me on Runbot
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/odoo-pim&target_branch=16.0
+ :alt: Try me on Runboat
-|badge1| |badge2| |badge3| |badge4| |badge5|
+|badge1| |badge2| |badge3| |badge4| |badge5|
This module allows the user to create Attributes to any model.
This is a basic module in the way that **it does not provide views to display these new Attributes.**
@@ -68,7 +71,7 @@ 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 smashing it by providing a detailed and welcomed
+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.
diff --git a/attribute_set/static/description/index.html b/attribute_set/static/description/index.html
index f0c45fe2e..b3ee2ab3d 100644
--- a/attribute_set/static/description/index.html
+++ b/attribute_set/static/description/index.html
@@ -1,20 +1,20 @@
-
+
-
+
Attribute Set