From 2988a68499f26e65c9518aa280f8543c81e63ac8 Mon Sep 17 00:00:00 2001 From: KNVx Date: Tue, 9 Jul 2024 11:20:08 +0200 Subject: [PATCH 1/9] [ADD] connector_woocommerce_wpml --- connector_woocommerce_wpml/README.rst | 80 ++++ connector_woocommerce_wpml/__init__.py | 1 + connector_woocommerce_wpml/__manifest__.py | 27 ++ .../migrations/14.0.0.1.1/pre-migration.py | 36 ++ .../migrations/14.0.0.1.2/pre-migration.py | 20 + connector_woocommerce_wpml/models/__init__.py | 7 + .../models/backend/__init__.py | 1 + .../models/backend/backend.py | 24 + .../product_attribute_value/__init__.py | 5 + .../models/product_attribute_value/adapter.py | 21 + .../models/product_attribute_value/binder.py | 25 + .../models/product_attribute_value/binding.py | 27 ++ .../product_attribute_value/export_mapper.py | 68 +++ .../product_attribute_value/exporter.py | 15 + .../models/product_product/__init__.py | 5 + .../models/product_product/adapter.py | 55 +++ .../models/product_product/binder.py | 49 ++ .../models/product_product/binding.py | 40 ++ .../models/product_product/export_mapper.py | 63 +++ .../models/product_product/exporter.py | 15 + .../product_public_category/__init__.py | 5 + .../models/product_public_category/adapter.py | 51 ++ .../models/product_public_category/binder.py | 25 + .../models/product_public_category/binding.py | 26 + .../product_public_category/export_mapper.py | 60 +++ .../product_public_category/exporter.py | 15 + .../models/product_template/__init__.py | 5 + .../models/product_template/adapter.py | 62 +++ .../models/product_template/binder.py | 42 ++ .../models/product_template/binding.py | 40 ++ .../models/product_template/export_mapper.py | 60 +++ .../models/product_template/exporter.py | 15 + .../models/product_wpml_mixin/__init__.py | 4 + .../models/product_wpml_mixin/adapter.py | 31 ++ .../models/product_wpml_mixin/binder.py | 32 ++ .../models/product_wpml_mixin/binding.py | 16 + .../models/product_wpml_mixin/exporter.py | 31 ++ .../models/sale_order/__init__.py | 1 + .../models/sale_order/adapter.py | 20 + .../readme/CONFIGURE.rst | 4 + .../readme/CONTRIBUTORS.rst | 3 + .../readme/DESCRIPTION.rst | 2 + connector_woocommerce_wpml/readme/ROADMAP.rst | 4 + .../static/description/icon.png | Bin 0 -> 6342 bytes .../static/description/index.html | 443 ++++++++++++++++++ .../views/product_attribute_value.xml | 31 ++ .../views/product_product.xml | 31 ++ .../views/product_public_category.xml | 31 ++ .../views/product_template.xml | 31 ++ .../views/woocommerce_backend_view.xml | 22 + .../odoo/addons/connector_woocommerce_wpml | 1 + setup/connector_woocommerce_wpml/setup.py | 6 + 52 files changed, 1734 insertions(+) create mode 100644 connector_woocommerce_wpml/README.rst create mode 100644 connector_woocommerce_wpml/__init__.py create mode 100644 connector_woocommerce_wpml/__manifest__.py create mode 100644 connector_woocommerce_wpml/migrations/14.0.0.1.1/pre-migration.py create mode 100644 connector_woocommerce_wpml/migrations/14.0.0.1.2/pre-migration.py create mode 100644 connector_woocommerce_wpml/models/__init__.py create mode 100644 connector_woocommerce_wpml/models/backend/__init__.py create mode 100644 connector_woocommerce_wpml/models/backend/backend.py create mode 100644 connector_woocommerce_wpml/models/product_attribute_value/__init__.py create mode 100644 connector_woocommerce_wpml/models/product_attribute_value/adapter.py create mode 100644 connector_woocommerce_wpml/models/product_attribute_value/binder.py create mode 100644 connector_woocommerce_wpml/models/product_attribute_value/binding.py create mode 100644 connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py create mode 100644 connector_woocommerce_wpml/models/product_attribute_value/exporter.py create mode 100644 connector_woocommerce_wpml/models/product_product/__init__.py create mode 100644 connector_woocommerce_wpml/models/product_product/adapter.py create mode 100644 connector_woocommerce_wpml/models/product_product/binder.py create mode 100644 connector_woocommerce_wpml/models/product_product/binding.py create mode 100644 connector_woocommerce_wpml/models/product_product/export_mapper.py create mode 100644 connector_woocommerce_wpml/models/product_product/exporter.py create mode 100644 connector_woocommerce_wpml/models/product_public_category/__init__.py create mode 100644 connector_woocommerce_wpml/models/product_public_category/adapter.py create mode 100644 connector_woocommerce_wpml/models/product_public_category/binder.py create mode 100644 connector_woocommerce_wpml/models/product_public_category/binding.py create mode 100644 connector_woocommerce_wpml/models/product_public_category/export_mapper.py create mode 100644 connector_woocommerce_wpml/models/product_public_category/exporter.py create mode 100644 connector_woocommerce_wpml/models/product_template/__init__.py create mode 100644 connector_woocommerce_wpml/models/product_template/adapter.py create mode 100644 connector_woocommerce_wpml/models/product_template/binder.py create mode 100644 connector_woocommerce_wpml/models/product_template/binding.py create mode 100644 connector_woocommerce_wpml/models/product_template/export_mapper.py create mode 100644 connector_woocommerce_wpml/models/product_template/exporter.py create mode 100644 connector_woocommerce_wpml/models/product_wpml_mixin/__init__.py create mode 100644 connector_woocommerce_wpml/models/product_wpml_mixin/adapter.py create mode 100644 connector_woocommerce_wpml/models/product_wpml_mixin/binder.py create mode 100644 connector_woocommerce_wpml/models/product_wpml_mixin/binding.py create mode 100644 connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py create mode 100644 connector_woocommerce_wpml/models/sale_order/__init__.py create mode 100644 connector_woocommerce_wpml/models/sale_order/adapter.py create mode 100644 connector_woocommerce_wpml/readme/CONFIGURE.rst create mode 100644 connector_woocommerce_wpml/readme/CONTRIBUTORS.rst create mode 100644 connector_woocommerce_wpml/readme/DESCRIPTION.rst create mode 100644 connector_woocommerce_wpml/readme/ROADMAP.rst create mode 100644 connector_woocommerce_wpml/static/description/icon.png create mode 100644 connector_woocommerce_wpml/static/description/index.html create mode 100644 connector_woocommerce_wpml/views/product_attribute_value.xml create mode 100644 connector_woocommerce_wpml/views/product_product.xml create mode 100644 connector_woocommerce_wpml/views/product_public_category.xml create mode 100644 connector_woocommerce_wpml/views/product_template.xml create mode 100644 connector_woocommerce_wpml/views/woocommerce_backend_view.xml create mode 120000 setup/connector_woocommerce_wpml/odoo/addons/connector_woocommerce_wpml create mode 100644 setup/connector_woocommerce_wpml/setup.py diff --git a/connector_woocommerce_wpml/README.rst b/connector_woocommerce_wpml/README.rst new file mode 100644 index 000000000..0f696a1f5 --- /dev/null +++ b/connector_woocommerce_wpml/README.rst @@ -0,0 +1,80 @@ +========================== +Connector WooCommerce WMPL +========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:99c6152a250b134a922160cc755712e7e4c7493967116e4ca199dd0b144c1c07 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-NuoBiT%2Fodoo--addons-lightgray.png?logo=github + :target: https://github.com/NuoBiT/odoo-addons/tree/14.0/connector_woocommerce_wpml + :alt: NuoBiT/odoo-addons + +|badge1| |badge2| |badge3| + +* This module works with plugin WordPress Multi Language. +* https://wpml.org/ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +On WPML setup, select the option Language name added as a parameter. +If it's already set, modify: + +* WPML > Languages > Language URL format, select the option "Language name added as a parameter" and save the changes. + +Known issues / Roadmap +====================== + +For a better maintenance, we can try to use a mixin component to define the common methods and properties. + + * binding: woocommerce_lang field and sql_constrains woocommerce_internal_uniq overwriting the common for all + * export_mapper: Include the translation_of and lang? + +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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* NuoBiT Solutions +* S.L. + +Contributors +~~~~~~~~~~~~ + +* `NuoBiT `__: + + * Kilian Niubo + +Maintainers +~~~~~~~~~~~ + +This module is part of the `NuoBiT/odoo-addons `_ project on GitHub. + +You are welcome to contribute. diff --git a/connector_woocommerce_wpml/__init__.py b/connector_woocommerce_wpml/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/connector_woocommerce_wpml/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/connector_woocommerce_wpml/__manifest__.py b/connector_woocommerce_wpml/__manifest__.py new file mode 100644 index 000000000..19b89a790 --- /dev/null +++ b/connector_woocommerce_wpml/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +{ + "name": "Connector WooCommerce WMPL", + "version": "14.0.0.1.3", + "author": "NuoBiT Solutions, S.L.", + "license": "AGPL-3", + "category": "Connector", + "website": "https://github.com/nuobit/odoo-addons", + "external_dependencies": { + "python": [ + "woocommerce", + ], + }, + "depends": [ + "connector_woocommerce", + "connector_wordpress_wpml", + ], + "data": [ + "views/woocommerce_backend_view.xml", + "views/product_attribute_value.xml", + "views/product_product.xml", + "views/product_public_category.xml", + "views/product_template.xml", + ], +} diff --git a/connector_woocommerce_wpml/migrations/14.0.0.1.1/pre-migration.py b/connector_woocommerce_wpml/migrations/14.0.0.1.1/pre-migration.py new file mode 100644 index 000000000..d972ed13d --- /dev/null +++ b/connector_woocommerce_wpml/migrations/14.0.0.1.1/pre-migration.py @@ -0,0 +1,36 @@ +# Copyright 2025 NuoBiT Solutions - Deniz Gallo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from openupgradelib import openupgrade + +_column_renames = { + "res_lang_woocommerce_backend_rel": [ + ("woocommerce_backend_id", "backend_id"), + ("res_lang_id", "lang_id"), + ] +} + + +@openupgrade.migrate(use_env=True) +def migrate(env, version): + if not version: + return + openupgrade.rename_columns(env.cr, _column_renames) + + env.cr.execute( + """ + ALTER TABLE res_lang_woocommerce_backend_rel + RENAME CONSTRAINT + res_lang_woocommerce_backend_rel_woocommerce_backend_id_fkey + TO res_lang_woocommerce_backend_rel_backend_id_fkey; + """ + ) + + env.cr.execute( + """ + ALTER TABLE res_lang_woocommerce_backend_rel + RENAME CONSTRAINT + res_lang_woocommerce_backend_rel_res_lang_id_fkey + TO res_lang_woocommerce_backend_rel_lang_id_fkey; + """ + ) diff --git a/connector_woocommerce_wpml/migrations/14.0.0.1.2/pre-migration.py b/connector_woocommerce_wpml/migrations/14.0.0.1.2/pre-migration.py new file mode 100644 index 000000000..5081dc517 --- /dev/null +++ b/connector_woocommerce_wpml/migrations/14.0.0.1.2/pre-migration.py @@ -0,0 +1,20 @@ +# Copyright 2025 NuoBiT Solutions - Deniz Gallo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from openupgradelib import openupgrade + +_field_renames = [ + ( + "woocommerce.backend", + "woocommerce_backend", + "language_ids", + "lang_ids", + ) +] + + +@openupgrade.migrate(use_env=True) +def migrate(env, version): + if not version: + return + openupgrade.rename_fields(env, _field_renames) diff --git a/connector_woocommerce_wpml/models/__init__.py b/connector_woocommerce_wpml/models/__init__.py new file mode 100644 index 000000000..2ab3d2f32 --- /dev/null +++ b/connector_woocommerce_wpml/models/__init__.py @@ -0,0 +1,7 @@ +from . import product_wpml_mixin +from . import backend +from . import product_attribute_value +from . import product_product +from . import product_public_category +from . import product_template +from . import sale_order diff --git a/connector_woocommerce_wpml/models/backend/__init__.py b/connector_woocommerce_wpml/models/backend/__init__.py new file mode 100644 index 000000000..baacd255d --- /dev/null +++ b/connector_woocommerce_wpml/models/backend/__init__.py @@ -0,0 +1 @@ +from . import backend diff --git a/connector_woocommerce_wpml/models/backend/backend.py b/connector_woocommerce_wpml/models/backend/backend.py new file mode 100644 index 000000000..6b0ae17e7 --- /dev/null +++ b/connector_woocommerce_wpml/models/backend/backend.py @@ -0,0 +1,24 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import logging + +from odoo import _, api, models +from odoo.exceptions import ValidationError + +_logger = logging.getLogger(__name__) + + +class WooCommerceBackend(models.Model): + _inherit = "woocommerce.backend" + + @api.constrains("lang_ids") + def check_lang_ids(self): + for rec in self: + for lang in rec.lang_ids: + if not lang.wordpress_wpml_lang_code: + raise ValidationError( + _( + "The language %s has no WPML code, please define " + "this code in language before using it." % lang.name + ) + ) diff --git a/connector_woocommerce_wpml/models/product_attribute_value/__init__.py b/connector_woocommerce_wpml/models/product_attribute_value/__init__.py new file mode 100644 index 000000000..b6ea9e5ec --- /dev/null +++ b/connector_woocommerce_wpml/models/product_attribute_value/__init__.py @@ -0,0 +1,5 @@ +from . import adapter +from . import binder +from . import binding +from . import export_mapper +from . import exporter diff --git a/connector_woocommerce_wpml/models/product_attribute_value/adapter.py b/connector_woocommerce_wpml/models/product_attribute_value/adapter.py new file mode 100644 index 000000000..1b0ef1f2d --- /dev/null +++ b/connector_woocommerce_wpml/models/product_attribute_value/adapter.py @@ -0,0 +1,21 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class WooCommerceProductAttributeValueAdapter(Component): + _name = "woocommerce.product.attribute.value.adapter" + _inherit = [ + "woocommerce.product.attribute.value.adapter", + "woocommerce.product.wpml.mixin.adapter", + ] + + def _get_search_fields(self): + return self.wpml_get_search_fields() + + # def _domain_to_normalized_dict(self, real_domain): + # return self.wpml_domain_to_normalized_dict(real_domain) + + def _extract_domain_clauses(self, domain, search_fields): + return self.wpml_extract_domain_clauses(domain, search_fields) diff --git a/connector_woocommerce_wpml/models/product_attribute_value/binder.py b/connector_woocommerce_wpml/models/product_attribute_value/binder.py new file mode 100644 index 000000000..d7ad3cf37 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_attribute_value/binder.py @@ -0,0 +1,25 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class WooCommerceProductAttributeValueBinder(Component): + _name = "woocommerce.product.attribute.value.binder" + _inherit = [ + "woocommerce.product.attribute.value.binder", + "woocommerce.product.wpml.mixin.binder", + ] + + @property + def external_alt_id(self): + return super().external_alt_id + ["lang"] + + def get_binding_domain(self, record): + return self.wpml_get_binding_domain(record) + + def _additional_external_binding_fields(self, external_data): + return self.wpml_additional_external_binding_fields(external_data) + + # def unwrap_binding(self, binding): + # return self.wpml_unwrap_binding(binding) diff --git a/connector_woocommerce_wpml/models/product_attribute_value/binding.py b/connector_woocommerce_wpml/models/product_attribute_value/binding.py new file mode 100644 index 000000000..0d55b2d0e --- /dev/null +++ b/connector_woocommerce_wpml/models/product_attribute_value/binding.py @@ -0,0 +1,27 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class WooCommerceProductAttributeValue(models.Model): + _inherit = "woocommerce.product.attribute.value" + + woocommerce_lang = fields.Char( + string="Language", + required=True, + ) + + _sql_constraints = [ + ( + "woocommerce_internal_uniq", + "unique(backend_id, woocommerce_lang, odoo_id)", + "A binding already exists with the same Internal (Odoo) ID.", + ), + ( + "external_uniq", + "unique(backend_id, woocommerce_lang, woocommerce_idattributevalue)", + "A binding already exists with the same External " + "(woocommerce_idattributevalue) ID.", + ), + ] diff --git a/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py b/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py new file mode 100644 index 000000000..72cbecf7c --- /dev/null +++ b/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py @@ -0,0 +1,68 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + + +from odoo.addons.component.core import Component +from odoo.addons.connector.components.mapper import changed_by, mapping, only_create +from odoo.addons.connector_extension.components.mapper import required + + +class WooCommerceProductAttributeValueExportMapper(Component): + _inherit = "woocommerce.product.attribute.value.export.mapper" + + @required("name") + @changed_by("name") + @mapping + def name(self, record): + dict_name = super().name(record) + if "name" in dict_name: + if dict_name["name"] != record.name: + dict_name["name"] = record.name + return dict_name + + @required("parent_id") + @changed_by("attribute_id") + @mapping + def parent_id(self, record): + parent_dict = super().parent_id(record) + if "parent_id" in parent_dict: + parent_dict["parent_id"] = parent_dict["parent_id"] + binder = self.binder_for("woocommerce.product.attribute") + values = binder.get_external_dict_ids(record.attribute_id) + return {"parent_id": values["id"] or None} + + @changed_by("attribute_id") + @mapping + def parent_name(self, record): + dict_name = super().parent_name(record) + if "parent_name" in dict_name: + if dict_name["parent_name"] != record.attribute_id.name: + dict_name["parent_name"] = record.attribute_id.name + return dict_name + + @changed_by("lang") + @mapping + def lang(self, record): + # TODO: unify this code. Probably do a function in res lang + return { + "lang": self.env["res.lang"]._get_wpml_code_from_iso_code( + record._context.get("lang") + ) + } + + @only_create + @mapping + def translation_of(self, record): + lang_code = record._context.get("lang") + if lang_code: + other_binding_backend = record.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == self.backend_record + and x.woocommerce_lang + != self.env["res.lang"]._get_wpml_code_from_iso_code( + record._context.get("lang") + ) + ) + translation_of = None + for obb in other_binding_backend: + translation_of = obb.woocommerce_idattributevalue + return {"translation_of": translation_of} diff --git a/connector_woocommerce_wpml/models/product_attribute_value/exporter.py b/connector_woocommerce_wpml/models/product_attribute_value/exporter.py new file mode 100644 index 000000000..1d4714270 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_attribute_value/exporter.py @@ -0,0 +1,15 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class WooCommerceProductAttributeValueExporter(Component): + _name = "woocommerce.product.attribute.value.record.direct.exporter" + _inherit = [ + "woocommerce.product.attribute.value.record.direct.exporter", + "woocommerce.product.wpml.mixin.record.direct.exporter", + ] + + def run(self, relation, always=True, internal_fields=None): + return self.wpml_run(relation, always=always, internal_fields=internal_fields) diff --git a/connector_woocommerce_wpml/models/product_product/__init__.py b/connector_woocommerce_wpml/models/product_product/__init__.py new file mode 100644 index 000000000..b6ea9e5ec --- /dev/null +++ b/connector_woocommerce_wpml/models/product_product/__init__.py @@ -0,0 +1,5 @@ +from . import adapter +from . import binder +from . import binding +from . import export_mapper +from . import exporter diff --git a/connector_woocommerce_wpml/models/product_product/adapter.py b/connector_woocommerce_wpml/models/product_product/adapter.py new file mode 100644 index 000000000..d288ffde2 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_product/adapter.py @@ -0,0 +1,55 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class WooCommerceProductProductAdapter(Component): + _name = "woocommerce.product.product.adapter" + _inherit = [ + "woocommerce.product.product.adapter", + "woocommerce.product.wpml.mixin.adapter", + ] + + def create(self, data): + if data.get("translation_of"): + sku = data.pop("sku") + res = super().create(data) + if data.get("translation_of"): + if res and isinstance(res, dict): + external_id = res.get("id") + parent_id = res.get("parent_id") + if external_id: + url_l = "products/%s/variations/%s" % (parent_id, external_id) + self._exec("put", url_l, data={"sku": sku}) + return res + + def write(self, external_id, data): # pylint: disable=W8106 + old_sku = None + if data.get("sku"): + old_sku = data.pop("sku") + res = super().write(external_id, data) + if old_sku and res.get("data").get("sku") != old_sku: + data["sku"] = old_sku + res = super().write(external_id, data) + return res + + # TODO: REVIEW: can we return this in better way? + def _get_search_fields(self): + res = list( + set(self.wpml_get_search_fields()) | set(super()._get_search_fields()) + ) + return res + + def _domain_to_normalized_dict(self, real_domain): + return self.wpml_domain_to_normalized_dict(real_domain) + + # on Product variations the query return all variations with sku + def _extract_domain_clauses(self, domain, search_fields): + real_domain, common_domain = super()._extract_domain_clauses( + domain, search_fields + ) + for clause in domain: + if "lang" in clause[0]: + common_domain.append(clause) + return real_domain, common_domain diff --git a/connector_woocommerce_wpml/models/product_product/binder.py b/connector_woocommerce_wpml/models/product_product/binder.py new file mode 100644 index 000000000..9738b322f --- /dev/null +++ b/connector_woocommerce_wpml/models/product_product/binder.py @@ -0,0 +1,49 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class WooCommerceProductProductBinder(Component): + _name = "woocommerce.product.product.binder" + _inherit = [ + "woocommerce.product.product.binder", + "woocommerce.product.wpml.mixin.binder", + ] + # _inherit = "woocommerce.product.product.binder" + + @property + def external_alt_id(self): + return super().external_alt_id + ["lang"] + + def get_binding_domain(self, record): + return self.wpml_get_binding_domain(record) + + def _additional_external_binding_fields(self, external_data): + return self.wpml_additional_external_binding_fields(external_data) + + # def unwrap_binding(self, binding): + # return self.wpml_unwrap_binding(binding) + + # TODO: code commented. It's not necessary? delete! + # def to_external(self, binding, wrap=True, binding_extra_vals=None): + # return super().to_external( + # binding, wrap=wrap, binding_extra_vals={"lang": binding.woocommerce_lang} + # ) + + # We need this because we can't filter sku and lang + def _get_external_record_alt(self, relation, id_values): + res = super()._get_external_record_alt(relation, id_values) + if res: + relation_wp_lang = self.env["res.lang"]._get_wpml_code_from_iso_code( + relation.env.context.get("lang") + ) + if res.get("lang") != relation_wp_lang: + if res.get("translations") and res["translations"].get( + relation_wp_lang + ): + adapter = self.component(usage="backend.adapter") + res = adapter.read(res["translations"][relation_wp_lang]) + else: + return None + return res diff --git a/connector_woocommerce_wpml/models/product_product/binding.py b/connector_woocommerce_wpml/models/product_product/binding.py new file mode 100644 index 000000000..932ed18b3 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_product/binding.py @@ -0,0 +1,40 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class WooCommerceProductProduct(models.Model): + _inherit = "woocommerce.product.product" + + woocommerce_lang = fields.Char( + string="Language", + required=True, + ) + + _sql_constraints = [ + ( + "woocommerce_internal_uniq", + "unique(backend_id, woocommerce_lang, odoo_id)", + "A binding already exists with the same Internal (Odoo) ID.", + ), + ( + "external_uniq", + "unique(backend_id, woocommerce_lang, woocommerce_idproduct)", + "A binding already exists with the same External (idProduct) ID.", + ), + ] + + # TODO: This function should be an overwrite of the original one, + # it should be refactored to avoid code duplication + # doing a hook to set a context variable with lang + # TODO: The optimization needs to be done at the language level, + # just as we do in the upper module connector_woocommerce + # def resync_export(self): + # super().resync_export() + # if not self.env.context.get("resync_product_template", False): + # for rec in self: + # rec.product_tmpl_id.woocommerce_bind_ids.filtered( + # lambda x: x.backend_id == rec.backend_id + # and x.woocommerce_lang == rec.woocommerce_lang + # ).with_context(resync_product_product=True).resync_export() diff --git a/connector_woocommerce_wpml/models/product_product/export_mapper.py b/connector_woocommerce_wpml/models/product_product/export_mapper.py new file mode 100644 index 000000000..8e3704ee9 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_product/export_mapper.py @@ -0,0 +1,63 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component +from odoo.addons.connector.components.mapper import changed_by, mapping, only_create + + +class WooCommerceProductProductExportMapper(Component): + _inherit = "woocommerce.product.product.export.mapper" + + # TODO: REMOVE THIS COMMENT: we need lang on write because woocommerce + # can't be write name with id as a external_id, we need name+lang. + # TODO: REMOVE THIS LANG FROM MAPPER!! + # @only_create + @changed_by("lang") + @mapping + def lang(self, record): + # TODO: unify this code. Probably do a function in res lang + lang = self.env["res.lang"]._get_wpml_code_from_iso_code( + record._context.get("lang") + ) + return {"lang": lang} + + @only_create + @mapping + def translation_of(self, record): + lang_code = record._context.get("lang") + if lang_code: + source_lang_code = self.backend_record.lang_ids[0].code + if lang_code == source_lang_code: + # We don't need to set translation_of for the default lang + return {} + else: + wpml_code = self.env["res.lang"]._get_wpml_code_from_iso_code( + source_lang_code + ) + master_binding_backend = record.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == self.backend_record + and x.woocommerce_lang == wpml_code + ) + translation_of = None + if master_binding_backend: + translation_of = master_binding_backend.woocommerce_idproduct + return {"translation_of": translation_of} + + def _get_product_description(self, record): + # We don't need check backend_record lang + # because record already has lang on context + return record.variant_public_description + + @mapping + def attributes(self, record): + binder = self.binder_for("woocommerce.product.attribute") + attr_list = [] + for value in record.product_template_attribute_value_ids: + values = binder.get_external_dict_ids(value.attribute_id) + attr_list.append( + { + "id": values["id"], + "option": value.name, + } + ) + return {"attributes": attr_list} diff --git a/connector_woocommerce_wpml/models/product_product/exporter.py b/connector_woocommerce_wpml/models/product_product/exporter.py new file mode 100644 index 000000000..fa391ceb3 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_product/exporter.py @@ -0,0 +1,15 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class WooCommerceProductProductExporter(Component): + _name = "woocommerce.product.product.record.direct.exporter" + _inherit = [ + "woocommerce.product.product.record.direct.exporter", + "woocommerce.product.wpml.mixin.record.direct.exporter", + ] + + def run(self, relation, always=True, internal_fields=None): + return self.wpml_run(relation, always=always, internal_fields=internal_fields) diff --git a/connector_woocommerce_wpml/models/product_public_category/__init__.py b/connector_woocommerce_wpml/models/product_public_category/__init__.py new file mode 100644 index 000000000..b6ea9e5ec --- /dev/null +++ b/connector_woocommerce_wpml/models/product_public_category/__init__.py @@ -0,0 +1,5 @@ +from . import adapter +from . import binder +from . import binding +from . import export_mapper +from . import exporter diff --git a/connector_woocommerce_wpml/models/product_public_category/adapter.py b/connector_woocommerce_wpml/models/product_public_category/adapter.py new file mode 100644 index 000000000..b5d6cb8b7 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_public_category/adapter.py @@ -0,0 +1,51 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import _ +from odoo.exceptions import ValidationError + +from odoo.addons.component.core import Component + + +class WooCommerceProductPublicCategoryAdapter(Component): + _name = "woocommerce.product.public.category.adapter" + _inherit = [ + "woocommerce.product.public.category.adapter", + "woocommerce.product.wpml.mixin.adapter", + ] + + def _manage_error_codes( + self, res_data, res, resource, raise_on_error=True, **kwargs + ): + if res.status_code == 500: + if res_data.get("code") == "duplicate_term_slug": + error_message = _( + "Error: '%s'. " + "WPML plugin allows set the same slug for different " + "languages on FrontEnd but this can't be done via API. " + "Probably we need a solution in plugin code, it can't " + "be solved in Odoo without a workaround modifying raw data. " + "Review the slug of the category '%s' in lang [%s] and try again." + "" + % ( + res_data["message"], + kwargs["data"].get("name"), + kwargs["data"].get("lang"), + ) + ) + if raise_on_error: + raise ValidationError(error_message) + else: + return error_message + + return super()._manage_error_codes( + res_data, res, resource, raise_on_error=True, **kwargs + ) + + def _get_search_fields(self): + return self.wpml_get_search_fields() + + def _domain_to_normalized_dict(self, real_domain): + return self.wpml_domain_to_normalized_dict(real_domain) + + def _extract_domain_clauses(self, domain, search_fields): + return self.wpml_extract_domain_clauses(domain, search_fields) diff --git a/connector_woocommerce_wpml/models/product_public_category/binder.py b/connector_woocommerce_wpml/models/product_public_category/binder.py new file mode 100644 index 000000000..acd2646a9 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_public_category/binder.py @@ -0,0 +1,25 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import AbstractComponent + + +class WooCommerceProductPublicCategoryBinder(AbstractComponent): + _name = "woocommerce.product.public.category.binder" + _inherit = [ + "woocommerce.product.public.category.binder", + "woocommerce.product.wpml.mixin.binder", + ] + + @property + def external_alt_id(self): + return super().external_alt_id + ["lang"] + + def get_binding_domain(self, record): + return self.wpml_get_binding_domain(record) + + def _additional_external_binding_fields(self, external_data): + return self.wpml_additional_external_binding_fields(external_data) + + # def unwrap_binding(self, binding): + # return self.wpml_unwrap_binding(binding) diff --git a/connector_woocommerce_wpml/models/product_public_category/binding.py b/connector_woocommerce_wpml/models/product_public_category/binding.py new file mode 100644 index 000000000..ddf0313ce --- /dev/null +++ b/connector_woocommerce_wpml/models/product_public_category/binding.py @@ -0,0 +1,26 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class WooCommerceProductPublicCategory(models.Model): + _inherit = "woocommerce.product.public.category" + + woocommerce_lang = fields.Char( + string="Language", + required=True, + ) + + _sql_constraints = [ + ( + "woocommerce_internal_uniq", + "unique(backend_id, woocommerce_lang, odoo_id)", + "A binding already exists with the same Internal (Odoo) ID.", + ), + ( + "external_uniq", + "unique(backend_id, woocommerce_lang, woocommerce_idpubliccategory)", + "A binding already exists with the same External (idpublidcategory) ID.", + ), + ] diff --git a/connector_woocommerce_wpml/models/product_public_category/export_mapper.py b/connector_woocommerce_wpml/models/product_public_category/export_mapper.py new file mode 100644 index 000000000..0bdd4ac46 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_public_category/export_mapper.py @@ -0,0 +1,60 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + + +from odoo.addons.component.core import Component +from odoo.addons.connector.components.mapper import changed_by, mapping, only_create + + +class WooCommerceProductPublicCategoryExportMapper(Component): + _inherit = "woocommerce.product.public.category.export.mapper" + + # TODO: Try to don't repeat this code + @changed_by("name") + @mapping + def name(self, record): + dict_name = super().name(record) + if "name" in dict_name: + if dict_name["name"] != record.name: + dict_name["name"] = record.name + return dict_name + + @mapping + def description(self, record): + dict_description = super().description(record) + if dict_description["description"] != record.description: + dict_description["description"] = record.description or None + return dict_description + + # TODO: REMOVE THIS COMMENT: we need lang on write because woocommerce + # can't be write name with id as a external_id, we need name+lang. + # TODO: REMOVE THIS LANG FROM MAPPER!! + # @only_create + @changed_by("lang") + @mapping + def lang(self, record): + # TODO: unify this code. Probably do a function in res lang + lang = self.env["res.lang"]._get_wpml_code_from_iso_code( + record._context.get("lang") + ) + return {"lang": lang} + + @only_create + @mapping + def translation_of(self, record): + lang_code = record._context.get("lang") + if lang_code: + other_binding_backend = record.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == self.backend_record + and x.woocommerce_lang + != self.env["res.lang"]._get_wpml_code_from_iso_code( + record._context.get("lang") + ) + ) + translation_of = None + for obb in other_binding_backend: + translation_of = obb.woocommerce_idpubliccategory + return {"translation_of": translation_of} + + def _get_slug_name(self, record): + return record.slug_name diff --git a/connector_woocommerce_wpml/models/product_public_category/exporter.py b/connector_woocommerce_wpml/models/product_public_category/exporter.py new file mode 100644 index 000000000..404d95652 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_public_category/exporter.py @@ -0,0 +1,15 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class WooCommerceProductPublicCategoryExporter(Component): + _name = "woocommerce.product.public.category.record.direct.exporter" + _inherit = [ + "woocommerce.product.public.category.record.direct.exporter", + "woocommerce.product.wpml.mixin.record.direct.exporter", + ] + + def run(self, relation, always=True, internal_fields=None): + return self.wpml_run(relation, always=always, internal_fields=internal_fields) diff --git a/connector_woocommerce_wpml/models/product_template/__init__.py b/connector_woocommerce_wpml/models/product_template/__init__.py new file mode 100644 index 000000000..b6ea9e5ec --- /dev/null +++ b/connector_woocommerce_wpml/models/product_template/__init__.py @@ -0,0 +1,5 @@ +from . import adapter +from . import binder +from . import binding +from . import export_mapper +from . import exporter diff --git a/connector_woocommerce_wpml/models/product_template/adapter.py b/connector_woocommerce_wpml/models/product_template/adapter.py new file mode 100644 index 000000000..d16fc852b --- /dev/null +++ b/connector_woocommerce_wpml/models/product_template/adapter.py @@ -0,0 +1,62 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class WooCommerceProductTemplateAdapter(Component): + _name = "woocommerce.product.template.adapter" + _inherit = [ + "woocommerce.product.template.adapter", + "woocommerce.product.wpml.mixin.adapter", + ] + + def create(self, data): + if data.get("type") == "simple" and data.get("translation_of"): + sku = data.pop("sku") + res = super().create(data) + if data.get("type") == "simple" and data.get("translation_of"): + if res and isinstance(res, dict): + external_id = res.get("id") + if external_id: + sku = self._normalize_simple_sku(sku) + url_l = ["products", str(external_id)] + self._exec("put", "/".join(url_l), data={"sku": sku}) + return res + + def write(self, external_id, data): # pylint: disable=W8106 + old_sku = None + if data.get("type") == "simple": + if data.get("translation_of"): + data.pop("sku") + else: + old_sku = data.pop("sku") + if isinstance(old_sku, list): + old_sku = old_sku[0] + res = super().write(external_id, data) + if old_sku and res["data"].get("sku") != old_sku: + data["sku"] = old_sku + # This conversion is to "revert" first conversion done on prepare_data + if isinstance(data["regular_price"], str): + data["regular_price"] = float(data["regular_price"]) + if isinstance(data["sale_price"], str): + data["sale_price"] = float(data["sale_price"]) + res = super().write(external_id, data) + return res + + # TODO: REVIEW: can we return this in better way? + def _get_search_fields(self): + return list( + set(self.wpml_get_search_fields()) | set(super()._get_search_fields()) + ) + + def _modify_res_on_search_read(self, parent_ids, domain_dict): + res = super()._modify_res_on_search_read(parent_ids, domain_dict) + res[0]["lang"] = domain_dict.get("lang") + return res + + def _domain_to_normalized_dict(self, real_domain): + return self.wpml_domain_to_normalized_dict(real_domain) + + def _extract_domain_clauses(self, domain, search_fields): + return self.wpml_extract_domain_clauses(domain, search_fields) diff --git a/connector_woocommerce_wpml/models/product_template/binder.py b/connector_woocommerce_wpml/models/product_template/binder.py new file mode 100644 index 000000000..7c08a7787 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_template/binder.py @@ -0,0 +1,42 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class WooCommerceProductTemplateBinder(Component): + _name = "woocommerce.product.template.binder" + _inherit = [ + "woocommerce.product.template.binder", + "woocommerce.product.wpml.mixin.binder", + ] + + @property + def external_alt_id(self): + return super().external_alt_id + ["lang"] + + def get_binding_domain(self, record): + return self.wpml_get_binding_domain(record) + + def _additional_external_binding_fields(self, external_data): + return self.wpml_additional_external_binding_fields(external_data) + + # def unwrap_binding(self, binding): + # return self.wpml_unwrap_binding(binding) + + # We need this because we can't filter sku and lang + def _get_external_record_alt(self, relation, id_values): + res = super()._get_external_record_alt(relation, id_values) + if res: + relation_wp_lang = self.env["res.lang"]._get_wpml_code_from_iso_code( + relation.env.context.get("lang") + ) + if res.get("lang") != relation_wp_lang: + if res.get("translations") and res["translations"].get( + relation_wp_lang + ): + adapter = self.component(usage="backend.adapter") + res = adapter.read(res["translations"][relation_wp_lang]) + else: + return None + return res diff --git a/connector_woocommerce_wpml/models/product_template/binding.py b/connector_woocommerce_wpml/models/product_template/binding.py new file mode 100644 index 000000000..258595398 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_template/binding.py @@ -0,0 +1,40 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class WooCommerceProductTemplate(models.Model): + _inherit = "woocommerce.product.template" + + woocommerce_lang = fields.Char( + string="Language", + required=True, + ) + + _sql_constraints = [ + ( + "woocommerce_internal_uniq", + "unique(backend_id, woocommerce_lang, odoo_id)", + "A binding already exists with the same Internal (Odoo) ID.", + ), + ( + "external_uniq", + "unique(backend_id, woocommerce_lang, woocommerce_idproduct)", + "A binding already exists with the same External (idProduct) ID.", + ), + ] + + # TODO: This function should be an overwrite of the original one, + # it should be refactored to avoid code duplication + # doing a hook to set a context variable with lang + # TODO: The optimization needs to be done at the language level, + # just as we do in the upper module connector_woocommerce + # def resync_export(self): + # super().resync_export() + # if not self.env.context.get("resync_product_product", False): + # for rec in self: + # rec.product_variant_ids.woocommerce_bind_ids.filtered( + # lambda x: x.backend_id == self.backend_id + # and x.woocommerce_lang == rec.woocommerce_lang + # ).with_context(resync_product_template=True).resync_export() diff --git a/connector_woocommerce_wpml/models/product_template/export_mapper.py b/connector_woocommerce_wpml/models/product_template/export_mapper.py new file mode 100644 index 000000000..cb5a3c4f4 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_template/export_mapper.py @@ -0,0 +1,60 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + + +from odoo.addons.component.core import Component +from odoo.addons.connector.components.mapper import changed_by, mapping +from odoo.addons.connector_extension.common import tools + + +class WooCommerceProductTemplateExportMapper(Component): + _inherit = "woocommerce.product.template.export.mapper" + + @changed_by("lang") + @mapping + def lang(self, record): + # TODO: unify this code. Probably do a function in res lang + lang = self.env["res.lang"]._get_wpml_code_from_iso_code( + record._context.get("lang") + ) + return {"lang": lang} + + @mapping + def translation_of(self, record): + lang_code = record._context.get("lang") + if lang_code: + source_lang_code = self.backend_record.lang_ids[0].code + if lang_code == source_lang_code: + # We don't need to set translation_of for the default lang + return {} + else: + wpml_code = self.env["res.lang"]._get_wpml_code_from_iso_code( + source_lang_code + ) + master_binding_backend = record.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == self.backend_record + and x.woocommerce_lang == wpml_code + ) + translation_of = None + if master_binding_backend: + translation_of = master_binding_backend.woocommerce_idproduct + return {"translation_of": translation_of} + + def _get_product_description(self, record): + # We don't need check backend_record lang + # because record already has lang on context + return tools.color_rgb2hex(record.public_description) + + def _get_short_description(self, record): + return record.public_short_description + + def _get_product_variant_description(self, record): + # We don't need check backend_record lang + # because record already has lang on context + return tools.color_rgb2hex(record.product_variant_id.variant_public_description) + + def _get_value_ids(self, attribute_line): + return attribute_line.product_template_value_ids.mapped("name") + + def _get_slug_name(self, record): + return record.slug_name diff --git a/connector_woocommerce_wpml/models/product_template/exporter.py b/connector_woocommerce_wpml/models/product_template/exporter.py new file mode 100644 index 000000000..d34fc6a4b --- /dev/null +++ b/connector_woocommerce_wpml/models/product_template/exporter.py @@ -0,0 +1,15 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class WooCommerceProductTemplateExporter(Component): + _name = "woocommerce.product.template.record.direct.exporter" + _inherit = [ + "woocommerce.product.template.record.direct.exporter", + "woocommerce.product.wpml.mixin.record.direct.exporter", + ] + + def run(self, relation, always=True, internal_fields=None): + return self.wpml_run(relation, always=always, internal_fields=internal_fields) diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/__init__.py b/connector_woocommerce_wpml/models/product_wpml_mixin/__init__.py new file mode 100644 index 000000000..65af6d7fa --- /dev/null +++ b/connector_woocommerce_wpml/models/product_wpml_mixin/__init__.py @@ -0,0 +1,4 @@ +from . import adapter +from . import binder +from . import binding +from . import exporter diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/adapter.py b/connector_woocommerce_wpml/models/product_wpml_mixin/adapter.py new file mode 100644 index 000000000..3ace1fefb --- /dev/null +++ b/connector_woocommerce_wpml/models/product_wpml_mixin/adapter.py @@ -0,0 +1,31 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import AbstractComponent + + +# TODO: we shoudn't need to use this if we do component aproach. +class WooCommerceProductWPMLMixinAdapter(AbstractComponent): + _name = "woocommerce.product.wpml.mixin.adapter" + _inherit = "connector.extension.woocommerce.adapter.crud" + + def wpml_get_search_fields(self): + res = super()._get_search_fields() + res.append("lang") + return res + + def wpml_domain_to_normalized_dict(self, real_domain): + domain = super()._domain_to_normalized_dict(real_domain) + if not domain.get("lang"): + domain["lang"] = "all" + return domain + + def wpml_extract_domain_clauses(self, domain, search_fields): + real_domain, common_domain = super()._extract_domain_clauses( + domain, search_fields + ) + lang_clause_exists = any(clause[0] == "lang" for clause in common_domain) + for clause in domain: + if "lang" in clause[0] and not lang_clause_exists: + common_domain.append(clause) + return real_domain, common_domain diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/binder.py b/connector_woocommerce_wpml/models/product_wpml_mixin/binder.py new file mode 100644 index 000000000..37a926087 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_wpml_mixin/binder.py @@ -0,0 +1,32 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import AbstractComponent + + +class WooCommerceProductWPMLMixinBinder(AbstractComponent): + _name = "woocommerce.product.wpml.mixin.binder" + _inherit = "connector.extension.generic.binder" + + def wpml_get_binding_domain(self, record): + domain = super().get_binding_domain(record) + wp_wpml_code = self.env["res.lang"]._get_wpml_code_from_iso_code( + record._context.get("lang") + ) + if wp_wpml_code: + domain += [ + ( + "woocommerce_lang", + "=", + wp_wpml_code, + ) + ] + return domain + + def wpml_additional_external_binding_fields(self, external_data): + # TODO: this additional fields probably should be + # included in binding as m2o to res lang on upper binder + return { + **super()._additional_external_binding_fields(external_data), + "woocommerce_lang": external_data["lang"], + } diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/binding.py b/connector_woocommerce_wpml/models/product_wpml_mixin/binding.py new file mode 100644 index 000000000..62af27ec3 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_wpml_mixin/binding.py @@ -0,0 +1,16 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + + +# TODO: delete this file +# class WooCommerceBinding(models.AbstractModel): +# _inherit = "woocommerce.binding" +# +# def _prepare_relation(self, relation, record): +# relation = super()._prepare_relation(relation, record) +# iso_lang = self.env["res.lang"]._get_iso_code_from_wpml_code( +# record.woocommerce_lang +# ) +# if iso_lang: +# return relation.with_context(lang=iso_lang, resync_export=True) +# return relation diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py b/connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py new file mode 100644 index 000000000..9ab381e54 --- /dev/null +++ b/connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py @@ -0,0 +1,31 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import _ +from odoo.exceptions import ValidationError + +from odoo.addons.component.core import AbstractComponent + + +class WooCommerceProductWPMLMixinExporter(AbstractComponent): + _name = "woocommerce.product.wpml.mixin.record.direct.exporter" + _inherit = "connector.extension.generic.record.direct.exporter" + + def wpml_run(self, relation, always=True, internal_fields=None): + res = [] + langs_to_export = self.backend_record.lang_ids.mapped("code") + if not langs_to_export: + raise ValidationError( + _( + "You need to define at least one language to export " + "in the WooCommerce WPML Backend (%s)." + ) + % self.backend_record.name + ) + for lang in langs_to_export: + result = super().run( + relation.with_context(lang=lang), + always=always, + internal_fields=internal_fields, + ) + res.append(result) + return res diff --git a/connector_woocommerce_wpml/models/sale_order/__init__.py b/connector_woocommerce_wpml/models/sale_order/__init__.py new file mode 100644 index 000000000..f502287fe --- /dev/null +++ b/connector_woocommerce_wpml/models/sale_order/__init__.py @@ -0,0 +1 @@ +from . import adapter diff --git a/connector_woocommerce_wpml/models/sale_order/adapter.py b/connector_woocommerce_wpml/models/sale_order/adapter.py new file mode 100644 index 000000000..ad23779a6 --- /dev/null +++ b/connector_woocommerce_wpml/models/sale_order/adapter.py @@ -0,0 +1,20 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class WooCommerceSaleOrderAdapter(Component): + _name = "woocommerce.sale.order.adapter" + _inherit = "woocommerce.sale.order.adapter" + + def _reorg_order_data(self, values): + res = super()._reorg_order_data(values) + for value in values: + if "meta_data" in value: + for meta_data in value["meta_data"]: + if meta_data["key"] == "wpml_language": + for product in value["products"]: + product["lang"] = meta_data["value"] + continue + return res diff --git a/connector_woocommerce_wpml/readme/CONFIGURE.rst b/connector_woocommerce_wpml/readme/CONFIGURE.rst new file mode 100644 index 000000000..533e10110 --- /dev/null +++ b/connector_woocommerce_wpml/readme/CONFIGURE.rst @@ -0,0 +1,4 @@ +On WPML setup, select the option Language name added as a parameter. +If it's already set, modify: + +* WPML > Languages > Language URL format, select the option "Language name added as a parameter" and save the changes. diff --git a/connector_woocommerce_wpml/readme/CONTRIBUTORS.rst b/connector_woocommerce_wpml/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..d018d7d8f --- /dev/null +++ b/connector_woocommerce_wpml/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `NuoBiT `__: + + * Kilian Niubo diff --git a/connector_woocommerce_wpml/readme/DESCRIPTION.rst b/connector_woocommerce_wpml/readme/DESCRIPTION.rst new file mode 100644 index 000000000..02b9a6bb4 --- /dev/null +++ b/connector_woocommerce_wpml/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +* This module works with plugin WordPress Multi Language. +* https://wpml.org/ diff --git a/connector_woocommerce_wpml/readme/ROADMAP.rst b/connector_woocommerce_wpml/readme/ROADMAP.rst new file mode 100644 index 000000000..002c88205 --- /dev/null +++ b/connector_woocommerce_wpml/readme/ROADMAP.rst @@ -0,0 +1,4 @@ +For a better maintenance, we can try to use a mixin component to define the common methods and properties. + + * binding: woocommerce_lang field and sql_constrains woocommerce_internal_uniq overwriting the common for all + * export_mapper: Include the translation_of and lang? diff --git a/connector_woocommerce_wpml/static/description/icon.png b/connector_woocommerce_wpml/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cd641e792c30455187ca30940bc0f329ce8bbb0 GIT binary patch literal 6342 zcmd^^hf`C}*TzHWpfm-MZa|7OjYtjE(4`3pP0Eid3J8V{0s)mKJ<q zp^9|rp$mb~2}po9-@oIXJG(oxcjoS%d!O@s&d!Z9HP*e##KQyt0IurmK_64bp8pyH z9i^|ds>-JfbWVo4P{8GX*QeIfbjl2)kDfIG0ALvZuTgp2ZfK=U();NfY11z-vM>r= zo6RyI007+P`cO@apy}VqnaiVCLL`CEUGVGYE&5WpdhhbZv%|*-Y|2t(4~Cq|y`-Nmm-W zxaTf4+R69rVU1b%qjm?yu*PFgHFYd#J82-D8cpXqO&omwG2*Hd6ZIUiK@+ zNCo8Lg{1^vn^0ZQgz*~*ZR3wsULxnnSBN%7p()3EYs>sX9In)T{*nJ2q*qxXPNhFk z=z=+?4VOOdAF!ZYAVisYzF29g?udLQJtx@=HoAK_Kjx;4SO7>H_v*McB7(}RHMa> z+PNao{Hw&Mjo0P}CBR&l(k@iIeRI@PRH6R9^lR3e?TL?ZHra#GHvKmkeVBHG8nv4{ zz$nHGR7`D$ae@TrcXCSA=$~Yvp@J|bKul>6s-`yT7>JaM5?KcltZ)(ilt^74fqLA{ z1k!bKw(GMV*AOgI*glG_($h!cZgArkEAa1SkSG`0yF8JLWTq^J->2CRaqKH1ZSQt7 z29|+OBS3Rj91K1XL~_9&zn1p z)2Ez)&{9Of1X#b+mpgJ`{gurrlYqKrwrWXTOH{M%kEUhcgSp1J2FK4FF`JS|NfaAA6)?-&1}B`@lI2~kKWK) zhQ|}GQ$j(rNS}9?Yu9}MzWxz*HMwR=u8$RYY6sr2pu3x5Yx*P!Z&c|X zFZcC{+kqJV=XTZH=cMb6)MtgWo%C~XU8TEXDKx9;0hEV*74Z6i8vuzXp zw<8QvI~;n;3@<^G0C#HHf2{N6E~2DO3jw!?w}z?_vV6Q>?kJ>IF-kEc*TtP}k7cVd zvtdPgQ^jWhMXAL$Lqn!_A_IL+!hbY37)n@Sqc)6JwD4)3LP`up1cy^EXzh>B{$ce0 zgX~Iat{I@DM|zU|>9DuD?g}h7zCqV;o1*~3Hr=DYjDq;SG?3HS)(x+l@HAa-@>5wH zhw`oqg>hP$e41h5)>$#qFWq?LGX`dC8ph`RyR&_z&og>psSHzZ=_8<-M4yk+3HK-+ zxqe%Ntx88}49jJazM_Vov;)83cSeeLv@taHOL>zP>~bqdmEyfHl9M%`@ivb|7{I;N zzyHw9P7EH0$ww52RejJv>zvSr8v*iuX@X;(Z~NuUv$D0I_>OkcZWSulBUJjHUN=n| zSI$q@$)`(E;^(|}q|2utYl8}>IcXkPX#{6Z%JnhUBly1B@B}sECm2Y88-QrQZd2n2 zKL=1_&Z87xM=GaycA-Ac*R<^bJk>-^k%lt;DjswC+AM`71*2iG?;!3Bc)I>55v)^C zkt+Uzn&dhv|58XAY6{%ybSiVMl-sATTy=SUADQWD+(@-AVqg@Y+_fBV$LJnIEfujI4B5%4a@8S4M*50Lh7NqKSW>K=U5dW@)Hd{^oR4v% zCM2(rAq7Qe-)R0ko{l@iCHGsxhkCNWby zf&gByp!>=?r1ecWMqz5e-BmOED6n!_1V4<)R!!QNwM!AyGty8>p>ebEzdp*_(kAYA z5*F^g_K}%Rm;V}4Q46qJpU+&3bU10WYg{j`T>lv9{B)J}RHC}yzy9x)wm4ju23yQ& zUNm(i_(ChqD8d7AVUFMw zXmia0A{l#}Sfq!GmHjatiTk$f|OvS0iG>W{p<8cZu^6HX`rMuX?l8<+?WVAW6 z3!MLV*VOFpd&STaeN2qdwU* zk1ni(wdh{`{hLj-hCz&59jVIp~SmgtSQDf!FrPYKIF6_c_NJr zn<-BdXVU}OSE{-No~b(6tG)250`-S%YB9Si@&}{d@FUGqjcNE@SlSdG`}H-#!~M1& z;{E-SKUBb6)KwP1XB|S8MB=F>9k$#1$|^*t%%5zq#(35~S#+TgC^oj&COt~T>axhU0t zQff{8Jt+NH^_pqPzec@Iv#L^r?qs$jdiCY&xOU2pve78Pc{a8y+D;2N0aEJe5d#uL}ZkkYQ&XA;NK5v>r@NUaj=<_V$*Ll@&CF!{LWI zh@|EE!!M(B5qeQ40YHy86TVkX6Te=v4ytV_-JnKl93#Z9clghd^lywoBtgj)4%mxKR<#pH0*hxyHFQNJ zGW`7CtD9C6)ehKni=#!gKj#ZO7L$d_i4nJZhR!z$B(rX9j$$L8X1>~^2By%Dp*IJj z8QiI6*w*|IoF{UpFaD{!PWdOxja{DQq9?BK%2(Xuh#Tv2s_ELIvb@YAd{Af)Lph(9 z>DTXZ`|*!Jnw)?`BzPrdYx(?S2&<(1>1>-f=c}gi8^)=KW973rikh?!-B$fOy@x-Rd+?x= zM(0SbmCz!gY#)CqB9J_^v4K$urOnoj|E||~D>%ndVMwe)ef3BuZH0l!Z&M@fyN}{1 zD;n{juZF|*{lehy$NlM{B`Q0Z18O|&=wX!Nt*rLKfak}ww{ zJ$9BJA3Tq4n~%w3V$0UA(+PgZ#j-35$=_xzuk(w5o2f(WOCu%+h>cg3B*aqaQdfeQ zj@VutKTWtH8{S+}vR3Z`KIQl-h!4tFi1vG-Kuh^Lb0N=LN0+1ZP!WL39=Age)HS_E z8khUbE>xA^59Nmj`B0@u0IR<04wqF@ssF4AP6ZVhslN61xT#8o@ymhOWJ5zkUQN07 zyDEYVZ4#Z$(%wnd04Y_^B_4gjFoKPWgD&OUsj^ezcuXa}E4yjc@xi#az zyRy6>?#h2*VNdNO_jYQ1{@qaYoN7moT}cnd8cmK*&R@SeSYZgIBaJklh!n-3#3dyO z!@*@06=Y8#wl9|Bj3=C0Fi!SfzVz7$Stc4_Q`K2P?2|gT!JIBhc*P&-IkB?Mb5I&% z%BN*TF#vYzIW>)|=X`Chr};G5EZXg?_yvlDC|f%AP!ty{i{{pXQnHm<^|{P$D; z9ZAW#l9Cd2($R5@*5}FeUd#l;N11WwITb1nJSm8r@`#sXHPsuq!3S2&h>U)y=3MjV;j3oWLY>5EOvuruXC*WH2G){378-0tpcMF}1(^PSWUe>XEJN%5 zl|m59cX=GC{^$_E-4Wm1=5|!;Ek&{<4lIOt5M&GMq=+JQdyt?WI#6C!)i!s4;k9T0 z{;`B*>VQ%iU)>Zbhgb4|vd=Wy4>107#gyeqi^+-^2E~0Ja&rFpRb<)oirMj4-KuLg zSo1*y98TZlD<3^A&^bRESh~S*Lzqn0l;JfX-fdjA`M#a!@?b?zWdEr3mIiqS{m2J% z3nWGoQG6+FQ~&gQF-DLGWF}WfwHL(4$EUt(5Jcx#l79K-x~qdu!_gs;XaP0`8m(8a z2J#B{UvEhLT=w9*(6bFWp{9CI=Z&Hh)e}}1hnK6fPlSYqu4H|>g|Erg5fVWl5w&~Kdf{3+V{dCaNhFDg<~sELf1dC($hw|SmSkZ zKD6>nsj6Q+aHEZDHC9{UJxPZ9y{6)F5hg5bm*}ihsxQxj~`xNo%QnaTEJn)f#{CK-H5HYAM7kK zL!XvElM^Y!yC=uSu54Gj zTEgKhtTCOqx1EcIl=VA7`!xLiUj%p*eH??_??@gOJJxVX)#(G`=31lw3whFi2Y7Mq z1bXLvi+~U5E4R{v15H@yQI@=d!V9LD&P!p?0u7L&Rg=D<<*+ zouj?2?aYI{Ac%Gx!r&EkXmmvR`!Xl?06WsGs_Ts8ojW?id!X$>C}@~q>BMfGeGohw zkR}NImw2grp7>W(5s*(iPYn$1*t@i%(W7u#6m}l)%TmD-221>N?VBna!@FO-7!xjM z{`_^-yt<@e?fK$Sqzc7O%3&~A>HB|stQr64jx(U3y+}d}vp(r7c=iB8>t~T7HmYg1qJe4SLo$e62=EZUuFS7UqbSP}M^@%aI7g!ztzj{)_R0x*X6OMLAky)_Sv&%2DNGv zxH}pEr{gEYf&ZF&RJoII9*=yd^~fxKtFc@1f_3}Vqqi8_U?;lC`7etN$3$u0dW+-%7P zQ~iX&gr(5xd1M>3yrzZav9ZLIhbS&|=U$t!9iq*i5vy)(RsBw0TU#?~zdTKUXjyIl z%7Q)Vp}YoU$acz-9y_`%Oig!%TPyC=ie3*Qut3@4V`+A4d<*f%jOx>*bX%#Ao+@wM z;NW0DZKvmp%_oxvFw2#S9r8Sc?wXh}`3gVG`rBKr&jpxwTRQ7WtKY06QQVhs$u$!e zs;Y%~2xwpH*9vxfQ~q#gAwn+P+=YE(L>|P(Fl&H27@?);kUI4FW%LjHZKYGk#f~@3 zXW;a;3+{&c`g+uCR+``$V9)N#RBCk_#RQ(K-PxlQ7Ym;XdCqGn$j%JmAwgtkWKn1} z8^>3&)Q05VbBm+t`9B_${w9F7WfM{Jvawk;HDc*{Sa_Sla|zqX!vbKV%>gB|z6BCc z8_bdnPnzloGP1I)!^5hnC6CLZUU`;nO2NF2)FaAkYhQL$Z58+`p75dj7RKse#Z!uacCm z0@|m~U!QZOdb|V~`ktFK4;lg_ZOCjFXeV4`jGj&bh7Q6BEyN8~yGd*JyzwFbIRaAf z#KG$rvQxWFvqwn`i6jBQ?6o+k+oOC)Gj9ChlgabiScr};b5|opxUYjCZOwmhjTj6W zFzJt_htTuopW4IRiQ}r0L}`w=pE{HN<@(9Hl11P5cHmN6A1F^sg2OWXcw<+q2x>I5 zq9Bu>PBob6#^vrr<|IC)m+zJpFRRcCVsqbspNybriu&!R=H^@RcG#aBGz9RH}ZI=>4 zi(m?IA?Vr$Q7?wN6ZW7H`S?3}K8=$7J5MjWKri=_igw1%J?0~*6e_Ii*1&23dGcF} z&=vaMgF!^veGQ1f$3k?WK5Jaw%==+Bb!tI6zQ68&-dQ3Orl+Tqh#Nt?dBEV_w^wkjY+qJ+X*NCMs%J-Lc4%}pKryM#O)O&9 un*HHVB-AlUN`suyDkKONktc!@Ievk;6wT20MOSqhE{1gM*SZGeqiYU literal 0 HcmV?d00001 diff --git a/connector_woocommerce_wpml/static/description/index.html b/connector_woocommerce_wpml/static/description/index.html new file mode 100644 index 000000000..39a30aae1 --- /dev/null +++ b/connector_woocommerce_wpml/static/description/index.html @@ -0,0 +1,443 @@ + + + + + +Connector WooCommerce WMPL + + + +
+

