[CLN] product: style pass on split files

closes odoo/odoo#113612

Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
This commit is contained in:
Victor Feyens
2023-02-24 12:14:45 +01:00
parent efbe5d837e
commit aefbb3d17c
6 changed files with 217 additions and 120 deletions
+46 -24
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
@@ -12,14 +11,13 @@ class ProductAttribute(models.Model):
# `_sort_key_attribute_value` in `product.template`
_order = 'sequence, id'
name = fields.Char('Attribute', required=True, translate=True)
value_ids = fields.One2many('product.attribute.value', 'attribute_id', 'Values', copy=True)
sequence = fields.Integer('Sequence', help="Determine the display order", index=True)
attribute_line_ids = fields.One2many('product.template.attribute.line', 'attribute_id', 'Lines')
create_variant = fields.Selection([
('always', 'Instantly'),
('dynamic', 'Dynamically'),
('no_variant', 'Never (option)')],
name = fields.Char(string="Attribute", required=True, translate=True)
create_variant = fields.Selection(
selection=[
('always', 'Instantly'),
('dynamic', 'Dynamically'),
('no_variant', 'Never (option)'),
],
default='always',
string="Variants Creation Mode",
help="""- Instantly: All possible variants are created as soon as the attribute and its values are added to a product.
@@ -27,13 +25,33 @@ class ProductAttribute(models.Model):
- Never: Variants are never created for the attribute.
Note: the variants creation mode cannot be changed once the attribute is used on at least one product.""",
required=True)
display_type = fields.Selection(
selection=[
('radio', 'Radio'),
('pills', 'Pills'),
('select', 'Select'),
('color', 'Color'),
],
default='radio',
required=True,
help="The display type used in the Product Configurator.")
sequence = fields.Integer(string="Sequence", help="Determine the display order", index=True)
value_ids = fields.One2many(
comodel_name='product.attribute.value',
inverse_name='attribute_id',
string="Values", copy=True)
attribute_line_ids = fields.One2many(
comodel_name='product.template.attribute.line',
inverse_name='attribute_id',
string="Lines")
product_tmpl_ids = fields.Many2many(
comodel_name='product.template',
string="Related Products",
compute='_compute_products',
store=True)
number_related_products = fields.Integer(compute='_compute_number_related_products')
product_tmpl_ids = fields.Many2many('product.template', string="Related Products", compute='_compute_products', store=True)
display_type = fields.Selection([
('radio', 'Radio'),
('pills', 'Pills'),
('select', 'Select'),
('color', 'Color')], default='radio', required=True, help="The display type used in the Product Configurator.")
@api.depends('product_tmpl_ids')
def _compute_number_related_products(self):
@@ -59,12 +77,14 @@ class ProductAttribute(models.Model):
if 'create_variant' in vals:
for pa in self:
if vals['create_variant'] != pa.create_variant and pa.number_related_products:
raise UserError(
_("You cannot change the Variants Creation Mode of the attribute %s because it is used on the following products:\n%s") %
(pa.display_name, ", ".join(pa.product_tmpl_ids.mapped('display_name')))
)
raise UserError(_(
"You cannot change the Variants Creation Mode of the attribute %(attribute)s"
" because it is used on the following products:\n%(products)s",
attribute=pa.display_name,
products=", ".join(pa.product_tmpl_ids.mapped('display_name')),
))
invalidate = 'sequence' in vals and any(record.sequence != vals['sequence'] for record in self)
res = super(ProductAttribute, self).write(vals)
res = super().write(vals)
if invalidate:
# prefetched o2m have to be resequenced
# (eg. product.template: attribute_line_ids)
@@ -76,10 +96,12 @@ class ProductAttribute(models.Model):
def _unlink_except_used_on_product(self):
for pa in self:
if pa.number_related_products:
raise UserError(
_("You cannot delete the attribute %s because it is used on the following products:\n%s") %
(pa.display_name, ", ".join(pa.product_tmpl_ids.mapped('display_name')))
)
raise UserError(_(
"You cannot delete the attribute %(attribute)s because it is used on the"
" following products:\n%(products)s",
attribute=pa.display_name,
products=", ".join(pa.product_tmpl_ids.mapped('display_name')),
))
def action_open_related_products(self):
return {
@@ -1,17 +1,20 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class ProductAttributeCustomValue(models.Model):
_name = "product.attribute.custom.value"
_description = 'Product Attribute Custom Value'
_name = 'product.attribute.custom.value'
_description = "Product Attribute Custom Value"
_order = 'custom_product_template_attribute_value_id, id'
name = fields.Char("Name", compute='_compute_name')
custom_product_template_attribute_value_id = fields.Many2one('product.template.attribute.value', string="Attribute Value", required=True, ondelete='restrict')
custom_value = fields.Char("Custom Value")
name = fields.Char(string="Name", compute='_compute_name')
custom_product_template_attribute_value_id = fields.Many2one(
comodel_name='product.template.attribute.value',
string="Attribute Value",
required=True,
ondelete='restrict')
custom_value = fields.Char(string="Custom Value")
@api.depends('custom_product_template_attribute_value_id.name', 'custom_value')
def _compute_name(self):
@@ -7,33 +7,47 @@ from odoo.exceptions import UserError
class ProductAttributeValue(models.Model):
_name = "product.attribute.value"
_name = 'product.attribute.value'
# if you change this _order, keep it in sync with the method
# `_sort_key_variant` in `product.template'
_order = 'attribute_id, sequence, id'
_description = 'Attribute Value'
_description = "Attribute Value"
def _get_default_color(self):
return randint(1, 11)
name = fields.Char(string='Value', required=True, translate=True)
sequence = fields.Integer(string='Sequence', help="Determine the display order", index=True)
attribute_id = fields.Many2one('product.attribute', string="Attribute", ondelete='cascade', required=True, index=True,
help="The attribute cannot be changed once the value is used on at least one product.")
name = fields.Char(string="Value", required=True, translate=True)
sequence = fields.Integer(string="Sequence", help="Determine the display order", index=True)
attribute_id = fields.Many2one(
comodel_name='product.attribute',
string="Attribute",
help="The attribute cannot be changed once the value is used on at least one product.",
ondelete='cascade',
required=True,
index=True)
pav_attribute_line_ids = fields.Many2many('product.template.attribute.line', string="Lines",
relation='product_attribute_value_product_template_attribute_line_rel', copy=False)
is_used_on_products = fields.Boolean('Used on Products', compute='_compute_is_used_on_products')
pav_attribute_line_ids = fields.Many2many(
comodel_name='product.template.attribute.line',
relation='product_attribute_value_product_template_attribute_line_rel',
string="Lines",
copy=False)
is_used_on_products = fields.Boolean(
string="Used on Products", compute='_compute_is_used_on_products')
is_custom = fields.Boolean('Is custom value', help="Allow users to input custom values for this attribute value")
is_custom = fields.Boolean(
string="Is custom value",
help="Allow users to input custom values for this attribute value")
html_color = fields.Char(
string='Color',
help="Here you can set a specific HTML color index (e.g. #ff0000) to display the color if the attribute type is 'Color'.")
display_type = fields.Selection(related='attribute_id.display_type', readonly=True)
color = fields.Integer('Color Index', default=_get_default_color)
string="Color",
help="Here you can set a specific HTML color index (e.g. #ff0000)"
" to display the color if the attribute type is 'Color'.")
display_type = fields.Selection(related='attribute_id.display_type')
color = fields.Integer(string="Color Index", default=_get_default_color)
_sql_constraints = [
('value_company_uniq', 'unique (name, attribute_id)', "You cannot create two values with the same name for the same attribute.")
('value_company_uniq',
'unique (name, attribute_id)',
"You cannot create two values with the same name for the same attribute.")
]
@api.depends('pav_attribute_line_ids')
@@ -50,21 +64,23 @@ class ProductAttributeValue(models.Model):
the attribute is already on each line so there is no need to repeat it
on every value.
"""
if not self._context.get('show_attribute', True):
return super(ProductAttributeValue, self).name_get()
if not self.env.context.get('show_attribute', True):
return super().name_get()
return [(value.id, "%s: %s" % (value.attribute_id.name, value.name)) for value in self]
def write(self, values):
if 'attribute_id' in values:
for pav in self:
if pav.attribute_id.id != values['attribute_id'] and pav.is_used_on_products:
raise UserError(
_("You cannot change the attribute of the value %s because it is used on the following products:%s") %
(pav.display_name, ", ".join(pav.pav_attribute_line_ids.product_tmpl_id.mapped('display_name')))
)
raise UserError(_(
"You cannot change the attribute of the value %(value)s because it is used"
" on the following products: %(products)s",
value=pav.display_name,
products=", ".join(pav.pav_attribute_line_ids.product_tmpl_id.mapped('display_name')),
))
invalidate = 'sequence' in values and any(record.sequence != values['sequence'] for record in self)
res = super(ProductAttributeValue, self).write(values)
res = super().write(values)
if invalidate:
# prefetched o2m have to be resequenced
# (eg. product.template.attribute.line: value_ids)
@@ -76,10 +92,11 @@ class ProductAttributeValue(models.Model):
def _unlink_except_used_on_product(self):
for pav in self:
if pav.is_used_on_products:
raise UserError(
_("You cannot delete the value %s because it is used on the following products:\n%s") %
(pav.display_name, ", ".join(pav.pav_attribute_line_ids.product_tmpl_id.mapped('display_name')))
)
raise UserError(_(
"You cannot delete the value %(value)s because it is used on the following products:\n%(products)s",
value=pav.display_name,
products=", ".join(pav.pav_attribute_line_ids.product_tmpl_id.mapped('display_name')),
))
def _without_no_variant_attributes(self):
return self.filtered(lambda pav: pav.attribute_id.create_variant != 'no_variant')
@@ -4,14 +4,23 @@ from odoo import fields, models
class ProductTemplateAttributeExclusion(models.Model):
_name = "product.template.attribute.exclusion"
_description = 'Product Template Attribute Exclusion'
_name = 'product.template.attribute.exclusion'
_description = "Product Template Attribute Exclusion"
_order = 'product_tmpl_id, id'
product_template_attribute_value_id = fields.Many2one(
'product.template.attribute.value', string="Attribute Value", ondelete='cascade', index=True)
comodel_name='product.template.attribute.value',
string="Attribute Value",
ondelete='cascade',
index=True)
product_tmpl_id = fields.Many2one(
'product.template', string='Product Template', ondelete='cascade', required=True, index=True)
comodel_name='product.template',
string="Product Template",
ondelete='cascade',
required=True,
index=True)
value_ids = fields.Many2many(
'product.template.attribute.value', relation="product_attr_exclusion_value_ids_rel",
string='Attribute Values', domain="[('product_tmpl_id', '=', product_tmpl_id), ('ptav_active', '=', True)]")
comodel_name='product.template.attribute.value',
relation='product_attr_exclusion_value_ids_rel',
string="Attribute Values",
domain="[('product_tmpl_id', '=', product_tmpl_id), ('ptav_active', '=', True)]")
@@ -2,25 +2,43 @@
from odoo import api, fields, models, tools, _
from odoo.exceptions import UserError, ValidationError
from odoo.fields import Command
class ProductTemplateAttributeLine(models.Model):
"""Attributes available on product.template with their selected values in a m2m.
Used as a configuration model to generate the appropriate product.template.attribute.value"""
_name = "product.template.attribute.line"
_name = 'product.template.attribute.line'
_rec_name = 'attribute_id'
_rec_names_search = ['attribute_id', 'value_ids']
_description = 'Product Template Attribute Line'
_description = "Product Template Attribute Line"
_order = 'attribute_id, id'
active = fields.Boolean(default=True)
product_tmpl_id = fields.Many2one('product.template', string="Product Template", ondelete='cascade', required=True, index=True)
attribute_id = fields.Many2one('product.attribute', string="Attribute", ondelete='restrict', required=True, index=True)
value_ids = fields.Many2many('product.attribute.value', string="Values", domain="[('attribute_id', '=', attribute_id)]",
relation='product_attribute_value_product_template_attribute_line_rel', ondelete='restrict')
value_count = fields.Integer(compute='_compute_value_count', store=True, readonly=True)
product_template_value_ids = fields.One2many('product.template.attribute.value', 'attribute_line_id', string="Product Attribute Values")
product_tmpl_id = fields.Many2one(
comodel_name='product.template',
string="Product Template",
ondelete='cascade',
required=True,
index=True)
attribute_id = fields.Many2one(
comodel_name='product.attribute',
string="Attribute",
ondelete='restrict',
required=True,
index=True)
value_ids = fields.Many2many(
comodel_name='product.attribute.value',
relation='product_attribute_value_product_template_attribute_line_rel',
string="Values",
domain="[('attribute_id', '=', attribute_id)]",
ondelete='restrict')
value_count = fields.Integer(compute='_compute_value_count', store=True)
product_template_value_ids = fields.One2many(
comodel_name='product.template.attribute.value',
inverse_name='attribute_line_id',
string="Product Attribute Values")
@api.depends('value_ids')
def _compute_value_count(self):
@@ -35,16 +53,20 @@ class ProductTemplateAttributeLine(models.Model):
def _check_valid_values(self):
for ptal in self:
if ptal.active and not ptal.value_ids:
raise ValidationError(
_("The attribute %s must have at least one value for the product %s.") %
(ptal.attribute_id.display_name, ptal.product_tmpl_id.display_name)
)
raise ValidationError(_(
"The attribute %(attribute)s must have at least one value for the product %(product)s.",
attribute=ptal.attribute_id.display_name,
product=ptal.product_tmpl_id.display_name,
))
for pav in ptal.value_ids:
if pav.attribute_id != ptal.attribute_id:
raise ValidationError(
_("On the product %s you cannot associate the value %s with the attribute %s because they do not match.") %
(ptal.product_tmpl_id.display_name, pav.display_name, ptal.attribute_id.display_name)
)
raise ValidationError(_(
"On the product %(product)s you cannot associate the value %(value)s"
" with the attribute %(attribute)s because they do not match.",
product=ptal.product_tmpl_id.display_name,
value=pav.display_name,
attribute=ptal.attribute_id.display_name,
))
return True
@api.model_create_multi
@@ -79,7 +101,7 @@ class ProductTemplateAttributeLine(models.Model):
activated_lines += archived_ptal
else:
create_values.append(value)
res = activated_lines + super(ProductTemplateAttributeLine, self).create(create_values)
res = activated_lines + super().create(create_values)
res._update_product_template_attribute_values()
return res
@@ -93,23 +115,29 @@ class ProductTemplateAttributeLine(models.Model):
if 'product_tmpl_id' in values:
for ptal in self:
if ptal.product_tmpl_id.id != values['product_tmpl_id']:
raise UserError(
_("You cannot move the attribute %s from the product %s to the product %s.") %
(ptal.attribute_id.display_name, ptal.product_tmpl_id.display_name, values['product_tmpl_id'])
)
raise UserError(_(
"You cannot move the attribute %(attribute)s from the product"
" %(product_src)s to the product %(product_dest)s.",
attribute=ptal.attribute_id.display_name,
product_src=ptal.product_tmpl_id.display_name,
product_dest=values['product_tmpl_id'],
))
if 'attribute_id' in values:
for ptal in self:
if ptal.attribute_id.id != values['attribute_id']:
raise UserError(
_("On the product %s you cannot transform the attribute %s into the attribute %s.") %
(ptal.product_tmpl_id.display_name, ptal.attribute_id.display_name, values['attribute_id'])
)
raise UserError(_(
"On the product %(product)s you cannot transform the attribute"
" %(attribute_src)s into the attribute %(attribute_dest)s.",
product=ptal.product_tmpl_id.display_name,
attribute_src=ptal.attribute_id.display_name,
attribute_dest=values['attribute_id'],
))
# Remove all values while archiving to make sure the line is clean if it
# is ever activated again.
if not values.get('active', True):
values['value_ids'] = [(5, 0, 0)]
res = super(ProductTemplateAttributeLine, self).write(values)
values['value_ids'] = [Command.clear()]
res = super().write(values)
if 'active' in values:
self.env.flush_all()
self.env['product.template'].invalidate_model(['attribute_line_ids'])
@@ -4,13 +4,14 @@ from random import randint
from odoo import api, fields, models, tools, _
from odoo.exceptions import UserError, ValidationError
from odoo.fields import Command
class ProductTemplateAttributeValue(models.Model):
"""Materialized relationship between attribute values
and product template generated by the product.template.attribute.line"""
_name = "product.template.attribute.value"
_name = 'product.template.attribute.value'
_description = "Product Template Attribute Value"
_order = 'attribute_line_id, product_attribute_value_id, id'
@@ -19,51 +20,64 @@ class ProductTemplateAttributeValue(models.Model):
# Not just `active` because we always want to show the values except in
# specific case, as opposed to `active_test`.
ptav_active = fields.Boolean("Active", default=True)
name = fields.Char('Value', related="product_attribute_value_id.name")
ptav_active = fields.Boolean(string="Active", default=True)
name = fields.Char(string="Value", related="product_attribute_value_id.name")
# defining fields: the product template attribute line and the product attribute value
product_attribute_value_id = fields.Many2one(
'product.attribute.value', string='Attribute Value',
comodel_name='product.attribute.value',
string="Attribute Value",
required=True, ondelete='cascade', index=True)
attribute_line_id = fields.Many2one(
comodel_name='product.template.attribute.line',
required=True, ondelete='cascade', index=True)
attribute_line_id = fields.Many2one('product.template.attribute.line', required=True, ondelete='cascade', index=True)
# configuration fields: the price_extra and the exclusion rules
price_extra = fields.Float(
string="Value Price Extra",
default=0.0,
digits='Product Price',
help="Extra price for the variant with this attribute value on sale price. eg. 200 price extra, 1000 + 200 = 1200.")
help="Extra price for the variant with this attribute value on sale price."
" eg. 200 price extra, 1000 + 200 = 1200.")
currency_id = fields.Many2one(related='attribute_line_id.product_tmpl_id.currency_id')
exclude_for = fields.One2many(
'product.template.attribute.exclusion',
'product_template_attribute_value_id',
comodel_name='product.template.attribute.exclusion',
inverse_name='product_template_attribute_value_id',
string="Exclude for",
help="Make this attribute value not compatible with "
"other values of the product or some attribute values of optional and accessory products.")
# related fields: product template and product attribute
product_tmpl_id = fields.Many2one('product.template', string="Product Template", related='attribute_line_id.product_tmpl_id', store=True, index=True)
attribute_id = fields.Many2one('product.attribute', string="Attribute", related='attribute_line_id.attribute_id', store=True, index=True)
ptav_product_variant_ids = fields.Many2many('product.product', relation='product_variant_combination', string="Related Variants", readonly=True)
product_tmpl_id = fields.Many2one(
related='attribute_line_id.product_tmpl_id', store=True, index=True)
attribute_id = fields.Many2one(
related='attribute_line_id.attribute_id', store=True, index=True)
ptav_product_variant_ids = fields.Many2many(
comodel_name='product.product', relation='product_variant_combination',
string="Related Variants", readonly=True)
html_color = fields.Char('HTML Color Index', related="product_attribute_value_id.html_color")
is_custom = fields.Boolean('Is custom value', related="product_attribute_value_id.is_custom")
display_type = fields.Selection(related='product_attribute_value_id.display_type', readonly=True)
color = fields.Integer('Color', default=_get_default_color)
html_color = fields.Char(string="HTML Color Index", related='product_attribute_value_id.html_color')
is_custom = fields.Boolean(related='product_attribute_value_id.is_custom')
display_type = fields.Selection(related='product_attribute_value_id.display_type')
color = fields.Integer(string="Color", default=_get_default_color)
_sql_constraints = [
('attribute_value_unique', 'unique(attribute_line_id, product_attribute_value_id)', "Each value should be defined only once per attribute per product."),
('attribute_value_unique',
'unique(attribute_line_id, product_attribute_value_id)',
"Each value should be defined only once per attribute per product."),
]
@api.constrains('attribute_line_id', 'product_attribute_value_id')
def _check_valid_values(self):
for ptav in self:
if ptav.product_attribute_value_id not in ptav.attribute_line_id.value_ids:
raise ValidationError(
_("The value %s is not defined for the attribute %s on the product %s.") %
(ptav.product_attribute_value_id.display_name, ptav.attribute_id.display_name, ptav.product_tmpl_id.display_name)
)
raise ValidationError(_(
"The value %(value)s is not defined for the attribute %(attribute)s"
" on the product %(product)s.",
value=ptav.product_attribute_value_id.display_name,
attribute=ptav.attribute_id.display_name,
product=ptav.product_tmpl_id.display_name,
))
@api.model_create_multi
def create(self, vals_list):
@@ -71,7 +85,7 @@ class ProductTemplateAttributeValue(models.Model):
# Force write on this relation from `product.product` to properly
# trigger `_compute_combination_indices`.
raise UserError(_("You cannot update related variants from the values. Please update related values from the variants."))
return super(ProductTemplateAttributeValue, self).create(vals_list)
return super().create(vals_list)
def write(self, values):
if 'ptav_product_variant_ids' in values:
@@ -83,16 +97,18 @@ class ProductTemplateAttributeValue(models.Model):
if pav_in_values or product_in_values:
for ptav in self:
if pav_in_values and ptav.product_attribute_value_id.id != values['product_attribute_value_id']:
raise UserError(
_("You cannot change the value of the value %s set on product %s.") %
(ptav.display_name, ptav.product_tmpl_id.display_name)
)
raise UserError(_(
"You cannot change the value of the value %(value)s set on product %(product)s.",
value=ptav.display_name,
product=ptav.product_tmpl_id.display_name,
))
if product_in_values and ptav.product_tmpl_id.id != values['product_tmpl_id']:
raise UserError(
_("You cannot change the product of the value %s set on product %s.") %
(ptav.display_name, ptav.product_tmpl_id.display_name)
)
res = super(ProductTemplateAttributeValue, self).write(values)
raise UserError(_(
"You cannot change the product of the value %(value)s set on product %(product)s.",
value=ptav.display_name,
product=ptav.product_tmpl_id.display_name,
))
res = super().write(values)
if 'exclude_for' in values:
self.product_tmpl_id._create_variant_ids()
return res
@@ -112,7 +128,9 @@ class ProductTemplateAttributeValue(models.Model):
# value (counting also the values that are archived).
single_values = self.filtered(lambda ptav: len(ptav.attribute_line_id.product_template_value_ids) == 1)
for ptav in single_values:
ptav.ptav_product_variant_ids.write({'product_template_attribute_value_ids': [(3, ptav.id, 0)]})
ptav.ptav_product_variant_ids.write({
'product_template_attribute_value_ids': [Command.unlink(ptav.id)],
})
# Try to remove the variants before deleting to potentially remove some
# blocking references.
self.ptav_product_variant_ids._unlink_or_archive()