diff --git a/attribute_set/models/attribute_attribute.py b/attribute_set/models/attribute_attribute.py
index 0fbdbcae..ca7e7802 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')]`",
+ )
+ % dict(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 cb4850d3..ae1e4bb7 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 7127dcc2..c016d296 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 23109421..046d326e 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 55adcb52..e21dbd22 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 f630178b..1b5b8dab 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: