diff --git a/pandoc-3.1.11.1-1-amd64.deb b/pandoc-3.1.11.1-1-amd64.deb new file mode 100644 index 0000000000..1af3187f70 Binary files /dev/null and b/pandoc-3.1.11.1-1-amd64.deb differ diff --git a/product_configurator/README.rst b/product_configurator/README.rst index 8f87ad37e9..77b307ec53 100644 --- a/product_configurator/README.rst +++ b/product_configurator/README.rst @@ -17,19 +17,19 @@ Product Configurator :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fproduct--configurator-lightgray.png?logo=github - :target: https://github.com/OCA/product-configurator/tree/16.0/product_configurator + :target: https://github.com/OCA/product-configurator/tree/17.0/product_configurator :alt: OCA/product-configurator .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/product-configurator-16-0/product-configurator-16-0-product_configurator + :target: https://translation.odoo-community.org/projects/product-configurator-17-0/product-configurator-17-0-product_configurator :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/product-configurator&target_branch=16.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/product-configurator&target_branch=17.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| -This module has all the mechanics to support product configuration. It serves as a base -dependency for configuration interfaces. +This module has all the mechanics to support product configuration. It +serves as a base dependency for configuration interfaces. **Table of contents** @@ -42,7 +42,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 to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -50,12 +50,12 @@ Credits ======= Authors -~~~~~~~ +------- * Pledra Maintainers -~~~~~~~~~~~ +----------- This module is maintained by the OCA. @@ -75,6 +75,6 @@ Current `maintainer `__: |maintainer-PCatinean| -This module is part of the `OCA/product-configurator `_ project on GitHub. +This module is part of the `OCA/product-configurator `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_configurator/__manifest__.py b/product_configurator/__manifest__.py index 4015979783..9a0f4313a6 100644 --- a/product_configurator/__manifest__.py +++ b/product_configurator/__manifest__.py @@ -1,6 +1,6 @@ { "name": "Product Configurator", - "version": "16.0.1.0.0", + "version": "17.0.1.0.0", "category": "Generic Modules/Base", "summary": "Base for product configuration interface modules", "author": "Pledra, Odoo Community Association (OCA)", @@ -28,10 +28,10 @@ "assets": { "web.assets_backend": [ "/product_configurator/static/src/scss/form_widget.scss", - "/product_configurator/static/src/js/form_widgets.js", + "/product_configurator/static/src/js/form_widgets.esm.js", "/product_configurator/static/src/js/boolean_button_widget.esm.js", "/product_configurator/static/src/js/boolean_button_widget.xml", - "/product_configurator/static/src/js/relational_fields.js", + "/product_configurator/static/src/js/relational_fields.esm.js", ] }, "demo": [ @@ -44,7 +44,7 @@ ], "images": ["static/description/cover.png"], "post_init_hook": "post_init_hook", - "qweb": ["static/xml/create_button.xml"], + # "qweb": ["static/xml/create_button.xml"], "development_status": "Beta", "maintainers": ["PCatinean"], "installable": True, diff --git a/product_configurator/init_hook.py b/product_configurator/init_hook.py index 5e18812a13..59e41bdf18 100644 --- a/product_configurator/init_hook.py +++ b/product_configurator/init_hook.py @@ -3,8 +3,8 @@ logger = logging.getLogger(__name__) -def post_init_hook(cr, registry): +def post_init_hook(env): """Transfer existing weight values to weight_dummy after installation since now the weight field is computed """ - cr.execute("UPDATE product_product SET weight_dummy = weight") + env.cr.execute("UPDATE product_product SET weight_dummy = weight") diff --git a/product_configurator/models/ir_ui_view.py b/product_configurator/models/ir_ui_view.py index 79412b8f09..7ad1ae1c95 100644 --- a/product_configurator/models/ir_ui_view.py +++ b/product_configurator/models/ir_ui_view.py @@ -8,5 +8,4 @@ def _validate_tag_button(self, node, name_manager, node_info): special = node.get("special") if special and special == "no_save": return - else: - return super()._validate_tag_button(node, name_manager, node_info) + return super()._validate_tag_button(node, name_manager, node_info) diff --git a/product_configurator/models/product.py b/product_configurator/models/product.py index b8d0354e8a..c491213e51 100644 --- a/product_configurator/models/product.py +++ b/product_configurator/models/product.py @@ -19,7 +19,7 @@ def _compute_product_variant_count(self): 1 as many views and methods trigger only when a template has at least one variant attached. Since we create them from the template we should have access to them always""" - result = super(ProductTemplate, self)._compute_product_variant_count() + result = super()._compute_product_variant_count() for product_tmpl in self: config_ok = product_tmpl.config_ok variant_count = product_tmpl.product_variant_count @@ -148,7 +148,7 @@ def _search_weight(self, operator, value): def _check_default_values(self): default_val_ids = ( - self.attribute_line_ids.filtered(lambda l: l.default_val) + self.attribute_line_ids.filtered(lambda line: line.default_val) .mapped("default_val") .ids ) @@ -159,7 +159,7 @@ def _check_default_values(self): value_ids=default_val_ids, product_tmpl_id=self.id, final=False ) except ValidationError as exc: - raise exc + raise ValidationError(exc.args[0]) from exc except Exception as exc: raise ValidationError( _("Default values provided generate an invalid configuration") @@ -177,7 +177,7 @@ def _check_default_value_domains(self): "generate an invalid configuration.\ \n%s" ) - % (exc.args[0]) + % (exc.name) ) from exc def toggle_config(self): @@ -205,7 +205,7 @@ def unlink(self): ) if variant_unlink: self -= config_template - res = super(ProductTemplate, self).unlink() + res = super().unlink() return res def copy(self, default=None): @@ -214,7 +214,7 @@ def copy(self, default=None): if not default: default = {} self = self.with_context(check_constraint=False) - res = super(ProductTemplate, self).copy(default=default) + res = super().copy(default=default) # Attribute lines attribute_line_dict = {} @@ -325,7 +325,7 @@ def create(self, vals_list): config_ok = vals.get("config_ok", False) if config_ok: self.check_config_user_access() - return super(ProductTemplate, self).create(vals_list) + return super().create(vals_list) def write(self, vals): """Patch for check access rights of user(configurable products)""" @@ -334,7 +334,7 @@ def write(self, vals): if change_config_ok or configurable_templates: self[:1].check_config_user_access() - return super(ProductTemplate, self).write(vals) + return super().write(vals) @api.constrains("config_line_ids") def _check_config_line_domain(self): @@ -347,7 +347,7 @@ def _check_config_line_domain(self): domain_value_ids = domain_id.domain_line_ids.mapped("value_ids") invalid_value_ids = domain_value_ids - tmpl_value_ids invalid_attribute_ids = domain_attr_ids - tmpl_attribute_ids - if not invalid_attribute_ids and not invalid_value_ids: + if not invalid_value_ids and not invalid_value_ids: continue if not error_message: error_message = _( @@ -355,27 +355,36 @@ def _check_config_line_domain(self): "are not present in template attributes/values. " "Please make sure you are adding right restriction" ) - error_message += _("\nRestriction: %s", domain_id.name) + error_message += _("\nRestriction: %s") % (domain_id.name) error_message += ( invalid_attribute_ids - and _( - "\nAttribute/s: %s", ", ".join(invalid_attribute_ids.mapped("name")) - ) + and _("\nAttribute/s: %s") + % (", ".join(invalid_attribute_ids.mapped("name"))) or "" ) error_message += ( invalid_value_ids - and _("\nValue/s: %s\n", ", ".join(invalid_value_ids.mapped("name"))) + and _("\nValue/s: %s\n") % (", ".join(invalid_value_ids.mapped("name"))) or "" ) if error_message: raise ValidationError(error_message) + @api.model + def _name_search(self, name, domain=None, operator="ilike", limit=None, order=None): + domain = domain or [] + domain += ["|", ("name", operator, name), ("default_code", operator, name)] + return self._search(domain, limit=limit, order=order) + class ProductProduct(models.Model): _inherit = "product.product" _rec_name = "config_name" + def _get_conversions_dict(self): + conversions = {"float": float, "integer": int} + return conversions + @api.constrains("product_template_attribute_value_ids") def _check_duplicate_product(self): """Check for prducts with same attribute values/custom values""" @@ -537,7 +546,7 @@ def unlink(self): self.env["product.product"].check_config_user_access(mode="delete") ctx = dict(self.env.context, unlink_from_variant=True) self.env.context = ctx - return super(ProductProduct, self).unlink() + return super().unlink() @api.model_create_multi def create(self, vals_list): @@ -546,7 +555,7 @@ def create(self, vals_list): config_ok = vals.get("config_ok", False) if config_ok: self.check_config_user_access(mode="create") - return super(ProductProduct, self).create(vals_list) + return super().create(vals_list) def write(self, vals): """Patch for check access rights of user(configurable products)""" @@ -555,7 +564,7 @@ def write(self, vals): if change_config_ok or configurable_products: self[:1].check_config_user_access(mode="write") - return super(ProductProduct, self).write(vals) + return super().write(vals) def _compute_product_price_extra(self): standard_products = self.filtered(lambda product: not product.config_ok) diff --git a/product_configurator/models/product_attribute.py b/product_configurator/models/product_attribute.py index de1f9336de..76dd4630f0 100644 --- a/product_configurator/models/product_attribute.py +++ b/product_configurator/models/product_attribute.py @@ -93,10 +93,8 @@ def check_searchable_field(self): nosearch_fields = attribute._get_nosearch_fields() if attribute.custom_type in nosearch_fields and attribute.search_ok: raise ValidationError( - _( - "Selected custom field type '%s' is not searchable", - attribute.custom_type, - ) + _("Selected custom field type '%s' is not searchable") + % attribute.custom_type ) def validate_custom_val(self, val): @@ -112,27 +110,27 @@ def validate_custom_val(self, val): raise ValidationError( _( "Selected custom value '%(name)s' must be " - "between %(min_val)s and %(max_val)s", - **{ + "between %(min_val)s and %(max_val)s" + ) + % ( + { "name": self.name, "min_val": self.min_val, "max_val": self.max_val, - }, + } ) ) elif minv and val < minv: raise ValidationError( - _( - "Selected custom value '%(name)s' must be at least %(min_val)s", - **{"name": self.name, "min_val": self.min_val}, - ) + _("Selected custom value '%(name)s' must be at least %(min_val)s") + % ({"name": self.name, "min_val": self.min_val}) ) elif maxv and val > maxv: raise ValidationError( _( - "Selected custom value '%(name)s' must be lower than %(max_value)s", - **{"name": self.name, "max_val": self.max_val + 1}, + "Selected custom value '%(name)s' must be lower than %(max_value)s" ) + % ({"name": self.name, "max_val": self.max_val + 1}) ) @api.constrains("min_val", "max_val") @@ -183,16 +181,18 @@ def onchange_values(self): def _check_default_values(self): """default value should not be outside of the values selected in attribute line""" - for line in self.filtered(lambda l: l.default_val): + for line in self.filtered(lambda line: line.default_val): if line.default_val not in line.value_ids: raise ValidationError( _( "Default values for each attribute line must exist in " - "the attribute values (%(attr_name)s: %(default_val)s)", - **{ + "the attribute values (%(attr_name)s: %(default_val)s)" + ) + % ( + { "attr_name": line.attribute_id.name, "default_val": line.default_val.name, - }, + } ) ) @@ -209,11 +209,13 @@ def _check_valid_values(self): raise ValidationError( _( "The attribute %(attr)s must have at least one value for " - "the product %(product)s.", - **{ + "the product %(product)s." + ) + % ( + { "attr": ptal.attribute_id.display_name, "product": ptal.product_tmpl_id.display_name, - }, + } ) ) for pav in ptal.value_ids: @@ -222,12 +224,14 @@ def _check_valid_values(self): _( "On the product %(product)s you cannot associate the " "value %(value)s with the attribute %(attr)s because they " - "do not match.", - **{ + "do not match." + ) + % ( + { "product": ptal.product_tmpl_id.display_name, "value": pav.display_name, "attr": ptal.attribute_id.display_name, - }, + } ) ) return True @@ -242,7 +246,7 @@ def copy(self, default=None): if not default: default = {} default.update({"name": self.name + " (copy)"}) - product = super(ProductAttributeValue, self).copy(default) + product = super().copy(default) return product active = fields.Boolean( @@ -287,31 +291,24 @@ def get_attribute_value_extra_prices( extra_prices[attr_val_id.id] += line.price_extra return extra_prices - def name_get(self): - res = super(ProductAttributeValue, self).name_get() - if not self._context.get("show_price_extra"): - return res - product_template_id = self.env.context.get("active_id", False) - - price_precision = self.env["decimal.precision"].precision_get("Product Price") - extra_prices = self.get_attribute_value_extra_prices( - product_tmpl_id=product_template_id, pt_attr_value_ids=self - ) - - res_prices = [] - for val in res: - price_extra = extra_prices.get(val[0]) - if price_extra: - val = ( - val[0], - "%s ( +%s )" - % ( - val[1], - ("{0:,.%sf}" % (price_precision)).format(price_extra), - ), + def _compute_display_name(self): + super()._compute_display_name() + if self._context.get("show_price_extra"): + product_template_id = self.env.context.get("active_id", False) + price_precision = self.env["decimal.precision"].precision_get( + "Product Price" + ) + for rec in self: + extra_prices = rec.get_attribute_value_extra_prices( + product_tmpl_id=product_template_id, pt_attr_value_ids=rec ) - res_prices.append(val) - return res_prices + price_extra = extra_prices.get(rec.id) + if price_extra: + name = ("{} ( +{} )").format( + rec.name, + ("{0:,.%sf}" % (price_precision)).format(price_extra), + ) + rec.display_name = name or rec.display_name @api.model def name_search(self, name="", args=None, operator="ilike", limit=100): @@ -321,6 +318,30 @@ def name_search(self, name="", args=None, operator="ilike", limit=100): TODO: This only works when activating the selection not when typing """ + if self.env.context.get("wizard_id"): + wiz_id = self.env["product.configurator"].browse( + self.env.context.get("wizard_id") + ) + if ( + wiz_id.domain_attr_ids + and self.env.context.get("field_name") == wiz_id.dyn_field_value + ): + if self.env.context.get("is_m2m"): + if len(args) > 2: + vals1 = args[-1] + vals2 = args[1] + if vals2 and vals1: + vals = list(set(vals2[2]) - set(vals1[2])) + args = [("id", "in", vals)] + else: + args = [("id", "in", wiz_id.domain_attr_ids.ids)] + + elif wiz_id.domain_attr_2_ids and ( + self.env.context.get("field_name") == wiz_id.dyn_field_2_value + or not wiz_id.dyn_field_2_value + ): + args = [("id", "in", wiz_id.domain_attr_2_ids.ids)] + product_tmpl_id = self.env.context.get("_cfg_product_tmpl_id") if product_tmpl_id: # TODO: Avoiding browse here could be a good performance enhancer @@ -344,7 +365,7 @@ def name_search(self, name="", args=None, operator="ilike", limit=100): ) new_args.append(("id", "in", val_ids)) mono_tmpl_lines = product_tmpl.attribute_line_ids.filtered( - lambda l: not l.multi + lambda line: not line.multi ) for line in mono_tmpl_lines: line_val_ids = set(line.mapped("value_ids").ids) @@ -353,18 +374,9 @@ def name_search(self, name="", args=None, operator="ilike", limit=100): if attr_restrict_ids: new_args.append(("attribute_id", "not in", attr_restrict_ids)) args = new_args - res = super(ProductAttributeValue, self).name_search( - name=name, args=args, operator=operator, limit=limit - ) + res = super().name_search(name=name, args=args, operator=operator, limit=limit) return res - # TODO: Prevent unlinking custom options by overriding unlink - - # _sql_constraints = [ - # ('unique_custom', 'unique(id,allow_custom_value)', - # 'Only one custom value per dimension type is allowed') - # ] - class ProductAttributePrice(models.Model): _inherit = "product.template.attribute.value" @@ -388,7 +400,7 @@ class ProductAttributeValueLine(models.Model): ) value_id = fields.Many2one( comodel_name="product.attribute.value", - required="True", + required=True, string="Attribute Value", ) attribute_id = fields.Many2one( diff --git a/product_configurator/models/product_config.py b/product_configurator/models/product_config.py index 9ff3ec7f68..121a34b030 100644 --- a/product_configurator/models/product_config.py +++ b/product_configurator/models/product_config.py @@ -3,7 +3,7 @@ from odoo import _, api, fields, models from odoo.exceptions import UserError, ValidationError -from odoo.tools.misc import flatten, formatLang +from odoo.tools.misc import formatLang _logger = logging.getLogger(__name__) @@ -270,9 +270,9 @@ def _check_value_ids(self): raise ValidationError( _( "Values entered for line '%s' generate " - "a incompatible configuration", - cfg_img.name, + "a incompatible configuration" ) + % cfg_img.name ) from exc @@ -409,7 +409,7 @@ def get_cfg_weight(self, value_ids=None, custom_vals=None): self = self.with_context(active_id=product_tmpl.id) - value_ids = flatten(value_ids) + value_ids = self.flatten_val_ids(value_ids) weight_extra = 0.0 product_attr_val_obj = self.env["product.template.attribute.value"] @@ -548,15 +548,15 @@ def update_session_configuration_value(self, vals, product_tmpl_id=None): if not vals[field_name]: field_val = None else: - field_val = vals[field_name][0][2] + field_val = [ + i[1] for i in vals[field_name] if vals[field_name][0] + ] or vals[field_name][0][1] elif not attr_line.multi and isinstance(vals[field_name], int): field_val = vals[field_name] else: raise UserError( - _( - "An error occurred while parsing value for attribute %s", - attr_line.attribute_id.name, - ) + _("An error occurred while parsing value for attribute %s") + % attr_line.attribute_id.name ) attr_val_dict.update({attr_id: field_val}) # Ensure there is no custom value stored if we have switched @@ -669,7 +669,7 @@ def update_config(self, attr_val_dict=None, custom_val_dict=None): def write(self, vals): """Validate configuration when writing new values to session""" # TODO: Issue warning when writing to value_ids or custom_val_ids - res = super(ProductConfigSession, self).write(vals) + res = super().write(vals) if not self.product_tmpl_id: return res value_ids = self.value_ids.ids @@ -679,7 +679,7 @@ def write(self, vals): try: self.validate_configuration(final=False) except ValidationError as exc: - raise exc + raise ValidationError(_("%s") % exc.name) from exc except Exception as exc: raise ValidationError(_("Invalid Configuration")) from exc return res @@ -697,7 +697,9 @@ def create(self, vals_list): ) if product_tmpl: default_val_ids = ( - product_tmpl.attribute_line_ids.filtered(lambda l: l.default_val) + product_tmpl.attribute_line_ids.filtered( + lambda line: line.default_val + ) .mapped("default_val") .ids ) @@ -713,7 +715,7 @@ def create(self, vals_list): # TODO: Remove if cond when PR with # raise error on github is merged except ValidationError as exc: - raise exc + raise ValidationError(_("%s") % exc.name) from exc except Exception as exc: raise ValidationError( _( @@ -722,7 +724,7 @@ def create(self, vals_list): ) ) from exc vals.update({"value_ids": [(6, 0, default_val_ids)]}) - return super(ProductConfigSession, self).create(vals_list) + return super().create(vals_list) def create_get_variant(self, value_ids=None, custom_vals=None): """Creates a new product variant with the attributes passed @@ -746,7 +748,7 @@ def create_get_variant(self, value_ids=None, custom_vals=None): try: self.validate_configuration() except ValidationError as exc: - raise exc + raise ValidationError(_("%s") % exc.name) from exc except Exception as exc: raise ValidationError(_("Invalid Configuration")) from exc @@ -779,9 +781,7 @@ def _get_option_values(self, pricelist, value_ids=None): pricelist=pricelist.id ) values = ( - value_obj.sudo() - .browse(value_ids) - .filtered(lambda x: x.product_id._get_contextual_price()) + value_obj.sudo().browse(value_ids).filtered(lambda x: x.product_id.price) ) return values @@ -796,12 +796,12 @@ def get_components_prices(self, prices, pricelist, value_ids=None): ( val.attribute_id.name, val.product_id.name, - val.product_id._get_contextual_price(), + val.product_id.price, ) ) product = val.product_id.with_context(pricelist=pricelist.id) product_prices = product.taxes_id.sudo().compute_all( - price_unit=product._get_contextual_price(), + price_unit=product.price, currency=pricelist.currency_id, quantity=1, product=self, @@ -1081,7 +1081,7 @@ def get_adjacent_steps(self, value_ids=None, active_step_line_id=None): return {} active_cfg_step_line = config_step_lines.filtered( - lambda l: l.id == active_step_line_id + lambda line: line.id == active_step_line_id ) open_step_lines = self.get_open_step_lines(value_ids) @@ -1160,7 +1160,6 @@ def get_variant_search_domain(self, product_tmpl_id, value_ids=None): return domain def validate_domains_against_sels(self, domains, value_ids=None, custom_vals=None): - if custom_vals is None: custom_vals = self._get_custom_vals_dict() @@ -1237,7 +1236,7 @@ def values_available( avail_val_ids = [] for attr_val_id in check_val_ids: config_lines = product_tmpl.config_line_ids.filtered( - lambda l: attr_val_id in l.value_ids.ids + lambda line: attr_val_id in line.value_ids.ids ) domains = config_lines.mapped("domain_id").compute_domain() avail = self.validate_domains_against_sels(domains, value_ids, custom_vals) @@ -1283,7 +1282,7 @@ def check_attributes_configuration( ): # TODO: Verify custom value type to be correct raise ValidationError( - _("Required attribute '%s' is empty", attr.name) + _("Required attribute '%s' is empty") % (attr.name) ) @api.model @@ -1344,14 +1343,10 @@ def validate_configuration( else: group_by_attr[val.attribute_id] = val - message = "The following values are not available:%s" - message_vals = "" + message = _("The following values are not available:") for attr, val in group_by_attr.items(): - message_vals += "\n%s: %s" % ( - attr.name, - ", ".join(val.mapped("name")), - ) - raise ValidationError(_(message, message_vals)) + message += "\n {}: {}".format(attr.name, ", ".join(val.mapped("name"))) + raise ValidationError(message) # Check if custom values are allowed custom_attr_ids = ( @@ -1366,7 +1361,7 @@ def validate_configuration( custom_attrs_with_error = self.env["product.attribute"].browse( custom_attrs_with_error ) - error_message = ( + error_message = _( "The following custom values are not permitted " "according to the product template - %s.\n\nIt is possible " "that a change has been made to allowed custom values " @@ -1376,15 +1371,12 @@ def validate_configuration( ) message_vals = "" for attr_id in custom_attrs_with_error: - message_vals += "\n%s: %s" % ( - attr_id.name, - custom_vals.get(attr_id.id), - ) - raise ValidationError(_(error_message, message_vals)) + message_vals += f"\n {attr_id.name}: {custom_vals.get(attr_id.id)}" + raise ValidationError(error_message % (message_vals)) # Check if there are multiple values passed for non-multi attributes mono_attr_lines = product_tmpl.attribute_line_ids.filtered( - lambda l: not l.multi + lambda line: not line.multi ) attrs_with_error = {} for line in mono_attr_lines: @@ -1394,7 +1386,7 @@ def validate_configuration( ) attrs_with_error[line.attribute_id] = wrong_vals if attrs_with_error: - error_message = ( + error_message = _( "The following multi values are not permitted " "according to the product template - %s.\n\nIt is possible " "that a change has been made to allowed multi values " @@ -1404,11 +1396,10 @@ def validate_configuration( ) message_vals = "" for attr_id, vals in attrs_with_error.items(): - message_vals += "\n%s: %s" % ( - attr_id.name, - ", ".join(vals.mapped("name")), + message_vals += "\n {}: {}".format( + attr_id.name, ", ".join(vals.mapped("name")) ) - raise ValidationError(_(error_message, message_vals)) + raise ValidationError(error_message % (message_vals)) return True @api.model @@ -1484,7 +1475,15 @@ def flatten_val_ids(self, value_ids): :param value_ids: list of value ids or mix of ids and list of ids (e.g: [1, 2, 3, [4, 5, 6]]) :returns: flattened list of ids ([1, 2, 3, 4, 5, 6])""" - flat_val_ids = set(flatten(value_ids)) + flat_val_ids = set() + if value_ids and value_ids[0]: + for val in value_ids: + if not val: + continue + if isinstance(val, list): + flat_val_ids.add(val[1]) + elif isinstance(val, int): + flat_val_ids.add(val) return list(flat_val_ids) def formatPrices(self, prices=None, dp="Product Price"): @@ -1529,7 +1528,7 @@ def get_child_specification(self, model, parent): specs = model_obj._onchange_spec() new_specs = {} for key, val in specs.items(): - new_specs["%s.%s" % (parent, key)] = val + new_specs[f"{parent}.{key}"] = val return new_specs @api.model @@ -1585,7 +1584,7 @@ class ProductConfigSessionCustomValue(models.Model): def _compute_val_name(self): for attr_val_custom in self: uom = attr_val_custom.attribute_id.uom_id.name - attr_val_custom.name = "%s%s" % ( + attr_val_custom.name = "{}{}".format( attr_val_custom.value, (" %s" % uom) or "", ) diff --git a/product_configurator/pyproject.toml b/product_configurator/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/product_configurator/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/product_configurator/readme/DESCRIPTION.md b/product_configurator/readme/DESCRIPTION.md new file mode 100644 index 0000000000..d3dc3ea6eb --- /dev/null +++ b/product_configurator/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module has all the mechanics to support product configuration. It +serves as a base dependency for configuration interfaces. diff --git a/product_configurator/readme/DESCRIPTION.rst b/product_configurator/readme/DESCRIPTION.rst deleted file mode 100644 index a0b4c65152..0000000000 --- a/product_configurator/readme/DESCRIPTION.rst +++ /dev/null @@ -1,2 +0,0 @@ -This module has all the mechanics to support product configuration. It serves as a base -dependency for configuration interfaces. diff --git a/product_configurator/static/src/js/boolean_button_widget.esm.js b/product_configurator/static/src/js/boolean_button_widget.esm.js index 0973764baf..ef78926bc7 100644 --- a/product_configurator/static/src/js/boolean_button_widget.esm.js +++ b/product_configurator/static/src/js/boolean_button_widget.esm.js @@ -1,10 +1,13 @@ /** @odoo-module **/ -const {onMounted, onRendered, useRef, useState} = owl; -import {BooleanField} from "@web/views/fields/boolean/boolean_field"; +/*eslint-disable*/ import {registry} from "@web/core/registry"; +import {onMounted, onRendered, useRef, useState} from "@odoo/owl"; +import {BooleanField, booleanField} from "@web/views/fields/boolean/boolean_field"; import {standardFieldProps} from "@web/views/fields/standard_field_props"; -export class BooleanButtonField extends BooleanField { +export class BooleanButton extends BooleanField { + // Static template = "product_configurator.BooleanButtonField"; + setup() { super.setup(); this.state1 = useState({value: 0}); @@ -17,10 +20,6 @@ export class BooleanButtonField extends BooleanField { }); } - onChange() { - this.state1.value++; - } - updateConfigurableButton() { this.text = this.props.value ? this.props.activeString @@ -41,18 +40,21 @@ export class BooleanButtonField extends BooleanField { } } -BooleanButtonField.props = { - ...standardFieldProps, - activeString: {type: String, optional: true}, - inactiveString: {type: String, optional: true}, +export const BooleanButtonField = { + ...booleanField, + component: BooleanButton, + extractProps: ({options}) => { + return { + activeString: options.active, + inactiveString: options.inactive, + }; + }, }; -BooleanButtonField.extractProps = ({attrs}) => { - return { - activeString: attrs.options.active, - inactiveString: attrs.options.inactive, - }; +BooleanButton.props = { + ...standardFieldProps, + activeString: {type: String}, + inactiveString: {type: String, optional: true}, }; -BooleanButtonField.template = "product_configurator.BooleanButtonField"; registry.category("fields").add("boolean_button", BooleanButtonField); diff --git a/product_configurator/static/src/js/form_widgets.esm.js b/product_configurator/static/src/js/form_widgets.esm.js new file mode 100644 index 0000000000..69ebf52c74 --- /dev/null +++ b/product_configurator/static/src/js/form_widgets.esm.js @@ -0,0 +1,81 @@ +/* @odoo-module */ +/*eslint-disable*/ +import {patch} from "@web/core/utils/patch"; +import {FormController} from "@web/views/form/form_controller"; +import {ListController} from "@web/views/list/list_controller"; +import {KanbanController} from "@web/views/kanban/kanban_controller"; +import {onMounted} from "@odoo/owl"; + +patch(FormController.prototype, { + setup() { + super.setup(...arguments); + onMounted(() => { + var form_element = this.rootRef.el; + var self = this; + if ( + self.model.config.resModel === "product.product" && + self.model.config.context.custom_create_variant + ) { + var buttons = form_element.querySelector( + ".o_control_panel_main_buttons" + ); + var createButtons = buttons.querySelectorAll(".o_form_button_create"); + createButtons.forEach((button) => { + button.style.display = "none"; + }); + } + }); + }, + // Async beforeExecuteActionButton(clickParams) { + // console.log("beforeExecuteActionButton", clickParams); + // if (clickParams.special === "no_save") { + // delete clickParams.special; + // return true; + // } + // return super.beforeExecuteActionButton(...arguments); + // }, +}); + +patch(ListController.prototype, { + setup() { + super.setup(...arguments); + onMounted(() => { + var form_element = this.rootRef.el; + var self = this; + if ( + self.model.config.resModel === "product.product" && + self.model.config.context.custom_create_variant + ) { + var buttons = form_element.querySelector( + ".o_control_panel_main_buttons" + ); + var createButtons = buttons.querySelectorAll(".o_list_button_add"); + createButtons.forEach((button) => { + button.style.display = "none"; + }); + } + }); + }, +}); + +patch(KanbanController.prototype, { + setup() { + super.setup(...arguments); + onMounted(() => { + var form_element = this.rootRef.el; + var self = this; + if ( + self.model.config.resModel === "product.product" && + self.model.config.context.custom_create_variant + ) { + var buttons = form_element.querySelector( + ".o_control_panel_main_buttons" + ); + var createButtons = buttons.querySelectorAll(".o-kanban-button-new"); + createButtons.forEach((button) => { + button.style.display = "none"; + }); + } + }); + }, +}); diff --git a/product_configurator/static/src/js/form_widgets.js b/product_configurator/static/src/js/form_widgets.js deleted file mode 100644 index b45e17f26f..0000000000 --- a/product_configurator/static/src/js/form_widgets.js +++ /dev/null @@ -1,79 +0,0 @@ -odoo.define("product_configurator.FieldBooleanButton", function (require) { - "use strict"; - - var FormController = require("web.FormController"); - var ListController = require("web.ListController"); - var KanbanController = require("web.KanbanController"); - - var pyUtils = require("web.py_utils"); - - FormController.include({ - /* eslint-disable no-unused-vars*/ - renderButtons: function ($node) { - var self = this; - this._super.apply(this, arguments); - if ( - self.modelName === "product.product" && - self.initialState.context.custom_create_variant - ) { - this.$buttons.find(".o_form_button_create").css("display", "none"); - } - }, - /* eslint-disable no-unused-vars*/ - - _onButtonClicked: function (event) { - var self = this; - var attrs = event.data.attrs; - if (event.data.attrs.context) { - var record_ctx = self.model.get(event.data.record.id).context; - var btn_ctx = pyUtils.eval( - "context", - record_ctx, - event.data.attrs.context - ); - self.model.localData[event.data.record.id].context = _.extend( - {}, - btn_ctx, - record_ctx - ); - } - if (attrs.special === "no_save") { - this.canBeSaved = function () { - return true; - }; - var event_no_save = $.extend(true, {}, event); - event_no_save.data.attrs.special = false; - return this._super(event_no_save); - } - this._super(event); - }, - }); - ListController.include({ - /* eslint-disable no-unused-vars*/ - renderButtons: function ($node) { - var self = this; - this._super.apply(this, arguments); - if ( - self.modelName === "product.product" && - self.initialState.context.custom_create_variant - ) { - this.$buttons.find(".o_list_button_add").css("display", "none"); - } - }, - /* eslint-disable no-unused-vars*/ - }); - KanbanController.include({ - /* eslint-disable no-unused-vars*/ - renderButtons: function ($node) { - var self = this; - this._super.apply(this, arguments); - if ( - self.modelName === "product.product" && - self.initialState.context.custom_create_variant - ) { - this.$buttons.find(".o-kanban-button-new").css("display", "none"); - } - }, - /* eslint-disable no-unused-vars*/ - }); -}); diff --git a/product_configurator/static/src/js/relational_fields.esm.js b/product_configurator/static/src/js/relational_fields.esm.js new file mode 100644 index 0000000000..93777106a7 --- /dev/null +++ b/product_configurator/static/src/js/relational_fields.esm.js @@ -0,0 +1,14 @@ +/** @odoo-module **/ +/*eslint-disable*/ +import {patch} from "@web/core/utils/patch"; +import {Many2OneField} from "@web/views/fields/many2one/many2one_field"; + +patch(Many2OneField.prototype, { + computeActiveActions(props) { + var element = super.computeActiveActions(...arguments); + if (element === undefined) { + return $(); + } + return element; + }, +}); diff --git a/product_configurator/static/src/js/relational_fields.js b/product_configurator/static/src/js/relational_fields.js deleted file mode 100644 index cabb0950e7..0000000000 --- a/product_configurator/static/src/js/relational_fields.js +++ /dev/null @@ -1,26 +0,0 @@ -odoo.define("product_configurator.FieldStatus", function (require) { - "use strict"; - - var fields = require("web.relational_fields"); - var FieldStatus = fields.FieldStatus; - - FieldStatus.include({ - /* Prase input as string in order to have a clickable statusbar*/ - _onClickStage: function (e) { - this._setValue(String($(e.currentTarget).data("value"))); - }, - }); - - /* Bug from odoo: in case of widget many2many_tags $input and $el do not exist - in 'this', so it returns 'undefine', but setIDForLabel(method in AbstractField) - expecting getFocusableElement always return object*/ - fields.FieldMany2One.include({ - getFocusableElement: function () { - var element = this._super.apply(this, arguments); - if (element === undefined) { - return $(); - } - return element; - }, - }); -}); diff --git a/product_configurator/tests/test_configuration_rules.py b/product_configurator/tests/test_configuration_rules.py index 8d51b3bf90..50e197a33d 100644 --- a/product_configurator/tests/test_configuration_rules.py +++ b/product_configurator/tests/test_configuration_rules.py @@ -5,7 +5,7 @@ class ConfigurationRules(TransactionCase): def setUp(self): - super(ConfigurationRules, self).setUp() + super().setUp() self.cfg_tmpl = self.env.ref("product_configurator.bmw_2_series") self.cfg_session = self.env["product.config.session"].create( @@ -55,7 +55,6 @@ def test_valid_configuration(self): self.assertTrue(validation, "Valid configuration failed validation") def test_invalid_configuration(self): - conf = [ "diesel", "228i", diff --git a/product_configurator/tests/test_create.py b/product_configurator/tests/test_create.py index dc97ee1c46..47c5fab260 100644 --- a/product_configurator/tests/test_create.py +++ b/product_configurator/tests/test_create.py @@ -3,7 +3,7 @@ class ConfigurationCreate(TransactionCase): def setUp(self): - super(ConfigurationCreate, self).setUp() + super().setUp() self.ProductConfWizard = self.env["product.configurator"] self.config_product = self.env.ref("product_configurator.bmw_2_series") @@ -115,54 +115,48 @@ def test_02_previous_step_incompatible_changes(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__attribute_{self.attr_engine.id}": self.value_218i.id, } ) product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_color.id): self.value_red.id, - "__attribute_{}".format(self.attr_rims.id): self.value_rims_378.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, + f"__attribute_{self.attr_rims.id}": self.value_rims_378.id, } ) product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format( - self.attr_model_line.id - ): self.value_sport_line.id, + f"__attribute_{self.attr_model_line.id}": self.value_sport_line.id, } ) product_config_wizard.action_previous_step() product_config_wizard.action_previous_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_engine.id): self.value_220i.id, + f"__attribute_{self.attr_engine.id}": self.value_220i.id, } ) product_config_wizard.action_next_step() product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format( - self.attr_model_line.id - ): self.value_model_sport_line.id, + f"__attribute_{self.attr_model_line.id}": self.value_model_sport_line.id, } ) product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_tapistry.id): self.value_tapistry.id, + f"__attribute_{self.attr_tapistry.id}": self.value_tapistry.id, } ) product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format( - self.attr_transmission.id - ): self.value_transmission.id, - "__attribute_{}".format(self.attr_options.id): [ + f"__attribute_{self.attr_transmission.id}": self.value_transmission.id, + f"__attribute_{self.attr_options.id}": [ [6, 0, [self.value_options_1.id, self.value_options_2.id]] ], } diff --git a/product_configurator/tests/test_product.py b/product_configurator/tests/test_product.py index 608cf6c2e3..cc0e6ad2f7 100644 --- a/product_configurator/tests/test_product.py +++ b/product_configurator/tests/test_product.py @@ -5,7 +5,7 @@ class TestProduct(ProductConfiguratorTestCases): def setUp(self): - super(TestProduct, self).setUp() + super().setUp() self.productTemplate = self.env["product.template"] self.productAttributeLine = self.env["product.template.attribute.line"] self.productConfigStepLine = self.env["product.config.step.line"] @@ -144,9 +144,9 @@ def test_04_unlink(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, - "__attribute_{}".format(self.attr_color.id): self.value_red.id, + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__attribute_{self.attr_engine.id}": self.value_218i.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, } ) product_config_wizard.action_next_step() @@ -180,9 +180,9 @@ def test_06_configure_product(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, - "__attribute_{}".format(self.attr_color.id): self.value_red.id, + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__attribute_{self.attr_engine.id}": self.value_218i.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, } ) wizard_action = product_config_wizard.action_next_step() @@ -236,14 +236,14 @@ def test_06_configure_product(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__attribute_{self.attr_engine.id}": self.value_218i.id, } ) product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_color.id): self.value_red.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, } ) product_config_wizard.action_previous_step() @@ -331,9 +331,9 @@ def test_10_reconfigure_product(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, - "__attribute_{}".format(self.attr_color.id): self.value_red.id, + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__attribute_{self.attr_engine.id}": self.value_218i.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, } ) product_config_wizard.action_next_step() @@ -348,14 +348,14 @@ def test_10_reconfigure_product(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__attribute_{}".format(self.attr_engine.id): self.value_218d.id, + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__attribute_{self.attr_engine.id}": self.value_218d.id, } ) product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_color.id): self.value_silver.id, + f"__attribute_{self.attr_color.id}": self.value_silver.id, } ) product_config_wizard.action_next_step() @@ -619,9 +619,9 @@ def test_16_check_duplicate_product(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, - "__attribute_{}".format(self.attr_color.id): self.value_red.id, + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__attribute_{self.attr_engine.id}": self.value_218i.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, } ) product_config_wizard.action_next_step() @@ -645,6 +645,10 @@ def test_17_fields_view_get(self): product_product = self._get_product_id() product_product.with_context(default_config_ok=True).get_view() + def test_18_get_conversions_dict(self): + product_product = self._get_product_id() + product_product._get_conversions_dict() + def test_19_compute_product_variant_count(self): self.product_tmpl_id = self.env["product.template"].create( { diff --git a/product_configurator/tests/test_product_attribute.py b/product_configurator/tests/test_product_attribute.py index 6e65796133..bb875c4674 100644 --- a/product_configurator/tests/test_product_attribute.py +++ b/product_configurator/tests/test_product_attribute.py @@ -4,7 +4,7 @@ class ProductAttributes(TransactionCase): def setUp(self): - super(ProductAttributes, self).setUp() + super().setUp() self.productAttributeLine = self.env["product.template.attribute.line"] self.ProductAttributeFuel = self.env.ref( "product_configurator.product_attribute_fuel" diff --git a/product_configurator/tests/test_product_config.py b/product_configurator/tests/test_product_config.py index 9dbff9acad..bd00a7113a 100644 --- a/product_configurator/tests/test_product_config.py +++ b/product_configurator/tests/test_product_config.py @@ -5,7 +5,7 @@ class ProductConfig(ProductConfiguratorTestCases): def setUp(self): - super(ProductConfig, self).setUp() + super().setUp() self.productConfWizard = self.env["product.configurator"] self.productTemplate = self.env["product.template"] self.productAttribute = self.env["product.attribute"] @@ -355,8 +355,8 @@ def test_09_create_get_variant(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attribute_1.id): self.attribute_vals_1.id, - "__attribute_{}".format(self.attribute_2.id): self.attribute_vals_3.id, + f"__attribute_{self.attribute_1.id}": self.attribute_vals_1.id, + f"__attribute_{self.attribute_2.id}": self.attribute_vals_3.id, } ) product_config_wizard.action_next_step() @@ -471,10 +471,10 @@ def test_13_update_session_configuration_value(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attribute_1.id): self.custom_vals.id, - "__custom_{}".format(self.attribute_1.id): self.irAttachement.id, - "__attribute_{}".format(self.attribute_1.id): self.custom_vals.id, - "__custom_{}".format(self.attribute_1.id): "Test", + f"__attribute_{self.attribute_1.id}": self.custom_vals.id, + f"__custom_{self.attribute_1.id}": self.irAttachement.id, + f"__attribute_{self.attribute_1.id}": self.custom_vals.id, + f"__custom_{self.attribute_1.id}": "Test", } ) product_config_wizard.action_next_step() @@ -566,10 +566,10 @@ def test_17_custom_value_validate_configuration(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attribute_1.id): self.custom_vals.id, - "__custom_{}".format(self.attribute_1.id): self.irAttachement.id, - "__attribute_{}".format(self.attribute_1.id): self.custom_vals.id, - "__custom_{}".format(self.attribute_1.id): "Test", + f"__attribute_{self.attribute_1.id}": self.custom_vals.id, + f"__custom_{self.attribute_1.id}": self.irAttachement.id, + f"__attribute_{self.attribute_1.id}": self.custom_vals.id, + f"__custom_{self.attribute_1.id}": "Test", } ) self.attributeLine1.custom = False diff --git a/product_configurator/tests/test_product_configurator_test_cases.py b/product_configurator/tests/test_product_configurator_test_cases.py index 15a721cfeb..e8c9c99bf4 100644 --- a/product_configurator/tests/test_product_configurator_test_cases.py +++ b/product_configurator/tests/test_product_configurator_test_cases.py @@ -3,7 +3,7 @@ class ProductConfiguratorTestCases(TransactionCase): def setUp(self): - super(ProductConfiguratorTestCases, self).setUp() + super().setUp() self.ProductConfWizard = self.env["product.configurator"] self.config_product = self.env.ref("product_configurator.bmw_2_series") @@ -71,54 +71,48 @@ def _configure_product_nxt_step(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__attribute_{self.attr_engine.id}": self.value_218i.id, } ) product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_color.id): self.value_red.id, - "__attribute_{}".format(self.attr_rims.id): self.value_rims_378.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, + f"__attribute_{self.attr_rims.id}": self.value_rims_378.id, } ) product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format( - self.attr_model_line.id - ): self.value_sport_line.id, + f"__attribute_{self.attr_model_line.id}": self.value_sport_line.id, } ) product_config_wizard.action_previous_step() product_config_wizard.action_previous_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_engine.id): self.value_220i.id, + f"__attribute_{self.attr_engine.id}": self.value_220i.id, } ) product_config_wizard.action_next_step() product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format( - self.attr_model_line.id - ): self.value_model_sport_line.id, + f"__attribute_{self.attr_model_line.id}": self.value_model_sport_line.id, } ) product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_tapistry.id): self.value_tapistry.id, + f"__attribute_{self.attr_tapistry.id}": self.value_tapistry.id, } ) product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format( - self.attr_transmission.id - ): self.value_transmission.id, - "__attribute_{}".format(self.attr_options.id): [ + f"__attribute_{self.attr_transmission.id}": self.value_transmission.id, + f"__attribute_{self.attr_options.id}": [ [6, 0, [self.value_options_2.id]] ], } diff --git a/product_configurator/tests/test_wizard.py b/product_configurator/tests/test_wizard.py index 0c35b93194..b18805e451 100644 --- a/product_configurator/tests/test_wizard.py +++ b/product_configurator/tests/test_wizard.py @@ -5,7 +5,7 @@ class ConfigurationWizard(ProductConfiguratorTestCases): def setUp(self): - super(ConfigurationWizard, self).setUp() + super().setUp() self.productTemplate = self.env["product.template"] self.productAttributeLine = self.env["product.template.attribute.line"] self.productConfigStepLine = self.env["product.config.step.line"] @@ -113,14 +113,14 @@ def _check_wizard_nxt_step(self): product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__attribute_{self.attr_engine.id}": self.value_218i.id, } ) product_config_wizard.action_next_step() product_config_wizard.write( { - "__attribute_{}".format(self.attr_color.id): self.value_red.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, } ) return product_config_wizard @@ -239,10 +239,10 @@ def test_10_open_step(self): def test_11_onchange(self): field_name = "" - values = {"__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id} + values = {f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id} product_config_wizard = self._check_wizard_nxt_step() field_prefix = product_config_wizard._prefixes.get("field_prefix") - field_name = "%s%s" % (field_prefix, field_name) + field_name = f"{field_prefix}{field_name}" specs = product_config_wizard._onchange_spec() product_config_wizard.onchange(values, field_name, specs) @@ -253,8 +253,8 @@ def test_11_onchange(self): } ) values2 = { - "__attribute_{}".format(self.attr_fuel.id): self.custom_vals.id, - "__custom_{}".format(self.attr_fuel.id): "Test1", + f"__attribute_{self.attr_fuel.id}": self.custom_vals.id, + f"__custom_{self.attr_fuel.id}": "Test1", } product_config_wizard.onchange(values2, field_name, specs) @@ -276,56 +276,50 @@ def test_12_fields_get(self): product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__custom_{}".format(self.attr_fuel.id): "Test1", - "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, - "__custom_{}".format(self.attr_engine.id): "Test2", + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__custom_{self.attr_fuel.id}": "Test1", + f"__attribute_{self.attr_engine.id}": self.value_218i.id, + f"__custom_{self.attr_engine.id}": "Test2", } ) product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format(self.attr_color.id): self.value_red.id, - "__attribute_{}".format(self.attr_rims.id): self.value_rims_378.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, + f"__attribute_{self.attr_rims.id}": self.value_rims_378.id, } ) product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format( - self.attr_model_line.id - ): self.value_sport_line.id, + f"__attribute_{self.attr_model_line.id}": self.value_sport_line.id, } ) product_config_wizard_1.action_previous_step() product_config_wizard_1.action_previous_step() product_config_wizard_1.write( { - "__attribute_{}".format(self.attr_engine.id): self.value_220i.id, + f"__attribute_{self.attr_engine.id}": self.value_220i.id, } ) product_config_wizard_1.action_next_step() product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format( - self.attr_model_line.id - ): self.value_model_sport_line.id, + f"__attribute_{self.attr_model_line.id}": self.value_model_sport_line.id, } ) product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format(self.attr_tapistry.id): self.value_tapistry.id, + f"__attribute_{self.attr_tapistry.id}": self.value_tapistry.id, } ) product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format( - self.attr_transmission.id - ): self.value_transmission.id, - "__attribute_{}".format(self.attr_options.id): [ + f"__attribute_{self.attr_transmission.id}": self.value_transmission.id, + f"__attribute_{self.attr_options.id}": [ [6, 0, [self.value_options_2.id]] ], } @@ -353,56 +347,50 @@ def test_13_fields_view_get(self): product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__custom_{}".format(self.attr_fuel.id): "Test1", - "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, - "__custom_{}".format(self.attr_engine.id): "Test2", + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__custom_{self.attr_fuel.id}": "Test1", + f"__attribute_{self.attr_engine.id}": self.value_218i.id, + f"__custom_{self.attr_engine.id}": "Test2", } ) product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format(self.attr_color.id): self.value_red.id, - "__attribute_{}".format(self.attr_rims.id): self.value_rims_378.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, + f"__attribute_{self.attr_rims.id}": self.value_rims_378.id, } ) product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format( - self.attr_model_line.id - ): self.value_sport_line.id, + f"__attribute_{self.attr_model_line.id}": self.value_sport_line.id, } ) product_config_wizard_1.action_previous_step() product_config_wizard_1.action_previous_step() product_config_wizard_1.write( { - "__attribute_{}".format(self.attr_engine.id): self.value_220i.id, + f"__attribute_{self.attr_engine.id}": self.value_220i.id, } ) product_config_wizard_1.action_next_step() product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format( - self.attr_model_line.id - ): self.value_model_sport_line.id, + f"__attribute_{self.attr_model_line.id}": self.value_model_sport_line.id, } ) product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format(self.attr_tapistry.id): self.value_tapistry.id, + f"__attribute_{self.attr_tapistry.id}": self.value_tapistry.id, } ) product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format( - self.attr_transmission.id - ): self.value_transmission.id, - "__attribute_{}".format(self.attr_options.id): [ + f"__attribute_{self.attr_transmission.id}": self.value_transmission.id, + f"__attribute_{self.attr_options.id}": [ [6, 0, [self.value_options_2.id]] ], } @@ -424,9 +412,9 @@ def test_14_unlink(self): def test_15_read(self): product_config_wizard = self._check_wizard_nxt_step() values = { - "__attribute_{}".format(self.attr_fuel.id): self.value_gasoline.id, - "__attribute_{}".format(self.attr_engine.id): self.value_218i.id, - "__attribute_{}".format(self.attr_color.id): self.value_red.id, + f"__attribute_{self.attr_fuel.id}": self.value_gasoline.id, + f"__attribute_{self.attr_engine.id}": self.value_218i.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, } product_config_wizard.read(values) product_tmpl = self.env["product.template"].create( @@ -500,25 +488,25 @@ def test_15_read(self): product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format(self.attr_fuel.id): self.custom_vals.id, - "__custom_{}".format(self.attr_fuel.id): "#DEFSRE", - "__attribute_{}".format(self.attr_engine.id): self.custom_vals.id, - "__custom_{}".format(self.attr_engine.id): "#FERDFGR", + f"__attribute_{self.attr_fuel.id}": self.custom_vals.id, + f"__custom_{self.attr_fuel.id}": "#DEFSRE", + f"__attribute_{self.attr_engine.id}": self.custom_vals.id, + f"__custom_{self.attr_engine.id}": "#FERDFGR", } ) product_config_wizard_1.action_next_step() product_config_wizard_1.write( { - "__attribute_{}".format(self.attr_color.id): self.value_red.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, } ) # check for custom value custom_vals = { - "__attribute_{}".format(self.attr_fuel.id): self.custom_vals.id, - "__custom_{}".format(self.attr_fuel.id): "#DEFSRE", - "__attribute_{}".format(self.attr_engine.id): self.custom_vals.id, - "__custom_{}".format(self.attr_engine.id): "#FERDFGR", - "__attribute_{}".format(self.attr_color.id): self.value_red.id, + f"__attribute_{self.attr_fuel.id}": self.custom_vals.id, + f"__custom_{self.attr_fuel.id}": "#DEFSRE", + f"__attribute_{self.attr_engine.id}": self.custom_vals.id, + f"__custom_{self.attr_engine.id}": "#FERDFGR", + f"__attribute_{self.attr_color.id}": self.value_red.id, } product_config_wizard_1.read(custom_vals) session = self.productConfigSession.search( @@ -536,27 +524,27 @@ def test_15_read(self): product_config_wizard_2.action_next_step() product_config_wizard_2.write( { - "__attribute_{}".format(self.attr_fuel.id): [ + f"__attribute_{self.attr_fuel.id}": [ (6, 0, [self.value_diesel.id, self.value_gasoline.id]) ], - "__attribute_{}".format(self.attr_engine.id): self.custom_vals.id, - "__custom_{}".format(self.attr_engine.id): "#FERDFGR", + f"__attribute_{self.attr_engine.id}": self.custom_vals.id, + f"__custom_{self.attr_engine.id}": "#FERDFGR", } ) product_config_wizard_2.action_next_step() product_config_wizard_2.write( { - "__attribute_{}".format(self.attr_color.id): self.value_red.id, + f"__attribute_{self.attr_color.id}": self.value_red.id, } ) # check for multi value multi_vals = { - "__attribute_{}".format(self.attr_fuel.id): [ + f"__attribute_{self.attr_fuel.id}": [ (6, 0, [self.value_diesel.id, self.value_gasoline.id]) ], - "__attribute_{}".format(self.attr_engine.id): self.custom_vals.id, - "__custom_{}".format(self.attr_engine.id): "#FERDFGR", - "__attribute_{}".format(self.attr_color.id): self.value_red.id, + f"__attribute_{self.attr_engine.id}": self.custom_vals.id, + f"__custom_{self.attr_engine.id}": "#FERDFGR", + f"__attribute_{self.attr_color.id}": self.value_red.id, } product_config_wizard_2.read(multi_vals) diff --git a/product_configurator/views/product_attribute_view.xml b/product_configurator/views/product_attribute_view.xml index 2316db3acc..16fb343374 100644 --- a/product_configurator/views/product_attribute_view.xml +++ b/product_configurator/views/product_attribute_view.xml @@ -21,13 +21,12 @@ -
- -
+ />
@@ -44,16 +43,8 @@ - - + + @@ -64,7 +55,6 @@ @@ -72,11 +62,11 @@ diff --git a/product_configurator/views/product_view.xml b/product_configurator/views/product_view.xml index 3d8d26c068..8a24545bb0 100644 --- a/product_configurator/views/product_view.xml +++ b/product_configurator/views/product_view.xml @@ -22,11 +22,12 @@