Connector WooCommerce WMPL

+ + +

Beta License: AGPL-3 NuoBiT/odoo-addons

+ +

Table of contents

+ +
+

Configuration

+

On WPML setup, select the option Language name added as a parameter. +If it’s already set, modify:

+
    +
  • WPML > Languages > Language URL format, select the option “Language name added as a parameter” and save the changes.
  • +
+
+
+

Known issues / Roadmap

+

For a better maintenance, we can try to use a mixin component to define the common methods and properties.

+
+
    +
  • binding: woocommerce_lang field and sql_constrains woocommerce_internal_uniq overwriting the common for all
  • +
  • export_mapper: Include the translation_of and lang?
  • +
+
+
+
+

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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • NuoBiT Solutions
  • +
  • S.L.
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the NuoBiT/odoo-addons project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/connector_woocommerce_wpml/views/product_attribute_value.xml b/connector_woocommerce_wpml/views/product_attribute_value.xml new file mode 100644 index 000000000..a3f1a1ddd --- /dev/null +++ b/connector_woocommerce_wpml/views/product_attribute_value.xml @@ -0,0 +1,31 @@ + + + + + woocommerce.product.attribute.value.view.form + woocommerce.product.attribute.value + + + + + + + + + woocommerce.product.attribute.value.view.tree + woocommerce.product.attribute.value + + + + + + + + diff --git a/connector_woocommerce_wpml/views/product_product.xml b/connector_woocommerce_wpml/views/product_product.xml new file mode 100644 index 000000000..8c9020d42 --- /dev/null +++ b/connector_woocommerce_wpml/views/product_product.xml @@ -0,0 +1,31 @@ + + + + + woocommerce.product.product.view.form + woocommerce.product.product + + + + + + + + + woocommerce.product.product.view.tree + woocommerce.product.product + + + + + + + + diff --git a/connector_woocommerce_wpml/views/product_public_category.xml b/connector_woocommerce_wpml/views/product_public_category.xml new file mode 100644 index 000000000..b3fb56b0f --- /dev/null +++ b/connector_woocommerce_wpml/views/product_public_category.xml @@ -0,0 +1,31 @@ + + + + + woocommerce.product.public.category.view.form + woocommerce.product.public.category + + + + + + + + + woocommerce.product.public.category.view.tree + woocommerce.product.public.category + + + + + + + + diff --git a/connector_woocommerce_wpml/views/product_template.xml b/connector_woocommerce_wpml/views/product_template.xml new file mode 100644 index 000000000..73c8f1320 --- /dev/null +++ b/connector_woocommerce_wpml/views/product_template.xml @@ -0,0 +1,31 @@ + + + + + woocommerce.product.template.view.form + woocommerce.product.template + + + + + + + + + woocommerce.product.template.view.tree + woocommerce.product.template + + + + + + + + diff --git a/connector_woocommerce_wpml/views/woocommerce_backend_view.xml b/connector_woocommerce_wpml/views/woocommerce_backend_view.xml new file mode 100644 index 000000000..67d7c1fe7 --- /dev/null +++ b/connector_woocommerce_wpml/views/woocommerce_backend_view.xml @@ -0,0 +1,22 @@ + + + + + woocommerce.backend.form + woocommerce.backend + + + + + + + + diff --git a/setup/connector_woocommerce_wpml/odoo/addons/connector_woocommerce_wpml b/setup/connector_woocommerce_wpml/odoo/addons/connector_woocommerce_wpml new file mode 120000 index 000000000..e1b06d23f --- /dev/null +++ b/setup/connector_woocommerce_wpml/odoo/addons/connector_woocommerce_wpml @@ -0,0 +1 @@ +../../../../connector_woocommerce_wpml \ No newline at end of file diff --git a/setup/connector_woocommerce_wpml/setup.py b/setup/connector_woocommerce_wpml/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/connector_woocommerce_wpml/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From 83744adf796acadd3373be7bf73167d123d1b9df Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Tue, 18 Nov 2025 12:39:52 +0100 Subject: [PATCH 2/9] [REF] connector_wordpress_wpml: rename sql constraints --- .../models/product_attribute_value/binding.py | 2 +- connector_woocommerce_wpml/models/product_product/binding.py | 2 +- .../models/product_public_category/binding.py | 2 +- connector_woocommerce_wpml/models/product_template/binding.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/connector_woocommerce_wpml/models/product_attribute_value/binding.py b/connector_woocommerce_wpml/models/product_attribute_value/binding.py index 0d55b2d0e..36dc73006 100644 --- a/connector_woocommerce_wpml/models/product_attribute_value/binding.py +++ b/connector_woocommerce_wpml/models/product_attribute_value/binding.py @@ -14,7 +14,7 @@ class WooCommerceProductAttributeValue(models.Model): _sql_constraints = [ ( - "woocommerce_internal_uniq", + "internal_uniq", "unique(backend_id, woocommerce_lang, odoo_id)", "A binding already exists with the same Internal (Odoo) ID.", ), diff --git a/connector_woocommerce_wpml/models/product_product/binding.py b/connector_woocommerce_wpml/models/product_product/binding.py index 932ed18b3..6cd9a841d 100644 --- a/connector_woocommerce_wpml/models/product_product/binding.py +++ b/connector_woocommerce_wpml/models/product_product/binding.py @@ -14,7 +14,7 @@ class WooCommerceProductProduct(models.Model): _sql_constraints = [ ( - "woocommerce_internal_uniq", + "internal_uniq", "unique(backend_id, woocommerce_lang, odoo_id)", "A binding already exists with the same Internal (Odoo) ID.", ), diff --git a/connector_woocommerce_wpml/models/product_public_category/binding.py b/connector_woocommerce_wpml/models/product_public_category/binding.py index ddf0313ce..46e514949 100644 --- a/connector_woocommerce_wpml/models/product_public_category/binding.py +++ b/connector_woocommerce_wpml/models/product_public_category/binding.py @@ -14,7 +14,7 @@ class WooCommerceProductPublicCategory(models.Model): _sql_constraints = [ ( - "woocommerce_internal_uniq", + "internal_uniq", "unique(backend_id, woocommerce_lang, odoo_id)", "A binding already exists with the same Internal (Odoo) ID.", ), diff --git a/connector_woocommerce_wpml/models/product_template/binding.py b/connector_woocommerce_wpml/models/product_template/binding.py index 258595398..901fbbdb5 100644 --- a/connector_woocommerce_wpml/models/product_template/binding.py +++ b/connector_woocommerce_wpml/models/product_template/binding.py @@ -14,7 +14,7 @@ class WooCommerceProductTemplate(models.Model): _sql_constraints = [ ( - "woocommerce_internal_uniq", + "internal_uniq", "unique(backend_id, woocommerce_lang, odoo_id)", "A binding already exists with the same Internal (Odoo) ID.", ), From 2cd682b28de639abdeb78c19eff0296326934ad3 Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Tue, 18 Nov 2025 16:04:06 +0100 Subject: [PATCH 3/9] [REF+IMP] connector_woocommerce_wpml: deduplicate code using a binding mixin --- connector_woocommerce_wpml/models/__init__.py | 1 + .../models/binding/__init__.py | 1 + .../models/binding/binding.py | 25 ++++++++++++++++ .../models/product_attribute_value/binding.py | 30 +++++++------------ .../models/product_product/binding.py | 29 +++++++----------- .../models/product_public_category/binding.py | 29 +++++++----------- .../models/product_template/binding.py | 29 +++++++----------- .../models/product_wpml_mixin/__init__.py | 1 - .../models/product_wpml_mixin/binding.py | 16 ---------- 9 files changed, 67 insertions(+), 94 deletions(-) create mode 100644 connector_woocommerce_wpml/models/binding/__init__.py create mode 100644 connector_woocommerce_wpml/models/binding/binding.py delete mode 100644 connector_woocommerce_wpml/models/product_wpml_mixin/binding.py diff --git a/connector_woocommerce_wpml/models/__init__.py b/connector_woocommerce_wpml/models/__init__.py index 2ab3d2f32..2f0a161f8 100644 --- a/connector_woocommerce_wpml/models/__init__.py +++ b/connector_woocommerce_wpml/models/__init__.py @@ -1,5 +1,6 @@ from . import product_wpml_mixin from . import backend +from . import binding from . import product_attribute_value from . import product_product from . import product_public_category diff --git a/connector_woocommerce_wpml/models/binding/__init__.py b/connector_woocommerce_wpml/models/binding/__init__.py new file mode 100644 index 000000000..0fec82e8a --- /dev/null +++ b/connector_woocommerce_wpml/models/binding/__init__.py @@ -0,0 +1 @@ +from . import binding diff --git a/connector_woocommerce_wpml/models/binding/binding.py b/connector_woocommerce_wpml/models/binding/binding.py new file mode 100644 index 000000000..197daa242 --- /dev/null +++ b/connector_woocommerce_wpml/models/binding/binding.py @@ -0,0 +1,25 @@ +# Copyright 2025 NuoBiT Solutions - Eric Antones +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class WoocommerceWPMLBindingMixin(models.AbstractModel): + _name = "woocommerce.wpml.binding.mixin" + _description = "WooCommerce WPML Binding Mixin" + + woocommerce_lang = fields.Char( + string="Language", + required=True, + ) + + # overwrite the constraint of the parent with the lang field + # the names should be this one to be sure they overwrite the parents + _sql_constraints = [ + ( + "internal_uniq", + "unique(backend_id, woocommerce_lang)", + "A binding already exists with the same language, " + "hence with the same Internal (Odoo) ID.", + ), + ] diff --git a/connector_woocommerce_wpml/models/product_attribute_value/binding.py b/connector_woocommerce_wpml/models/product_attribute_value/binding.py index 36dc73006..e680743c7 100644 --- a/connector_woocommerce_wpml/models/product_attribute_value/binding.py +++ b/connector_woocommerce_wpml/models/product_attribute_value/binding.py @@ -1,27 +1,17 @@ # Copyright NuoBiT Solutions - Kilian Niubo +# Copyright 2025 NuoBiT Solutions - Eric Antones # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo import fields, models +from odoo import models +from ..binding.binding import WoocommerceWPMLBindingMixin -class WooCommerceProductAttributeValue(models.Model): - _inherit = "woocommerce.product.attribute.value" - - woocommerce_lang = fields.Char( - string="Language", - required=True, - ) - _sql_constraints = [ - ( - "internal_uniq", - "unique(backend_id, woocommerce_lang, odoo_id)", - "A binding already exists with the same Internal (Odoo) ID.", - ), - ( - "external_uniq", - "unique(backend_id, woocommerce_lang, woocommerce_idattributevalue)", - "A binding already exists with the same External " - "(woocommerce_idattributevalue) ID.", - ), +class WooCommerceProductAttributeValue(models.Model): + _name = "woocommerce.product.attribute.value" + _inherit = [ + "woocommerce.product.attribute.value", + "woocommerce.wpml.binding.mixin", ] + + _sql_constraints = WoocommerceWPMLBindingMixin._sql_constraints diff --git a/connector_woocommerce_wpml/models/product_product/binding.py b/connector_woocommerce_wpml/models/product_product/binding.py index 6cd9a841d..a8038c169 100644 --- a/connector_woocommerce_wpml/models/product_product/binding.py +++ b/connector_woocommerce_wpml/models/product_product/binding.py @@ -1,30 +1,21 @@ # Copyright NuoBiT Solutions - Kilian Niubo +# Copyright 2025 NuoBiT Solutions - Eric Antones # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo import fields, models +from odoo import models +from ..binding.binding import WoocommerceWPMLBindingMixin -class WooCommerceProductProduct(models.Model): - _inherit = "woocommerce.product.product" - - woocommerce_lang = fields.Char( - string="Language", - required=True, - ) - _sql_constraints = [ - ( - "internal_uniq", - "unique(backend_id, woocommerce_lang, odoo_id)", - "A binding already exists with the same Internal (Odoo) ID.", - ), - ( - "external_uniq", - "unique(backend_id, woocommerce_lang, woocommerce_idproduct)", - "A binding already exists with the same External (idProduct) ID.", - ), +class WooCommerceProductProduct(models.Model): + _name = "woocommerce.product.product" + _inherit = [ + "woocommerce.product.product", + "woocommerce.wpml.binding.mixin", ] + _sql_constraints = WoocommerceWPMLBindingMixin._sql_constraints + # TODO: This function should be an overwrite of the original one, # it should be refactored to avoid code duplication # doing a hook to set a context variable with lang diff --git a/connector_woocommerce_wpml/models/product_public_category/binding.py b/connector_woocommerce_wpml/models/product_public_category/binding.py index 46e514949..fd9e28f3c 100644 --- a/connector_woocommerce_wpml/models/product_public_category/binding.py +++ b/connector_woocommerce_wpml/models/product_public_category/binding.py @@ -1,26 +1,17 @@ # Copyright NuoBiT Solutions - Kilian Niubo +# Copyright 2025 NuoBiT Solutions - Eric Antones # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo import fields, models +from odoo import models +from ..binding.binding import WoocommerceWPMLBindingMixin -class WooCommerceProductPublicCategory(models.Model): - _inherit = "woocommerce.product.public.category" - - woocommerce_lang = fields.Char( - string="Language", - required=True, - ) - _sql_constraints = [ - ( - "internal_uniq", - "unique(backend_id, woocommerce_lang, odoo_id)", - "A binding already exists with the same Internal (Odoo) ID.", - ), - ( - "external_uniq", - "unique(backend_id, woocommerce_lang, woocommerce_idpubliccategory)", - "A binding already exists with the same External (idpublidcategory) ID.", - ), +class WooCommerceProductPublicCategory(models.Model): + _name = "woocommerce.product.public.category" + _inherit = [ + "woocommerce.product.public.category", + "woocommerce.wpml.binding.mixin", ] + + _sql_constraints = WoocommerceWPMLBindingMixin._sql_constraints diff --git a/connector_woocommerce_wpml/models/product_template/binding.py b/connector_woocommerce_wpml/models/product_template/binding.py index 901fbbdb5..9a4506bca 100644 --- a/connector_woocommerce_wpml/models/product_template/binding.py +++ b/connector_woocommerce_wpml/models/product_template/binding.py @@ -1,30 +1,21 @@ # Copyright NuoBiT Solutions - Kilian Niubo +# Copyright 2025 NuoBiT Solutions - Eric Antones # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo import fields, models +from odoo import models +from ..binding.binding import WoocommerceWPMLBindingMixin -class WooCommerceProductTemplate(models.Model): - _inherit = "woocommerce.product.template" - - woocommerce_lang = fields.Char( - string="Language", - required=True, - ) - _sql_constraints = [ - ( - "internal_uniq", - "unique(backend_id, woocommerce_lang, odoo_id)", - "A binding already exists with the same Internal (Odoo) ID.", - ), - ( - "external_uniq", - "unique(backend_id, woocommerce_lang, woocommerce_idproduct)", - "A binding already exists with the same External (idProduct) ID.", - ), +class WooCommerceProductTemplate(models.Model): + _name = "woocommerce.product.template" + _inherit = [ + "woocommerce.product.template", + "woocommerce.wpml.binding.mixin", ] + _sql_constraints = WoocommerceWPMLBindingMixin._sql_constraints + # TODO: This function should be an overwrite of the original one, # it should be refactored to avoid code duplication # doing a hook to set a context variable with lang diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/__init__.py b/connector_woocommerce_wpml/models/product_wpml_mixin/__init__.py index 65af6d7fa..e51edd774 100644 --- a/connector_woocommerce_wpml/models/product_wpml_mixin/__init__.py +++ b/connector_woocommerce_wpml/models/product_wpml_mixin/__init__.py @@ -1,4 +1,3 @@ from . import adapter from . import binder -from . import binding from . import exporter diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/binding.py b/connector_woocommerce_wpml/models/product_wpml_mixin/binding.py deleted file mode 100644 index 62af27ec3..000000000 --- a/connector_woocommerce_wpml/models/product_wpml_mixin/binding.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright NuoBiT Solutions - Kilian Niubo -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) - - -# TODO: delete this file -# class WooCommerceBinding(models.AbstractModel): -# _inherit = "woocommerce.binding" -# -# def _prepare_relation(self, relation, record): -# relation = super()._prepare_relation(relation, record) -# iso_lang = self.env["res.lang"]._get_iso_code_from_wpml_code( -# record.woocommerce_lang -# ) -# if iso_lang: -# return relation.with_context(lang=iso_lang, resync_export=True) -# return relation From e7c25b0140bbccb8591ac5119a6a76bb301b4fa9 Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Tue, 2 Dec 2025 13:22:41 +0100 Subject: [PATCH 4/9] [IMP+FIX+REF] connector_woocommerce_wpml --- connector_woocommerce_wpml/models/__init__.py | 1 + .../models/binding/binding.py | 2 +- .../models/product/__init__.py | 1 + .../models/product/export_mapper.py | 11 ++ .../models/product_attribute_value/binder.py | 6 - .../product_attribute_value/export_mapper.py | 44 +++-- .../models/product_product/adapter.py | 45 ++--- .../models/product_product/binder.py | 10 +- .../models/product_product/binding.py | 44 ++++- .../models/product_product/export_mapper.py | 171 ++++++++++++++---- .../models/product_public_category/adapter.py | 4 +- .../models/product_public_category/binder.py | 4 +- .../models/product_template/adapter.py | 65 +++---- .../models/product_template/binder.py | 10 +- .../models/product_template/binding.py | 59 +++++- .../models/product_template/export_mapper.py | 100 ++++++++-- .../models/product_wpml_mixin/adapter.py | 9 +- .../models/product_wpml_mixin/binder.py | 4 +- .../models/product_wpml_mixin/exporter.py | 11 +- .../views/product_product.xml | 6 + .../views/product_template.xml | 6 + 21 files changed, 465 insertions(+), 148 deletions(-) create mode 100644 connector_woocommerce_wpml/models/product/__init__.py create mode 100644 connector_woocommerce_wpml/models/product/export_mapper.py diff --git a/connector_woocommerce_wpml/models/__init__.py b/connector_woocommerce_wpml/models/__init__.py index 2f0a161f8..a3677d54e 100644 --- a/connector_woocommerce_wpml/models/__init__.py +++ b/connector_woocommerce_wpml/models/__init__.py @@ -1,3 +1,4 @@ +from . import product from . import product_wpml_mixin from . import backend from . import binding diff --git a/connector_woocommerce_wpml/models/binding/binding.py b/connector_woocommerce_wpml/models/binding/binding.py index 197daa242..786ac8b83 100644 --- a/connector_woocommerce_wpml/models/binding/binding.py +++ b/connector_woocommerce_wpml/models/binding/binding.py @@ -18,7 +18,7 @@ class WoocommerceWPMLBindingMixin(models.AbstractModel): _sql_constraints = [ ( "internal_uniq", - "unique(backend_id, woocommerce_lang)", + "unique(backend_id, odoo_id, woocommerce_lang)", "A binding already exists with the same language, " "hence with the same Internal (Odoo) ID.", ), diff --git a/connector_woocommerce_wpml/models/product/__init__.py b/connector_woocommerce_wpml/models/product/__init__.py new file mode 100644 index 000000000..34ea264d2 --- /dev/null +++ b/connector_woocommerce_wpml/models/product/__init__.py @@ -0,0 +1 @@ +from . import export_mapper diff --git a/connector_woocommerce_wpml/models/product/export_mapper.py b/connector_woocommerce_wpml/models/product/export_mapper.py new file mode 100644 index 000000000..a1d4be692 --- /dev/null +++ b/connector_woocommerce_wpml/models/product/export_mapper.py @@ -0,0 +1,11 @@ +# Copyright NuoBiT Solutions - Kilian Niubo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import AbstractComponent + + +class WooCommerceProductExportMapper(AbstractComponent): + _inherit = "woocommerce.product.export.mapper" + + def _get_lang_doc(self, obj): + return obj diff --git a/connector_woocommerce_wpml/models/product_attribute_value/binder.py b/connector_woocommerce_wpml/models/product_attribute_value/binder.py index d7ad3cf37..568068784 100644 --- a/connector_woocommerce_wpml/models/product_attribute_value/binder.py +++ b/connector_woocommerce_wpml/models/product_attribute_value/binder.py @@ -17,9 +17,3 @@ def external_alt_id(self): def get_binding_domain(self, record): return self.wpml_get_binding_domain(record) - - def _additional_external_binding_fields(self, external_data): - return self.wpml_additional_external_binding_fields(external_data) - - # def unwrap_binding(self, binding): - # return self.wpml_unwrap_binding(binding) diff --git a/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py b/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py index 72cbecf7c..05a8af19b 100644 --- a/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py +++ b/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py @@ -20,18 +20,18 @@ def name(self, record): dict_name["name"] = record.name return dict_name - @required("parent_id") - @changed_by("attribute_id") - @mapping - def parent_id(self, record): - parent_dict = super().parent_id(record) - if "parent_id" in parent_dict: - parent_dict["parent_id"] = parent_dict["parent_id"] - binder = self.binder_for("woocommerce.product.attribute") - values = binder.get_external_dict_ids(record.attribute_id) - return {"parent_id": values["id"] or None} + # @required("parent_id") + # @changed_by("attribute_id") + # @mapping + # def parent_id(self, record): + # parent_dict = super().parent_id(record) + # if "parent_id" in parent_dict: + # parent_dict["parent_id"] = parent_dict["parent_id"] + # binder = self.binder_for("woocommerce.product.attribute") + # values = binder.get_external_dict_ids(record.attribute_id) + # return {"parent_id": values["id"] or None} - @changed_by("attribute_id") + @changed_by("parent_name") @mapping def parent_name(self, record): dict_name = super().parent_name(record) @@ -53,16 +53,24 @@ def lang(self, record): @only_create @mapping def translation_of(self, record): - lang_code = record._context.get("lang") - if lang_code: + odoo_lang_code = record._context.get("lang") + if odoo_lang_code: + wpml_lang_code = self.env["res.lang"]._get_wpml_code_from_iso_code( + odoo_lang_code + ) + default_woml_lang_code = self.env["res.lang"]._get_wpml_code_from_iso_code( + self.backend_record.language_id.code + ) other_binding_backend = record.woocommerce_bind_ids.filtered( lambda x: x.backend_id == self.backend_record - and x.woocommerce_lang - != self.env["res.lang"]._get_wpml_code_from_iso_code( - record._context.get("lang") + and x.woocommerce_lang != wpml_lang_code + ).sorted( + lambda x: ( + x.woocommerce_lang != default_woml_lang_code, + x.woocommerce_lang, ) ) translation_of = None - for obb in other_binding_backend: - translation_of = obb.woocommerce_idattributevalue + if other_binding_backend: + translation_of = other_binding_backend[0].woocommerce_idattributevalue return {"translation_of": translation_of} diff --git a/connector_woocommerce_wpml/models/product_product/adapter.py b/connector_woocommerce_wpml/models/product_product/adapter.py index d288ffde2..839d8c278 100644 --- a/connector_woocommerce_wpml/models/product_product/adapter.py +++ b/connector_woocommerce_wpml/models/product_product/adapter.py @@ -11,28 +11,29 @@ class WooCommerceProductProductAdapter(Component): "woocommerce.product.wpml.mixin.adapter", ] - def create(self, data): - if data.get("translation_of"): - sku = data.pop("sku") - res = super().create(data) - if data.get("translation_of"): - if res and isinstance(res, dict): - external_id = res.get("id") - parent_id = res.get("parent_id") - if external_id: - url_l = "products/%s/variations/%s" % (parent_id, external_id) - self._exec("put", url_l, data={"sku": sku}) - return res - - def write(self, external_id, data): # pylint: disable=W8106 - old_sku = None - if data.get("sku"): - old_sku = data.pop("sku") - res = super().write(external_id, data) - if old_sku and res.get("data").get("sku") != old_sku: - data["sku"] = old_sku - res = super().write(external_id, data) - return res + # TODO AQUI: do we need this 2 next methods + # def create(self, data): + # if data.get("translation_of"): + # sku = data.pop("sku") + # res = super().create(data) + # if data.get("translation_of"): + # if res and isinstance(res, dict): + # external_id = res.get("id") + # parent_id = res.get("parent_id") + # if external_id: + # url_l = "products/%s/variations/%s" % (parent_id, external_id) + # self._exec("put", url_l, data={"sku": sku}) + # return res + + # def write(self, external_id, data): # pylint: disable=W8106 + # old_sku = None + # if data.get("sku"): + # old_sku = data.pop("sku") + # res = super().write(external_id, data) + # if old_sku and res.get("data").get("sku") != old_sku: + # data["sku"] = old_sku + # res = super().write(external_id, data) + # return res # TODO: REVIEW: can we return this in better way? def _get_search_fields(self): diff --git a/connector_woocommerce_wpml/models/product_product/binder.py b/connector_woocommerce_wpml/models/product_product/binder.py index 9738b322f..c42c2b8fc 100644 --- a/connector_woocommerce_wpml/models/product_product/binder.py +++ b/connector_woocommerce_wpml/models/product_product/binder.py @@ -19,8 +19,14 @@ def external_alt_id(self): def get_binding_domain(self, record): return self.wpml_get_binding_domain(record) - def _additional_external_binding_fields(self, external_data): - return self.wpml_additional_external_binding_fields(external_data) + def _additional_external_binding_fields(self, external_data, relation): + return self.wpml_additional_external_binding_fields(external_data, relation) + + def wpml_additional_external_binding_fields(self, external_data, relation): + return { + **super().wpml_additional_external_binding_fields(external_data, relation), + "woocommerce_master_lang": relation._context["first_lang"], + } # def unwrap_binding(self, binding): # return self.wpml_unwrap_binding(binding) diff --git a/connector_woocommerce_wpml/models/product_product/binding.py b/connector_woocommerce_wpml/models/product_product/binding.py index a8038c169..7760a7a92 100644 --- a/connector_woocommerce_wpml/models/product_product/binding.py +++ b/connector_woocommerce_wpml/models/product_product/binding.py @@ -2,7 +2,8 @@ # Copyright 2025 NuoBiT Solutions - Eric Antones # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo import models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError from ..binding.binding import WoocommerceWPMLBindingMixin @@ -13,9 +14,36 @@ class WooCommerceProductProduct(models.Model): "woocommerce.product.product", "woocommerce.wpml.binding.mixin", ] + _order = ( + "backend_id, product_tmpl_id, odoo_id, woocommerce_master_lang desc, " + " woocommerce_lang, woocommerce_idparent," + "woocommerce_idproduct" + ) + + # TODO: add this to the mixin + woocommerce_master_lang = fields.Boolean( + string="WooCommerce Master Language", + readonly=True, + required=True, + default=False, + ) _sql_constraints = WoocommerceWPMLBindingMixin._sql_constraints + @api.constrains("woocommerce_master_lang", "backend_id", "odoo_id") + def _check_woocommerce_master_lang(self): + for rec in self: + master_bindings = rec.odoo_id.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == rec.backend_id and x.woocommerce_master_lang + ) + if len(master_bindings) != 1: + raise ValidationError( + _( + "It should always be one and exactly one binding with" + " master language enabled" + ) + ) + # TODO: This function should be an overwrite of the original one, # it should be refactored to avoid code duplication # doing a hook to set a context variable with lang @@ -29,3 +57,17 @@ class WooCommerceProductProduct(models.Model): # lambda x: x.backend_id == rec.backend_id # and x.woocommerce_lang == rec.woocommerce_lang # ).with_context(resync_product_product=True).resync_export() + + # def unlink(self): + # to_remove, to_remove_variants = self.env[self._name], + # self.env["woocommerce.product.product"] + # for rec in self: + # to_remove |= rec.odoo_id.woocommerce_bind_ids.filtered( + # lambda x: x.backend_id == rec.backend_id #and x != rec + # ) + # to_remove_variants |= rec.odoo_id.with_context(active_test=False) + # .product_variant_ids.woocommerce_bind_ids.filtered( + # lambda x: x.backend_id == rec.backend_id + # ) + # to_remove_variants.with_context().unlink() + # return super(WooCommerceProductTemplate, to_remove).unlink() diff --git a/connector_woocommerce_wpml/models/product_product/export_mapper.py b/connector_woocommerce_wpml/models/product_product/export_mapper.py index 8e3704ee9..31eb9353c 100644 --- a/connector_woocommerce_wpml/models/product_product/export_mapper.py +++ b/connector_woocommerce_wpml/models/product_product/export_mapper.py @@ -1,8 +1,12 @@ # Copyright NuoBiT Solutions - Kilian Niubo +# Copyright NuoBiT Solutions - Eric Antones # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import _ +from odoo.exceptions import ValidationError from odoo.addons.component.core import Component -from odoo.addons.connector.components.mapper import changed_by, mapping, only_create +from odoo.addons.connector.components.mapper import changed_by, mapping +from odoo.addons.connector_extension.common import tools class WooCommerceProductProductExportMapper(Component): @@ -16,48 +20,145 @@ class WooCommerceProductProductExportMapper(Component): @mapping def lang(self, record): # TODO: unify this code. Probably do a function in res lang - lang = self.env["res.lang"]._get_wpml_code_from_iso_code( - record._context.get("lang") - ) - return {"lang": lang} + odoo_lang = record._context.get("lang") + if not odoo_lang: + raise ValidationError(_("Language must be always set")) + wc_lang = self.env["res.lang"]._get_wpml_code_from_iso_code(odoo_lang) + return {"lang": wc_lang} - @only_create + # @only_create + # @mapping + # def translation_of(self, record): + # lang_code = record._context.get("lang") + # if lang_code: + # source_lang_code = self.backend_record.lang_ids[0].code + # if lang_code == source_lang_code: + # # We don't need to set translation_of for the default lang + # return {} + # else: + # wpml_code = self.env["res.lang"]._get_wpml_code_from_iso_code( + # source_lang_code + # ) + # master_binding_backend = record.woocommerce_bind_ids.filtered( + # lambda x: x.backend_id == self.backend_record + # and x.woocommerce_lang == wpml_code + # ) + # translation_of = None + # if master_binding_backend: + # translation_of = master_binding_backend.woocommerce_idproduct + # return {"translation_of": translation_of} + + # TDOO: Make this only_create @mapping def translation_of(self, record): - lang_code = record._context.get("lang") - if lang_code: - source_lang_code = self.backend_record.lang_ids[0].code - if lang_code == source_lang_code: - # We don't need to set translation_of for the default lang - return {} + binding = self.options["binding"] if self.options else None + if not binding: + if not record._context.get("lang"): + raise ValidationError(_("Language must be always set")) + if "first_lang" not in record._context: + raise ValidationError(_("First lang must be always set on context")) + if not isinstance(record._context["first_lang"], bool): + raise ValidationError(_("First lang must be a boolean")) + first_lang = record._context["first_lang"] + if first_lang: + odoo_lang = record._context["lang"] + if odoo_lang != self.backend_record.language_id.code: + raise ValidationError( + _( + "Unexpected!! The first language on creation should be " + "always the same defined in the backend." + ) + ) else: - wpml_code = self.env["res.lang"]._get_wpml_code_from_iso_code( - source_lang_code - ) - master_binding_backend = record.woocommerce_bind_ids.filtered( + other_bindings = record.woocommerce_bind_ids.filtered( lambda x: x.backend_id == self.backend_record - and x.woocommerce_lang == wpml_code ) - translation_of = None - if master_binding_backend: - translation_of = master_binding_backend.woocommerce_idproduct - return {"translation_of": translation_of} + if not other_bindings: + raise ValidationError( + _( + "Unexpected. No other bindings found when it should because" + " this is not the first language!!" + ) + ) + master_binding = other_bindings.filtered( + lambda x: x.woocommerce_master_lang + ) + if not master_binding: + raise ValidationError( + _( + "Unexpected. No Master language found! On creation " + "of additional languages it shoould " + "always already exists the master language should be " + "always the same defined in the backend." + ) + ) + + return {"translation_of": master_binding.woocommerce_idproduct} def _get_product_description(self, record): - # We don't need check backend_record lang - # because record already has lang on context - return record.variant_public_description + res = False + odoo_lang = record._context.get("lang") + if not odoo_lang: + raise ValidationError(_("Language must be always set")) + if odoo_lang == self.backend_record.language_id.code: + res = super()._get_product_description(record) + else: + # We don't need check backend_record lang + # because record already has lang on context + description = record.variant_public_description + if description: + res = tools.color_rgb2hex(description) + return res + @changed_by("default_code") @mapping - def attributes(self, record): - binder = self.binder_for("woocommerce.product.attribute") - attr_list = [] - for value in record.product_template_attribute_value_ids: - values = binder.get_external_dict_ids(value.attribute_id) - attr_list.append( - { - "id": values["id"], - "option": value.name, - } + def sku(self, record): + binding = self.options["binding"] if self.options else None + if binding: + if binding.woocommerce_master_lang: + return super().sku(record) + else: + master_binding = record.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == self.backend_record + and x.woocommerce_master_lang ) - return {"attributes": attr_list} + if master_binding: + if len(master_binding) > 1: + raise ValidationError( + _( + "It should always be one and exactly one binding " + "with master language emabled" + ) + ) + else: + if not record._context.get("lang"): + raise ValidationError(_("Language must be always set")) + if "first_lang" not in record._context: + raise ValidationError(_("First lang must be always set on context")) + if not isinstance(record._context["first_lang"], bool): + raise ValidationError(_("First lang must be a boolean")) + first_lang = record._context["first_lang"] + if first_lang: + odoo_lang = record._context["lang"] + if odoo_lang != self.backend_record.language_id.code: + raise ValidationError( + _( + "Unexpected!! The first language on creation should " + "be always the same defined in the backend." + ) + ) + return super().sku(record) + + # @mapping + # def attributes(self, record): + # binder = self.binder_for("woocommerce.product.attribute") + # attr_list = [] + # for value in record.product_template_attribute_value_ids: + # values = binder.get_external_dict_ids(value.attribute_id) + # attr_list.append( + # { + # "id": values["id"], + # "option": value.name, + # } + # ) + # return {"attributes": attr_list} diff --git a/connector_woocommerce_wpml/models/product_public_category/adapter.py b/connector_woocommerce_wpml/models/product_public_category/adapter.py index b5d6cb8b7..2b9aa168d 100644 --- a/connector_woocommerce_wpml/models/product_public_category/adapter.py +++ b/connector_woocommerce_wpml/models/product_public_category/adapter.py @@ -14,7 +14,7 @@ class WooCommerceProductPublicCategoryAdapter(Component): ] def _manage_error_codes( - self, res_data, res, resource, raise_on_error=True, **kwargs + self, op, res_data, res, resource, *args, raise_on_error=True, **kwargs ): if res.status_code == 500: if res_data.get("code") == "duplicate_term_slug": @@ -38,7 +38,7 @@ def _manage_error_codes( return error_message return super()._manage_error_codes( - res_data, res, resource, raise_on_error=True, **kwargs + op, res_data, res, resource, *args, raise_on_error=raise_on_error, **kwargs ) def _get_search_fields(self): diff --git a/connector_woocommerce_wpml/models/product_public_category/binder.py b/connector_woocommerce_wpml/models/product_public_category/binder.py index acd2646a9..9a817ca24 100644 --- a/connector_woocommerce_wpml/models/product_public_category/binder.py +++ b/connector_woocommerce_wpml/models/product_public_category/binder.py @@ -18,8 +18,8 @@ def external_alt_id(self): def get_binding_domain(self, record): return self.wpml_get_binding_domain(record) - def _additional_external_binding_fields(self, external_data): - return self.wpml_additional_external_binding_fields(external_data) + def _additional_external_binding_fields(self, external_data, relation): + return self.wpml_additional_external_binding_fields(external_data, relation) # def unwrap_binding(self, binding): # return self.wpml_unwrap_binding(binding) diff --git a/connector_woocommerce_wpml/models/product_template/adapter.py b/connector_woocommerce_wpml/models/product_template/adapter.py index d16fc852b..57f3c7bfb 100644 --- a/connector_woocommerce_wpml/models/product_template/adapter.py +++ b/connector_woocommerce_wpml/models/product_template/adapter.py @@ -11,38 +11,39 @@ class WooCommerceProductTemplateAdapter(Component): "woocommerce.product.wpml.mixin.adapter", ] - def create(self, data): - if data.get("type") == "simple" and data.get("translation_of"): - sku = data.pop("sku") - res = super().create(data) - if data.get("type") == "simple" and data.get("translation_of"): - if res and isinstance(res, dict): - external_id = res.get("id") - if external_id: - sku = self._normalize_simple_sku(sku) - url_l = ["products", str(external_id)] - self._exec("put", "/".join(url_l), data={"sku": sku}) - return res - - def write(self, external_id, data): # pylint: disable=W8106 - old_sku = None - if data.get("type") == "simple": - if data.get("translation_of"): - data.pop("sku") - else: - old_sku = data.pop("sku") - if isinstance(old_sku, list): - old_sku = old_sku[0] - res = super().write(external_id, data) - if old_sku and res["data"].get("sku") != old_sku: - data["sku"] = old_sku - # This conversion is to "revert" first conversion done on prepare_data - if isinstance(data["regular_price"], str): - data["regular_price"] = float(data["regular_price"]) - if isinstance(data["sale_price"], str): - data["sale_price"] = float(data["sale_price"]) - res = super().write(external_id, data) - return res + # TODO: do we really need this next 2 methods??? + # def create(self, data): + # if data.get("type") == "simple" and data.get("translation_of"): + # sku = data.pop("sku") + # res = super().create(data) + # if data.get("type") == "simple" and data.get("translation_of"): + # if res and isinstance(res, dict): + # external_id = res.get("id") + # if external_id: + # sku = self._normalize_simple_sku(sku) + # url = "products/%i" % external_id + # self._exec("put", url, data={"sku": sku}) + # return res + + # def write(self, external_id, data): # pylint: disable=W8106 + # old_sku = None + # if data.get("type") == "simple": + # if data.get("translation_of"): + # data.pop("sku") + # else: + # old_sku = data.pop("sku") + # if isinstance(old_sku, list): + # old_sku = old_sku[0] + # res = super().write(external_id, data) + # if old_sku and res["data"].get("sku") != old_sku: + # data["sku"] = old_sku + # # This conversion is to "revert" first conversion done on prepare_data + # if isinstance(data["regular_price"], str): + # data["regular_price"] = float(data["regular_price"]) + # if isinstance(data["sale_price"], str): + # data["sale_price"] = float(data["sale_price"]) + # res = super().write(external_id, data) + # return res # TODO: REVIEW: can we return this in better way? def _get_search_fields(self): diff --git a/connector_woocommerce_wpml/models/product_template/binder.py b/connector_woocommerce_wpml/models/product_template/binder.py index 7c08a7787..6e2b49d19 100644 --- a/connector_woocommerce_wpml/models/product_template/binder.py +++ b/connector_woocommerce_wpml/models/product_template/binder.py @@ -18,8 +18,14 @@ def external_alt_id(self): def get_binding_domain(self, record): return self.wpml_get_binding_domain(record) - def _additional_external_binding_fields(self, external_data): - return self.wpml_additional_external_binding_fields(external_data) + def _additional_external_binding_fields(self, external_data, relation): + return self.wpml_additional_external_binding_fields(external_data, relation) + + def wpml_additional_external_binding_fields(self, external_data, relation): + return { + **super().wpml_additional_external_binding_fields(external_data, relation), + "woocommerce_master_lang": relation._context["first_lang"], + } # def unwrap_binding(self, binding): # return self.wpml_unwrap_binding(binding) diff --git a/connector_woocommerce_wpml/models/product_template/binding.py b/connector_woocommerce_wpml/models/product_template/binding.py index 9a4506bca..1eef8937c 100644 --- a/connector_woocommerce_wpml/models/product_template/binding.py +++ b/connector_woocommerce_wpml/models/product_template/binding.py @@ -2,7 +2,8 @@ # Copyright 2025 NuoBiT Solutions - Eric Antones # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo import models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError from ..binding.binding import WoocommerceWPMLBindingMixin @@ -13,9 +14,51 @@ class WooCommerceProductTemplate(models.Model): "woocommerce.product.template", "woocommerce.wpml.binding.mixin", ] + _order = ( + "backend_id, odoo_id, woocommerce_master_lang desc, " + " woocommerce_lang, woocommerce_idproduct" + ) + + woocommerce_master_lang = fields.Boolean( + string="WooCommerce Master Language", + readonly=True, + required=True, + default=False, + ) _sql_constraints = WoocommerceWPMLBindingMixin._sql_constraints + # TODO: duplicated in product.product + @api.constrains("woocommerce_master_lang", "backend_id", "odoo_id") + def _check_woocommerce_master_lang(self): + for rec in self: + master_bindings = rec.odoo_id.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == rec.backend_id and x.woocommerce_master_lang + ) + if len(master_bindings) != 1: + raise ValidationError( + _( + "It should always be one and exactly one binding with " + "master language enabled" + ) + ) + + # @api.constrains("woocommerce_master_lang") + # def _check_woocommerce_master_lang(self): + # for rec in self: + # if not rec.woocommerce_master_lang: + # master_binding = rec.odoo_id.woocommerce_bind_ids.filtered( + # lambda x: x.backend_id == self.backend_id + # and x.woocommerce_master_lang + # ) + # if not master_binding: + # raise ValidationError( + # _( + # "WooCommerce master Language binding does not exists. + # At least should exist 1" + # ) + # ) + # TODO: This function should be an overwrite of the original one, # it should be refactored to avoid code duplication # doing a hook to set a context variable with lang @@ -29,3 +72,17 @@ class WooCommerceProductTemplate(models.Model): # lambda x: x.backend_id == self.backend_id # and x.woocommerce_lang == rec.woocommerce_lang # ).with_context(resync_product_template=True).resync_export() + + # def unlink(self): + # to_remove, to_remove_variants = self.env[self._name], + # self.env["woocommerce.product.product"] + # for rec in self: + # to_remove |= rec.odoo_id.woocommerce_bind_ids.filtered( + # lambda x: x.backend_id == rec.backend_id #and x != rec + # ) + # to_remove_variants |= rec.odoo_id.with_context(active_test=False) + # .product_variant_ids.woocommerce_bind_ids.filtered( + # lambda x: x.backend_id == rec.backend_id + # ) + # to_remove_variants.with_context().unlink() + # return super(WooCommerceProductTemplate, to_remove).unlink() diff --git a/connector_woocommerce_wpml/models/product_template/export_mapper.py b/connector_woocommerce_wpml/models/product_template/export_mapper.py index cb5a3c4f4..dc9f6eb90 100644 --- a/connector_woocommerce_wpml/models/product_template/export_mapper.py +++ b/connector_woocommerce_wpml/models/product_template/export_mapper.py @@ -1,6 +1,7 @@ # Copyright NuoBiT Solutions - Kilian Niubo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) - +from odoo import _ +from odoo.exceptions import ValidationError from odoo.addons.component.core import Component from odoo.addons.connector.components.mapper import changed_by, mapping @@ -21,24 +22,91 @@ def lang(self, record): @mapping def translation_of(self, record): - lang_code = record._context.get("lang") - if lang_code: - source_lang_code = self.backend_record.lang_ids[0].code - if lang_code == source_lang_code: - # We don't need to set translation_of for the default lang - return {} + binding = self.options["binding"] + if not binding: + if not record._context.get("lang"): + raise ValidationError(_("Language must be always set")) + if "first_lang" not in record._context: + raise ValidationError(_("First lang must be always set on context")) + if not isinstance(record._context["first_lang"], bool): + raise ValidationError(_("First lang must be a boolean")) + first_lang = record._context["first_lang"] + if first_lang: + odoo_lang = record._context["lang"] + if odoo_lang != self.backend_record.language_id.code: + raise ValidationError( + _( + "Unexpected!! The first language on creation should be " + "always the same defined in the backend." + ) + ) else: - wpml_code = self.env["res.lang"]._get_wpml_code_from_iso_code( - source_lang_code - ) - master_binding_backend = record.woocommerce_bind_ids.filtered( + other_bindings = record.woocommerce_bind_ids.filtered( lambda x: x.backend_id == self.backend_record - and x.woocommerce_lang == wpml_code ) - translation_of = None - if master_binding_backend: - translation_of = master_binding_backend.woocommerce_idproduct - return {"translation_of": translation_of} + if not other_bindings: + raise ValidationError( + _( + "Unexpected. No other bindings found when it should because" + " this is not the first language!!" + ) + ) + master_binding = other_bindings.filtered( + lambda x: x.woocommerce_master_lang + ) + if not master_binding: + raise ValidationError( + _( + "Unexpected. No master language found! On creation of " + "additional languages it should " + "always already exists the master language should be " + "always the same defined in the backend." + ) + ) + + return {"translation_of": master_binding.woocommerce_idproduct} + + # TODO: this is exactly the same in product_product, unify, + # via inheritance or mixim, whatever + @changed_by("default_code") + @mapping + def sku(self, record): + + binding = self.options["binding"] if self.options else None + if binding: + if binding.woocommerce_master_lang: + return super().sku(record) + else: + master_binding = record.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == self.backend_record + and x.woocommerce_master_lang + ) + if master_binding: + if len(master_binding) > 1: + raise ValidationError( + _( + "It should always be one and exactly one binding " + "with master language emabled" + ) + ) + else: + if not record._context.get("lang"): + raise ValidationError(_("Language must be always set")) + if "first_lang" not in record._context: + raise ValidationError(_("First lang must be always set on context")) + if not isinstance(record._context["first_lang"], bool): + raise ValidationError(_("First lang must be a boolean")) + first_lang = record._context["first_lang"] + if first_lang: + odoo_lang = record._context["lang"] + if odoo_lang != self.backend_record.language_id.code: + raise ValidationError( + _( + "Unexpected!! The first language on creation should " + "be always the same defined in the backend." + ) + ) + return super().sku(record) def _get_product_description(self, record): # We don't need check backend_record lang diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/adapter.py b/connector_woocommerce_wpml/models/product_wpml_mixin/adapter.py index 3ace1fefb..b6974edb9 100644 --- a/connector_woocommerce_wpml/models/product_wpml_mixin/adapter.py +++ b/connector_woocommerce_wpml/models/product_wpml_mixin/adapter.py @@ -24,8 +24,9 @@ def wpml_extract_domain_clauses(self, domain, search_fields): real_domain, common_domain = super()._extract_domain_clauses( domain, search_fields ) - lang_clause_exists = any(clause[0] == "lang" for clause in common_domain) - for clause in domain: - if "lang" in clause[0] and not lang_clause_exists: - common_domain.append(clause) + # TODO: does the following code needed??? + # lang_clause_exists = any(clause[0] == "lang" for clause in common_domain) + # for clause in domain: + # if "lang" in clause[0] and not lang_clause_exists: + # common_domain.append(clause) return real_domain, common_domain diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/binder.py b/connector_woocommerce_wpml/models/product_wpml_mixin/binder.py index 37a926087..32523d219 100644 --- a/connector_woocommerce_wpml/models/product_wpml_mixin/binder.py +++ b/connector_woocommerce_wpml/models/product_wpml_mixin/binder.py @@ -23,10 +23,10 @@ def wpml_get_binding_domain(self, record): ] return domain - def wpml_additional_external_binding_fields(self, external_data): + def wpml_additional_external_binding_fields(self, external_data, relation): # TODO: this additional fields probably should be # included in binding as m2o to res lang on upper binder return { - **super()._additional_external_binding_fields(external_data), + **super()._additional_external_binding_fields(external_data, relation), "woocommerce_lang": external_data["lang"], } diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py b/connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py index 9ab381e54..67f4c9d9e 100644 --- a/connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py +++ b/connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py @@ -21,11 +21,18 @@ def wpml_run(self, relation, always=True, internal_fields=None): ) % self.backend_record.name ) - for lang in langs_to_export: + langs_first_default = sorted( + langs_to_export, + key=lambda lang: (lang != self.backend_record.language_id.code, lang), + ) + first_lang = True + for lang in langs_first_default: result = super().run( - relation.with_context(lang=lang), + relation.with_context(lang=lang, first_lang=first_lang), always=always, internal_fields=internal_fields, ) res.append(result) + if first_lang: + first_lang = False return res diff --git a/connector_woocommerce_wpml/views/product_product.xml b/connector_woocommerce_wpml/views/product_product.xml index 8c9020d42..810f5e347 100644 --- a/connector_woocommerce_wpml/views/product_product.xml +++ b/connector_woocommerce_wpml/views/product_product.xml @@ -13,6 +13,9 @@ + + + @@ -26,6 +29,9 @@ + + + diff --git a/connector_woocommerce_wpml/views/product_template.xml b/connector_woocommerce_wpml/views/product_template.xml index 73c8f1320..1221c2357 100644 --- a/connector_woocommerce_wpml/views/product_template.xml +++ b/connector_woocommerce_wpml/views/product_template.xml @@ -13,6 +13,9 @@ + + + @@ -26,6 +29,9 @@ + + + From bcda588933109bb087ff6b96e47f698b6e8f8ff7 Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Tue, 23 Dec 2025 17:28:11 +0100 Subject: [PATCH 5/9] [FIX+IMP+REF] connector_woocommerce_wpml: add master language to attribute values and categories --- .../models/product_attribute_value/binder.py | 9 ++ .../models/product_attribute_value/binding.py | 25 +++- .../product_attribute_value/export_mapper.py | 122 ++++++++++++------ .../models/product_product/export_mapper.py | 59 +++------ .../models/product_public_category/binder.py | 6 + .../models/product_public_category/binding.py | 25 +++- .../product_public_category/export_mapper.py | 109 +++++++++++----- .../models/product_template/export_mapper.py | 13 +- .../views/product_attribute_value.xml | 6 + .../views/product_public_category.xml | 6 + 10 files changed, 264 insertions(+), 116 deletions(-) diff --git a/connector_woocommerce_wpml/models/product_attribute_value/binder.py b/connector_woocommerce_wpml/models/product_attribute_value/binder.py index 568068784..f6da0062d 100644 --- a/connector_woocommerce_wpml/models/product_attribute_value/binder.py +++ b/connector_woocommerce_wpml/models/product_attribute_value/binder.py @@ -17,3 +17,12 @@ def external_alt_id(self): def get_binding_domain(self, record): return self.wpml_get_binding_domain(record) + + def _additional_external_binding_fields(self, external_data, relation): + return self.wpml_additional_external_binding_fields(external_data, relation) + + def wpml_additional_external_binding_fields(self, external_data, relation): + return { + **super().wpml_additional_external_binding_fields(external_data, relation), + "woocommerce_master_lang": relation._context["first_lang"], + } diff --git a/connector_woocommerce_wpml/models/product_attribute_value/binding.py b/connector_woocommerce_wpml/models/product_attribute_value/binding.py index e680743c7..9eeed6ec8 100644 --- a/connector_woocommerce_wpml/models/product_attribute_value/binding.py +++ b/connector_woocommerce_wpml/models/product_attribute_value/binding.py @@ -2,7 +2,8 @@ # Copyright 2025 NuoBiT Solutions - Eric Antones # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo import models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError from ..binding.binding import WoocommerceWPMLBindingMixin @@ -14,4 +15,26 @@ class WooCommerceProductAttributeValue(models.Model): "woocommerce.wpml.binding.mixin", ] + # TODO: add this to the mixin + woocommerce_master_lang = fields.Boolean( + string="WooCommerce Master Language", + readonly=True, + required=True, + default=False, + ) + _sql_constraints = WoocommerceWPMLBindingMixin._sql_constraints + + @api.constrains("woocommerce_master_lang", "backend_id", "odoo_id") + def _check_woocommerce_master_lang(self): + for rec in self: + master_bindings = rec.odoo_id.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == rec.backend_id and x.woocommerce_master_lang + ) + if len(master_bindings) != 1: + raise ValidationError( + _( + "It should always be one and exactly one binding with" + " master language enabled" + ) + ) diff --git a/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py b/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py index 05a8af19b..6e7af7588 100644 --- a/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py +++ b/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py @@ -1,15 +1,100 @@ # Copyright NuoBiT Solutions - Kilian Niubo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import _ +from odoo.exceptions import ValidationError from odoo.addons.component.core import Component -from odoo.addons.connector.components.mapper import changed_by, mapping, only_create +from odoo.addons.connector.components.mapper import changed_by, mapping from odoo.addons.connector_extension.components.mapper import required class WooCommerceProductAttributeValueExportMapper(Component): _inherit = "woocommerce.product.attribute.value.export.mapper" + # TODO: Make this only_create??? + @changed_by("lang") + @mapping + def lang(self, record): + # TODO: unify this code. Probably do a function in res lang + odoo_lang = record._context.get("lang") + if not odoo_lang: + raise ValidationError(_("Language must be always set")) + wc_lang = self.env["res.lang"]._get_wpml_code_from_iso_code(odoo_lang) + return {"lang": wc_lang} + + # @only_create + # @mapping + # def translation_of(self, record): + # odoo_lang_code = record._context.get("lang") + # if odoo_lang_code: + # wpml_lang_code = self.env["res.lang"]._get_wpml_code_from_iso_code( + # odoo_lang_code + # ) + # default_woml_lang_code = self.env["res.lang"]._get_wpml_code_from_iso_code( + # self.backend_record.language_id.code + # ) + # other_binding_backend = record.woocommerce_bind_ids.filtered( + # lambda x: x.backend_id == self.backend_record + # and x.woocommerce_lang != wpml_lang_code + # ).sorted( + # lambda x: ( + # x.woocommerce_lang != default_woml_lang_code, + # x.woocommerce_lang, + # ) + # ) + # translation_of = None + # if other_binding_backend: + # translation_of = other_binding_backend[0].woocommerce_idattributevalue + # return {"translation_of": translation_of} + + # TODO: Make this only_create??? + @mapping + def translation_of(self, record): + binding = self.options["binding"] if self.options else None + if not binding: + if not record._context.get("lang"): + raise ValidationError(_("Language must be always set")) + if "first_lang" not in record._context: + raise ValidationError(_("First lang must be always set on context")) + if not isinstance(record._context["first_lang"], bool): + raise ValidationError(_("First lang must be a boolean")) + first_lang = record._context["first_lang"] + if first_lang: + odoo_lang = record._context["lang"] + if odoo_lang != self.backend_record.language_id.code: + raise ValidationError( + _( + "Unexpected!! The first language on creation should be " + "always the same defined in the backend." + ) + ) + else: + other_bindings = record.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == self.backend_record + ) + if not other_bindings: + raise ValidationError( + _( + "Unexpected. No other bindings found when it should because" + " this is not the first language!!" + ) + ) + master_binding = other_bindings.filtered( + lambda x: x.woocommerce_master_lang + ) + if not master_binding: + raise ValidationError( + _( + "Unexpected. No Master language found! On creation " + "of additional languages it shoould " + "always already exists the master language should be " + "always the same defined in the backend." + ) + ) + + return {"translation_of": master_binding.woocommerce_idattributevalue} + @required("name") @changed_by("name") @mapping @@ -39,38 +124,3 @@ def parent_name(self, record): if dict_name["parent_name"] != record.attribute_id.name: dict_name["parent_name"] = record.attribute_id.name return dict_name - - @changed_by("lang") - @mapping - def lang(self, record): - # TODO: unify this code. Probably do a function in res lang - return { - "lang": self.env["res.lang"]._get_wpml_code_from_iso_code( - record._context.get("lang") - ) - } - - @only_create - @mapping - def translation_of(self, record): - odoo_lang_code = record._context.get("lang") - if odoo_lang_code: - wpml_lang_code = self.env["res.lang"]._get_wpml_code_from_iso_code( - odoo_lang_code - ) - default_woml_lang_code = self.env["res.lang"]._get_wpml_code_from_iso_code( - self.backend_record.language_id.code - ) - other_binding_backend = record.woocommerce_bind_ids.filtered( - lambda x: x.backend_id == self.backend_record - and x.woocommerce_lang != wpml_lang_code - ).sorted( - lambda x: ( - x.woocommerce_lang != default_woml_lang_code, - x.woocommerce_lang, - ) - ) - translation_of = None - if other_binding_backend: - translation_of = other_binding_backend[0].woocommerce_idattributevalue - return {"translation_of": translation_of} diff --git a/connector_woocommerce_wpml/models/product_product/export_mapper.py b/connector_woocommerce_wpml/models/product_product/export_mapper.py index 31eb9353c..0ffbd4c8b 100644 --- a/connector_woocommerce_wpml/models/product_product/export_mapper.py +++ b/connector_woocommerce_wpml/models/product_product/export_mapper.py @@ -12,10 +12,7 @@ class WooCommerceProductProductExportMapper(Component): _inherit = "woocommerce.product.product.export.mapper" - # TODO: REMOVE THIS COMMENT: we need lang on write because woocommerce - # can't be write name with id as a external_id, we need name+lang. - # TODO: REMOVE THIS LANG FROM MAPPER!! - # @only_create + # TODO: Make this only_create??? @changed_by("lang") @mapping def lang(self, record): @@ -26,29 +23,7 @@ def lang(self, record): wc_lang = self.env["res.lang"]._get_wpml_code_from_iso_code(odoo_lang) return {"lang": wc_lang} - # @only_create - # @mapping - # def translation_of(self, record): - # lang_code = record._context.get("lang") - # if lang_code: - # source_lang_code = self.backend_record.lang_ids[0].code - # if lang_code == source_lang_code: - # # We don't need to set translation_of for the default lang - # return {} - # else: - # wpml_code = self.env["res.lang"]._get_wpml_code_from_iso_code( - # source_lang_code - # ) - # master_binding_backend = record.woocommerce_bind_ids.filtered( - # lambda x: x.backend_id == self.backend_record - # and x.woocommerce_lang == wpml_code - # ) - # translation_of = None - # if master_binding_backend: - # translation_of = master_binding_backend.woocommerce_idproduct - # return {"translation_of": translation_of} - - # TDOO: Make this only_create + # TODO: Make this only_create??? @mapping def translation_of(self, record): binding = self.options["binding"] if self.options else None @@ -95,21 +70,6 @@ def translation_of(self, record): return {"translation_of": master_binding.woocommerce_idproduct} - def _get_product_description(self, record): - res = False - odoo_lang = record._context.get("lang") - if not odoo_lang: - raise ValidationError(_("Language must be always set")) - if odoo_lang == self.backend_record.language_id.code: - res = super()._get_product_description(record) - else: - # We don't need check backend_record lang - # because record already has lang on context - description = record.variant_public_description - if description: - res = tools.color_rgb2hex(description) - return res - @changed_by("default_code") @mapping def sku(self, record): @@ -149,6 +109,21 @@ def sku(self, record): ) return super().sku(record) + def _get_product_description(self, record): + res = False + odoo_lang = record._context.get("lang") + if not odoo_lang: + raise ValidationError(_("Language must be always set")) + if odoo_lang == self.backend_record.language_id.code: + res = super()._get_product_description(record) + else: + # We don't need check backend_record lang + # because record already has lang on context + description = record.variant_public_description + if description: + res = tools.color_rgb2hex(description) + return res + # @mapping # def attributes(self, record): # binder = self.binder_for("woocommerce.product.attribute") diff --git a/connector_woocommerce_wpml/models/product_public_category/binder.py b/connector_woocommerce_wpml/models/product_public_category/binder.py index 9a817ca24..c5db1882c 100644 --- a/connector_woocommerce_wpml/models/product_public_category/binder.py +++ b/connector_woocommerce_wpml/models/product_public_category/binder.py @@ -21,5 +21,11 @@ def get_binding_domain(self, record): def _additional_external_binding_fields(self, external_data, relation): return self.wpml_additional_external_binding_fields(external_data, relation) + def wpml_additional_external_binding_fields(self, external_data, relation): + return { + **super().wpml_additional_external_binding_fields(external_data, relation), + "woocommerce_master_lang": relation._context["first_lang"], + } + # def unwrap_binding(self, binding): # return self.wpml_unwrap_binding(binding) diff --git a/connector_woocommerce_wpml/models/product_public_category/binding.py b/connector_woocommerce_wpml/models/product_public_category/binding.py index fd9e28f3c..45519a9cd 100644 --- a/connector_woocommerce_wpml/models/product_public_category/binding.py +++ b/connector_woocommerce_wpml/models/product_public_category/binding.py @@ -2,7 +2,8 @@ # Copyright 2025 NuoBiT Solutions - Eric Antones # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -from odoo import models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError from ..binding.binding import WoocommerceWPMLBindingMixin @@ -14,4 +15,26 @@ class WooCommerceProductPublicCategory(models.Model): "woocommerce.wpml.binding.mixin", ] + # TODO: add this to the mixin + woocommerce_master_lang = fields.Boolean( + string="WooCommerce Master Language", + readonly=True, + required=True, + default=False, + ) + _sql_constraints = WoocommerceWPMLBindingMixin._sql_constraints + + @api.constrains("woocommerce_master_lang", "backend_id", "odoo_id") + def _check_woocommerce_master_lang(self): + for rec in self: + master_bindings = rec.odoo_id.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == rec.backend_id and x.woocommerce_master_lang + ) + if len(master_bindings) != 1: + raise ValidationError( + _( + "It should always be one and exactly one binding with" + " master language enabled" + ) + ) diff --git a/connector_woocommerce_wpml/models/product_public_category/export_mapper.py b/connector_woocommerce_wpml/models/product_public_category/export_mapper.py index 0bdd4ac46..a3105f04f 100644 --- a/connector_woocommerce_wpml/models/product_public_category/export_mapper.py +++ b/connector_woocommerce_wpml/models/product_public_category/export_mapper.py @@ -1,14 +1,91 @@ # Copyright NuoBiT Solutions - Kilian Niubo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import _ +from odoo.exceptions import ValidationError from odoo.addons.component.core import Component -from odoo.addons.connector.components.mapper import changed_by, mapping, only_create +from odoo.addons.connector.components.mapper import changed_by, mapping class WooCommerceProductPublicCategoryExportMapper(Component): _inherit = "woocommerce.product.public.category.export.mapper" + # TODO: Make this only_create??? + @changed_by("lang") + @mapping + def lang(self, record): + # TODO: unify this code. Probably do a function in res lang + odoo_lang = record._context.get("lang") + if not odoo_lang: + raise ValidationError(_("Language must be always set")) + wc_lang = self.env["res.lang"]._get_wpml_code_from_iso_code(odoo_lang) + return {"lang": wc_lang} + + # @only_create + # @mapping + # def translation_of(self, record): + # lang_code = record._context.get("lang") + # if lang_code: + # other_binding_backend = record.woocommerce_bind_ids.filtered( + # lambda x: x.backend_id == self.backend_record + # and x.woocommerce_lang + # != self.env["res.lang"]._get_wpml_code_from_iso_code( + # record._context.get("lang") + # ) + # ) + # translation_of = None + # for obb in other_binding_backend: + # translation_of = obb.woocommerce_idpubliccategory + # return {"translation_of": translation_of} + + # TODO: Make this only_create??? + @mapping + def translation_of(self, record): + binding = self.options["binding"] if self.options else None + if not binding: + if not record._context.get("lang"): + raise ValidationError(_("Language must be always set")) + if "first_lang" not in record._context: + raise ValidationError(_("First lang must be always set on context")) + if not isinstance(record._context["first_lang"], bool): + raise ValidationError(_("First lang must be a boolean")) + first_lang = record._context["first_lang"] + if first_lang: + odoo_lang = record._context["lang"] + if odoo_lang != self.backend_record.language_id.code: + raise ValidationError( + _( + "Unexpected!! The first language on creation should be " + "always the same defined in the backend." + ) + ) + else: + other_bindings = record.woocommerce_bind_ids.filtered( + lambda x: x.backend_id == self.backend_record + ) + if not other_bindings: + raise ValidationError( + _( + "Unexpected. No other bindings found when it should because" + " this is not the first language!!" + ) + ) + master_binding = other_bindings.filtered( + lambda x: x.woocommerce_master_lang + ) + if not master_binding: + raise ValidationError( + _( + "Unexpected. No Master language found! On creation " + "of additional languages it shoould " + "always already exists the master language should be " + "always the same defined in the backend." + ) + ) + + return {"translation_of": master_binding.woocommerce_idpubliccategory} + # TODO: Try to don't repeat this code @changed_by("name") @mapping @@ -26,35 +103,5 @@ def description(self, record): dict_description["description"] = record.description or None return dict_description - # TODO: REMOVE THIS COMMENT: we need lang on write because woocommerce - # can't be write name with id as a external_id, we need name+lang. - # TODO: REMOVE THIS LANG FROM MAPPER!! - # @only_create - @changed_by("lang") - @mapping - def lang(self, record): - # TODO: unify this code. Probably do a function in res lang - lang = self.env["res.lang"]._get_wpml_code_from_iso_code( - record._context.get("lang") - ) - return {"lang": lang} - - @only_create - @mapping - def translation_of(self, record): - lang_code = record._context.get("lang") - if lang_code: - other_binding_backend = record.woocommerce_bind_ids.filtered( - lambda x: x.backend_id == self.backend_record - and x.woocommerce_lang - != self.env["res.lang"]._get_wpml_code_from_iso_code( - record._context.get("lang") - ) - ) - translation_of = None - for obb in other_binding_backend: - translation_of = obb.woocommerce_idpubliccategory - return {"translation_of": translation_of} - def _get_slug_name(self, record): return record.slug_name diff --git a/connector_woocommerce_wpml/models/product_template/export_mapper.py b/connector_woocommerce_wpml/models/product_template/export_mapper.py index dc9f6eb90..7f404afda 100644 --- a/connector_woocommerce_wpml/models/product_template/export_mapper.py +++ b/connector_woocommerce_wpml/models/product_template/export_mapper.py @@ -1,4 +1,5 @@ # Copyright NuoBiT Solutions - Kilian Niubo +# Copyright 2025 NuoBiT Solutions - Eric Antones # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) from odoo import _ from odoo.exceptions import ValidationError @@ -11,15 +12,18 @@ class WooCommerceProductTemplateExportMapper(Component): _inherit = "woocommerce.product.template.export.mapper" + # TODO: Make this only_create??? @changed_by("lang") @mapping def lang(self, record): # TODO: unify this code. Probably do a function in res lang - lang = self.env["res.lang"]._get_wpml_code_from_iso_code( - record._context.get("lang") - ) - return {"lang": lang} + odoo_lang = record._context.get("lang") + if not odoo_lang: + raise ValidationError(_("Language must be always set")) + wc_lang = self.env["res.lang"]._get_wpml_code_from_iso_code(odoo_lang) + return {"lang": wc_lang} + # TODO: Make this only_create??? @mapping def translation_of(self, record): binding = self.options["binding"] @@ -71,7 +75,6 @@ def translation_of(self, record): @changed_by("default_code") @mapping def sku(self, record): - binding = self.options["binding"] if self.options else None if binding: if binding.woocommerce_master_lang: diff --git a/connector_woocommerce_wpml/views/product_attribute_value.xml b/connector_woocommerce_wpml/views/product_attribute_value.xml index a3f1a1ddd..7a390f0fd 100644 --- a/connector_woocommerce_wpml/views/product_attribute_value.xml +++ b/connector_woocommerce_wpml/views/product_attribute_value.xml @@ -13,6 +13,9 @@ + + + @@ -26,6 +29,9 @@ + + + diff --git a/connector_woocommerce_wpml/views/product_public_category.xml b/connector_woocommerce_wpml/views/product_public_category.xml index b3fb56b0f..e8a0f2729 100644 --- a/connector_woocommerce_wpml/views/product_public_category.xml +++ b/connector_woocommerce_wpml/views/product_public_category.xml @@ -13,6 +13,9 @@ + + + @@ -26,6 +29,9 @@ + + + From c8726905d38e603ac30fcf3fc06688866f024acb Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Mon, 26 Jan 2026 21:01:55 +0100 Subject: [PATCH 6/9] [REF] connector_woocommerce_wpml: mention Woocommerce plugin needed to make the connector work --- connector_woocommerce_wpml/README.rst | 16 ++++++-- .../models/product_public_category/adapter.py | 16 ++++++++ .../readme/CONFIGURE.rst | 16 ++++++-- .../static/description/index.html | 38 +++++++++++-------- 4 files changed, 65 insertions(+), 21 deletions(-) diff --git a/connector_woocommerce_wpml/README.rst b/connector_woocommerce_wpml/README.rst index 0f696a1f5..480bc06df 100644 --- a/connector_woocommerce_wpml/README.rst +++ b/connector_woocommerce_wpml/README.rst @@ -33,10 +33,20 @@ Connector WooCommerce WMPL Configuration ============= -On WPML setup, select the option Language name added as a parameter. -If it's already set, modify: +Required WooCommerce Plugin +=========================== -* WPML > Languages > Language URL format, select the option "Language name added as a parameter" and save the changes. +This module requires the **WooCommerce WPML API REST Extension** plugin to function properly. + +Plugin URL: https://github.com/nuobit/woocommerce-wpml-api-rest-extension + +This plugin is necessary to work around several bugs in the WPML REST API related to: + +* Language parameter handling +* Retrieving language-specific product data +* Setting and updating content per language + +Without this extension, the connector will not be able to properly synchronize multilingual content between Odoo and WooCommerce. Known issues / Roadmap ====================== diff --git a/connector_woocommerce_wpml/models/product_public_category/adapter.py b/connector_woocommerce_wpml/models/product_public_category/adapter.py index 2b9aa168d..ed9184800 100644 --- a/connector_woocommerce_wpml/models/product_public_category/adapter.py +++ b/connector_woocommerce_wpml/models/product_public_category/adapter.py @@ -44,6 +44,22 @@ def _manage_error_codes( def _get_search_fields(self): return self.wpml_get_search_fields() + # def _get_search_fields(self): + # res_new = [] + # res = self.wpml_get_search_fields() + # # Workaround for a WooCommerce API bug: the API sometimes fails to filter by + # # language. This is another bug in the WPML API. Because of this, we cannot + # # rely on server-side filtering. There is no other option but to fetch all + # # records and filter them locally (inefficient, but reliable). If the WPML + # # WooCommerce API is fixed in the future, we can keep the language as a + # # search field and perform server-side filtering instead of removing it here. + # # With the version 1.0.3 of the WooCommerce plugin this should not be necessary + # # https://github.com/nuobit/woocommerce-wpml-api-rest-extension + # for f in res: + # if f != "lang": + # res_new.append(f) + # return res_new + def _domain_to_normalized_dict(self, real_domain): return self.wpml_domain_to_normalized_dict(real_domain) diff --git a/connector_woocommerce_wpml/readme/CONFIGURE.rst b/connector_woocommerce_wpml/readme/CONFIGURE.rst index 533e10110..4d0be9ee8 100644 --- a/connector_woocommerce_wpml/readme/CONFIGURE.rst +++ b/connector_woocommerce_wpml/readme/CONFIGURE.rst @@ -1,4 +1,14 @@ -On WPML setup, select the option Language name added as a parameter. -If it's already set, modify: +Required WooCommerce Plugin +=========================== -* WPML > Languages > Language URL format, select the option "Language name added as a parameter" and save the changes. +This module requires the **WooCommerce WPML API REST Extension** plugin to function properly. + +Plugin URL: https://github.com/nuobit/woocommerce-wpml-api-rest-extension + +This plugin is necessary to work around several bugs in the WPML REST API related to: + +* Language parameter handling +* Retrieving language-specific product data +* Setting and updating content per language + +Without this extension, the connector will not be able to properly synchronize multilingual content between Odoo and WooCommerce. diff --git a/connector_woocommerce_wpml/static/description/index.html b/connector_woocommerce_wpml/static/description/index.html index 39a30aae1..1cc2530f3 100644 --- a/connector_woocommerce_wpml/static/description/index.html +++ b/connector_woocommerce_wpml/static/description/index.html @@ -378,26 +378,34 @@

Connector WooCommerce WMPL

Configuration

-

On WPML setup, select the option Language name added as a parameter. -If it’s already set, modify:

+
+
+

Required WooCommerce Plugin

+

This module requires the WooCommerce WPML API REST Extension plugin to function properly.

+

Plugin URL: https://github.com/nuobit/woocommerce-wpml-api-rest-extension

+

This plugin is necessary to work around several bugs in the WPML REST API related to:

    -
  • WPML > Languages > Language URL format, select the option “Language name added as a parameter” and save the changes.
  • +
  • Language parameter handling
  • +
  • Retrieving language-specific product data
  • +
  • Setting and updating content per language
+

Without this extension, the connector will not be able to properly synchronize multilingual content between Odoo and WooCommerce.

-

Known issues / Roadmap

+

Known issues / Roadmap

For a better maintenance, we can try to use a mixin component to define the common methods and properties.

-

Bug Tracker

+

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 @@ -415,16 +423,16 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • NuoBiT Solutions
  • S.L.
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is part of the NuoBiT/odoo-addons project on GitHub.

You are welcome to contribute.

From 284fdf68af121196b10232782645fa646f90a00c Mon Sep 17 00:00:00 2001 From: ??? Date: Mon, 30 Mar 2026 13:06:19 +0200 Subject: [PATCH 7/9] [IMP] connector_woocommerce_wpml: pre-commit auto fixes --- connector_woocommerce_wpml/README.rst | 48 +++++++++++-------- connector_woocommerce_wpml/__manifest__.py | 2 +- connector_woocommerce_wpml/pyproject.toml | 3 ++ .../readme/CONFIGURE.md | 17 +++++++ .../readme/CONFIGURE.rst | 14 ------ .../readme/CONTRIBUTORS.md | 2 + .../readme/CONTRIBUTORS.rst | 3 -- .../readme/DESCRIPTION.md | 2 + .../readme/DESCRIPTION.rst | 2 - connector_woocommerce_wpml/readme/ROADMAP.md | 6 +++ connector_woocommerce_wpml/readme/ROADMAP.rst | 4 -- .../static/description/index.html | 32 ++++++++----- .../views/woocommerce_backend_view.xml | 4 +- requirements.txt | 2 + 14 files changed, 82 insertions(+), 59 deletions(-) create mode 100644 connector_woocommerce_wpml/pyproject.toml create mode 100644 connector_woocommerce_wpml/readme/CONFIGURE.md delete mode 100644 connector_woocommerce_wpml/readme/CONFIGURE.rst create mode 100644 connector_woocommerce_wpml/readme/CONTRIBUTORS.md delete mode 100644 connector_woocommerce_wpml/readme/CONTRIBUTORS.rst create mode 100644 connector_woocommerce_wpml/readme/DESCRIPTION.md delete mode 100644 connector_woocommerce_wpml/readme/DESCRIPTION.rst create mode 100644 connector_woocommerce_wpml/readme/ROADMAP.md delete mode 100644 connector_woocommerce_wpml/readme/ROADMAP.rst create mode 100644 requirements.txt diff --git a/connector_woocommerce_wpml/README.rst b/connector_woocommerce_wpml/README.rst index 480bc06df..ac97f4520 100644 --- a/connector_woocommerce_wpml/README.rst +++ b/connector_woocommerce_wpml/README.rst @@ -17,13 +17,13 @@ Connector WooCommerce WMPL :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-NuoBiT%2Fodoo--addons-lightgray.png?logo=github - :target: https://github.com/NuoBiT/odoo-addons/tree/14.0/connector_woocommerce_wpml + :target: https://github.com/NuoBiT/odoo-addons/tree/18.0/connector_woocommerce_wpml :alt: NuoBiT/odoo-addons |badge1| |badge2| |badge3| -* This module works with plugin WordPress Multi Language. -* https://wpml.org/ +- This module works with plugin WordPress Multi Language. +- https://wpml.org/ **Table of contents** @@ -34,27 +34,33 @@ Configuration ============= Required WooCommerce Plugin -=========================== +--------------------------- -This module requires the **WooCommerce WPML API REST Extension** plugin to function properly. +This module requires the **WooCommerce WPML API REST Extension** plugin +to function properly. -Plugin URL: https://github.com/nuobit/woocommerce-wpml-api-rest-extension +Plugin URL: +https://github.com/nuobit/woocommerce-wpml-api-rest-extension -This plugin is necessary to work around several bugs in the WPML REST API related to: +This plugin is necessary to work around several bugs in the WPML REST +API related to: -* Language parameter handling -* Retrieving language-specific product data -* Setting and updating content per language +- Language parameter handling +- Retrieving language-specific product data +- Setting and updating content per language -Without this extension, the connector will not be able to properly synchronize multilingual content between Odoo and WooCommerce. +Without this extension, the connector will not be able to properly +synchronize multilingual content between Odoo and WooCommerce. Known issues / Roadmap ====================== -For a better maintenance, we can try to use a mixin component to define the common methods and properties. +For a better maintenance, we can try to use a mixin component to define +the common methods and properties. - * binding: woocommerce_lang field and sql_constrains woocommerce_internal_uniq overwriting the common for all - * export_mapper: Include the translation_of and lang? + - binding: woocommerce_lang field and sql_constrains + woocommerce_internal_uniq overwriting the common for all + - export_mapper: Include the translation_of and lang? Bug Tracker =========== @@ -62,7 +68,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. @@ -70,21 +76,21 @@ Credits ======= Authors -~~~~~~~ +------- * NuoBiT Solutions * S.L. Contributors -~~~~~~~~~~~~ +------------ -* `NuoBiT `__: +- `NuoBiT `__: - * Kilian Niubo + - Kilian Niubo Maintainers -~~~~~~~~~~~ +----------- -This module is part of the `NuoBiT/odoo-addons `_ project on GitHub. +This module is part of the `NuoBiT/odoo-addons `_ project on GitHub. You are welcome to contribute. diff --git a/connector_woocommerce_wpml/__manifest__.py b/connector_woocommerce_wpml/__manifest__.py index 19b89a790..7f8e1dac3 100644 --- a/connector_woocommerce_wpml/__manifest__.py +++ b/connector_woocommerce_wpml/__manifest__.py @@ -7,7 +7,7 @@ "author": "NuoBiT Solutions, S.L.", "license": "AGPL-3", "category": "Connector", - "website": "https://github.com/nuobit/odoo-addons", + "website": "https://github.com/NuoBiT/odoo-addons", "external_dependencies": { "python": [ "woocommerce", diff --git a/connector_woocommerce_wpml/pyproject.toml b/connector_woocommerce_wpml/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/connector_woocommerce_wpml/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/connector_woocommerce_wpml/readme/CONFIGURE.md b/connector_woocommerce_wpml/readme/CONFIGURE.md new file mode 100644 index 000000000..1b0246dee --- /dev/null +++ b/connector_woocommerce_wpml/readme/CONFIGURE.md @@ -0,0 +1,17 @@ +## Required WooCommerce Plugin + +This module requires the **WooCommerce WPML API REST Extension** plugin +to function properly. + +Plugin URL: + + +This plugin is necessary to work around several bugs in the WPML REST +API related to: + +- Language parameter handling +- Retrieving language-specific product data +- Setting and updating content per language + +Without this extension, the connector will not be able to properly +synchronize multilingual content between Odoo and WooCommerce. diff --git a/connector_woocommerce_wpml/readme/CONFIGURE.rst b/connector_woocommerce_wpml/readme/CONFIGURE.rst deleted file mode 100644 index 4d0be9ee8..000000000 --- a/connector_woocommerce_wpml/readme/CONFIGURE.rst +++ /dev/null @@ -1,14 +0,0 @@ -Required WooCommerce Plugin -=========================== - -This module requires the **WooCommerce WPML API REST Extension** plugin to function properly. - -Plugin URL: https://github.com/nuobit/woocommerce-wpml-api-rest-extension - -This plugin is necessary to work around several bugs in the WPML REST API related to: - -* Language parameter handling -* Retrieving language-specific product data -* Setting and updating content per language - -Without this extension, the connector will not be able to properly synchronize multilingual content between Odoo and WooCommerce. diff --git a/connector_woocommerce_wpml/readme/CONTRIBUTORS.md b/connector_woocommerce_wpml/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..827f27baa --- /dev/null +++ b/connector_woocommerce_wpml/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [NuoBiT](https://www.nuobit.com): + - Kilian Niubo \ diff --git a/connector_woocommerce_wpml/readme/CONTRIBUTORS.rst b/connector_woocommerce_wpml/readme/CONTRIBUTORS.rst deleted file mode 100644 index d018d7d8f..000000000 --- a/connector_woocommerce_wpml/readme/CONTRIBUTORS.rst +++ /dev/null @@ -1,3 +0,0 @@ -* `NuoBiT `__: - - * Kilian Niubo diff --git a/connector_woocommerce_wpml/readme/DESCRIPTION.md b/connector_woocommerce_wpml/readme/DESCRIPTION.md new file mode 100644 index 000000000..4f6674cda --- /dev/null +++ b/connector_woocommerce_wpml/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +- This module works with plugin WordPress Multi Language. +- diff --git a/connector_woocommerce_wpml/readme/DESCRIPTION.rst b/connector_woocommerce_wpml/readme/DESCRIPTION.rst deleted file mode 100644 index 02b9a6bb4..000000000 --- a/connector_woocommerce_wpml/readme/DESCRIPTION.rst +++ /dev/null @@ -1,2 +0,0 @@ -* This module works with plugin WordPress Multi Language. -* https://wpml.org/ diff --git a/connector_woocommerce_wpml/readme/ROADMAP.md b/connector_woocommerce_wpml/readme/ROADMAP.md new file mode 100644 index 000000000..9b963766e --- /dev/null +++ b/connector_woocommerce_wpml/readme/ROADMAP.md @@ -0,0 +1,6 @@ +For a better maintenance, we can try to use a mixin component to define +the common methods and properties. + +> - binding: woocommerce_lang field and sql_constrains +> woocommerce_internal_uniq overwriting the common for all +> - export_mapper: Include the translation_of and lang? diff --git a/connector_woocommerce_wpml/readme/ROADMAP.rst b/connector_woocommerce_wpml/readme/ROADMAP.rst deleted file mode 100644 index 002c88205..000000000 --- a/connector_woocommerce_wpml/readme/ROADMAP.rst +++ /dev/null @@ -1,4 +0,0 @@ -For a better maintenance, we can try to use a mixin component to define the common methods and properties. - - * binding: woocommerce_lang field and sql_constrains woocommerce_internal_uniq overwriting the common for all - * export_mapper: Include the translation_of and lang? diff --git a/connector_woocommerce_wpml/static/description/index.html b/connector_woocommerce_wpml/static/description/index.html index 1cc2530f3..b2d937180 100644 --- a/connector_woocommerce_wpml/static/description/index.html +++ b/connector_woocommerce_wpml/static/description/index.html @@ -369,7 +369,7 @@

Connector WooCommerce WMPL

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:99c6152a250b134a922160cc755712e7e4c7493967116e4ca199dd0b144c1c07 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 NuoBiT/odoo-addons

+

Beta License: AGPL-3 NuoBiT/odoo-addons

Known issues / Roadmap

-

For a better maintenance, we can try to use a mixin component to define the common methods and properties.

+

For a better maintenance, we can try to use a mixin component to define +the common methods and properties.

    -
  • binding: woocommerce_lang field and sql_constrains woocommerce_internal_uniq overwriting the common for all
  • +
  • binding: woocommerce_lang field and sql_constrains +woocommerce_internal_uniq overwriting the common for all
  • export_mapper: Include the translation_of and lang?
@@ -419,7 +427,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.

@@ -442,7 +450,7 @@

Contributors

Maintainers

-

This module is part of the NuoBiT/odoo-addons project on GitHub.

+

This module is part of the NuoBiT/odoo-addons project on GitHub.

You are welcome to contribute.

diff --git a/connector_woocommerce_wpml/views/woocommerce_backend_view.xml b/connector_woocommerce_wpml/views/woocommerce_backend_view.xml index 67d7c1fe7..d05f58737 100644 --- a/connector_woocommerce_wpml/views/woocommerce_backend_view.xml +++ b/connector_woocommerce_wpml/views/woocommerce_backend_view.xml @@ -10,8 +10,8 @@ ref="connector_woocommerce.woocommerce_backend_view_form" /> - - + Date: Wed, 8 Apr 2026 16:05:49 +0200 Subject: [PATCH 8/9] [MIG] connector_woocommerce_wpml: Migration to 18.0 --- connector_woocommerce_wpml/README.rst | 6 ++-- connector_woocommerce_wpml/__manifest__.py | 5 +-- .../migrations/14.0.0.1.1/pre-migration.py | 36 ------------------- .../migrations/14.0.0.1.2/pre-migration.py | 20 ----------- .../models/backend/backend.py | 8 +++-- .../models/product_attribute_value/binding.py | 4 ++- .../product_attribute_value/export_mapper.py | 6 ++-- .../models/product_product/binding.py | 4 ++- .../models/product_public_category/adapter.py | 20 ++++++----- .../models/product_public_category/binding.py | 4 ++- .../models/product_template/binding.py | 4 ++- .../models/product_wpml_mixin/binder.py | 2 +- .../models/product_wpml_mixin/exporter.py | 2 +- .../readme/CONTRIBUTORS.md | 3 +- .../static/description/index.html | 6 ++-- .../views/product_attribute_value.xml | 3 +- .../views/product_product.xml | 3 +- .../views/product_public_category.xml | 3 +- .../views/product_template.xml | 3 +- .../views/woocommerce_backend_view.xml | 3 +- 20 files changed, 56 insertions(+), 89 deletions(-) delete mode 100644 connector_woocommerce_wpml/migrations/14.0.0.1.1/pre-migration.py delete mode 100644 connector_woocommerce_wpml/migrations/14.0.0.1.2/pre-migration.py diff --git a/connector_woocommerce_wpml/README.rst b/connector_woocommerce_wpml/README.rst index ac97f4520..a4bbcaeee 100644 --- a/connector_woocommerce_wpml/README.rst +++ b/connector_woocommerce_wpml/README.rst @@ -78,15 +78,15 @@ Credits Authors ------- -* NuoBiT Solutions -* S.L. +* NuoBiT Solutions SL Contributors ------------ - `NuoBiT `__: - - Kilian Niubo + - Kilian Niubo kniubo@nuobit.com + - Deniz Gallo dgallo@nuobit.com Maintainers ----------- diff --git a/connector_woocommerce_wpml/__manifest__.py b/connector_woocommerce_wpml/__manifest__.py index 7f8e1dac3..257197319 100644 --- a/connector_woocommerce_wpml/__manifest__.py +++ b/connector_woocommerce_wpml/__manifest__.py @@ -1,10 +1,11 @@ # Copyright NuoBiT Solutions - Kilian Niubo +# Copyright 2026 NuoBiT Solutions SL - Deniz Gallo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) { "name": "Connector WooCommerce WMPL", - "version": "14.0.0.1.3", - "author": "NuoBiT Solutions, S.L.", + "version": "18.0.1.0.0", + "author": "NuoBiT Solutions SL", "license": "AGPL-3", "category": "Connector", "website": "https://github.com/NuoBiT/odoo-addons", diff --git a/connector_woocommerce_wpml/migrations/14.0.0.1.1/pre-migration.py b/connector_woocommerce_wpml/migrations/14.0.0.1.1/pre-migration.py deleted file mode 100644 index d972ed13d..000000000 --- a/connector_woocommerce_wpml/migrations/14.0.0.1.1/pre-migration.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2025 NuoBiT Solutions - Deniz Gallo -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - -from openupgradelib import openupgrade - -_column_renames = { - "res_lang_woocommerce_backend_rel": [ - ("woocommerce_backend_id", "backend_id"), - ("res_lang_id", "lang_id"), - ] -} - - -@openupgrade.migrate(use_env=True) -def migrate(env, version): - if not version: - return - openupgrade.rename_columns(env.cr, _column_renames) - - env.cr.execute( - """ - ALTER TABLE res_lang_woocommerce_backend_rel - RENAME CONSTRAINT - res_lang_woocommerce_backend_rel_woocommerce_backend_id_fkey - TO res_lang_woocommerce_backend_rel_backend_id_fkey; - """ - ) - - env.cr.execute( - """ - ALTER TABLE res_lang_woocommerce_backend_rel - RENAME CONSTRAINT - res_lang_woocommerce_backend_rel_res_lang_id_fkey - TO res_lang_woocommerce_backend_rel_lang_id_fkey; - """ - ) diff --git a/connector_woocommerce_wpml/migrations/14.0.0.1.2/pre-migration.py b/connector_woocommerce_wpml/migrations/14.0.0.1.2/pre-migration.py deleted file mode 100644 index 5081dc517..000000000 --- a/connector_woocommerce_wpml/migrations/14.0.0.1.2/pre-migration.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2025 NuoBiT Solutions - Deniz Gallo -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - -from openupgradelib import openupgrade - -_field_renames = [ - ( - "woocommerce.backend", - "woocommerce_backend", - "language_ids", - "lang_ids", - ) -] - - -@openupgrade.migrate(use_env=True) -def migrate(env, version): - if not version: - return - openupgrade.rename_fields(env, _field_renames) diff --git a/connector_woocommerce_wpml/models/backend/backend.py b/connector_woocommerce_wpml/models/backend/backend.py index 6b0ae17e7..dac4b85b4 100644 --- a/connector_woocommerce_wpml/models/backend/backend.py +++ b/connector_woocommerce_wpml/models/backend/backend.py @@ -1,4 +1,5 @@ # Copyright NuoBiT Solutions - Kilian Niubo +# Copyright 2026 NuoBiT Solutions SL - Deniz Gallo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). import logging @@ -18,7 +19,10 @@ def check_lang_ids(self): if not lang.wordpress_wpml_lang_code: raise ValidationError( _( - "The language %s has no WPML code, please define " - "this code in language before using it." % lang.name + "The language %(lang)s has no WPML code, please define " + "this code in language before using it." ) + % { + "lang": lang.name, + } ) diff --git a/connector_woocommerce_wpml/models/product_attribute_value/binding.py b/connector_woocommerce_wpml/models/product_attribute_value/binding.py index 9eeed6ec8..5022ba040 100644 --- a/connector_woocommerce_wpml/models/product_attribute_value/binding.py +++ b/connector_woocommerce_wpml/models/product_attribute_value/binding.py @@ -1,5 +1,6 @@ # Copyright NuoBiT Solutions - Kilian Niubo # Copyright 2025 NuoBiT Solutions - Eric Antones +# Copyright 2026 NuoBiT Solutions SL - Deniz Gallo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) from odoo import _, api, fields, models @@ -29,7 +30,8 @@ class WooCommerceProductAttributeValue(models.Model): def _check_woocommerce_master_lang(self): for rec in self: master_bindings = rec.odoo_id.woocommerce_bind_ids.filtered( - lambda x: x.backend_id == rec.backend_id and x.woocommerce_master_lang + lambda x, rec=rec: x.backend_id == rec.backend_id + and x.woocommerce_master_lang ) if len(master_bindings) != 1: raise ValidationError( diff --git a/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py b/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py index 6e7af7588..e2b8cfa69 100644 --- a/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py +++ b/connector_woocommerce_wpml/models/product_attribute_value/export_mapper.py @@ -1,4 +1,5 @@ # Copyright NuoBiT Solutions - Kilian Niubo +# Copyright 2026 NuoBiT Solutions SL - Deniz Gallo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) from odoo import _ @@ -31,9 +32,10 @@ def lang(self, record): # wpml_lang_code = self.env["res.lang"]._get_wpml_code_from_iso_code( # odoo_lang_code # ) - # default_woml_lang_code = self.env["res.lang"]._get_wpml_code_from_iso_code( + # default_woml_lang_code = ( + # self.env["res.lang"]._get_wpml_code_from_iso_code( # self.backend_record.language_id.code - # ) + # )) # other_binding_backend = record.woocommerce_bind_ids.filtered( # lambda x: x.backend_id == self.backend_record # and x.woocommerce_lang != wpml_lang_code diff --git a/connector_woocommerce_wpml/models/product_product/binding.py b/connector_woocommerce_wpml/models/product_product/binding.py index 7760a7a92..96bebae72 100644 --- a/connector_woocommerce_wpml/models/product_product/binding.py +++ b/connector_woocommerce_wpml/models/product_product/binding.py @@ -1,5 +1,6 @@ # Copyright NuoBiT Solutions - Kilian Niubo # Copyright 2025 NuoBiT Solutions - Eric Antones +# Copyright 2026 NuoBiT Solutions SL - Deniz Gallo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) from odoo import _, api, fields, models @@ -34,7 +35,8 @@ class WooCommerceProductProduct(models.Model): def _check_woocommerce_master_lang(self): for rec in self: master_bindings = rec.odoo_id.woocommerce_bind_ids.filtered( - lambda x: x.backend_id == rec.backend_id and x.woocommerce_master_lang + lambda x, rec=rec: x.backend_id == rec.backend_id + and x.woocommerce_master_lang ) if len(master_bindings) != 1: raise ValidationError( diff --git a/connector_woocommerce_wpml/models/product_public_category/adapter.py b/connector_woocommerce_wpml/models/product_public_category/adapter.py index ed9184800..1db1ef6a0 100644 --- a/connector_woocommerce_wpml/models/product_public_category/adapter.py +++ b/connector_woocommerce_wpml/models/product_public_category/adapter.py @@ -1,4 +1,5 @@ # Copyright NuoBiT Solutions - Kilian Niubo +# Copyright 2026 NuoBiT Solutions SL - Deniz Gallo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) from odoo import _ from odoo.exceptions import ValidationError @@ -19,19 +20,19 @@ def _manage_error_codes( if res.status_code == 500: if res_data.get("code") == "duplicate_term_slug": error_message = _( - "Error: '%s'. " + "Error: '%(message)s'. " "WPML plugin allows set the same slug for different " "languages on FrontEnd but this can't be done via API. " "Probably we need a solution in plugin code, it can't " "be solved in Odoo without a workaround modifying raw data. " - "Review the slug of the category '%s' in lang [%s] and try again." + "Review the slug of the category '%(name)s' in lang [%(lang)s]" + " and try again." "" - % ( - res_data["message"], - kwargs["data"].get("name"), - kwargs["data"].get("lang"), - ) - ) + ) % { + "message": res_data["message"], + "name": kwargs["data"].get("name"), + "lang": kwargs["data"].get("lang"), + } if raise_on_error: raise ValidationError(error_message) else: @@ -53,7 +54,8 @@ def _get_search_fields(self): # # records and filter them locally (inefficient, but reliable). If the WPML # # WooCommerce API is fixed in the future, we can keep the language as a # # search field and perform server-side filtering instead of removing it here. - # # With the version 1.0.3 of the WooCommerce plugin this should not be necessary + # # With the version 1.0.3 of the WooCommerce plugin this should not be + # necessary # # https://github.com/nuobit/woocommerce-wpml-api-rest-extension # for f in res: # if f != "lang": diff --git a/connector_woocommerce_wpml/models/product_public_category/binding.py b/connector_woocommerce_wpml/models/product_public_category/binding.py index 45519a9cd..49dfa2f59 100644 --- a/connector_woocommerce_wpml/models/product_public_category/binding.py +++ b/connector_woocommerce_wpml/models/product_public_category/binding.py @@ -1,5 +1,6 @@ # Copyright NuoBiT Solutions - Kilian Niubo # Copyright 2025 NuoBiT Solutions - Eric Antones +# Copyright 2026 NuoBiT Solutions SL - Deniz Gallo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) from odoo import _, api, fields, models @@ -29,7 +30,8 @@ class WooCommerceProductPublicCategory(models.Model): def _check_woocommerce_master_lang(self): for rec in self: master_bindings = rec.odoo_id.woocommerce_bind_ids.filtered( - lambda x: x.backend_id == rec.backend_id and x.woocommerce_master_lang + lambda x, rec=rec: x.backend_id == rec.backend_id + and x.woocommerce_master_lang ) if len(master_bindings) != 1: raise ValidationError( diff --git a/connector_woocommerce_wpml/models/product_template/binding.py b/connector_woocommerce_wpml/models/product_template/binding.py index 1eef8937c..0a4a23177 100644 --- a/connector_woocommerce_wpml/models/product_template/binding.py +++ b/connector_woocommerce_wpml/models/product_template/binding.py @@ -1,5 +1,6 @@ # Copyright NuoBiT Solutions - Kilian Niubo # Copyright 2025 NuoBiT Solutions - Eric Antones +# Copyright 2026 NuoBiT Solutions SL - Deniz Gallo # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) from odoo import _, api, fields, models @@ -33,7 +34,8 @@ class WooCommerceProductTemplate(models.Model): def _check_woocommerce_master_lang(self): for rec in self: master_bindings = rec.odoo_id.woocommerce_bind_ids.filtered( - lambda x: x.backend_id == rec.backend_id and x.woocommerce_master_lang + lambda x, rec=rec: x.backend_id == rec.backend_id + and x.woocommerce_master_lang ) if len(master_bindings) != 1: raise ValidationError( diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/binder.py b/connector_woocommerce_wpml/models/product_wpml_mixin/binder.py index 32523d219..7e20729aa 100644 --- a/connector_woocommerce_wpml/models/product_wpml_mixin/binder.py +++ b/connector_woocommerce_wpml/models/product_wpml_mixin/binder.py @@ -6,7 +6,7 @@ class WooCommerceProductWPMLMixinBinder(AbstractComponent): _name = "woocommerce.product.wpml.mixin.binder" - _inherit = "connector.extension.generic.binder" + _inherit = "connector.extension.binder" def wpml_get_binding_domain(self, record): domain = super().get_binding_domain(record) diff --git a/connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py b/connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py index 67f4c9d9e..722b0658f 100644 --- a/connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py +++ b/connector_woocommerce_wpml/models/product_wpml_mixin/exporter.py @@ -8,7 +8,7 @@ class WooCommerceProductWPMLMixinExporter(AbstractComponent): _name = "woocommerce.product.wpml.mixin.record.direct.exporter" - _inherit = "connector.extension.generic.record.direct.exporter" + _inherit = "connector.extension.record.direct.exporter" def wpml_run(self, relation, always=True, internal_fields=None): res = [] diff --git a/connector_woocommerce_wpml/readme/CONTRIBUTORS.md b/connector_woocommerce_wpml/readme/CONTRIBUTORS.md index 827f27baa..5d535bfca 100644 --- a/connector_woocommerce_wpml/readme/CONTRIBUTORS.md +++ b/connector_woocommerce_wpml/readme/CONTRIBUTORS.md @@ -1,2 +1,3 @@ - [NuoBiT](https://www.nuobit.com): - - Kilian Niubo \ + - Kilian Niubo + - Deniz Gallo diff --git a/connector_woocommerce_wpml/static/description/index.html b/connector_woocommerce_wpml/static/description/index.html index b2d937180..1d653553a 100644 --- a/connector_woocommerce_wpml/static/description/index.html +++ b/connector_woocommerce_wpml/static/description/index.html @@ -435,15 +435,15 @@

Credits

Authors

    -
  • NuoBiT Solutions
  • -
  • S.L.
  • +
  • NuoBiT Solutions SL

Contributors

diff --git a/connector_woocommerce_wpml/views/product_attribute_value.xml b/connector_woocommerce_wpml/views/product_attribute_value.xml index 7a390f0fd..aab9a6679 100644 --- a/connector_woocommerce_wpml/views/product_attribute_value.xml +++ b/connector_woocommerce_wpml/views/product_attribute_value.xml @@ -1,5 +1,6 @@ @@ -19,7 +20,7 @@ - woocommerce.product.attribute.value.view.tree + woocommerce.product.attribute.value.view.list woocommerce.product.attribute.value @@ -19,7 +20,7 @@ - woocommerce.product.product.view.tree + woocommerce.product.product.view.list woocommerce.product.product @@ -19,7 +20,7 @@ - woocommerce.product.public.category.view.tree + woocommerce.product.public.category.view.list woocommerce.product.public.category @@ -19,7 +20,7 @@ - woocommerce.product.template.view.tree + woocommerce.product.template.view.list woocommerce.product.template @@ -14,7 +15,7 @@ From 7977f5646bb83db0b526badc6058bd9a1dfbb1f8 Mon Sep 17 00:00:00 2001 From: ??? Date: Wed, 8 Apr 2026 16:05:57 +0200 Subject: [PATCH 9/9] [DO NOT MERGE] test-requirements.txt --- test-requirements.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 test-requirements.txt diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 000000000..2dc13f76a --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,10 @@ +odoo-addon-website_sale_stock_variant@git+https://github.com/nuobit/odoo-addons.git@refs/pull/825/head#subdirectory=website_sale_stock_variant +odoo-addon-website_sale_extra_fields@git+https://github.com/nuobit/odoo-addons.git@refs/pull/826/head#subdirectory=website_sale_extra_fields +odoo-addon-connector_wordpress@git+https://github.com/nuobit/odoo-addons.git@refs/pull/876/head#subdirectory=connector_wordpress +odoo-addon-connector_extension_woocommerce@git+https://github.com/nuobit/odoo-addons.git@refs/pull/875/head#subdirectory=connector_extension_woocommerce +odoo-addon-tools_mimetypes_extension@git+https://github.com/nuobit/odoo-addons.git@refs/pull/878/head#subdirectory=tools_mimetypes_extension +odoo-addon-website_sale_variant@git+https://github.com/nuobit/odoo-addons.git@refs/pull/824/head#subdirectory=website_sale_variant +odoo-addon-website_sale_product_document@git+https://github.com/nuobit/odoo-addons.git@refs/pull/827/head#subdirectory=website_sale_product_document +odoo-addon-connector_extension_wordpress@git+https://github.com/nuobit/odoo-addons.git@refs/pull/877/head#subdirectory=connector_extension_wordpress +odoo-addon-connector_woocommerce@git+https://github.com/nuobit/odoo-addons.git@refs/pull/874/head#subdirectory=connector_woocommerce +odoo-addon-connector_wordpress_wpml@git+https://github.com/nuobit/odoo-addons.git@refs/pull/880/head#subdirectory=connector_wordpress_wpml