From 1d8cf97c3b7393f9e0900ef056e8d705bc1099b2 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Mon, 5 Sep 2016 14:20:22 +0200 Subject: [PATCH 01/34] [ADD] connector_prestashop_catalog_manager: ============================================= Catalog Manager for Odoo PrestaShop Connector ============================================= This module is an extension for *connector_prestashop*. With it, you will be able to manage your catalog directly from Odoo: * Create/modify attributtes and values in Odoo and push then in PrestaShop. * Create/modify products and push them in PrestaShop. * Create/modify products variants and push them in PrestaShop (combinations). * Create/modify category and push them in PrestaShop. * Create/modify image and push then in PrestaShop. Known issues / Roadmap ====================== * Tests. --- .../README.rst | 71 +++ .../__init__.py | 9 + .../__openerp__.py | 30 ++ .../connector.py | 13 + .../product.py | 444 ++++++++++++++++++ .../product_attribute.py | 160 +++++++ .../product_combination.py | 237 ++++++++++ .../product_image.py | 177 +++++++ .../static/description/icon.png | Bin 0 -> 8227 bytes .../static/description/icon.svg | 238 ++++++++++ .../views/product_attribute_view.xml | 86 ++++ .../views/product_image_view.xml | 15 + .../views/product_view.xml | 29 ++ .../wizard/__init__.py | 6 + .../wizard/active_deactive_products.py | 28 ++ .../wizard/active_deactive_products_view.xml | 61 +++ .../wizard/export_multiple_products.py | 138 ++++++ .../wizard/export_multiple_products_view.xml | 81 ++++ .../wizard/sync_products.py | 34 ++ .../wizard/sync_products_view.xml | 30 ++ 20 files changed, 1887 insertions(+) create mode 100644 connector_prestashop_catalog_manager/README.rst create mode 100644 connector_prestashop_catalog_manager/__init__.py create mode 100644 connector_prestashop_catalog_manager/__openerp__.py create mode 100644 connector_prestashop_catalog_manager/connector.py create mode 100644 connector_prestashop_catalog_manager/product.py create mode 100644 connector_prestashop_catalog_manager/product_attribute.py create mode 100644 connector_prestashop_catalog_manager/product_combination.py create mode 100644 connector_prestashop_catalog_manager/product_image.py create mode 100644 connector_prestashop_catalog_manager/static/description/icon.png create mode 100644 connector_prestashop_catalog_manager/static/description/icon.svg create mode 100644 connector_prestashop_catalog_manager/views/product_attribute_view.xml create mode 100644 connector_prestashop_catalog_manager/views/product_image_view.xml create mode 100644 connector_prestashop_catalog_manager/views/product_view.xml create mode 100644 connector_prestashop_catalog_manager/wizard/__init__.py create mode 100644 connector_prestashop_catalog_manager/wizard/active_deactive_products.py create mode 100644 connector_prestashop_catalog_manager/wizard/active_deactive_products_view.xml create mode 100644 connector_prestashop_catalog_manager/wizard/export_multiple_products.py create mode 100644 connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml create mode 100644 connector_prestashop_catalog_manager/wizard/sync_products.py create mode 100644 connector_prestashop_catalog_manager/wizard/sync_products_view.xml diff --git a/connector_prestashop_catalog_manager/README.rst b/connector_prestashop_catalog_manager/README.rst new file mode 100644 index 000000000..788ff77d5 --- /dev/null +++ b/connector_prestashop_catalog_manager/README.rst @@ -0,0 +1,71 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +============================================= +Catalog Manager for Odoo PrestaShop Connector +============================================= + +This module is an extension for *connector_prestashop*. With it, you will be +able to manage your catalog directly from Odoo: + +* Create/modify attributtes and values in Odoo and push then in PrestaShop. +* Create/modify products and push them in PrestaShop. +* Create/modify products variants and push them in PrestaShop (combinations). +* Create/modify category and push them in PrestaShop. +* Create/modify image and push then in PrestaShop. + +Usage +===== + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/108/8.0 + + +Known issues / Roadmap +====================== + +* Tests. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* `PrestaShop logo `_. +* `Odoo logo `_. +* `Cable `_. + +Contributors +------------ + +* Sébastien Beau +* Benoît Guillot +* Mikel Arregi +* Sergio Teruel +* Pedro M. Baeza + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/connector_prestashop_catalog_manager/__init__.py b/connector_prestashop_catalog_manager/__init__.py new file mode 100644 index 000000000..cc4a757a9 --- /dev/null +++ b/connector_prestashop_catalog_manager/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import connector +from . import product +from . import product_attribute +from . import product_combination +from . import wizard +from . import product_image diff --git a/connector_prestashop_catalog_manager/__openerp__.py b/connector_prestashop_catalog_manager/__openerp__.py new file mode 100644 index 000000000..02ce6c9d6 --- /dev/null +++ b/connector_prestashop_catalog_manager/__openerp__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Copyright 2011-2013 Camptocamp +# Copyright 2011-2013 Akretion +# Copyright 2015 AvanzOSC +# Copyright 2015-2016 Tecnativa +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Prestashop-Odoo Catalog Manager", + "version": "8.0.1.0.2", + "license": "AGPL-3", + "depends": [ + "connector_prestashop" + ], + "author": "Akretion," + "AvanzOSC," + "Tecnativa," + "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/connector-prestashop", + "category": "Connector", + "data": [ + 'views/product_attribute_view.xml', + 'views/product_view.xml', + 'wizard/export_multiple_products_view.xml', + 'wizard/sync_products_view.xml', + 'wizard/active_deactive_products_view.xml', + 'views/product_image_view.xml', + ], + "installable": True, +} diff --git a/connector_prestashop_catalog_manager/connector.py b/connector_prestashop_catalog_manager/connector.py new file mode 100644 index 000000000..59f98ae6c --- /dev/null +++ b/connector_prestashop_catalog_manager/connector.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models + + +class ConnectorPrestahopCatalogManagerInstalled(models.AbstractModel): + """Empty model used to know if the module is installed on the + database. + + If the model is in the registry, the module is installed. + """ + _name = 'connector_prestahop_catalog_manager.installed' diff --git a/connector_prestashop_catalog_manager/product.py b/connector_prestashop_catalog_manager/product.py new file mode 100644 index 000000000..b5045a69e --- /dev/null +++ b/connector_prestashop_catalog_manager/product.py @@ -0,0 +1,444 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import timedelta + +from openerp.addons.connector.event import on_record_create, on_record_write +from openerp.addons.connector.unit.mapper import mapping + +from openerp.addons.connector_prestashop.unit.import_synchronizer import \ + TemplateRecordImport + +from openerp.addons.connector_prestashop.unit.export_synchronizer import ( + PrestashopExporter, + export_record, + TranslationPrestashopExporter +) +from openerp.addons.connector_prestashop.unit.mapper import ( + TranslationPrestashopExportMapper, +) + +from openerp.addons.connector_prestashop.backend import prestashop +from openerp.addons.connector_prestashop.product import INVENTORY_FIELDS + +import openerp.addons.decimal_precision as dp +import unicodedata +import re +from openerp import models, fields + +try: + import slugify as slugify_lib +except ImportError: + slugify_lib = None + + +def get_slug(name): + if slugify_lib: + try: + return slugify_lib.slugify(name) + except TypeError: + pass + uni = unicodedata.normalize('NFKD', name).encode( + 'ascii', 'ignore').decode('ascii') + slug = re.sub(r'[\W_]', ' ', uni).strip().lower() + slug = re.sub(r'[-\s]+', '-', slug) + return slug + + +@on_record_create(model_names='prestashop.product.template') +def prestashop_product_template_create(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20) + + +@on_record_write(model_names='prestashop.product.template') +def prestashop_product_template_write(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + fields = list(set(fields).difference(set(INVENTORY_FIELDS))) + if fields: + export_record.delay( + session, model_name, record_id, fields, priority=20) + # Propagate minimal_quantity from template to variants + if 'minimal_quantity' in fields: + ps_template = session.env[model_name].browse(record_id) + for binding in ps_template.prestashop_bind_ids: + binding.odoo_id.mapped( + 'product_variant_ids.prestashop_bind_ids').write({ + 'minimal_quantity': binding.minimal_quantity + }) + + +@on_record_write(model_names='product.template') +def product_template_write(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + model = session.env[model_name] + record = model.browse(record_id) + for binding in record.prestashop_bind_ids: + export_record.delay( + session, 'prestashop.product.template', binding.id, fields, + priority=20) + + +class PrestashopProductTemplate(models.Model): + _inherit = 'prestashop.product.template' + + meta_title = fields.Char( + string='Meta Title', + translate=True + ) + meta_description = fields.Char( + string='Meta Description', + translate=True + ) + meta_keywords = fields.Char( + string='Meta Keywords', + translate=True + ) + tags = fields.Char( + string='Tags', + translate=True + ) + online_only = fields.Boolean(string='Online Only') + additional_shipping_cost = fields.Float( + string='Additional Shipping Price', + digits_compute=dp.get_precision('Product Price'), + help="Additionnal Shipping Price for the product on Prestashop") + available_now = fields.Char( + string='Available Now', + translate=True + ) + available_later = fields.Char( + string='Available Later', + translate=True + ) + available_date = fields.Date(string='Available Date') + minimal_quantity = fields.Integer( + string='Minimal Quantity', + help='Minimal Sale quantity', + default=1, + ) + + +@prestashop +class ProductCategoryExporter(PrestashopExporter): + _model_name = 'prestashop.product.category' + + def _create(self, record): + res = super(ProductCategoryExporter, self)._create(record) + return res['prestashop']['category']['id'] + + +@prestashop +class ProductCategoryExportMapper(TranslationPrestashopExportMapper): + _model_name = 'prestashop.product.category' + + direct = [ + ('sequence', 'position'), + ('description', 'description'), + ('meta_description', 'meta_description'), + ('meta_keywords', 'meta_keywords'), + ('meta_title', 'meta_title'), + ('default_shop_id', 'id_shop_default'), + ('active', 'active'), + ('position', 'position') + ] + + @mapping + def translatable_fields(self, record): + translatable_fields = [ + ('name', 'name'), + ('link_rewrite', 'link_rewrite') + ] + trans = TranslationPrestashopExporter(self.environment) + translated_fields = self.convert_languages( + trans.get_record_by_lang(record.id), translatable_fields) + return translated_fields + + @mapping + def parent_id(self, record): + if not record['parent_id']: + return {'id_parent': 2} + category_binder = self.binder_for( + 'prestashop.product.category') + ext_categ_id = category_binder.to_backend( + record['parent_id']['id'], wrap=True) + return {'id_parent': ext_categ_id} + + +@prestashop +class ProductTemplateExport(TranslationPrestashopExporter): + _model_name = 'prestashop.product.template' + + def _create(self, record): + res = super(ProductTemplateExport, self)._create(record) + self.write_binging_vals(self.erp_record, record) + return res['prestashop']['product']['id'] + + def _update(self, data): + """ Update an Prestashop record """ + assert self.prestashop_id + self.export_variants() + self.check_images() + self.backend_adapter.write(self.prestashop_id, data) + + def write_binging_vals(self, erp_record, ps_record): + keys_to_update = [ + ('description_short_html', 'description_short'), + ('description_html', 'description'), + ] + trans = TemplateRecordImport(self.connector_env) + splitted_record = trans._split_per_language(ps_record) + for lang_code, prestashop_record in splitted_record.items(): + vals = {} + for key in keys_to_update: + vals[key[0]] = prestashop_record[key[1]] + erp_record.with_context( + connector_no_export=True, + lang=lang_code).write(vals) + + def export_categories(self, category, binder, ps_categ_obj): + if not category: + return + ext_id = binder.to_backend(category.id, wrap=True) + if ext_id: + return ext_id + parent_cat_id = self.export_categories( + category.parent_id, binder, ps_categ_obj) + position_cat_id = ps_categ_obj.search( + [], order='position desc', limit=1) + obj_position = position_cat_id.position + 1 + res = { + 'backend_id': self.backend_record.id, + 'odoo_id': category.id, + 'link_rewrite': get_slug(category.name), + 'position': obj_position, + } + category_ext_id = ps_categ_obj.with_context( + connector_no_export=True).create(res) + parent_cat_id = export_record(self.session, + 'prestashop.product.category', + category_ext_id.id, + fields={'parent_id': parent_cat_id}) + return re.search(r'\d+', parent_cat_id).group() + + def _parent_length(self, categ): + if not categ.parent_id: + return 1 + else: + return 1 + self._parent_length(categ.parent_id) + + def _set_main_category(self): + if self.erp_record.categ_id.id == 1 and self.erp_record.categ_ids: + max_parent = {'length': 0} + for categ in self.erp_record.categ_ids: + parent_length = self._parent_length(categ.parent_id) + if parent_length > max_parent['length']: + max_parent = {'categ_id': categ.id, + 'length': parent_length} + self.erp_record.odoo_id.with_context( + connector_no_export=True).write({ + 'categ_id': max_parent['categ_id'], + 'categ_ids': [(3, max_parent['categ_id'])], + }) + + def _export_dependencies(self): + """ Export the dependencies for the product""" + attribute_binder = self.binder_for( + 'prestashop.product.combination.option') + option_binder = self.binder_for( + 'prestashop.product.combination.option.value') + category_binder = self.binder_for( + 'prestashop.product.category') + attribute_obj = self.session.env[ + 'prestashop.product.combination.option'] + categories_obj = self.session.env[ + 'prestashop.product.category'] + self._set_main_category() + + for category in self.erp_record.categ_id + self.erp_record.categ_ids: + self.export_categories(category, category_binder, categories_obj) + + for line in self.erp_record.attribute_line_ids: + attribute_ext_id = attribute_binder.to_backend( + line.attribute_id.id, wrap=True) + if not attribute_ext_id: + res = { + 'backend_id': self.backend_record.id, + 'odoo_id': line.attribute_id.id, + } + attribute_ext_id = attribute_obj.with_context( + connector_no_export=True).create(res) + export_record( + self.session, + 'prestashop.product.combination.option', + attribute_ext_id.id) + for value in line.value_ids: + value_ext_id = option_binder.to_backend(value.id, wrap=True) + if not value_ext_id: + value_ext_id = self.session.env[ + 'prestashop.product.combination.option.value'].\ + with_context(connector_no_export=True).create({ + 'backend_id': self.backend_record.id, + 'odoo_id': value.id, + }) + export_record( + self.session, + 'prestashop.product.combination.option.value', + value_ext_id.id + ) + + def export_variants(self): + combination_obj = self.session.env['prestashop.product.combination'] + for product in self.erp_record.product_variant_ids: + if not product.attribute_value_ids: + continue + combination_ext_id = combination_obj.search([ + ('backend_id', '=', self.backend_record.id), + ('odoo_id', '=', product.id), + ]) + if not combination_ext_id: + combination_ext_id = combination_obj.with_context( + connector_no_export=True).create({ + 'backend_id': self.backend_record.id, + 'odoo_id': product.id, + 'main_template_id': self.binding_id, + }) + export_record.delay( + self.session, + 'prestashop.product.combination', + combination_ext_id.id, priority=50, + eta=timedelta(seconds=20)) + + def _not_in_variant_images(self, image): + images = [] + if len(self.erp_record.product_variant_ids) > 1: + for product in self.erp_record.product_variant_ids: + images.extend(product.image_ids.ids) + return image.id not in images + + def check_images(self): + if self.erp_record.image_ids: + image_binder = self.binder_for('prestashop.product.image') + for image in self.erp_record.image_ids: + image_ext_id = image_binder.to_backend(image.id, wrap=True) + if not image_ext_id: + image_ext_id = self.session.env[ + 'prestashop.product.image'].with_context( + connector_no_export=True).create({ + 'backend_id': self.backend_record.id, + 'odoo_id': image.id, + }) + export_record.delay( + self.session, + 'prestashop.product.image', + image_ext_id.id, priority=15) + + def update_quantities(self): + if len(self.erp_record.product_variant_ids) == 1: + product = self.erp_record.odoo_id.product_variant_ids[0] + product.update_prestashop_quantities() + + def _after_export(self): + self.check_images() + self.export_variants() + self.update_quantities() + + +@prestashop +class ProductTemplateExportMapper(TranslationPrestashopExportMapper): + _model_name = 'prestashop.product.template' + + direct = [ + ('available_for_order', 'available_for_order'), + ('show_price', 'show_price'), + ('online_only', 'online_only'), + ('weight', 'weight'), + ('standard_price', 'wholesale_price'), + ('default_shop_id', 'id_shop_default'), + ('always_available', 'active'), + ('ean13', 'ean13'), + ('additional_shipping_cost', 'additional_shipping_cost'), + ('minimal_quantity', 'minimal_quantity'), + ('on_sale', 'on_sale'), + ] + + @mapping + def list_price(self, record): + dp_obj = self.env['decimal.precision'] + precision = dp_obj.precision_get('Product Price') + if record.taxes_id.price_include and record.taxes_id.type == 'percent': + return { + 'price': str( + round(record.list_price / ( + 1 + record.taxes_id.amount), precision)) + } + else: + return {'price': str(record.list_price)} + + @mapping + def reference(self, record): + return {'reference': record.reference or record.default_code or ''} + + def _get_product_category(self, record): + ext_categ_ids = [] + binder = self.binder_for('prestashop.product.category') + categories = list(set(record.categ_ids + record.categ_id)) + for category in categories: + ext_categ_ids.append( + {'id': binder.to_backend(category.id, wrap=True)}) + return ext_categ_ids + + @mapping + def associations(self, record): + return { + 'associations': { + 'categories': { + 'category_id': self._get_product_category(record)}, + } + } + + @mapping + def categ_id(self, record): + binder = self.binder_for('prestashop.product.category') + ext_categ_id = binder.to_backend(record.categ_id.id, wrap=True) + return {'id_category_default': ext_categ_id} + + @mapping + def tax_ids(self, record): + binder = self.binder_for('prestashop.account.tax.group') + ext_id = binder.to_backend(record.tax_group_id.id, wrap=True) + return {'id_tax_rules_group': ext_id} + + @mapping + def available_date(self, record): + if record.available_date: + return {'available_date': record.available_date} + return {} + + @mapping + def translatable_fields(self, record): + translatable_fields = [ + ('name', 'name'), + ('link_rewrite', 'link_rewrite'), + ('meta_title', 'meta_title'), + ('meta_description', 'meta_description'), + ('meta_keywords', 'meta_keywords'), + ('tags', 'tags'), + ('available_now', 'available_now'), + ('available_later', 'available_later'), + ('description_short_html', 'description_short'), + ('description_html', 'description'), + ] + + if not record.description_short_html: + translatable_fields.append(("description_sale", "description")) + if not record.description_html: + translatable_fields.append(('description', 'description_short')) + + trans = TranslationPrestashopExporter(self.connector_env) + translated_fields = self.convert_languages( + trans.get_record_by_lang(record.id), translatable_fields) + return translated_fields diff --git a/connector_prestashop_catalog_manager/product_attribute.py b/connector_prestashop_catalog_manager/product_attribute.py new file mode 100644 index 000000000..cba76a822 --- /dev/null +++ b/connector_prestashop_catalog_manager/product_attribute.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp.addons.connector.event import on_record_create, on_record_write +from openerp.addons.connector_prestashop.unit.export_synchronizer import ( + export_record, + TranslationPrestashopExporter, +) +from openerp.addons.connector_prestashop.unit.export_synchronizer import ( + export_record, + PrestashopExporter, +) +from openerp.addons.connector_prestashop.unit.mapper import \ + TranslationPrestashopExportMapper +from openerp.addons.connector_prestashop.backend import prestashop +from openerp.addons.connector.unit.mapper import mapping + + +@on_record_create(model_names='prestashop.product.combination.option') +def prestashop_product_attribute_created( + session, model_name, record_id, fields=None): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20) + + +@on_record_create(model_names='prestashop.product.combination.option.value') +def prestashop_product_atrribute_value_created( + session, model_name, record_id, fields=None): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20) + + +@on_record_write(model_names='prestashop.product.combination.option') +def prestashop_product_attribute_written(session, model_name, record_id, + fields=None): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20) + + +@on_record_write(model_names='prestashop.product.combination.option.value') +def prestashop_attribute_option_written(session, model_name, record_id, + fields=None): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20) + + +@on_record_write(model_names='product.attribute.value') +def product_attribute_written(session, model_name, record_id, fields=None): + if session.context.get('connector_no_export'): + return + model = session.pool.get(model_name) + record = model.browse(session.cr, session.uid, + record_id, context=session.context) + for binding in record.prestashop_bind_ids: + export_record.delay(session, 'prestashop.product.combination.option', + binding.id, fields, priority=20) + + +@on_record_write(model_names='produc.attribute.value') +def attribute_option_written(session, model_name, record_id, fields=None): + if session.context.get('connector_no_export'): + return + model = session.pool.get(model_name) + record = model.browse(session.cr, session.uid, + record_id, context=session.context) + for binding in record.prestashop_bind_ids: + export_record.delay(session, + 'prestashop.product.combination.option.value', + binding.id, fields, priority=20) + + +@prestashop +class ProductCombinationOptionExport(PrestashopExporter): + _model_name = 'prestashop.product.combination.option' + + def _create(self, record): + res = super(ProductCombinationOptionExport, self)._create(record) + return res['prestashop']['product_option']['id'] + + +@prestashop +class ProductCombinationOptionExportMapper(TranslationPrestashopExportMapper): + _model_name = 'prestashop.product.combination.option' + + direct = [ + ('prestashop_position', 'position'), + ('group_type', 'group_type'), + ] + + @mapping + def translatable_fields(self, record): + translatable_fields = [ + ('name', 'name'), + ('name', 'public_name'), + ] + trans = TranslationPrestashopExporter(self.connector_env) + translated_fields = self.convert_languages( + trans.get_record_by_lang(record.id), translatable_fields) + return translated_fields + + +@prestashop +class ProductCombinationOptionValueExport(PrestashopExporter): + _model_name = 'prestashop.product.combination.option.value' + + def _create(self, record): + res = super(ProductCombinationOptionValueExport, self)._create(record) + return res['prestashop']['product_option_value']['id'] + + def _export_dependencies(self): + """ Export the dependencies for the record""" + attribute_id = self.erp_record.attribute_id.id + # export product attribute + binder = self.binder_for('prestashop.product.combination.option') + if not binder.to_backend(attribute_id, wrap=True): + exporter = self.get_connector_unit_for_model( + TranslationPrestashopExporter, + 'prestashop.product.combination.option') + exporter.run(attribute_id) + return + + +@prestashop +class ProductCombinationOptionValueExportMapper( + TranslationPrestashopExportMapper): + _model_name = 'prestashop.product.combination.option.value' + + direct = [('name', 'value')] + + @mapping + def prestashop_product_attribute_id(self, record): + attribute_binder = self.binder_for( + 'prestashop.product.combination.option.value') + return { + 'id_feature': attribute_binder.to_backend( + record.attribute_id.id, wrap=True) + } + + @mapping + def prestashop_product_group_attribute_id(self, record): + attribute_binder = self.binder_for( + 'prestashop.product.combination.option') + return { + 'id_attribute_group': attribute_binder.to_backend( + record.attribute_id.id, wrap=True), + } + + @mapping + def translatable_fields(self, record): + translatable_fields = [ + ('name', 'name'), + ] + trans = TranslationPrestashopExporter(self.connector_env) + translated_fields = self.convert_languages( + trans.get_record_by_lang(record.id), translatable_fields) + return translated_fields diff --git a/connector_prestashop_catalog_manager/product_combination.py b/connector_prestashop_catalog_manager/product_combination.py new file mode 100644 index 000000000..101af4ba0 --- /dev/null +++ b/connector_prestashop_catalog_manager/product_combination.py @@ -0,0 +1,237 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp.addons.connector.event import on_record_create, on_record_write +from openerp.addons.connector.unit.mapper import mapping + +from openerp.addons.connector_prestashop.unit.export_synchronizer import ( + TranslationPrestashopExporter, + export_record +) +from openerp.addons.connector_prestashop.unit.mapper import \ + TranslationPrestashopExportMapper +from openerp.addons.connector_prestashop.unit.delete_synchronizer import ( + export_delete_record +) +from openerp.addons.connector_prestashop.backend import prestashop +from openerp.addons.connector_prestashop.product import INVENTORY_FIELDS +from openerp import models, fields +from collections import OrderedDict +import logging + +EXCLUDE_FIELDS = ['list_price', 'margin'] + +_logger = logging.getLogger(__name__) + + +@on_record_create(model_names='prestashop.product.combination') +def prestashop_product_combination_create(session, model_name, record_id, + fields=None): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20) + + +@on_record_write(model_names='prestashop.product.combination') +def prestashop_product_combination_write(session, model_name, + record_id, fields): + if session.context.get('connector_no_export'): + return + fields = list(set(fields).difference(set(INVENTORY_FIELDS))) + + if fields: + export_record.delay(session, model_name, record_id, + fields, priority=20) + + +@on_record_write(model_names='product.product') +def product_product_write(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + + for field in EXCLUDE_FIELDS: + fields.pop(field, None) + + model = session.env[model_name] + record = model.browse(record_id) + if not record.is_product_variant: + return + + if 'active' in fields and not fields['active']: + prestashop_product_combination_unlink(session, record_id) + return + + if fields: + for binding in record.prestashop_bind_ids: + export_record.delay( + session, + 'prestashop.product.combination', + binding.id, + fields, + priority=20 + ) + + +def prestashop_product_combination_unlink(session, record_id): + # binding is deactivate when deactive a product variant + ps_binding_product = session.env['prestashop.product.combination'].search([ + ('active', '=', False), + ('odoo_id', '=', record_id) + ]) + for binding in ps_binding_product: + resource = 'combinations/%s' % (binding.prestashop_id) + export_delete_record.delay( + session, 'prestashop.product.combination', binding.backend_id.id, + binding.prestashop_id, resource) + ps_binding_product.unlink() + + +class PrestashopProductCombination(models.Model): + _inherit = 'prestashop.product.combination' + minimal_quantity = fields.Integer( + string='Minimal Quantity', + default=1, + help='Minimal Sale quantity', + ) + + +@prestashop +class ProductCombinationExport(TranslationPrestashopExporter): + _model_name = 'prestashop.product.combination' + + def _create(self, record): + """ + :param record: browse record to create in prestashop + :return integer: Prestashop record id + """ + res = super(ProductCombinationExport, self)._create(record) + return res['prestashop']['combination']['id'] + + def _export_images(self): + if self.erp_record.image_ids: + image_binder = self.binder_for('prestashop.product.image') + for image_line in self.erp_record.image_ids: + image_ext_id = image_binder.to_backend( + image_line.id, wrap=True) + if not image_ext_id: + image_ext_id = \ + self.session.env['prestashop.product.image']\ + .with_context(connector_no_export=True).create({ + 'backend_id': self.backend_record.id, + 'odoo_id': image_line.id, + }).id + image_content = getattr(image_line, "_get_image_from_%s" % + image_line.storage)() + export_record( + self.session, + 'prestashop.product.image', + image_ext_id, + image_content) + + def _export_dependencies(self): + """ Export the dependencies for the product""" + # TODO add export of category + attribute_binder = self.binder_for( + 'prestashop.product.combination.option') + option_binder = self.binder_for( + 'prestashop.product.combination.option.value') + for value in self.erp_record.attribute_value_ids: + attribute_ext_id = attribute_binder.to_backend( + value.attribute_id.id, wrap=True) + if not attribute_ext_id: + attribute_ext_id = self.session.env[ + 'prestashop.product.combination.option'].with_context( + connector_no_export=True).create({ + 'backend_id': self.backend_record.id, + 'odoo_id': value.attribute_id.id, + }) + export_record( + self.session, + 'prestashop.product.combination.option', + attribute_ext_id + ) + value_ext_id = option_binder.to_backend(value.id, wrap=True) + if not value_ext_id: + value_ext_id = self.session.env[ + 'prestashop.product.combination.option.value']\ + .with_context(connector_no_export=True).create({ + 'backend_id': self.backend_record.id, + 'odoo_id': value.val_id.id, + 'id_attribute_group': attribute_ext_id + }) + export_record( + self.session, + 'prestashop.product.combination.option.value', + value_ext_id) + # self._export_images() + + def update_quantities(self): + self.erp_record.odoo_id.with_context( + self.session.context).update_prestashop_qty() + + def _after_export(self): + self.update_quantities() + + +@prestashop +class ProductCombinationExportMapper(TranslationPrestashopExportMapper): + _model_name = 'prestashop.product.combination' + + direct = [ + ('default_code', 'reference'), + ('active', 'active'), + ('ean13', 'ean13'), + ('minimal_quantity', 'minimal_quantity'), + ('weight', 'weight'), + ] + + @mapping + def combination_default(self, record): + return {'default_on': str(int(record['default_on']))} + + def get_main_template_id(self, record): + template_binder = self.binder_for('prestashop.product.template') + return template_binder.to_backend(record.main_template_id.id) + + @mapping + def main_template_id(self, record): + return {'id_product': self.get_main_template_id(record)} + + @mapping + def _unit_price_impact(self, record): + tax = record.taxes_id[:1] + factor_tax = tax.price_include and (1 + tax.amount) or 1.0 + return {'price': str(record.impact_price / factor_tax)} + + @mapping + def cost_price(self, record): + return {'wholesale_price': str(record.standard_price)} + + def _get_product_option_value(self, record): + option_value = [] + option_binder = self.binder_for( + 'prestashop.product.combination.option.value') + for value in record.attribute_value_ids: + value_ext_id = option_binder.to_backend(value.id, wrap=True) + if value_ext_id: + option_value.append({'id': value_ext_id}) + return option_value + + def _get_combination_image(self, record): + images = [] + image_binder = self.binder_for('prestashop.product.image') + for image in record.image_ids: + image_ext_id = image_binder.to_backend(image.id, wrap=True) + if image_ext_id: + images.append({'id': image_ext_id}) + return images + + @mapping + def associations(self, record): + associations = OrderedDict([ + ('product_option_values', + {'product_option_value': + self._get_product_option_value(record)}), + ('images', {'image': self._get_combination_image(record) or False}) + ]) + return {'associations': associations} diff --git a/connector_prestashop_catalog_manager/product_image.py b/connector_prestashop_catalog_manager/product_image.py new file mode 100644 index 000000000..6c0bae5bb --- /dev/null +++ b/connector_prestashop_catalog_manager/product_image.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import os.path +from openerp.addons.connector.event import on_record_write, on_record_unlink +from openerp.addons.connector.connector import Binder +from openerp.addons.connector.unit.mapper import mapping + +from openerp.addons.connector_prestashop.unit.export_synchronizer import ( + PrestashopExporter, + export_record) +from openerp.addons.connector_prestashop.unit.delete_synchronizer import ( + export_delete_record +) + +from openerp.addons.connector_prestashop.unit.mapper import ( + PrestashopExportMapper +) + +from openerp.addons.connector_prestashop.connector import get_environment +from openerp.addons.connector_prestashop.backend import prestashop + +import os +from openerp import models, fields +from openerp.tools.translate import _ + + +@on_record_write(model_names='base_multi_image.image') +def product_image_write(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + model = session.env[model_name] + record = model.browse(record_id) + for binding in record.prestashop_bind_ids: + export_record.delay(session, 'prestashop.product.image', + binding.id, record.file_db_store, + priority=20) + + +@on_record_unlink(model_names='base_multi_image.image') +def product_image_unlink(session, model_name, record_id): + if session.context.get('connector_no_export'): + return + model = session.env[model_name] + record = model.browse(record_id) + for binding in record.prestashop_bind_ids: + product = session.env[record.owner_model].browse(record.owner_id) + if product.exists(): + product_template = product.prestashop_bind_ids.filtered( + lambda x: x.backend_id == binding.backend_id) + env_product = get_environment( + session, 'prestashop.product.template', binding.backend_id.id) + binder_product = env_product.get_connector_unit(Binder) + external_product_id = binder_product.to_backend( + product_template.id) + + env = get_environment( + session, binding._name, binding.backend_id.id) + binder = env.get_connector_unit(Binder) + external_id = binder.to_backend(binding.id) + resource = 'images/products/%s' % (external_product_id) + if external_id: + export_delete_record.delay( + session, binding._name, binding.backend_id.id, + external_id, resource) + + +class ProductImage(models.Model): + _inherit = 'base_multi_image.image' + + front_image = fields.Boolean(string='Front image') + + +@prestashop +class ProductImageExport(PrestashopExporter): + _model_name = 'prestashop.product.image' + + def _create(self, record): + res = super(ProductImageExport, self)._create(record) + return res['prestashop']['image']['id'] + + def _update(self, record): + res = super(ProductImageExport, self)._update(record) + return res['prestashop']['image']['id'] + + def _run(self, fields=None): + """ Flow of the synchronization, implemented in inherited classes""" + assert self.binding_id + assert self.erp_record + + if self._has_to_skip(): + return + + # export the missing linked resources + self._export_dependencies() + map_record = self.mapper.map_record(self.erp_record) + + if self.prestashop_id: + record = map_record.values() + if not record: + return _('Nothing to export.') + # special check on data before export + self._validate_data(record) + self.prestashop_id = self._update(record) + else: + record = map_record.values(for_create=True) + if not record: + return _('Nothing to export.') + # special check on data before export + self._validate_data(record) + self.prestashop_id = self._create(record) + self._after_export() + message = _('Record exported with ID %s on Prestashop.') + return message % self.prestashop_id + + +@prestashop +class ProductImageExportMapper(PrestashopExportMapper): + _model_name = 'prestashop.product.image' + + direct = [ + ('name', 'name'), + ] + + def _get_file_name(self, record): + """ + Get file name with extension from depending storage. + :param record: browse record + :return: string: file name.extension. + """ + file_name = record.odoo_id.filename + if not file_name: + storage = record.odoo_id.storage + if storage == 'url': + file_name = os.path.splitext( + os.path.basename(record.odoo_id.url)) + elif storage == 'db': + if not record.odoo_id.filename: + file_name = '%s_%s.jpg' % ( + record.odoo_id.owner_model, + record.odoo_id.owner_id) + file_name = os.path.splitext( + os.path.basename(record.odoo_id.filename or file_name)) + elif storage == 'file': + file_name = os.path.splitext( + os.path.basename(record.odoo_id.path)) + return file_name + + @mapping + def source_image(self, record): + content = getattr( + record.odoo_id, "_get_image_from_%s" % record.odoo_id.storage)() + return {'content': content} + + @mapping + def product_id(self, record): + if record.odoo_id.owner_model == u'product.product': + product_tmpl_id = record.env['product.product'].browse( + record.odoo_id.owner_id).product_tmpl_id.id + else: + product_tmpl_id = record.odoo_id.owner_id + return {'id_product': product_tmpl_id} + + @mapping + def extension(self, record): + return {'extension': self._get_file_name(record)[1]} + + @mapping + def legend(self, record): + return {'legend': record.name} + + @mapping + def filename(self, record): + file_name = record.filename + if not file_name: + file_name = '.'.join(self._get_file_name(record)) + return {'filename': file_name} diff --git a/connector_prestashop_catalog_manager/static/description/icon.png b/connector_prestashop_catalog_manager/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2fc6eee5acc87931320e45247af74f5b7d4548ed GIT binary patch literal 8227 zcmV+;Al%=HP)!%uDq#x=UhsP6ciNd`)pqg?y0NCRp@Gw}^0*01d>#L9>rTBn#mhz|%Zd=vIVDqn1y3K3+{n z2@oS5KkspGQpbWjRCGGfwz=&o!*$aFhTs;MpN_`-m~m17|e1Ej6G>ggb9o zKKW@U!bb^E4;H1*9;GV32a=lGmO7yX+u{IU8C%kCS~lscSQ;NCKs7v_x}d$s z7h0+4dChH09QfAu*!{12?cUAxHME+-nj-~^`_A{Mj9ujKEyy&NctRqok=^Lt_kuND~N z^1{+V+n7Grt(^LG^*=O|FT3Ty4(mMJ>U?3jQIm1g@~LZ`h~Kzs{8nJTq6?lIoP9aM zZxqgU!LsCtWjz9izv5c;6JXJhS*dEQRm6WCc3^S)sMjaI=!RY8SUh5}T~fI5E|e@F zIbMAtGW>?+la^LF7O&DH6^hEDwCs<7v*J|srbzgj8D>C+vB;~ z?by6nam(`YdyJUjK+Mz2>QLn7^8dFw0xa%3-!ICD`diivh4Ha0vNR@cT0Ui+!bGR? zq73-c(0Sj3R!D&2HjyDfibL_YN99ba(WBPX*x0Z5G2jD-;#MRaz6M$$0l*A&EQw|c zqHbE_6oEj%fSK%EgEbghApt~4btDI?Z_1hSmdobEg;6b%Z44D3{uCtNni6^O{=4n-4#Z9@H86#*g%=KDDFeg+dmMK8E*j~fUk@B-jyMxw|M zS``6Q^%N&^0{S!uwP^W7LO@hUJ=lgM%q+)-oA^=zo+BsVxW#mM+EpZdU$Ob2Jf_1UmwzvwFOB{*87p;l_i1notF-6Z=m^Qbo+vd0h zg)}E}f_~Ym2oO=rsYlO3%(bo>;}V`5H0Q^FqcMPib*+j3H|I>)0(|30Sh+>f7PpVW zSlr@JG#pNpcUlzzqR>2tf@dsDpL2zqmc)yv(jM$ZG*6)uyg%lp$*pS^03afHj%8II z$W0BjnEjL3nrj9*Kv@#2r*YnY?_a;{-u1+x;E09@&&DC1Y-9f=+6m1B zHNOXE&L05f(C=@R1W0UK{0guo4i#sCS==1sfoId^1Q5pqKY*Qr)w&4~T|Q-AoN9)C zli*+abZVf-e!G z#Ulc~g=yK3sxmuHRl7rp^1I3hS}g%at{GpfFx>@J13VGyqvrI@czaNOa55>V2g8YHZ>Lwo^w9JKLOh%l_6E0!{V_Umrs7bp~7XANJay{ z0@}M}lfm@8Dd+IyRn1C(Kp2hTYs0^c|2#VV2}@FU#r7uCt5R!qsR(Lz^G6ayyargo3u8KaO6 zgu&VadIpdn=#vTss^mX3VCCPChDW+O!Qv5%?UH?)RwA0($fpVc2Y|gqtB$k*90jVz z3V~(U=S&(_+pgxOBY+~~-yC#{s+A642f1Tjdd`mWKi~c4;FE2`43On2k@bSy6b$RM zrg|zYN}tigsOJNLW18Z8O#y}fdV_YqYxC~iRa=`TBf$9F!IKf|eWQ_!A4*;MK#g~| zBIEPYvQ@}8;mGrrZ9g8i?OPwYc$uS6u7;v49ot(#$EG-6BV(tna^ZC=CjGarMolsc z7@s?6tTEOKREI{2y|Zh5vf?# zLalmWazSb)2eyANw*u-Uz-xyBj; z@%4#oL}Uoj_wtrs*0zB0D-zhU{5f5$4&s00Y)rY53vOh`;FLM6Cl zb2jM@*L=uj}FK?fRU{eb5pxFrmfX+)AA|nH0*g>WQ@w0G|x!_t41Zj*xW()s(N0wtwk4!OdeVS-vPG!Ng9Gwvo$|zk=Z4Jsp)uC~ z;*ML^6$rc)5xwK4%=ww8&#PBI7>F+4!#l5`>tO;c~QJ+>*6NB*u25lh>|YQ-9S%?-noss zJX#wBxqSYBoL3tAJc~yJe5HknzfiTNf;L@vn6g=r1>sQ9<8Gy@jr*M@%8!5>mF8br zQuNrSI8RwX$-+P&@Z9n5CJjaOT2Zc~amveO#oH$rMY{) z0@|}F&lb(r$t%Dsi&Fz_ihW7LL^T!kVnm##qyyLpnk&dE%WGb{?$xP{=97q<0G5#7 zB66n!iZsa+peB^1k^5`ehv!r0bq@N%r@yiL{YO2?<23*TTuQz6bhY`o}S)fRvY6cz@r254miwE)fh+~o15AwJ_e`A%gYN}mQ|6* z&luy`xpSwZl(mfkm(~KL#XJQJz83cy5UF~7LZMK-RlS=E?hrs|G2;N6NJ}I@m4KFD zFxa`dY^e@+2%s&80E*QT2~fprXN>XI*0!nP)^c!*c?DQ4Xew9*;hpzHl-|hGW%Q($1*rr(W-W{#v#M;R;+KfU&H6p-HQ#)(U|Z zMSy<&`gQ&0n{O)8xOu(a!lb06DghjUDN9cXD<2}XsVkmI5JI6d`xD(Z<_cW7Z`nLA+Bcc5rxv9?T0yF~l?AbH4@~Gdo?T_>F@~SN9Mr<8QG+3ry2>ilL`&@Yf1V%0i zj>{dqi)IQ0sgBS~$88-mW=y@0B-O(>yHiyc9yoAd_L3#B2e8&@ODI&OOs#F(In~=3 zB&h-G#;(R3xEbV_PXh@c2&Z@cF~_z!iEj!&=d(Tka>WpT2TwZ_symdhhRz4*0AwX4 zCG`S+8Lu7ZoO4d!ojZ39DgSO+R-|3Kb}z-N*G;%hfU_DRaQ*31Ij3tXCK$%*?P1x* zXIQcM`M3@7wiB%VH4~tgj<1f}CQX|3*X-=OX(7)dm?hv4@;`3J}K#Uz<8RG#* z1i0+7%Mvzh*s!p)wDkP)W0qx^cJ12DccQN`;o9U)8@L|ODKQ#UyXU=V{^q@FzoM0uasKJir??A_#`GKR{0*|XK)}8Xz*b2oO8~(`18*{fAiqMgTpHyPfAL9?BkC= z&WroUxeWK70NSw7u%L;$$Hf!%iWcWbcY*gv?#=$-p7@MO4C43uo!s@6k&v96+y|(& zXyk|yBf4+gxbd`tf`Ze4(<70{h1<7pKld<22?_t|(xuCkFTZq@t-ev=8Ua)!p9Wm; zUB%G{hQCJ#g1ZVS-M$YEN9dmXJ-*x<)4s-9VG@EQCdQw=?bTObefjv~kKeDVDk8-| zsWGOFh_qAHcJIFXZeo2TOGrrgSJ$pxFJHZSbsg?sF%6hSyE@%=jR2M*-!xz@v!{3) zMk6G4??}O?o6(4&Wcxn4Cx0()TO!_K{C?+&H_AKJO{EP+e5kP`l4Y99sMl{UclI zE*EhiRKR=Ry$-++I`^eCQo_33YlxV-L*pZMki;Y(q77ekt9(a0d4<@vU67EF@KT#L zZ65#lE7eeI!W=?JskJLA2Rm5M=0H1fVWE${d-&@zuUhs_0b|@rUs@pipQ{oqh0N%|*s++g8i63Pfa|Wm#K+&pjT``hmsM;>{k-b5Ei0^Hs1EaamNBY>|*3YOQ#gd!Agjm`vizxx>|lh#jjwp~ukk`E<&&pvq|(BN=|mT|D?f)inMQUnzdekvA~Tq#x8b2ek)=^lo=LKRx+&y7+st zyZAd^+xSeyR4}o7M~c7QjVTSW`@Js+6&6>>ZCkb$@g5q?6>Z$kjyL{|SW)g4uK$Ke zaggtKx_~YF56!a+N_VsGVBN1`seR=A!S3C=pRVuM#)6ED#XZ5kUzD?u7_YZ66^|~3 z0ht*~V@_wQunY9hGiD5rsV0KU1kmql+7H>Y%}LyT_8dz7yOW)3*V68c{g&=3WaATu7SuFiII!V6diJPVIqg7XFZ=d2oB$&7u4P$Q z-FDk;PRq(vAP{)2T_|L~Bhb4c+eEeMRKGoNUaYdw(@1(o#)~O4X8io&`r0?i6X5Kw z7bC^W=QEdJf?>XU<$aDFaS_9QdNT<4d&KAdE!no0l-`}H96Ol2eI0OPU2@gO03uQ_ zX3Q8z)kSMUBln~$B*)*H&ey+x4K>QQi{GYX z+a8X;CB4cMzzISH2kGoTahC{pi0XGHLVjDj_?ugAz17vd^bLigJjKcLQIck zuausy7ViKEyc&um#9Sf7W0!tPG__7l+vHp&K*W-Ky9Q6?`rYsG%(@4-q2E+aFqiSw zW6MC5U2mK2%#FwD$nslw|Cr z1m`D?US>z7;afXgeFDtB=+(XBR}G$kutSwCmdJJqvSnU+PFd%yV2u7|P`AP7yc{Yh zO5D2iT@G&A!S)p&VJCWt6gl&)_&T-)*zVd7X+>z3f7gHgTF_| z@)udXsnMGNF&A={s%;xk09q>Aiuf8-I9lZJ^E4{~L?i?Z_q6eEIC<=qSl))O-+Mb$ zRB%1gGJCfOH1`t#5!nljOg^!D$#FLfY_PnitslSu%~EjcNJq010HRB;z=Upt&!+Rm zKWLDw?ehYZh{TsOx3!{05CA|#9tD;hfAb&`yVafNNo|)0XRlW z%YT2XmEb-1#9s|Naed%eZQquI)+annGgN_9S#`kH2RpEMJA``9wEy9EAh4 zC<2H`fvOH~bMi5HKU_2+MXU-x)d8ZT{PNG2XzC{rLKtw)P)$_%Y3xRKlc5;i!KvE}!)hL5vYHR5`75_EX8nramgzFV@ z1qF#;r?e~HMzjV)eJHRKJcsQ|l*Nk|+v_)O?B)r1B^)mNuBPl0fw^8;4WNN@k zC!MrstP}H@3r)_Nt0wb3ME9og}27*KIX-ZyROwJMAzx%rkA;-_EwhWGZ#U9VlB>%$ylBMjW=wNwQ{UALP)U^HPKyBKEH`Gbd~T$eKC# zbq;x-46sgA$7N(peghzDR`%5>f2pRoG;`XdM1Ywy=bf&YXB2)|_P5E}%8XfacL5zmL)|knGO7&r#^nwg1oB5Je!Q$%Fd(NNz}VcuZwqu- zFknT+v|>VD+F+xaqpDXuG;rmM5G`L*i027Kt|fX0?i8`Eo}aor_t1F;@8M9#lme?1 z`;LbOyuPU>`;R18K$0LY7_rVrbUZ2pup+Mn0s#v^(IkW~jAed+k)I;?ROIDZvu0Is zL5k=!j9h_{D-?bXFn8|UWP^EGh4u);Rpe4trjH*#zSzQgQB$m-OQQUdknl|jP*0%!E!|gYt4O`6Z0x7FAb9hoPb!DWKOyFt+KPPH`9kB0^gLBU)IKKw@HIR{%unb-q~WCjgd5 zhu7D(7W5U7PZ9Z4;QT-!Q1QqW(N93y-m_{@dR+?Yf~uU0Vtn4DwUv(xdPEnmKc37l3KgCglOE zRAt8W8QFI=#IV4;d7V*CF{*1V^dlhI>rE;TuAqeQ6ToIm-`dT7l#4-co;K~?s_XB{Vyvid!{EsU28xKznl)>9MD5o_czF7( z>_>zo?z6L&oXq|$4Rt-tFB(g$nDkJ(QHO+CBh9+lX@bT`Uv=p8c2Pbp*<%vz^Aik$$e&=1P>i5PfpNpp{?O z_d8lTEC7LPEj1sMZ9UEo2M>;`+(@G%@e#l!!1}O#ceQ)w z+qFr&81zX|t}i%P@~09f3gL0Lm9w{J3(H-wu5J3yaXXpn0Iv$L0;1 zP)*Ag5k{7OK(ng+c)yvCLYOdqh}pu+82(WBMH}9A)oRdDde^ z?(fi{!*@0Bh|w=Hr%gULD{J2O5c7e@@CT|7k}O%5Idw{nBjU0<{+8*pvJ*tPJ}Ya^ zBbk|#->=Isg}xSevSwsQ_vi^pqE!ci01pnx+ZD)79a@0*Z4vp+l(&aHS;_fRSUFUD zLSEWFQ+5A;VWoZehhU^FU=DWPb?DU~^1KxZPurhTkpGAC*T%hTsHLFn9cK!rOqudc zecPcjGwtXE& zAieXxUiRrI8~rml9XrXQdYpH=ai2$vlA z00>LeAykuPR{@c*dSdRlM3471;GnS>Q+{lpct-$jL|7cFo+B9zc4cN}j^CO!Gkd2R zsk&oT&F#}?W&fo7t5?j!VPg+h%#SRw{-estpw|K`r%jujlaVo{_LGR!577;k7nA~l zKvCArIiG470mkMIeoVll!sfF2#luvX|48bxZ)??C4YMzLb??}`!E;1;U|e3>pA`LE zlnEefw(LlGG3JYM1j7Qp2B|fkP9XoVUB4`a|qH4V^&?yiISSViu7YN)5v=`MIAG&14 zy_IxsQsJx0@)05jfX_q@P5s*ET{uI9`%%4AR3`#22P2Wo%U2c4uo1M%>gN9!{2#;& VEHKZY;1B=+002ovPDHLkV1l#7@nrx2 literal 0 HcmV?d00001 diff --git a/connector_prestashop_catalog_manager/static/description/icon.svg b/connector_prestashop_catalog_manager/static/description/icon.svg new file mode 100644 index 000000000..29aa74d8c --- /dev/null +++ b/connector_prestashop_catalog_manager/static/description/icon.svg @@ -0,0 +1,238 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/connector_prestashop_catalog_manager/views/product_attribute_view.xml b/connector_prestashop_catalog_manager/views/product_attribute_view.xml new file mode 100644 index 000000000..706beea45 --- /dev/null +++ b/connector_prestashop_catalog_manager/views/product_attribute_view.xml @@ -0,0 +1,86 @@ + + + + + + + + + product.attribute.form + product.attribute + +
+ + + + +
+
+
+ + + prestashop.product.combination.option + +
+ + + + + +
+
+
+ + + prestashop.product.combination.option + + + + + + + + + + +
+
diff --git a/connector_prestashop_catalog_manager/views/product_image_view.xml b/connector_prestashop_catalog_manager/views/product_image_view.xml new file mode 100644 index 000000000..203c40010 --- /dev/null +++ b/connector_prestashop_catalog_manager/views/product_image_view.xml @@ -0,0 +1,15 @@ + + + + + connector_prestashop.product.image.form + base_multi_image.image + + + + + + + + + \ No newline at end of file diff --git a/connector_prestashop_catalog_manager/views/product_view.xml b/connector_prestashop_catalog_manager/views/product_view.xml new file mode 100644 index 000000000..97e0c1275 --- /dev/null +++ b/connector_prestashop_catalog_manager/views/product_view.xml @@ -0,0 +1,29 @@ + + + + + connector_prestashop.product.template.form + + prestashop.product.template + + form + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/connector_prestashop_catalog_manager/wizard/__init__.py b/connector_prestashop_catalog_manager/wizard/__init__.py new file mode 100644 index 000000000..6cbd97dda --- /dev/null +++ b/connector_prestashop_catalog_manager/wizard/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import export_multiple_products +from . import sync_products +from . import active_deactive_products diff --git a/connector_prestashop_catalog_manager/wizard/active_deactive_products.py b/connector_prestashop_catalog_manager/wizard/active_deactive_products.py new file mode 100644 index 000000000..59eee4d41 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizard/active_deactive_products.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, fields, api + + +class SyncProducts(models.TransientModel): + _name = 'active.deactive.products' + + force_status = fields.Boolean( + string='Force Status', + help='Check this option to force active product in prestashop') + + def _change_status(self, status): + self.ensure_one() + product_obj = self.env['product.template'] + for product in product_obj.browse(self.env.context['active_ids']): + for bind in product.prestashop_bind_ids: + if bind.always_available != status or self.force_status: + bind.always_available = status + + @api.multi + def active_products(self): + self._change_status(True) + + @api.multi + def deactive_products(self): + self._change_status(False) diff --git a/connector_prestashop_catalog_manager/wizard/active_deactive_products_view.xml b/connector_prestashop_catalog_manager/wizard/active_deactive_products_view.xml new file mode 100644 index 000000000..d03b12bd0 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizard/active_deactive_products_view.xml @@ -0,0 +1,61 @@ + + + + + active.product.form + active.deactive.products + +
+ + + + + +
+
+
+
+
+ + + + deactive.product.form + active.deactive.products + +
+ + +
+
+
+
+
+ +
+
\ No newline at end of file diff --git a/connector_prestashop_catalog_manager/wizard/export_multiple_products.py b/connector_prestashop_catalog_manager/wizard/export_multiple_products.py new file mode 100644 index 000000000..00978066a --- /dev/null +++ b/connector_prestashop_catalog_manager/wizard/export_multiple_products.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, fields, api +import unicodedata +import re + +try: + import slugify as slugify_lib +except ImportError: + slugify_lib = None + + +def get_slug(name): + if slugify_lib: + try: + return slugify_lib.slugify(name) + except TypeError: + pass + uni = unicodedata.normalize('NFKD', name).encode( + 'ascii', 'ignore').decode('ascii') + slug = re.sub(r'[\W_]', ' ', uni).strip().lower() + slug = re.sub(r'[-\s]+', '-', slug) + return slug + + +class ExportMultipleProducts(models.TransientModel): + _name = 'export.multiple.products' + + def _default_backend(self): + return self.env['prestashop.backend'].search([], limit=1).id + + def _default_shop(self): + return self.env['prestashop.shop'].search([], limit=1).id + + name = fields.Many2one( + comodel_name='prestashop.backend', + default=_default_backend, + string='Backend', + ) + shop = fields.Many2one( + comodel_name='prestashop.shop', + default=_default_shop, + string='Shop', + ) + + def _parent_length(self, categ): + if not categ.parent_id: + return 1 + else: + return 1 + self._parent_length(categ.parent_id) + + def _set_main_category(self, product): + if product.categ_ids and product.categ_id.parent_id: + max_parent = {'length': 0} + for categ in product.categ_ids: + parent_length = self._parent_length(categ.parent_id) + if parent_length > max_parent['length']: + max_parent = {'categ_id': categ.id, + 'length': parent_length} + categ_length = self._parent_length(product.categ_id.parent_id) + if categ_length < parent_length: + if product.categ_id.id not in product.categ_ids.ids: + product.write({ + 'categ_ids': [(4, product.categ_id.id)], + }) + product.write({ + 'categ_id': max_parent['categ_id'], + 'categ_ids': [(3, max_parent['categ_id'])] + }) + else: + product.write({ + 'categ_id': max_parent['categ_id'], + 'categ_ids': [(3, max_parent['categ_id'])], + }) + + @api.multi + def set_category(self): + product_obj = self.env['product.template'] + for product in product_obj.browse(self.env.context['active_ids']): + self._set_main_category(product) + + def _check_images(self, product): + for variant in product.product_variant_ids: + for image in variant.image_ids: + if image.owner_id != product.id: + image.product_id = product + + def _check_category(self, product): + if not (product.categ_id and product.categ_id.parent_id): + return False + return True + + def _check_variants(self, product): + if len(product.product_variant_ids) == 1: + return True + if (len(product.product_variant_ids) > 1 + and not product.attribute_line_ids): + check_count = reduce( + lambda x, y: x * y, map(lambda x: len(x.value_ids), + product.attribute_line_ids)) + if check_count < len(product.product_variant_ids): + return False + return True + + @api.multi + def export_variant_stock(self): + template_obj = self.env['product.template'] + products = template_obj.browse(self.env.context['active_ids']) + products.update_prestashop_quantities() + + @api.multi + def export_products(self): + self.ensure_one() + product_obj = self.env['product.template'] + presta_tmpl_obj = self.env['prestashop.product.template'] + for product in product_obj.browse(self.env.context['active_ids']): + presta_tmpl = presta_tmpl_obj.search([ + ('odoo_id', '=', product.id), + ('backend_id', '=', self.name.id), + ('default_shop_id', '=', self.shop.id), + ]) + if not presta_tmpl: + self._check_images(product) + cat = self._check_category(product) + var = self._check_variants(product) + if not(var and cat): + continue + presta_tmpl_obj.create({ + 'backend_id': self.name.id, + 'default_shop_id': self.shop.id, + 'link_rewrite': get_slug(product.name), + 'odoo_id': product.id, + }) + else: + for tmpl in presta_tmpl: + if ' ' in tmpl.link_rewrite: + tmpl.link_rewrite = get_slug(tmpl.link_rewrite) diff --git a/connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml b/connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml new file mode 100644 index 000000000..1d7c76d00 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml @@ -0,0 +1,81 @@ + + + + + export.multiple.products.form + export.multiple.products + +
+ + + + +
+
+
+
+
+ + + + export.variant.stock.form + export.multiple.products + +
+
+
+
+
+
+ + + + export.category.stock.form + export.multiple.products + +
+
+
+
+
+
+ + +
+
diff --git a/connector_prestashop_catalog_manager/wizard/sync_products.py b/connector_prestashop_catalog_manager/wizard/sync_products.py new file mode 100644 index 000000000..0461adf64 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizard/sync_products.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, api +import logging + +_logger = logging.getLogger(__name__) +handler = logging.FileHandler('/opt/odoo/v8/sync_log.log') +handler.setLevel(logging.INFO) +formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') +handler.setFormatter(formatter) +_logger.addHandler(handler) + + +class SyncProducts(models.TransientModel): + _name = 'sync.products' + + def _bind_resync(self, product_ids): + products = self.env['product.template'].browse(product_ids) + for product in products: + try: + for bind in product.prestashop_bind_ids: + bind.resync() + except Exception, e: + _logger.info('id %s, attributes %s\n', str(product.id), e) + + @api.multi + def sync_products(self): + self._bind_resync(self.env.context['active_ids']) + + @api.multi + def sync_all_products(self): + self._bind_resync([]) diff --git a/connector_prestashop_catalog_manager/wizard/sync_products_view.xml b/connector_prestashop_catalog_manager/wizard/sync_products_view.xml new file mode 100644 index 000000000..6684559f6 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizard/sync_products_view.xml @@ -0,0 +1,30 @@ + + + + + sync.products.form + sync.products + +
+ + +
+
+
+
+
+ +
+
From e82caccf5bc6d8825e0f1312009945fa11ee2db3 Mon Sep 17 00:00:00 2001 From: "sergio.teruel" Date: Sun, 11 Sep 2016 13:41:26 +0200 Subject: [PATCH 02/34] [IMP][8.0] connector_prestashop_catalog_manager: Export categories from category view. Better option when you want export a lot of categories --- .../__init__.py | 1 + .../__openerp__.py | 1 + .../product.py | 53 +------ .../product_attribute.py | 5 +- .../product_category.py | 135 ++++++++++++++++++ .../wizard/__init__.py | 1 + .../wizard/export_category.py | 64 +++++++++ .../wizard/export_category_view.xml | 35 +++++ .../wizard/export_multiple_products.py | 4 +- .../wizard/export_multiple_products_view.xml | 24 ---- 10 files changed, 242 insertions(+), 81 deletions(-) create mode 100644 connector_prestashop_catalog_manager/product_category.py create mode 100644 connector_prestashop_catalog_manager/wizard/export_category.py create mode 100644 connector_prestashop_catalog_manager/wizard/export_category_view.xml diff --git a/connector_prestashop_catalog_manager/__init__.py b/connector_prestashop_catalog_manager/__init__.py index cc4a757a9..1713e17fe 100644 --- a/connector_prestashop_catalog_manager/__init__.py +++ b/connector_prestashop_catalog_manager/__init__.py @@ -2,6 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import connector +from . import product_category from . import product from . import product_attribute from . import product_combination diff --git a/connector_prestashop_catalog_manager/__openerp__.py b/connector_prestashop_catalog_manager/__openerp__.py index 02ce6c9d6..be7477676 100644 --- a/connector_prestashop_catalog_manager/__openerp__.py +++ b/connector_prestashop_catalog_manager/__openerp__.py @@ -21,6 +21,7 @@ "data": [ 'views/product_attribute_view.xml', 'views/product_view.xml', + 'wizard/export_category_view.xml', 'wizard/export_multiple_products_view.xml', 'wizard/sync_products_view.xml', 'wizard/active_deactive_products_view.xml', diff --git a/connector_prestashop_catalog_manager/product.py b/connector_prestashop_catalog_manager/product.py index b5045a69e..ff8b50311 100644 --- a/connector_prestashop_catalog_manager/product.py +++ b/connector_prestashop_catalog_manager/product.py @@ -10,14 +10,13 @@ TemplateRecordImport from openerp.addons.connector_prestashop.unit.export_synchronizer import ( - PrestashopExporter, export_record, TranslationPrestashopExporter ) from openerp.addons.connector_prestashop.unit.mapper import ( TranslationPrestashopExportMapper, ) - +from openerp.addons.connector_prestashop.consumer import delay_export from openerp.addons.connector_prestashop.backend import prestashop from openerp.addons.connector_prestashop.product import INVENTORY_FIELDS @@ -47,9 +46,7 @@ def get_slug(name): @on_record_create(model_names='prestashop.product.template') def prestashop_product_template_create(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): - return - export_record.delay(session, model_name, record_id, priority=20) + delay_export(session, model_name, record_id, priority=20) @on_record_write(model_names='prestashop.product.template') @@ -122,52 +119,6 @@ class PrestashopProductTemplate(models.Model): ) -@prestashop -class ProductCategoryExporter(PrestashopExporter): - _model_name = 'prestashop.product.category' - - def _create(self, record): - res = super(ProductCategoryExporter, self)._create(record) - return res['prestashop']['category']['id'] - - -@prestashop -class ProductCategoryExportMapper(TranslationPrestashopExportMapper): - _model_name = 'prestashop.product.category' - - direct = [ - ('sequence', 'position'), - ('description', 'description'), - ('meta_description', 'meta_description'), - ('meta_keywords', 'meta_keywords'), - ('meta_title', 'meta_title'), - ('default_shop_id', 'id_shop_default'), - ('active', 'active'), - ('position', 'position') - ] - - @mapping - def translatable_fields(self, record): - translatable_fields = [ - ('name', 'name'), - ('link_rewrite', 'link_rewrite') - ] - trans = TranslationPrestashopExporter(self.environment) - translated_fields = self.convert_languages( - trans.get_record_by_lang(record.id), translatable_fields) - return translated_fields - - @mapping - def parent_id(self, record): - if not record['parent_id']: - return {'id_parent': 2} - category_binder = self.binder_for( - 'prestashop.product.category') - ext_categ_id = category_binder.to_backend( - record['parent_id']['id'], wrap=True) - return {'id_parent': ext_categ_id} - - @prestashop class ProductTemplateExport(TranslationPrestashopExporter): _model_name = 'prestashop.product.template' diff --git a/connector_prestashop_catalog_manager/product_attribute.py b/connector_prestashop_catalog_manager/product_attribute.py index cba76a822..0607056bc 100644 --- a/connector_prestashop_catalog_manager/product_attribute.py +++ b/connector_prestashop_catalog_manager/product_attribute.py @@ -2,13 +2,10 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp.addons.connector.event import on_record_create, on_record_write -from openerp.addons.connector_prestashop.unit.export_synchronizer import ( - export_record, - TranslationPrestashopExporter, -) from openerp.addons.connector_prestashop.unit.export_synchronizer import ( export_record, PrestashopExporter, + TranslationPrestashopExporter, ) from openerp.addons.connector_prestashop.unit.mapper import \ TranslationPrestashopExportMapper diff --git a/connector_prestashop_catalog_manager/product_category.py b/connector_prestashop_catalog_manager/product_category.py new file mode 100644 index 000000000..4641c50b2 --- /dev/null +++ b/connector_prestashop_catalog_manager/product_category.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp.addons.connector.event import on_record_create, on_record_write +from openerp.addons.connector.unit.mapper import mapping + +from openerp.addons.connector_prestashop.unit.export_synchronizer import ( + PrestashopExporter, + export_record, + TranslationPrestashopExporter +) +from openerp.addons.connector_prestashop.unit.mapper import ( + TranslationPrestashopExportMapper, +) +from openerp.addons.connector_prestashop.consumer import \ + delay_export, delay_export_all_bindings +from openerp.addons.connector_prestashop.backend import prestashop + +import unicodedata +import re + +try: + import slugify as slugify_lib +except ImportError: + slugify_lib = None + + +def get_slug(name): + if slugify_lib: + try: + return slugify_lib.slugify(name) + except TypeError: + pass + uni = unicodedata.normalize('NFKD', name).encode( + 'ascii', 'ignore').decode('ascii') + slug = re.sub(r'[\W_]', ' ', uni).strip().lower() + slug = re.sub(r'[-\s]+', '-', slug) + return slug + +CATEGORY_EXPORT_FIELDS = [ + 'name', + 'parent_id', + 'description', + 'link_rewrite', + 'meta_description', + 'meta_keywords', + 'meta_title', + 'position'] + + +@on_record_create(model_names='prestashop.product.category') +def prestashop_product_template_create(session, model_name, record_id, fields): + delay_export(session, model_name, record_id, priority=20) + + +@on_record_write(model_names='product.category') +def product_category_write(session, model_name, record_id, fields): + if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): + delay_export_all_bindings(session, model_name, record_id, fields) + + +@on_record_write(model_names='prestashop.product.category') +def prestashop_product_category_write(session, model_name, record_id, fields): + if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): + delay_export(session, model_name, record_id, fields) + + +@prestashop +class ProductCategoryExporter(PrestashopExporter): + _model_name = 'prestashop.product.category' + + def _create(self, record): + res = super(ProductCategoryExporter, self)._create(record) + return res['prestashop']['category']['id'] + + def _export_dependencies(self): + """ Export the dependencies for the category""" + category_binder = self.binder_for('prestashop.product.category') + categories_obj = self.session.env['prestashop.product.category'] + for category in self.erp_record: + self.export_parent_category( + category.odoo_id.parent_id, category_binder, categories_obj) + + def export_parent_category(self, category, binder, ps_categ_obj): + if not category: + return + ext_id = binder.to_backend(category.id, wrap=True) + if ext_id: + return ext_id + res = { + 'backend_id': self.backend_record.id, + 'odoo_id': category.id, + 'link_rewrite': get_slug(category.name), + } + category_ext_id = ps_categ_obj.with_context( + connector_no_export=True).create(res) + parent_cat_id = export_record( + self.session, 'prestashop.product.category', category_ext_id.id) + return parent_cat_id + + +@prestashop +class ProductCategoryExportMapper(TranslationPrestashopExportMapper): + _model_name = 'prestashop.product.category' + + direct = [ + ('sequence', 'position'), + ('default_shop_id', 'id_shop_default'), + ('active', 'active'), + ('position', 'position') + ] + + @mapping + def translatable_fields(self, record): + translatable_fields = [ + ('name', 'name'), + ('link_rewrite', 'link_rewrite'), + ('description', 'description'), + ('meta_description', 'meta_description'), + ('meta_keywords', 'meta_keywords'), + ('meta_title', 'meta_title'), + ] + trans = TranslationPrestashopExporter(self.environment) + translated_fields = self.convert_languages( + trans.get_record_by_lang(record.id), translatable_fields) + return translated_fields + + @mapping + def parent_id(self, record): + if not record['parent_id']: + return {'id_parent': 2} + category_binder = self.binder_for('prestashop.product.category') + ext_categ_id = category_binder.to_backend( + record.parent_id.id, wrap=True) + return {'id_parent': ext_categ_id} diff --git a/connector_prestashop_catalog_manager/wizard/__init__.py b/connector_prestashop_catalog_manager/wizard/__init__.py index 6cbd97dda..c10f7b14a 100644 --- a/connector_prestashop_catalog_manager/wizard/__init__.py +++ b/connector_prestashop_catalog_manager/wizard/__init__.py @@ -4,3 +4,4 @@ from . import export_multiple_products from . import sync_products from . import active_deactive_products +from . import export_category diff --git a/connector_prestashop_catalog_manager/wizard/export_category.py b/connector_prestashop_catalog_manager/wizard/export_category.py new file mode 100644 index 000000000..e8c6993b6 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizard/export_category.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, fields, api +import unicodedata +import re + +try: + import slugify as slugify_lib +except ImportError: + slugify_lib = None + + +def get_slug(name): + if slugify_lib: + try: + return slugify_lib.slugify(name) + except TypeError: + pass + uni = unicodedata.normalize('NFKD', name).encode( + 'ascii', 'ignore').decode('ascii') + slug = re.sub(r'[\W_]', ' ', uni).strip().lower() + slug = re.sub(r'[-\s]+', '-', slug) + return slug + + +class PrestashopExportCategory(models.TransientModel): + _name = 'wiz.prestashop.export.category' + + def _default_backend(self): + return self.env['prestashop.backend'].search([], limit=1).id + + def _default_shop(self): + return self.env['prestashop.shop'].search([], limit=1).id + + backend_id = fields.Many2one( + comodel_name='prestashop.backend', + default=_default_backend, + string='Backend', + ) + shop_id = fields.Many2one( + comodel_name='prestashop.shop', + default=_default_shop, + string='Shop', + ) + + @api.multi + def export_categories(self): + self.ensure_one() + category_obj = self.env['product.category'] + ps_category_obj = self.env['prestashop.product.category'] + for category in category_obj.browse(self.env.context['active_ids']): + ps_category = ps_category_obj.search([ + ('odoo_id', '=', category.id), + ('backend_id', '=', self.backend_id.id), + ('default_shop_id', '=', self.shop_id.id), + ]) + if not ps_category: + ps_category_obj.create({ + 'backend_id': self.backend_id.id, + 'default_shop_id': self.shop_id.id, + 'link_rewrite': get_slug(category.name), + 'odoo_id': category.id, + }) diff --git a/connector_prestashop_catalog_manager/wizard/export_category_view.xml b/connector_prestashop_catalog_manager/wizard/export_category_view.xml new file mode 100644 index 000000000..402962ff8 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizard/export_category_view.xml @@ -0,0 +1,35 @@ + + + + + + wiz.prestashop.export.category.form + wiz.prestashop.export.category + +
+ + + + +
+
+
+
+
+ + + +
+
diff --git a/connector_prestashop_catalog_manager/wizard/export_multiple_products.py b/connector_prestashop_catalog_manager/wizard/export_multiple_products.py index 00978066a..275d29c49 100644 --- a/connector_prestashop_catalog_manager/wizard/export_multiple_products.py +++ b/connector_prestashop_catalog_manager/wizard/export_multiple_products.py @@ -94,8 +94,8 @@ def _check_category(self, product): def _check_variants(self, product): if len(product.product_variant_ids) == 1: return True - if (len(product.product_variant_ids) > 1 - and not product.attribute_line_ids): + if (len(product.product_variant_ids) > 1 and + not product.attribute_line_ids): check_count = reduce( lambda x, y: x * y, map(lambda x: len(x.value_ids), product.attribute_line_ids)) diff --git a/connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml b/connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml index 1d7c76d00..d40083d9c 100644 --- a/connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml +++ b/connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml @@ -53,29 +53,5 @@ id="act_export_products_stock" view_id="export_variant_stock_form"/> - - export.category.stock.form - export.multiple.products - -
-
-
-
-
-
- - From 2b275197ac5bd24e7e1046c125b3eb98171ac8c4 Mon Sep 17 00:00:00 2001 From: "sergio.teruel" Date: Mon, 12 Sep 2016 21:51:52 +0200 Subject: [PATCH 03/34] [FIX][8.0] connector_prestashop_catalog_manager: Fixed error when export a binding without short description --- connector_prestashop_catalog_manager/product.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/connector_prestashop_catalog_manager/product.py b/connector_prestashop_catalog_manager/product.py index ff8b50311..ab51ca2e9 100644 --- a/connector_prestashop_catalog_manager/product.py +++ b/connector_prestashop_catalog_manager/product.py @@ -384,11 +384,6 @@ def translatable_fields(self, record): ('description_html', 'description'), ] - if not record.description_short_html: - translatable_fields.append(("description_sale", "description")) - if not record.description_html: - translatable_fields.append(('description', 'description_short')) - trans = TranslationPrestashopExporter(self.connector_env) translated_fields = self.convert_languages( trans.get_record_by_lang(record.id), translatable_fields) From 4ee7ed80cadcbd38f60ba2f5fbd1aa707324a724 Mon Sep 17 00:00:00 2001 From: Sergio Incaser Date: Wed, 14 Sep 2016 10:01:41 +0200 Subject: [PATCH 04/34] [FIX][8.0] connector_prestashop_catalog_manager: Export field create_date in product_mapper, in PrestaShop this field determine the 'new' state --- .../product.py | 5 +++ .../views/product_attribute_view.xml | 38 ------------------- .../views/product_image_view.xml | 2 +- .../views/product_view.xml | 2 +- 4 files changed, 7 insertions(+), 40 deletions(-) diff --git a/connector_prestashop_catalog_manager/product.py b/connector_prestashop_catalog_manager/product.py index ab51ca2e9..34e81e303 100644 --- a/connector_prestashop_catalog_manager/product.py +++ b/connector_prestashop_catalog_manager/product.py @@ -369,6 +369,11 @@ def available_date(self, record): return {'available_date': record.available_date} return {} + @mapping + def date_add(self, record): + # When export a record the date_add in PS is null. + return {'date_add': record.create_date} + @mapping def translatable_fields(self, record): translatable_fields = [ diff --git a/connector_prestashop_catalog_manager/views/product_attribute_view.xml b/connector_prestashop_catalog_manager/views/product_attribute_view.xml index 706beea45..f052fba84 100644 --- a/connector_prestashop_catalog_manager/views/product_attribute_view.xml +++ b/connector_prestashop_catalog_manager/views/product_attribute_view.xml @@ -44,43 +44,5 @@ - diff --git a/connector_prestashop_catalog_manager/views/product_image_view.xml b/connector_prestashop_catalog_manager/views/product_image_view.xml index 203c40010..8c2dcc919 100644 --- a/connector_prestashop_catalog_manager/views/product_image_view.xml +++ b/connector_prestashop_catalog_manager/views/product_image_view.xml @@ -12,4 +12,4 @@ - \ No newline at end of file + diff --git a/connector_prestashop_catalog_manager/views/product_view.xml b/connector_prestashop_catalog_manager/views/product_view.xml index 97e0c1275..f390e073b 100644 --- a/connector_prestashop_catalog_manager/views/product_view.xml +++ b/connector_prestashop_catalog_manager/views/product_view.xml @@ -26,4 +26,4 @@ - \ No newline at end of file + From 3e1b86574b819e1e7b838c9a3e99f57556c068b5 Mon Sep 17 00:00:00 2001 From: "sergio.teruel" Date: Sun, 18 Sep 2016 10:24:20 +0200 Subject: [PATCH 05/34] [IMP][8.0] connector_prestashop: Update ps combinations after export template --- connector_prestashop_catalog_manager/product.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/connector_prestashop_catalog_manager/product.py b/connector_prestashop_catalog_manager/product.py index 34e81e303..afeeb6819 100644 --- a/connector_prestashop_catalog_manager/product.py +++ b/connector_prestashop_catalog_manager/product.py @@ -257,11 +257,13 @@ def export_variants(self): 'odoo_id': product.id, 'main_template_id': self.binding_id, }) - export_record.delay( - self.session, - 'prestashop.product.combination', - combination_ext_id.id, priority=50, - eta=timedelta(seconds=20)) + # If a template has been modified then always update PrestaShop + # combinations + export_record.delay( + self.session, + 'prestashop.product.combination', + combination_ext_id.id, priority=50, + eta=timedelta(seconds=20)) def _not_in_variant_images(self, image): images = [] From 778888f28f4a541d6c5a4b8f0fc32b95fbf33216 Mon Sep 17 00:00:00 2001 From: Sergio Teruel Albert Date: Thu, 29 Sep 2016 01:48:51 +0200 Subject: [PATCH 06/34] [FIX][8.0] connector_prestashop_catalog_manager: Fix error to write in file log --- .../wizard/sync_products.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/connector_prestashop_catalog_manager/wizard/sync_products.py b/connector_prestashop_catalog_manager/wizard/sync_products.py index 0461adf64..fe895f1f9 100644 --- a/connector_prestashop_catalog_manager/wizard/sync_products.py +++ b/connector_prestashop_catalog_manager/wizard/sync_products.py @@ -5,12 +5,6 @@ import logging _logger = logging.getLogger(__name__) -handler = logging.FileHandler('/opt/odoo/v8/sync_log.log') -handler.setLevel(logging.INFO) -formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s') -handler.setFormatter(formatter) -_logger.addHandler(handler) class SyncProducts(models.TransientModel): From deecc7af6b3c29fd75bab448ed2b00709ec4403a Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 19 Oct 2016 09:24:23 +0200 Subject: [PATCH 07/34] [fix] catalog manager --- .../__init__.py | 1 - .../connector.py | 13 ------- .../product.py | 39 +++++++++---------- .../product_attribute.py | 2 +- .../product_category.py | 20 ++++++---- .../product_combination.py | 6 +-- .../product_image.py | 12 +++--- 7 files changed, 41 insertions(+), 52 deletions(-) delete mode 100644 connector_prestashop_catalog_manager/connector.py diff --git a/connector_prestashop_catalog_manager/__init__.py b/connector_prestashop_catalog_manager/__init__.py index 1713e17fe..4e0c8b588 100644 --- a/connector_prestashop_catalog_manager/__init__.py +++ b/connector_prestashop_catalog_manager/__init__.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import connector from . import product_category from . import product from . import product_attribute diff --git a/connector_prestashop_catalog_manager/connector.py b/connector_prestashop_catalog_manager/connector.py deleted file mode 100644 index 59f98ae6c..000000000 --- a/connector_prestashop_catalog_manager/connector.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from openerp import models - - -class ConnectorPrestahopCatalogManagerInstalled(models.AbstractModel): - """Empty model used to know if the module is installed on the - database. - - If the model is in the registry, the module is installed. - """ - _name = 'connector_prestahop_catalog_manager.installed' diff --git a/connector_prestashop_catalog_manager/product.py b/connector_prestashop_catalog_manager/product.py index afeeb6819..0f79e7ce4 100644 --- a/connector_prestashop_catalog_manager/product.py +++ b/connector_prestashop_catalog_manager/product.py @@ -6,19 +6,18 @@ from openerp.addons.connector.event import on_record_create, on_record_write from openerp.addons.connector.unit.mapper import mapping -from openerp.addons.connector_prestashop.unit.import_synchronizer import \ - TemplateRecordImport +from openerp.addons.connector_prestashop.\ + models.product_template.importer import ProductTemplateImporter -from openerp.addons.connector_prestashop.unit.export_synchronizer import ( +from openerp.addons.connector_prestashop.unit.exporter import ( export_record, TranslationPrestashopExporter ) from openerp.addons.connector_prestashop.unit.mapper import ( TranslationPrestashopExportMapper, ) -from openerp.addons.connector_prestashop.consumer import delay_export from openerp.addons.connector_prestashop.backend import prestashop -from openerp.addons.connector_prestashop.product import INVENTORY_FIELDS +from openerp.addons.connector_prestashop.consumer import INVENTORY_FIELDS import openerp.addons.decimal_precision as dp import unicodedata @@ -46,7 +45,7 @@ def get_slug(name): @on_record_create(model_names='prestashop.product.template') def prestashop_product_template_create(session, model_name, record_id, fields): - delay_export(session, model_name, record_id, priority=20) + export_record.delay(session, model_name, record_id, priority=20) @on_record_write(model_names='prestashop.product.template') @@ -125,7 +124,7 @@ class ProductTemplateExport(TranslationPrestashopExporter): def _create(self, record): res = super(ProductTemplateExport, self)._create(record) - self.write_binging_vals(self.erp_record, record) + self.write_binging_vals(self.binding, record) return res['prestashop']['product']['id'] def _update(self, data): @@ -140,7 +139,7 @@ def write_binging_vals(self, erp_record, ps_record): ('description_short_html', 'description_short'), ('description_html', 'description'), ] - trans = TemplateRecordImport(self.connector_env) + trans = ProductTemplateImporter(self.connector_env) splitted_record = trans._split_per_language(ps_record) for lang_code, prestashop_record in splitted_record.items(): vals = {} @@ -182,14 +181,14 @@ def _parent_length(self, categ): return 1 + self._parent_length(categ.parent_id) def _set_main_category(self): - if self.erp_record.categ_id.id == 1 and self.erp_record.categ_ids: + if self.binding.categ_id.id == 1 and self.binding.categ_ids: max_parent = {'length': 0} - for categ in self.erp_record.categ_ids: + for categ in self.binding.categ_ids: parent_length = self._parent_length(categ.parent_id) if parent_length > max_parent['length']: max_parent = {'categ_id': categ.id, 'length': parent_length} - self.erp_record.odoo_id.with_context( + self.binding.odoo_id.with_context( connector_no_export=True).write({ 'categ_id': max_parent['categ_id'], 'categ_ids': [(3, max_parent['categ_id'])], @@ -209,10 +208,10 @@ def _export_dependencies(self): 'prestashop.product.category'] self._set_main_category() - for category in self.erp_record.categ_id + self.erp_record.categ_ids: + for category in self.binding.categ_id + self.binding.categ_ids: self.export_categories(category, category_binder, categories_obj) - for line in self.erp_record.attribute_line_ids: + for line in self.binding.attribute_line_ids: attribute_ext_id = attribute_binder.to_backend( line.attribute_id.id, wrap=True) if not attribute_ext_id: @@ -243,7 +242,7 @@ def _export_dependencies(self): def export_variants(self): combination_obj = self.session.env['prestashop.product.combination'] - for product in self.erp_record.product_variant_ids: + for product in self.binding.product_variant_ids: if not product.attribute_value_ids: continue combination_ext_id = combination_obj.search([ @@ -267,15 +266,15 @@ def export_variants(self): def _not_in_variant_images(self, image): images = [] - if len(self.erp_record.product_variant_ids) > 1: - for product in self.erp_record.product_variant_ids: + if len(self.binding.product_variant_ids) > 1: + for product in self.binding.product_variant_ids: images.extend(product.image_ids.ids) return image.id not in images def check_images(self): - if self.erp_record.image_ids: + if self.binding.image_ids: image_binder = self.binder_for('prestashop.product.image') - for image in self.erp_record.image_ids: + for image in self.binding.image_ids: image_ext_id = image_binder.to_backend(image.id, wrap=True) if not image_ext_id: image_ext_id = self.session.env[ @@ -290,8 +289,8 @@ def check_images(self): image_ext_id.id, priority=15) def update_quantities(self): - if len(self.erp_record.product_variant_ids) == 1: - product = self.erp_record.odoo_id.product_variant_ids[0] + if len(self.binding.product_variant_ids) == 1: + product = self.binding.odoo_id.product_variant_ids[0] product.update_prestashop_quantities() def _after_export(self): diff --git a/connector_prestashop_catalog_manager/product_attribute.py b/connector_prestashop_catalog_manager/product_attribute.py index 0607056bc..8f8855938 100644 --- a/connector_prestashop_catalog_manager/product_attribute.py +++ b/connector_prestashop_catalog_manager/product_attribute.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp.addons.connector.event import on_record_create, on_record_write -from openerp.addons.connector_prestashop.unit.export_synchronizer import ( +from openerp.addons.connector_prestashop.unit.exporter import ( export_record, PrestashopExporter, TranslationPrestashopExporter, diff --git a/connector_prestashop_catalog_manager/product_category.py b/connector_prestashop_catalog_manager/product_category.py index 4641c50b2..bb44561a9 100644 --- a/connector_prestashop_catalog_manager/product_category.py +++ b/connector_prestashop_catalog_manager/product_category.py @@ -4,7 +4,7 @@ from openerp.addons.connector.event import on_record_create, on_record_write from openerp.addons.connector.unit.mapper import mapping -from openerp.addons.connector_prestashop.unit.export_synchronizer import ( +from openerp.addons.connector_prestashop.unit.exporter import ( PrestashopExporter, export_record, TranslationPrestashopExporter @@ -12,8 +12,6 @@ from openerp.addons.connector_prestashop.unit.mapper import ( TranslationPrestashopExportMapper, ) -from openerp.addons.connector_prestashop.consumer import \ - delay_export, delay_export_all_bindings from openerp.addons.connector_prestashop.backend import prestashop import unicodedata @@ -37,6 +35,7 @@ def get_slug(name): slug = re.sub(r'[-\s]+', '-', slug) return slug +# TODO: attach this to a model to ease override CATEGORY_EXPORT_FIELDS = [ 'name', 'parent_id', @@ -45,24 +44,31 @@ def get_slug(name): 'meta_description', 'meta_keywords', 'meta_title', - 'position'] + 'position' +] @on_record_create(model_names='prestashop.product.category') def prestashop_product_template_create(session, model_name, record_id, fields): - delay_export(session, model_name, record_id, priority=20) + export_record.delay(session, model_name, record_id, priority=20) @on_record_write(model_names='product.category') def product_category_write(session, model_name, record_id, fields): if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): - delay_export_all_bindings(session, model_name, record_id, fields) + if session.context.get('connector_no_export'): + return + model = session.env[model_name] + record = model.browse(record_id) + for binding in record.prestashop_bind_ids: + export_record.delay(session, binding._model._name, binding.id, + fields=fields, priority=20) @on_record_write(model_names='prestashop.product.category') def prestashop_product_category_write(session, model_name, record_id, fields): if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): - delay_export(session, model_name, record_id, fields) + export_record.delay(session, model_name, record_id, fields) @prestashop diff --git a/connector_prestashop_catalog_manager/product_combination.py b/connector_prestashop_catalog_manager/product_combination.py index 101af4ba0..8da8bde66 100644 --- a/connector_prestashop_catalog_manager/product_combination.py +++ b/connector_prestashop_catalog_manager/product_combination.py @@ -4,17 +4,17 @@ from openerp.addons.connector.event import on_record_create, on_record_write from openerp.addons.connector.unit.mapper import mapping -from openerp.addons.connector_prestashop.unit.export_synchronizer import ( +from openerp.addons.connector_prestashop.unit.exporter import ( TranslationPrestashopExporter, export_record ) from openerp.addons.connector_prestashop.unit.mapper import \ TranslationPrestashopExportMapper -from openerp.addons.connector_prestashop.unit.delete_synchronizer import ( +from openerp.addons.connector_prestashop.unit.deleter import ( export_delete_record ) from openerp.addons.connector_prestashop.backend import prestashop -from openerp.addons.connector_prestashop.product import INVENTORY_FIELDS +from openerp.addons.connector_prestashop.consumer import INVENTORY_FIELDS from openerp import models, fields from collections import OrderedDict import logging diff --git a/connector_prestashop_catalog_manager/product_image.py b/connector_prestashop_catalog_manager/product_image.py index 6c0bae5bb..d378d3908 100644 --- a/connector_prestashop_catalog_manager/product_image.py +++ b/connector_prestashop_catalog_manager/product_image.py @@ -1,29 +1,27 @@ # -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import os.path from openerp.addons.connector.event import on_record_write, on_record_unlink from openerp.addons.connector.connector import Binder from openerp.addons.connector.unit.mapper import mapping - -from openerp.addons.connector_prestashop.unit.export_synchronizer import ( +from openerp.addons.connector_prestashop.unit.exporter import ( PrestashopExporter, export_record) -from openerp.addons.connector_prestashop.unit.delete_synchronizer import ( +from openerp.addons.connector_prestashop.unit.deleter import ( export_delete_record ) - from openerp.addons.connector_prestashop.unit.mapper import ( PrestashopExportMapper ) - from openerp.addons.connector_prestashop.connector import get_environment from openerp.addons.connector_prestashop.backend import prestashop -import os from openerp import models, fields from openerp.tools.translate import _ +import os +import os.path + @on_record_write(model_names='base_multi_image.image') def product_image_write(session, model_name, record_id, fields): From b40951a1a3079c3e5bc9344372ad5e8ff366f53c Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 19 Oct 2016 14:48:21 +0200 Subject: [PATCH 08/34] [fix] translations --- .../product.py | 35 ++++++++----------- .../product_attribute.py | 30 +++++----------- .../product_category.py | 26 ++++++-------- .../product_combination.py | 10 +++--- .../product_image.py | 4 +-- 5 files changed, 40 insertions(+), 65 deletions(-) diff --git a/connector_prestashop_catalog_manager/product.py b/connector_prestashop_catalog_manager/product.py index 0f79e7ce4..48adba1a4 100644 --- a/connector_prestashop_catalog_manager/product.py +++ b/connector_prestashop_catalog_manager/product.py @@ -311,11 +311,24 @@ class ProductTemplateExportMapper(TranslationPrestashopExportMapper): ('standard_price', 'wholesale_price'), ('default_shop_id', 'id_shop_default'), ('always_available', 'active'), - ('ean13', 'ean13'), + ('barcode', 'barcode'), ('additional_shipping_cost', 'additional_shipping_cost'), ('minimal_quantity', 'minimal_quantity'), ('on_sale', 'on_sale'), ] + # handled by base mapping `translatable_fields` + _translatable_fields = [ + ('name', 'name'), + ('link_rewrite', 'link_rewrite'), + ('meta_title', 'meta_title'), + ('meta_description', 'meta_description'), + ('meta_keywords', 'meta_keywords'), + ('tags', 'tags'), + ('available_now', 'available_now'), + ('available_later', 'available_later'), + ('description_short_html', 'description_short'), + ('description_html', 'description'), + ] @mapping def list_price(self, record): @@ -374,23 +387,3 @@ def available_date(self, record): def date_add(self, record): # When export a record the date_add in PS is null. return {'date_add': record.create_date} - - @mapping - def translatable_fields(self, record): - translatable_fields = [ - ('name', 'name'), - ('link_rewrite', 'link_rewrite'), - ('meta_title', 'meta_title'), - ('meta_description', 'meta_description'), - ('meta_keywords', 'meta_keywords'), - ('tags', 'tags'), - ('available_now', 'available_now'), - ('available_later', 'available_later'), - ('description_short_html', 'description_short'), - ('description_html', 'description'), - ] - - trans = TranslationPrestashopExporter(self.connector_env) - translated_fields = self.convert_languages( - trans.get_record_by_lang(record.id), translatable_fields) - return translated_fields diff --git a/connector_prestashop_catalog_manager/product_attribute.py b/connector_prestashop_catalog_manager/product_attribute.py index 8f8855938..4532fd3b5 100644 --- a/connector_prestashop_catalog_manager/product_attribute.py +++ b/connector_prestashop_catalog_manager/product_attribute.py @@ -88,16 +88,10 @@ class ProductCombinationOptionExportMapper(TranslationPrestashopExportMapper): ('group_type', 'group_type'), ] - @mapping - def translatable_fields(self, record): - translatable_fields = [ - ('name', 'name'), - ('name', 'public_name'), - ] - trans = TranslationPrestashopExporter(self.connector_env) - translated_fields = self.convert_languages( - trans.get_record_by_lang(record.id), translatable_fields) - return translated_fields + _translatable_fields = [ + ('name', 'name'), + ('name', 'public_name'), + ] @prestashop @@ -110,7 +104,7 @@ def _create(self, record): def _export_dependencies(self): """ Export the dependencies for the record""" - attribute_id = self.erp_record.attribute_id.id + attribute_id = self.binding.attribute_id.id # export product attribute binder = self.binder_for('prestashop.product.combination.option') if not binder.to_backend(attribute_id, wrap=True): @@ -127,6 +121,10 @@ class ProductCombinationOptionValueExportMapper( _model_name = 'prestashop.product.combination.option.value' direct = [('name', 'value')] + # handled by base mapping `translatable_fields` + _translatable_fields = [ + ('name', 'name'), + ] @mapping def prestashop_product_attribute_id(self, record): @@ -145,13 +143,3 @@ def prestashop_product_group_attribute_id(self, record): 'id_attribute_group': attribute_binder.to_backend( record.attribute_id.id, wrap=True), } - - @mapping - def translatable_fields(self, record): - translatable_fields = [ - ('name', 'name'), - ] - trans = TranslationPrestashopExporter(self.connector_env) - translated_fields = self.convert_languages( - trans.get_record_by_lang(record.id), translatable_fields) - return translated_fields diff --git a/connector_prestashop_catalog_manager/product_category.py b/connector_prestashop_catalog_manager/product_category.py index bb44561a9..bf8ee4095 100644 --- a/connector_prestashop_catalog_manager/product_category.py +++ b/connector_prestashop_catalog_manager/product_category.py @@ -83,7 +83,7 @@ def _export_dependencies(self): """ Export the dependencies for the category""" category_binder = self.binder_for('prestashop.product.category') categories_obj = self.session.env['prestashop.product.category'] - for category in self.erp_record: + for category in self.binding: self.export_parent_category( category.odoo_id.parent_id, category_binder, categories_obj) @@ -115,21 +115,15 @@ class ProductCategoryExportMapper(TranslationPrestashopExportMapper): ('active', 'active'), ('position', 'position') ] - - @mapping - def translatable_fields(self, record): - translatable_fields = [ - ('name', 'name'), - ('link_rewrite', 'link_rewrite'), - ('description', 'description'), - ('meta_description', 'meta_description'), - ('meta_keywords', 'meta_keywords'), - ('meta_title', 'meta_title'), - ] - trans = TranslationPrestashopExporter(self.environment) - translated_fields = self.convert_languages( - trans.get_record_by_lang(record.id), translatable_fields) - return translated_fields + # handled by base mapping `translatable_fields` + _translatable_fields = [ + ('name', 'name'), + ('link_rewrite', 'link_rewrite'), + ('description', 'description'), + ('meta_description', 'meta_description'), + ('meta_keywords', 'meta_keywords'), + ('meta_title', 'meta_title'), + ] @mapping def parent_id(self, record): diff --git a/connector_prestashop_catalog_manager/product_combination.py b/connector_prestashop_catalog_manager/product_combination.py index 8da8bde66..9a34b564d 100644 --- a/connector_prestashop_catalog_manager/product_combination.py +++ b/connector_prestashop_catalog_manager/product_combination.py @@ -108,9 +108,9 @@ def _create(self, record): return res['prestashop']['combination']['id'] def _export_images(self): - if self.erp_record.image_ids: + if self.binding.image_ids: image_binder = self.binder_for('prestashop.product.image') - for image_line in self.erp_record.image_ids: + for image_line in self.binding.image_ids: image_ext_id = image_binder.to_backend( image_line.id, wrap=True) if not image_ext_id: @@ -135,7 +135,7 @@ def _export_dependencies(self): 'prestashop.product.combination.option') option_binder = self.binder_for( 'prestashop.product.combination.option.value') - for value in self.erp_record.attribute_value_ids: + for value in self.binding.attribute_value_ids: attribute_ext_id = attribute_binder.to_backend( value.attribute_id.id, wrap=True) if not attribute_ext_id: @@ -166,7 +166,7 @@ def _export_dependencies(self): # self._export_images() def update_quantities(self): - self.erp_record.odoo_id.with_context( + self.binding.odoo_id.with_context( self.session.context).update_prestashop_qty() def _after_export(self): @@ -180,7 +180,7 @@ class ProductCombinationExportMapper(TranslationPrestashopExportMapper): direct = [ ('default_code', 'reference'), ('active', 'active'), - ('ean13', 'ean13'), + ('barcode', 'barcode'), ('minimal_quantity', 'minimal_quantity'), ('weight', 'weight'), ] diff --git a/connector_prestashop_catalog_manager/product_image.py b/connector_prestashop_catalog_manager/product_image.py index d378d3908..bcd321afc 100644 --- a/connector_prestashop_catalog_manager/product_image.py +++ b/connector_prestashop_catalog_manager/product_image.py @@ -84,14 +84,14 @@ def _update(self, record): def _run(self, fields=None): """ Flow of the synchronization, implemented in inherited classes""" assert self.binding_id - assert self.erp_record + assert self.binding if self._has_to_skip(): return # export the missing linked resources self._export_dependencies() - map_record = self.mapper.map_record(self.erp_record) + map_record = self.mapper.map_record(self.binding) if self.prestashop_id: record = map_record.values() From f10dfc8b4b4f90f15402ab582ad73cceee203d94 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 19 Oct 2016 16:12:36 +0200 Subject: [PATCH 09/34] PEP8 + MT cleanup --- connector_prestashop_catalog_manager/product_category.py | 1 - 1 file changed, 1 deletion(-) diff --git a/connector_prestashop_catalog_manager/product_category.py b/connector_prestashop_catalog_manager/product_category.py index bf8ee4095..89a081c4c 100644 --- a/connector_prestashop_catalog_manager/product_category.py +++ b/connector_prestashop_catalog_manager/product_category.py @@ -7,7 +7,6 @@ from openerp.addons.connector_prestashop.unit.exporter import ( PrestashopExporter, export_record, - TranslationPrestashopExporter ) from openerp.addons.connector_prestashop.unit.mapper import ( TranslationPrestashopExportMapper, From 0ccb481755b646034b4c410ef25668bd42398a4c Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 29 Nov 2016 13:39:47 +0100 Subject: [PATCH 10/34] Do not export imported product templates --- connector_prestashop_catalog_manager/product.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/connector_prestashop_catalog_manager/product.py b/connector_prestashop_catalog_manager/product.py index 48adba1a4..78efb7b48 100644 --- a/connector_prestashop_catalog_manager/product.py +++ b/connector_prestashop_catalog_manager/product.py @@ -45,6 +45,8 @@ def get_slug(name): @on_record_create(model_names='prestashop.product.template') def prestashop_product_template_create(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return export_record.delay(session, model_name, record_id, priority=20) From 912911eb5ed91877b133bd459f636aec2cdda97b Mon Sep 17 00:00:00 2001 From: Sergio Teruel Albert Date: Tue, 29 Nov 2016 18:24:37 +0100 Subject: [PATCH 11/34] [9.0][MIG] connector_prestashop_catalog_manager: Migration and new structure --- .../__init__.py | 9 +- .../__openerp__.py | 8 +- .../consumer.py | 300 ++++++++++++++++++ .../models/__init__.py | 8 + .../models/product_category/__init__.py | 3 + .../product_category/exporter.py} | 58 +--- .../models/product_image/__init__.py | 3 + .../product_image/exporter.py} | 63 +--- .../models/product_product/__init__.py | 4 + .../models/product_product/common.py | 14 + .../product_product/exporter.py} | 163 +++++----- .../models/product_template/__init__.py | 4 + .../models/product_template/common.py | 46 +++ .../product_template/exporter.py} | 215 +++---------- .../product_attribute.py | 145 --------- .../wizards/__init__.py | 7 + .../wizards/active_deactive_products.py | 28 ++ .../wizards/active_deactive_products_view.xml | 61 ++++ .../wizards/export_category.py | 64 ++++ .../wizards/export_category_view.xml | 35 ++ .../wizards/export_multiple_products.py | 145 +++++++++ .../wizards/export_multiple_products_view.xml | 57 ++++ .../wizards/sync_products.py | 28 ++ .../wizards/sync_products_view.xml | 30 ++ 24 files changed, 987 insertions(+), 511 deletions(-) create mode 100644 connector_prestashop_catalog_manager/consumer.py create mode 100644 connector_prestashop_catalog_manager/models/__init__.py create mode 100644 connector_prestashop_catalog_manager/models/product_category/__init__.py rename connector_prestashop_catalog_manager/{product_category.py => models/product_category/exporter.py} (60%) create mode 100644 connector_prestashop_catalog_manager/models/product_image/__init__.py rename connector_prestashop_catalog_manager/{product_image.py => models/product_image/exporter.py} (61%) create mode 100644 connector_prestashop_catalog_manager/models/product_product/__init__.py create mode 100644 connector_prestashop_catalog_manager/models/product_product/common.py rename connector_prestashop_catalog_manager/{product_combination.py => models/product_product/exporter.py} (68%) create mode 100644 connector_prestashop_catalog_manager/models/product_template/__init__.py create mode 100644 connector_prestashop_catalog_manager/models/product_template/common.py rename connector_prestashop_catalog_manager/{product.py => models/product_template/exporter.py} (55%) delete mode 100644 connector_prestashop_catalog_manager/product_attribute.py create mode 100644 connector_prestashop_catalog_manager/wizards/__init__.py create mode 100644 connector_prestashop_catalog_manager/wizards/active_deactive_products.py create mode 100644 connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml create mode 100644 connector_prestashop_catalog_manager/wizards/export_category.py create mode 100644 connector_prestashop_catalog_manager/wizards/export_category_view.xml create mode 100644 connector_prestashop_catalog_manager/wizards/export_multiple_products.py create mode 100644 connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml create mode 100644 connector_prestashop_catalog_manager/wizards/sync_products.py create mode 100644 connector_prestashop_catalog_manager/wizards/sync_products_view.xml diff --git a/connector_prestashop_catalog_manager/__init__.py b/connector_prestashop_catalog_manager/__init__.py index 4e0c8b588..a6847e3b1 100644 --- a/connector_prestashop_catalog_manager/__init__.py +++ b/connector_prestashop_catalog_manager/__init__.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import product_category -from . import product -from . import product_attribute -from . import product_combination -from . import wizard -from . import product_image +from . import consumer +from . import models +from . import wizards diff --git a/connector_prestashop_catalog_manager/__openerp__.py b/connector_prestashop_catalog_manager/__openerp__.py index be7477676..3e03964ad 100644 --- a/connector_prestashop_catalog_manager/__openerp__.py +++ b/connector_prestashop_catalog_manager/__openerp__.py @@ -21,10 +21,10 @@ "data": [ 'views/product_attribute_view.xml', 'views/product_view.xml', - 'wizard/export_category_view.xml', - 'wizard/export_multiple_products_view.xml', - 'wizard/sync_products_view.xml', - 'wizard/active_deactive_products_view.xml', + 'wizards/export_category_view.xml', + 'wizards/export_multiple_products_view.xml', + 'wizards/sync_products_view.xml', + 'wizards/active_deactive_products_view.xml', 'views/product_image_view.xml', ], "installable": True, diff --git a/connector_prestashop_catalog_manager/consumer.py b/connector_prestashop_catalog_manager/consumer.py new file mode 100644 index 000000000..dff246584 --- /dev/null +++ b/connector_prestashop_catalog_manager/consumer.py @@ -0,0 +1,300 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from openerp.addons.connector.event import ( + on_record_create, + on_record_write, + on_record_unlink, +) +from openerp.addons.connector.connector import Binder +from openerp.addons.connector_prestashop.unit.exporter import export_record +from openerp.addons.connector_prestashop.connector import get_environment +from openerp.addons.connector_prestashop.unit.deleter import ( + export_delete_record +) +from openerp.addons.connector_prestashop.consumer import INVENTORY_FIELDS + +import unicodedata +import re + +try: + import slugify as slugify_lib +except ImportError: + slugify_lib = None + + +def get_slug(name): + if slugify_lib: + try: + return slugify_lib.slugify(name) + except TypeError: + pass + uni = unicodedata.normalize('NFKD', name).encode( + 'ascii', 'ignore').decode('ascii') + slug = re.sub(r'[\W_]', ' ', uni).strip().lower() + slug = re.sub(r'[-\s]+', '-', slug) + return slug + +# TODO: attach this to a model to ease override +CATEGORY_EXPORT_FIELDS = [ + 'name', + 'parent_id', + 'description', + 'link_rewrite', + 'meta_description', + 'meta_keywords', + 'meta_title', + 'position' +] + +EXCLUDE_FIELDS = ['list_price'] + + +def _get_shop_url(session): + return session.context.get('shop_url', None) + + +@on_record_create(model_names='prestashop.product.category') +def prestashop_product_category_create(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20, + shop_url=_get_shop_url(session)) + + +@on_record_write(model_names='product.category') +def product_category_write(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): + model = session.env[model_name] + record = model.browse(record_id) + for binding in record.prestashop_bind_ids: + export_record.delay( + session, binding._model._name, binding.id, fields=fields, + priority=20,shop_url=_get_shop_url(session)) + + +@on_record_write(model_names='prestashop.product.category') +def prestashop_product_category_write(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): + export_record.delay(session, model_name, record_id, fields, + shop_url=_get_shop_url(session)) + + +@on_record_write(model_names='base_multi_image.image') +def product_image_write(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + model = session.env[model_name] + record = model.browse(record_id) + for binding in record.prestashop_bind_ids: + export_record.delay(session, 'prestashop.product.image', + binding.id, record.file_db_store, + priority=20, shop_url=_get_shop_url(session)) + + +@on_record_unlink(model_names='base_multi_image.image') +def product_image_unlink(session, model_name, record_id): + if session.context.get('connector_no_export'): + return + model = session.env[model_name] + record = model.browse(record_id) + for binding in record.prestashop_bind_ids: + product = session.env[record.owner_model].browse(record.owner_id) + if product.exists(): + product_template = product.prestashop_bind_ids.filtered( + lambda x: x.backend_id == binding.backend_id) + if not product_template: + return + env_product = get_environment( + session, 'prestashop.product.template', binding.backend_id.id) + binder_product = env_product.get_connector_unit(Binder) + external_product_id = binder_product.to_backend( + product_template.id) + + env = get_environment( + session, binding._name, binding.backend_id.id) + binder = env.get_connector_unit(Binder) + external_id = binder.to_backend(binding.id) + resource = 'images/products/%s' % (external_product_id) + if external_id: + export_delete_record.delay( + session, binding._name, binding.backend_id.id, + external_id, resource) + + +@on_record_create(model_names='prestashop.product.template') +def prestashop_product_template_create(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + export_record.delay( + session, model_name, record_id, priority=20, + shop_url=_get_shop_url(session)) + + +@on_record_write(model_names='prestashop.product.template') +def prestashop_product_template_write(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + fields = list(set(fields).difference(set(INVENTORY_FIELDS))) + if fields: + export_record.delay( + session, model_name, record_id, fields, priority=20, + shop_url=_get_shop_url(session)) + # Propagate minimal_quantity from template to variants + if 'minimal_quantity' in fields: + ps_template = session.env[model_name].browse(record_id) + for binding in ps_template.prestashop_bind_ids: + binding.odoo_id.mapped( + 'product_variant_ids.prestashop_bind_ids').write({ + 'minimal_quantity': binding.minimal_quantity + }) + + +@on_record_write(model_names='product.template') +def product_template_write(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + model = session.env[model_name] + record = model.browse(record_id) + for binding in record.prestashop_bind_ids: + export_record.delay( + session, 'prestashop.product.template', binding.id, fields, + priority=20, shop_url=_get_shop_url(session)) + + +@on_record_create(model_names='prestashop.product.combination') +def prestashop_product_combination_create(session, model_name, record_id, + fields=None): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20, + shop_url=_get_shop_url(session)) + + +@on_record_write(model_names='prestashop.product.combination') +def prestashop_product_combination_write(session, model_name, + record_id, fields): + if session.context.get('connector_no_export'): + return + fields = list(set(fields).difference(set(INVENTORY_FIELDS))) + + if fields: + export_record.delay( + session, model_name, record_id, fields, priority=20, + shop_url=_get_shop_url(session)) + + +def prestashop_product_combination_unlink(session, record_id): + # binding is deactivate when deactive a product variant + ps_binding_product = session.env['prestashop.product.combination'].search([ + ('active', '=', False), + ('odoo_id', '=', record_id) + ]) + for binding in ps_binding_product: + resource = 'combinations/%s' % (binding.prestashop_id) + export_delete_record.delay( + session, 'prestashop.product.combination', binding.backend_id.id, + binding.prestashop_id, resource) + ps_binding_product.unlink() + + +@on_record_write(model_names='product.product') +def product_product_write(session, model_name, record_id, fields): + if session.context.get('connector_no_export'): + return + + for field in EXCLUDE_FIELDS: + fields.pop(field, None) + + model = session.env[model_name] + record = model.browse(record_id) + if not record.is_product_variant: + return + + if 'active' in fields and not fields['active']: + prestashop_product_combination_unlink(session, record_id) + return + + if fields: + for binding in record.prestashop_bind_ids: + export_record.delay( + session, + 'prestashop.product.combination', + binding.id, + fields, + priority=20, + shop_url=_get_shop_url(session) + ) + # We can not update directly default_on in combination because this field + # is unique key in PS so I trigger a write in product template to assign + # the default combination + if 'default_on' in fields: + product_template_write( + session, 'product.template', record.product_tmpl_id.id, 'name') + + +@on_record_create(model_names='prestashop.product.combination.option') +def prestashop_product_attribute_created( + session, model_name, record_id, fields=None): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20, + shop_url=_get_shop_url(session)) + + +@on_record_create(model_names='prestashop.product.combination.option.value') +def prestashop_product_atrribute_value_created( + session, model_name, record_id, fields=None): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20, + shop_url=_get_shop_url(session)) + + +@on_record_write(model_names='prestashop.product.combination.option') +def prestashop_product_attribute_written(session, model_name, record_id, + fields=None): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20, + shop_url=_get_shop_url(session)) + + +@on_record_write(model_names='prestashop.product.combination.option.value') +def prestashop_attribute_option_written(session, model_name, record_id, + fields=None): + if session.context.get('connector_no_export'): + return + export_record.delay(session, model_name, record_id, priority=20, + shop_url=_get_shop_url(session)) + + +@on_record_write(model_names='product.attribute.value') +def product_attribute_written(session, model_name, record_id, fields=None): + if session.context.get('connector_no_export'): + return + model = session.pool.get(model_name) + record = model.browse(session.cr, session.uid, + record_id, context=session.context) + for binding in record.prestashop_bind_ids: + export_record.delay(session, 'prestashop.product.combination.option', + binding.id, fields, priority=20, + shop_url=_get_shop_url(session)) + + +@on_record_write(model_names='produc.attribute.value') +def attribute_option_written(session, model_name, record_id, fields=None): + if session.context.get('connector_no_export'): + return + model = session.pool.get(model_name) + record = model.browse(session.cr, session.uid, + record_id, context=session.context) + for binding in record.prestashop_bind_ids: + export_record.delay(session, + 'prestashop.product.combination.option.value', + binding.id, fields, priority=20, + shop_url=_get_shop_url(session)) diff --git a/connector_prestashop_catalog_manager/models/__init__.py b/connector_prestashop_catalog_manager/models/__init__.py new file mode 100644 index 000000000..058e6d810 --- /dev/null +++ b/connector_prestashop_catalog_manager/models/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# © 2016 Sergio Teruel +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import product_category +from . import product_product +from . import product_template +from . import product_image diff --git a/connector_prestashop_catalog_manager/models/product_category/__init__.py b/connector_prestashop_catalog_manager/models/product_category/__init__.py new file mode 100644 index 000000000..d8ad335f9 --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_category/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import exporter diff --git a/connector_prestashop_catalog_manager/product_category.py b/connector_prestashop_catalog_manager/models/product_category/exporter.py similarity index 60% rename from connector_prestashop_catalog_manager/product_category.py rename to connector_prestashop_catalog_manager/models/product_category/exporter.py index 89a081c4c..75243f952 100644 --- a/connector_prestashop_catalog_manager/product_category.py +++ b/connector_prestashop_catalog_manager/models/product_category/exporter.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp.addons.connector.event import on_record_create, on_record_write from openerp.addons.connector.unit.mapper import mapping from openerp.addons.connector_prestashop.unit.exporter import ( @@ -11,64 +10,9 @@ from openerp.addons.connector_prestashop.unit.mapper import ( TranslationPrestashopExportMapper, ) +from ...consumer import get_slug from openerp.addons.connector_prestashop.backend import prestashop -import unicodedata -import re - -try: - import slugify as slugify_lib -except ImportError: - slugify_lib = None - - -def get_slug(name): - if slugify_lib: - try: - return slugify_lib.slugify(name) - except TypeError: - pass - uni = unicodedata.normalize('NFKD', name).encode( - 'ascii', 'ignore').decode('ascii') - slug = re.sub(r'[\W_]', ' ', uni).strip().lower() - slug = re.sub(r'[-\s]+', '-', slug) - return slug - -# TODO: attach this to a model to ease override -CATEGORY_EXPORT_FIELDS = [ - 'name', - 'parent_id', - 'description', - 'link_rewrite', - 'meta_description', - 'meta_keywords', - 'meta_title', - 'position' -] - - -@on_record_create(model_names='prestashop.product.category') -def prestashop_product_template_create(session, model_name, record_id, fields): - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names='product.category') -def product_category_write(session, model_name, record_id, fields): - if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): - if session.context.get('connector_no_export'): - return - model = session.env[model_name] - record = model.browse(record_id) - for binding in record.prestashop_bind_ids: - export_record.delay(session, binding._model._name, binding.id, - fields=fields, priority=20) - - -@on_record_write(model_names='prestashop.product.category') -def prestashop_product_category_write(session, model_name, record_id, fields): - if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): - export_record.delay(session, model_name, record_id, fields) - @prestashop class ProductCategoryExporter(PrestashopExporter): diff --git a/connector_prestashop_catalog_manager/models/product_image/__init__.py b/connector_prestashop_catalog_manager/models/product_image/__init__.py new file mode 100644 index 000000000..d8ad335f9 --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_image/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import exporter diff --git a/connector_prestashop_catalog_manager/product_image.py b/connector_prestashop_catalog_manager/models/product_image/exporter.py similarity index 61% rename from connector_prestashop_catalog_manager/product_image.py rename to connector_prestashop_catalog_manager/models/product_image/exporter.py index bcd321afc..f0f885648 100644 --- a/connector_prestashop_catalog_manager/product_image.py +++ b/connector_prestashop_catalog_manager/models/product_image/exporter.py @@ -1,19 +1,13 @@ # -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp.addons.connector.event import on_record_write, on_record_unlink -from openerp.addons.connector.connector import Binder from openerp.addons.connector.unit.mapper import mapping -from openerp.addons.connector_prestashop.unit.exporter import ( - PrestashopExporter, - export_record) -from openerp.addons.connector_prestashop.unit.deleter import ( - export_delete_record -) +from openerp.addons.connector_prestashop.unit.exporter import \ + PrestashopExporter from openerp.addons.connector_prestashop.unit.mapper import ( PrestashopExportMapper ) -from openerp.addons.connector_prestashop.connector import get_environment + from openerp.addons.connector_prestashop.backend import prestashop from openerp import models, fields @@ -23,46 +17,6 @@ import os.path -@on_record_write(model_names='base_multi_image.image') -def product_image_write(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): - return - model = session.env[model_name] - record = model.browse(record_id) - for binding in record.prestashop_bind_ids: - export_record.delay(session, 'prestashop.product.image', - binding.id, record.file_db_store, - priority=20) - - -@on_record_unlink(model_names='base_multi_image.image') -def product_image_unlink(session, model_name, record_id): - if session.context.get('connector_no_export'): - return - model = session.env[model_name] - record = model.browse(record_id) - for binding in record.prestashop_bind_ids: - product = session.env[record.owner_model].browse(record.owner_id) - if product.exists(): - product_template = product.prestashop_bind_ids.filtered( - lambda x: x.backend_id == binding.backend_id) - env_product = get_environment( - session, 'prestashop.product.template', binding.backend_id.id) - binder_product = env_product.get_connector_unit(Binder) - external_product_id = binder_product.to_backend( - product_template.id) - - env = get_environment( - session, binding._name, binding.backend_id.id) - binder = env.get_connector_unit(Binder) - external_id = binder.to_backend(binding.id) - resource = 'images/products/%s' % (external_product_id) - if external_id: - export_delete_record.delay( - session, binding._name, binding.backend_id.id, - external_id, resource) - - class ProductImage(models.Model): _inherit = 'base_multi_image.image' @@ -153,11 +107,14 @@ def source_image(self, record): @mapping def product_id(self, record): if record.odoo_id.owner_model == u'product.product': - product_tmpl_id = record.env['product.product'].browse( - record.odoo_id.owner_id).product_tmpl_id.id + product_tmpl = record.env['product.product'].browse( + record.odoo_id.owner_id).product_tmpl_id else: - product_tmpl_id = record.odoo_id.owner_id - return {'id_product': product_tmpl_id} + product_tmpl = record.env['product.template'].browse( + record.odoo_id.owner_id) + binder = self.binder_for('prestashop.product.template') + ps_product_id = binder.to_backend(product_tmpl, wrap=True) + return {'id_product': ps_product_id} @mapping def extension(self, record): diff --git a/connector_prestashop_catalog_manager/models/product_product/__init__.py b/connector_prestashop_catalog_manager/models/product_product/__init__.py new file mode 100644 index 000000000..6d964d9a7 --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_product/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import exporter +from . import common diff --git a/connector_prestashop_catalog_manager/models/product_product/common.py b/connector_prestashop_catalog_manager/models/product_product/common.py new file mode 100644 index 000000000..fc71a2031 --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_product/common.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# © 2016 Sergio Teruel +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from openerp import models, fields + + +class PrestashopProductCombination(models.Model): + _inherit = 'prestashop.product.combination' + minimal_quantity = fields.Integer( + string='Minimal Quantity', + default=1, + help='Minimal Sale quantity', + ) diff --git a/connector_prestashop_catalog_manager/product_combination.py b/connector_prestashop_catalog_manager/models/product_product/exporter.py similarity index 68% rename from connector_prestashop_catalog_manager/product_combination.py rename to connector_prestashop_catalog_manager/models/product_product/exporter.py index 9a34b564d..a649b7beb 100644 --- a/connector_prestashop_catalog_manager/product_combination.py +++ b/connector_prestashop_catalog_manager/models/product_product/exporter.py @@ -1,100 +1,23 @@ # -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp.addons.connector.event import on_record_create, on_record_write from openerp.addons.connector.unit.mapper import mapping from openerp.addons.connector_prestashop.unit.exporter import ( TranslationPrestashopExporter, - export_record + export_record, + PrestashopExporter, ) from openerp.addons.connector_prestashop.unit.mapper import \ TranslationPrestashopExportMapper -from openerp.addons.connector_prestashop.unit.deleter import ( - export_delete_record -) from openerp.addons.connector_prestashop.backend import prestashop -from openerp.addons.connector_prestashop.consumer import INVENTORY_FIELDS -from openerp import models, fields from collections import OrderedDict import logging -EXCLUDE_FIELDS = ['list_price', 'margin'] _logger = logging.getLogger(__name__) -@on_record_create(model_names='prestashop.product.combination') -def prestashop_product_combination_create(session, model_name, record_id, - fields=None): - if session.context.get('connector_no_export'): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names='prestashop.product.combination') -def prestashop_product_combination_write(session, model_name, - record_id, fields): - if session.context.get('connector_no_export'): - return - fields = list(set(fields).difference(set(INVENTORY_FIELDS))) - - if fields: - export_record.delay(session, model_name, record_id, - fields, priority=20) - - -@on_record_write(model_names='product.product') -def product_product_write(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): - return - - for field in EXCLUDE_FIELDS: - fields.pop(field, None) - - model = session.env[model_name] - record = model.browse(record_id) - if not record.is_product_variant: - return - - if 'active' in fields and not fields['active']: - prestashop_product_combination_unlink(session, record_id) - return - - if fields: - for binding in record.prestashop_bind_ids: - export_record.delay( - session, - 'prestashop.product.combination', - binding.id, - fields, - priority=20 - ) - - -def prestashop_product_combination_unlink(session, record_id): - # binding is deactivate when deactive a product variant - ps_binding_product = session.env['prestashop.product.combination'].search([ - ('active', '=', False), - ('odoo_id', '=', record_id) - ]) - for binding in ps_binding_product: - resource = 'combinations/%s' % (binding.prestashop_id) - export_delete_record.delay( - session, 'prestashop.product.combination', binding.backend_id.id, - binding.prestashop_id, resource) - ps_binding_product.unlink() - - -class PrestashopProductCombination(models.Model): - _inherit = 'prestashop.product.combination' - minimal_quantity = fields.Integer( - string='Minimal Quantity', - default=1, - help='Minimal Sale quantity', - ) - - @prestashop class ProductCombinationExport(TranslationPrestashopExporter): _model_name = 'prestashop.product.combination' @@ -180,14 +103,17 @@ class ProductCombinationExportMapper(TranslationPrestashopExportMapper): direct = [ ('default_code', 'reference'), ('active', 'active'), - ('barcode', 'barcode'), + ('barcode', 'ean13'), ('minimal_quantity', 'minimal_quantity'), ('weight', 'weight'), ] @mapping def combination_default(self, record): - return {'default_on': str(int(record['default_on']))} + return {} + # TODO Check issue to write default combination see + # TODO id_default_combination in product template export mapper + # return {'default_on': str(int(record['default_on']))} def get_main_template_id(self, record): template_binder = self.binder_for('prestashop.product.template') @@ -235,3 +161,78 @@ def associations(self, record): ('images', {'image': self._get_combination_image(record) or False}) ]) return {'associations': associations} + + +@prestashop +class ProductCombinationOptionExport(PrestashopExporter): + _model_name = 'prestashop.product.combination.option' + + def _create(self, record): + res = super(ProductCombinationOptionExport, self)._create(record) + return res['prestashop']['product_option']['id'] + + +@prestashop +class ProductCombinationOptionExportMapper(TranslationPrestashopExportMapper): + _model_name = 'prestashop.product.combination.option' + + direct = [ + ('prestashop_position', 'position'), + ('group_type', 'group_type'), + ] + + _translatable_fields = [ + ('name', 'name'), + ('name', 'public_name'), + ] + + +@prestashop +class ProductCombinationOptionValueExport(PrestashopExporter): + _model_name = 'prestashop.product.combination.option.value' + + def _create(self, record): + res = super(ProductCombinationOptionValueExport, self)._create(record) + return res['prestashop']['product_option_value']['id'] + + def _export_dependencies(self): + """ Export the dependencies for the record""" + attribute_id = self.binding.attribute_id.id + # export product attribute + binder = self.binder_for('prestashop.product.combination.option') + if not binder.to_backend(attribute_id, wrap=True): + exporter = self.get_connector_unit_for_model( + TranslationPrestashopExporter, + 'prestashop.product.combination.option') + exporter.run(attribute_id) + return + + +@prestashop +class ProductCombinationOptionValueExportMapper( + TranslationPrestashopExportMapper): + _model_name = 'prestashop.product.combination.option.value' + + direct = [('name', 'value')] + # handled by base mapping `translatable_fields` + _translatable_fields = [ + ('name', 'name'), + ] + + @mapping + def prestashop_product_attribute_id(self, record): + attribute_binder = self.binder_for( + 'prestashop.product.combination.option.value') + return { + 'id_feature': attribute_binder.to_backend( + record.attribute_id.id, wrap=True) + } + + @mapping + def prestashop_product_group_attribute_id(self, record): + attribute_binder = self.binder_for( + 'prestashop.product.combination.option') + return { + 'id_attribute_group': attribute_binder.to_backend( + record.attribute_id.id, wrap=True), + } diff --git a/connector_prestashop_catalog_manager/models/product_template/__init__.py b/connector_prestashop_catalog_manager/models/product_template/__init__.py new file mode 100644 index 000000000..6d964d9a7 --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_template/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import exporter +from . import common diff --git a/connector_prestashop_catalog_manager/models/product_template/common.py b/connector_prestashop_catalog_manager/models/product_template/common.py new file mode 100644 index 000000000..b4cca65c4 --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_template/common.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# © 2016 Sergio Teruel +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from openerp import models, fields +import openerp.addons.decimal_precision as dp + + +class PrestashopProductTemplate(models.Model): + _inherit = 'prestashop.product.template' + + meta_title = fields.Char( + string='Meta Title', + translate=True + ) + meta_description = fields.Char( + string='Meta Description', + translate=True + ) + meta_keywords = fields.Char( + string='Meta Keywords', + translate=True + ) + tags = fields.Char( + string='Tags', + translate=True + ) + online_only = fields.Boolean(string='Online Only') + additional_shipping_cost = fields.Float( + string='Additional Shipping Price', + digits_compute=dp.get_precision('Product Price'), + help="Additionnal Shipping Price for the product on Prestashop") + available_now = fields.Char( + string='Available Now', + translate=True + ) + available_later = fields.Char( + string='Available Later', + translate=True + ) + available_date = fields.Date(string='Available Date') + minimal_quantity = fields.Integer( + string='Minimal Quantity', + help='Minimal Sale quantity', + default=1, + ) diff --git a/connector_prestashop_catalog_manager/product.py b/connector_prestashop_catalog_manager/models/product_template/exporter.py similarity index 55% rename from connector_prestashop_catalog_manager/product.py rename to connector_prestashop_catalog_manager/models/product_template/exporter.py index 78efb7b48..8cbfbbf34 100644 --- a/connector_prestashop_catalog_manager/product.py +++ b/connector_prestashop_catalog_manager/models/product_template/exporter.py @@ -3,8 +3,7 @@ from datetime import timedelta -from openerp.addons.connector.event import on_record_create, on_record_write -from openerp.addons.connector.unit.mapper import mapping +from openerp.addons.connector.unit.mapper import mapping, m2o_to_backend from openerp.addons.connector_prestashop.\ models.product_template.importer import ProductTemplateImporter @@ -17,107 +16,9 @@ TranslationPrestashopExportMapper, ) from openerp.addons.connector_prestashop.backend import prestashop -from openerp.addons.connector_prestashop.consumer import INVENTORY_FIELDS +from ...consumer import get_slug -import openerp.addons.decimal_precision as dp -import unicodedata import re -from openerp import models, fields - -try: - import slugify as slugify_lib -except ImportError: - slugify_lib = None - - -def get_slug(name): - if slugify_lib: - try: - return slugify_lib.slugify(name) - except TypeError: - pass - uni = unicodedata.normalize('NFKD', name).encode( - 'ascii', 'ignore').decode('ascii') - slug = re.sub(r'[\W_]', ' ', uni).strip().lower() - slug = re.sub(r'[-\s]+', '-', slug) - return slug - - -@on_record_create(model_names='prestashop.product.template') -def prestashop_product_template_create(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names='prestashop.product.template') -def prestashop_product_template_write(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): - return - fields = list(set(fields).difference(set(INVENTORY_FIELDS))) - if fields: - export_record.delay( - session, model_name, record_id, fields, priority=20) - # Propagate minimal_quantity from template to variants - if 'minimal_quantity' in fields: - ps_template = session.env[model_name].browse(record_id) - for binding in ps_template.prestashop_bind_ids: - binding.odoo_id.mapped( - 'product_variant_ids.prestashop_bind_ids').write({ - 'minimal_quantity': binding.minimal_quantity - }) - - -@on_record_write(model_names='product.template') -def product_template_write(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): - return - model = session.env[model_name] - record = model.browse(record_id) - for binding in record.prestashop_bind_ids: - export_record.delay( - session, 'prestashop.product.template', binding.id, fields, - priority=20) - - -class PrestashopProductTemplate(models.Model): - _inherit = 'prestashop.product.template' - - meta_title = fields.Char( - string='Meta Title', - translate=True - ) - meta_description = fields.Char( - string='Meta Description', - translate=True - ) - meta_keywords = fields.Char( - string='Meta Keywords', - translate=True - ) - tags = fields.Char( - string='Tags', - translate=True - ) - online_only = fields.Boolean(string='Online Only') - additional_shipping_cost = fields.Float( - string='Additional Shipping Price', - digits_compute=dp.get_precision('Product Price'), - help="Additionnal Shipping Price for the product on Prestashop") - available_now = fields.Char( - string='Available Now', - translate=True - ) - available_later = fields.Char( - string='Available Later', - translate=True - ) - available_date = fields.Date(string='Available Date') - minimal_quantity = fields.Integer( - string='Minimal Quantity', - help='Minimal Sale quantity', - default=1, - ) @prestashop @@ -151,14 +52,15 @@ def write_binging_vals(self, erp_record, ps_record): connector_no_export=True, lang=lang_code).write(vals) - def export_categories(self, category, binder, ps_categ_obj): + def export_categories(self, category): if not category: return - ext_id = binder.to_backend(category.id, wrap=True) + category_binder = self.binder_for('prestashop.product.category') + ext_id = category_binder.to_backend(category.id, wrap=True) if ext_id: return ext_id - parent_cat_id = self.export_categories( - category.parent_id, binder, ps_categ_obj) + + ps_categ_obj = self.session.env['prestashop.product.category'] position_cat_id = ps_categ_obj.search( [], order='position desc', limit=1) obj_position = position_cat_id.position + 1 @@ -168,13 +70,12 @@ def export_categories(self, category, binder, ps_categ_obj): 'link_rewrite': get_slug(category.name), 'position': obj_position, } - category_ext_id = ps_categ_obj.with_context( + binding = ps_categ_obj.with_context( connector_no_export=True).create(res) - parent_cat_id = export_record(self.session, - 'prestashop.product.category', - category_ext_id.id, - fields={'parent_id': parent_cat_id}) - return re.search(r'\d+', parent_cat_id).group() + export_record( + self.session, + 'prestashop.product.category', + binding.id) def _parent_length(self, categ): if not categ.parent_id: @@ -182,66 +83,32 @@ def _parent_length(self, categ): else: return 1 + self._parent_length(categ.parent_id) - def _set_main_category(self): - if self.binding.categ_id.id == 1 and self.binding.categ_ids: - max_parent = {'length': 0} - for categ in self.binding.categ_ids: - parent_length = self._parent_length(categ.parent_id) - if parent_length > max_parent['length']: - max_parent = {'categ_id': categ.id, - 'length': parent_length} - self.binding.odoo_id.with_context( - connector_no_export=True).write({ - 'categ_id': max_parent['categ_id'], - 'categ_ids': [(3, max_parent['categ_id'])], - }) - def _export_dependencies(self): """ Export the dependencies for the product""" + super(ProductTemplateExport, self)._export_dependencies() attribute_binder = self.binder_for( 'prestashop.product.combination.option') option_binder = self.binder_for( 'prestashop.product.combination.option.value') - category_binder = self.binder_for( - 'prestashop.product.category') attribute_obj = self.session.env[ 'prestashop.product.combination.option'] - categories_obj = self.session.env[ - 'prestashop.product.category'] - self._set_main_category() - - for category in self.binding.categ_id + self.binding.categ_ids: - self.export_categories(category, category_binder, categories_obj) + + for category in self.binding.categ_ids: + self.export_categories(category) for line in self.binding.attribute_line_ids: attribute_ext_id = attribute_binder.to_backend( line.attribute_id.id, wrap=True) if not attribute_ext_id: - res = { - 'backend_id': self.backend_record.id, - 'odoo_id': line.attribute_id.id, - } - attribute_ext_id = attribute_obj.with_context( - connector_no_export=True).create(res) - export_record( - self.session, - 'prestashop.product.combination.option', - attribute_ext_id.id) + self._export_dependency( + line.attribute_id, + 'prestashop.product.combination.option') for value in line.value_ids: value_ext_id = option_binder.to_backend(value.id, wrap=True) if not value_ext_id: - value_ext_id = self.session.env[ - 'prestashop.product.combination.option.value'].\ - with_context(connector_no_export=True).create({ - 'backend_id': self.backend_record.id, - 'odoo_id': value.id, - }) - export_record( - self.session, - 'prestashop.product.combination.option.value', - value_ext_id.id - ) - + self._export_dependency( + value, 'prestashop.product.combination.option.value') + def export_variants(self): combination_obj = self.session.env['prestashop.product.combination'] for product in self.binding.product_variant_ids: @@ -311,12 +178,15 @@ class ProductTemplateExportMapper(TranslationPrestashopExportMapper): ('online_only', 'online_only'), ('weight', 'weight'), ('standard_price', 'wholesale_price'), - ('default_shop_id', 'id_shop_default'), + (m2o_to_backend('default_shop_id'), 'id_shop_default'), ('always_available', 'active'), ('barcode', 'barcode'), ('additional_shipping_cost', 'additional_shipping_cost'), ('minimal_quantity', 'minimal_quantity'), ('on_sale', 'on_sale'), + (m2o_to_backend( + 'prestashop_default_category_id', + binding='prestashop.product.category'), 'id_category_default'), ] # handled by base mapping `translatable_fields` _translatable_fields = [ @@ -332,15 +202,20 @@ class ProductTemplateExportMapper(TranslationPrestashopExportMapper): ('description_html', 'description'), ] + def _get_factor_tax(self, tax): + factor_tax = tax.price_include and (1 + tax.amount / 100) or 1.0 + return factor_tax + @mapping def list_price(self, record): dp_obj = self.env['decimal.precision'] precision = dp_obj.precision_get('Product Price') - if record.taxes_id.price_include and record.taxes_id.type == 'percent': + tax = record.taxes_id + if tax.price_include and tax.amount_type == 'percent': return { 'price': str( - round(record.list_price / ( - 1 + record.taxes_id.amount), precision)) + round(record.list_price / + self._get_factor_tax(tax), precision)) } else: return {'price': str(record.list_price)} @@ -352,8 +227,7 @@ def reference(self, record): def _get_product_category(self, record): ext_categ_ids = [] binder = self.binder_for('prestashop.product.category') - categories = list(set(record.categ_ids + record.categ_id)) - for category in categories: + for category in record.categ_ids: ext_categ_ids.append( {'id': binder.to_backend(category.id, wrap=True)}) return ext_categ_ids @@ -368,10 +242,12 @@ def associations(self, record): } @mapping - def categ_id(self, record): - binder = self.binder_for('prestashop.product.category') - ext_categ_id = binder.to_backend(record.categ_id.id, wrap=True) - return {'id_category_default': ext_categ_id} + def default_combination(self, record): + if record.product_variant_count > 1: + default_variant = record.product_variant_ids.filtered('default_on') + binder = self.binder_for('prestashop.product.combination') + ps_variant_id = binder.to_backend(default_variant.id, wrap=True) + return {'id_default_combination': ps_variant_id} @mapping def tax_ids(self, record): @@ -389,3 +265,12 @@ def available_date(self, record): def date_add(self, record): # When export a record the date_add in PS is null. return {'date_add': record.create_date} + + @mapping + def default_image(self, record): + default_image = record.image_ids.filtered('front_image')[:1] + if default_image: + binder = self.binder_for('prestashop.product.image') + ps_image_id = binder.to_backend(default_image, wrap=True) + if ps_image_id: + return {'id_default_image': ps_image_id} \ No newline at end of file diff --git a/connector_prestashop_catalog_manager/product_attribute.py b/connector_prestashop_catalog_manager/product_attribute.py deleted file mode 100644 index 4532fd3b5..000000000 --- a/connector_prestashop_catalog_manager/product_attribute.py +++ /dev/null @@ -1,145 +0,0 @@ -# -*- coding: utf-8 -*- -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from openerp.addons.connector.event import on_record_create, on_record_write -from openerp.addons.connector_prestashop.unit.exporter import ( - export_record, - PrestashopExporter, - TranslationPrestashopExporter, -) -from openerp.addons.connector_prestashop.unit.mapper import \ - TranslationPrestashopExportMapper -from openerp.addons.connector_prestashop.backend import prestashop -from openerp.addons.connector.unit.mapper import mapping - - -@on_record_create(model_names='prestashop.product.combination.option') -def prestashop_product_attribute_created( - session, model_name, record_id, fields=None): - if session.context.get('connector_no_export'): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_create(model_names='prestashop.product.combination.option.value') -def prestashop_product_atrribute_value_created( - session, model_name, record_id, fields=None): - if session.context.get('connector_no_export'): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names='prestashop.product.combination.option') -def prestashop_product_attribute_written(session, model_name, record_id, - fields=None): - if session.context.get('connector_no_export'): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names='prestashop.product.combination.option.value') -def prestashop_attribute_option_written(session, model_name, record_id, - fields=None): - if session.context.get('connector_no_export'): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names='product.attribute.value') -def product_attribute_written(session, model_name, record_id, fields=None): - if session.context.get('connector_no_export'): - return - model = session.pool.get(model_name) - record = model.browse(session.cr, session.uid, - record_id, context=session.context) - for binding in record.prestashop_bind_ids: - export_record.delay(session, 'prestashop.product.combination.option', - binding.id, fields, priority=20) - - -@on_record_write(model_names='produc.attribute.value') -def attribute_option_written(session, model_name, record_id, fields=None): - if session.context.get('connector_no_export'): - return - model = session.pool.get(model_name) - record = model.browse(session.cr, session.uid, - record_id, context=session.context) - for binding in record.prestashop_bind_ids: - export_record.delay(session, - 'prestashop.product.combination.option.value', - binding.id, fields, priority=20) - - -@prestashop -class ProductCombinationOptionExport(PrestashopExporter): - _model_name = 'prestashop.product.combination.option' - - def _create(self, record): - res = super(ProductCombinationOptionExport, self)._create(record) - return res['prestashop']['product_option']['id'] - - -@prestashop -class ProductCombinationOptionExportMapper(TranslationPrestashopExportMapper): - _model_name = 'prestashop.product.combination.option' - - direct = [ - ('prestashop_position', 'position'), - ('group_type', 'group_type'), - ] - - _translatable_fields = [ - ('name', 'name'), - ('name', 'public_name'), - ] - - -@prestashop -class ProductCombinationOptionValueExport(PrestashopExporter): - _model_name = 'prestashop.product.combination.option.value' - - def _create(self, record): - res = super(ProductCombinationOptionValueExport, self)._create(record) - return res['prestashop']['product_option_value']['id'] - - def _export_dependencies(self): - """ Export the dependencies for the record""" - attribute_id = self.binding.attribute_id.id - # export product attribute - binder = self.binder_for('prestashop.product.combination.option') - if not binder.to_backend(attribute_id, wrap=True): - exporter = self.get_connector_unit_for_model( - TranslationPrestashopExporter, - 'prestashop.product.combination.option') - exporter.run(attribute_id) - return - - -@prestashop -class ProductCombinationOptionValueExportMapper( - TranslationPrestashopExportMapper): - _model_name = 'prestashop.product.combination.option.value' - - direct = [('name', 'value')] - # handled by base mapping `translatable_fields` - _translatable_fields = [ - ('name', 'name'), - ] - - @mapping - def prestashop_product_attribute_id(self, record): - attribute_binder = self.binder_for( - 'prestashop.product.combination.option.value') - return { - 'id_feature': attribute_binder.to_backend( - record.attribute_id.id, wrap=True) - } - - @mapping - def prestashop_product_group_attribute_id(self, record): - attribute_binder = self.binder_for( - 'prestashop.product.combination.option') - return { - 'id_attribute_group': attribute_binder.to_backend( - record.attribute_id.id, wrap=True), - } diff --git a/connector_prestashop_catalog_manager/wizards/__init__.py b/connector_prestashop_catalog_manager/wizards/__init__.py new file mode 100644 index 000000000..c10f7b14a --- /dev/null +++ b/connector_prestashop_catalog_manager/wizards/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import export_multiple_products +from . import sync_products +from . import active_deactive_products +from . import export_category diff --git a/connector_prestashop_catalog_manager/wizards/active_deactive_products.py b/connector_prestashop_catalog_manager/wizards/active_deactive_products.py new file mode 100644 index 000000000..59eee4d41 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizards/active_deactive_products.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, fields, api + + +class SyncProducts(models.TransientModel): + _name = 'active.deactive.products' + + force_status = fields.Boolean( + string='Force Status', + help='Check this option to force active product in prestashop') + + def _change_status(self, status): + self.ensure_one() + product_obj = self.env['product.template'] + for product in product_obj.browse(self.env.context['active_ids']): + for bind in product.prestashop_bind_ids: + if bind.always_available != status or self.force_status: + bind.always_available = status + + @api.multi + def active_products(self): + self._change_status(True) + + @api.multi + def deactive_products(self): + self._change_status(False) diff --git a/connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml b/connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml new file mode 100644 index 000000000..d03b12bd0 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml @@ -0,0 +1,61 @@ + + + + + active.product.form + active.deactive.products + +
+ + + + + +
+
+
+
+
+ + + + deactive.product.form + active.deactive.products + +
+ + +
+
+
+
+
+ +
+
\ No newline at end of file diff --git a/connector_prestashop_catalog_manager/wizards/export_category.py b/connector_prestashop_catalog_manager/wizards/export_category.py new file mode 100644 index 000000000..e8c6993b6 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizards/export_category.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, fields, api +import unicodedata +import re + +try: + import slugify as slugify_lib +except ImportError: + slugify_lib = None + + +def get_slug(name): + if slugify_lib: + try: + return slugify_lib.slugify(name) + except TypeError: + pass + uni = unicodedata.normalize('NFKD', name).encode( + 'ascii', 'ignore').decode('ascii') + slug = re.sub(r'[\W_]', ' ', uni).strip().lower() + slug = re.sub(r'[-\s]+', '-', slug) + return slug + + +class PrestashopExportCategory(models.TransientModel): + _name = 'wiz.prestashop.export.category' + + def _default_backend(self): + return self.env['prestashop.backend'].search([], limit=1).id + + def _default_shop(self): + return self.env['prestashop.shop'].search([], limit=1).id + + backend_id = fields.Many2one( + comodel_name='prestashop.backend', + default=_default_backend, + string='Backend', + ) + shop_id = fields.Many2one( + comodel_name='prestashop.shop', + default=_default_shop, + string='Shop', + ) + + @api.multi + def export_categories(self): + self.ensure_one() + category_obj = self.env['product.category'] + ps_category_obj = self.env['prestashop.product.category'] + for category in category_obj.browse(self.env.context['active_ids']): + ps_category = ps_category_obj.search([ + ('odoo_id', '=', category.id), + ('backend_id', '=', self.backend_id.id), + ('default_shop_id', '=', self.shop_id.id), + ]) + if not ps_category: + ps_category_obj.create({ + 'backend_id': self.backend_id.id, + 'default_shop_id': self.shop_id.id, + 'link_rewrite': get_slug(category.name), + 'odoo_id': category.id, + }) diff --git a/connector_prestashop_catalog_manager/wizards/export_category_view.xml b/connector_prestashop_catalog_manager/wizards/export_category_view.xml new file mode 100644 index 000000000..44dd26c7b --- /dev/null +++ b/connector_prestashop_catalog_manager/wizards/export_category_view.xml @@ -0,0 +1,35 @@ + + + + + + wiz.prestashop.export.category.form + wiz.prestashop.export.category + +
+ + + + +
+
+
+
+
+ + + +
+
diff --git a/connector_prestashop_catalog_manager/wizards/export_multiple_products.py b/connector_prestashop_catalog_manager/wizards/export_multiple_products.py new file mode 100644 index 000000000..beb5dbff2 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizards/export_multiple_products.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, fields, api +import unicodedata +import re + +try: + import slugify as slugify_lib +except ImportError: + slugify_lib = None + + +def get_slug(name): + if slugify_lib: + try: + return slugify_lib.slugify(name) + except TypeError: + pass + uni = unicodedata.normalize('NFKD', name).encode( + 'ascii', 'ignore').decode('ascii') + slug = re.sub(r'[\W_]', ' ', uni).strip().lower() + slug = re.sub(r'[-\s]+', '-', slug) + return slug + + +class ExportMultipleProducts(models.TransientModel): + _name = 'export.multiple.products' + + @api.multi + def _default_backend(self): + return self.env['prestashop.backend'].search([], limit=1).id + + @api.multi + def _default_shop(self): + return self.env['prestashop.shop'].search([], limit=1).id + + backend_id = fields.Many2one( + comodel_name='prestashop.backend', + default=_default_backend, + string='Backend', + ) + shop_id = fields.Many2one( + comodel_name='prestashop.shop', + default=_default_shop, + string='Default Shop', + ) + + def _parent_length(self, categ): + if not categ.parent_id: + return 1 + else: + return 1 + self._parent_length(categ.parent_id) + + def _set_main_category(self, product): + if product.categ_ids and product.categ_id.parent_id: + max_parent = {'length': 0} + for categ in product.categ_ids: + parent_length = self._parent_length(categ.parent_id) + if parent_length > max_parent['length']: + max_parent = {'categ_id': categ.id, + 'length': parent_length} + categ_length = self._parent_length(product.categ_id.parent_id) + if categ_length < parent_length: + if product.categ_id.id not in product.categ_ids.ids: + product.write({ + 'categ_ids': [(4, product.categ_id.id)], + }) + product.write({ + 'categ_id': max_parent['categ_id'], + 'categ_ids': [(3, max_parent['categ_id'])] + }) + else: + product.write({ + 'categ_id': max_parent['categ_id'], + 'categ_ids': [(3, max_parent['categ_id'])], + }) + + @api.multi + def set_category(self): + product_obj = self.env['product.template'] + for product in product_obj.browse(self.env.context['active_ids']): + self._set_main_category(product) + + def _check_images(self, product): + for variant in product.product_variant_ids: + for image in variant.image_ids: + if image.owner_id != product.id: + image.product_id = product + + def _check_category(self, product): + if not (product.categ_ids): + return False + return True + + def _check_variants(self, product): + if len(product.product_variant_ids) == 1: + return True + if (len(product.product_variant_ids) > 1 and + not product.attribute_line_ids): + check_count = reduce( + lambda x, y: x * y, map(lambda x: len(x.value_ids), + product.attribute_line_ids)) + if check_count < len(product.product_variant_ids): + return False + return True + + @api.multi + def export_variant_stock(self): + template_obj = self.env['product.template'] + products = template_obj.browse(self.env.context['active_ids']) + products.update_prestashop_quantities() + + @api.multi + def create_prestashop_template(self, product): + presta_tmpl_obj = self.env['prestashop.product.template'] + return presta_tmpl_obj.create({ + 'backend_id': self.backend_id.id, + 'default_shop_id': self.shop_id.id, + 'link_rewrite': get_slug(product.name), + 'odoo_id': product.id, + }) + + @api.multi + def export_products(self): + self.ensure_one() + product_obj = self.env['product.template'] + presta_tmpl_obj = self.env['prestashop.product.template'] + for product in product_obj.browse(self.env.context['active_ids']): + presta_tmpl = presta_tmpl_obj.search([ + ('odoo_id', '=', product.id), + ('backend_id', '=', self.backend_id.id), + ('default_shop_id', '=', self.shop_id.id), + ]) + if not presta_tmpl: + self._check_images(product) + cat = self._check_category(product) + var = self._check_variants(product) + if not(var and cat): + continue + self.create_prestashop_template(product) + else: + for tmpl in presta_tmpl: + if ' ' in tmpl.link_rewrite: + tmpl.link_rewrite = get_slug(tmpl.link_rewrite) diff --git a/connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml b/connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml new file mode 100644 index 000000000..bffd78867 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml @@ -0,0 +1,57 @@ + + + + + export.multiple.products.form + export.multiple.products + +
+ + + + +
+
+
+
+
+ + + + export.variant.stock.form + export.multiple.products + +
+
+
+
+
+
+ + +
+
diff --git a/connector_prestashop_catalog_manager/wizards/sync_products.py b/connector_prestashop_catalog_manager/wizards/sync_products.py new file mode 100644 index 000000000..fe895f1f9 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizards/sync_products.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, api +import logging + +_logger = logging.getLogger(__name__) + + +class SyncProducts(models.TransientModel): + _name = 'sync.products' + + def _bind_resync(self, product_ids): + products = self.env['product.template'].browse(product_ids) + for product in products: + try: + for bind in product.prestashop_bind_ids: + bind.resync() + except Exception, e: + _logger.info('id %s, attributes %s\n', str(product.id), e) + + @api.multi + def sync_products(self): + self._bind_resync(self.env.context['active_ids']) + + @api.multi + def sync_all_products(self): + self._bind_resync([]) diff --git a/connector_prestashop_catalog_manager/wizards/sync_products_view.xml b/connector_prestashop_catalog_manager/wizards/sync_products_view.xml new file mode 100644 index 000000000..6684559f6 --- /dev/null +++ b/connector_prestashop_catalog_manager/wizards/sync_products_view.xml @@ -0,0 +1,30 @@ + + + + + sync.products.form + sync.products + +
+ + +
+
+
+
+
+ +
+
From 39c8e2bfaec1a89272896e033c380e46f6ba7ce8 Mon Sep 17 00:00:00 2001 From: Sergio Teruel Albert Date: Wed, 30 Nov 2016 08:58:45 +0100 Subject: [PATCH 12/34] [9.0][FIX] connector_prestashop_catalog_manager: Fix export impact price --- .../models/product_product/exporter.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/connector_prestashop_catalog_manager/models/product_product/exporter.py b/connector_prestashop_catalog_manager/models/product_product/exporter.py index a649b7beb..20e479c0e 100644 --- a/connector_prestashop_catalog_manager/models/product_product/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_product/exporter.py @@ -108,6 +108,10 @@ class ProductCombinationExportMapper(TranslationPrestashopExportMapper): ('weight', 'weight'), ] + def _get_factor_tax(self, tax): + factor_tax = tax.price_include and (1 + tax.amount / 100) or 1.0 + return factor_tax + @mapping def combination_default(self, record): return {} @@ -125,13 +129,20 @@ def main_template_id(self, record): @mapping def _unit_price_impact(self, record): + dp_obj = self.env['decimal.precision'] + precision = dp_obj.precision_get('Product Price') tax = record.taxes_id[:1] - factor_tax = tax.price_include and (1 + tax.amount) or 1.0 - return {'price': str(record.impact_price / factor_tax)} + if tax.price_include and tax.amount_type == 'percent': + return { + 'price': round( + record.impact_price / self._get_factor_tax(tax), precision) + } + else: + return {'price': record.impact_price} @mapping def cost_price(self, record): - return {'wholesale_price': str(record.standard_price)} + return {'wholesale_price': record.standard_price} def _get_product_option_value(self, record): option_value = [] From 965b5d240036617a71cd84b14fb1eae27a307d4b Mon Sep 17 00:00:00 2001 From: Sergio Teruel Albert Date: Wed, 30 Nov 2016 13:14:53 +0100 Subject: [PATCH 13/34] [9.0][FIX] connector_prestashop_catalog_manager: Fix export default combination --- connector_prestashop_catalog_manager/consumer.py | 12 +++++------- .../models/product_product/exporter.py | 5 +---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/connector_prestashop_catalog_manager/consumer.py b/connector_prestashop_catalog_manager/consumer.py index dff246584..9bb01710c 100644 --- a/connector_prestashop_catalog_manager/consumer.py +++ b/connector_prestashop_catalog_manager/consumer.py @@ -221,20 +221,18 @@ def product_product_write(session, model_name, record_id, fields): if fields: for binding in record.prestashop_bind_ids: + priority = 20 + if 'default_on' in fields and fields['default_on']: + # PS has to uncheck actual default combination first + priority = 99 export_record.delay( session, 'prestashop.product.combination', binding.id, fields, - priority=20, + priority=priority, shop_url=_get_shop_url(session) ) - # We can not update directly default_on in combination because this field - # is unique key in PS so I trigger a write in product template to assign - # the default combination - if 'default_on' in fields: - product_template_write( - session, 'product.template', record.product_tmpl_id.id, 'name') @on_record_create(model_names='prestashop.product.combination.option') diff --git a/connector_prestashop_catalog_manager/models/product_product/exporter.py b/connector_prestashop_catalog_manager/models/product_product/exporter.py index 20e479c0e..4db4230a9 100644 --- a/connector_prestashop_catalog_manager/models/product_product/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_product/exporter.py @@ -114,10 +114,7 @@ def _get_factor_tax(self, tax): @mapping def combination_default(self, record): - return {} - # TODO Check issue to write default combination see - # TODO id_default_combination in product template export mapper - # return {'default_on': str(int(record['default_on']))} + return {'default_on': int(record['default_on'])} def get_main_template_id(self, record): template_binder = self.binder_for('prestashop.product.template') From 33a8eeb9c28de57ff047b8c933c08708f844f78e Mon Sep 17 00:00:00 2001 From: Sergio Teruel Albert Date: Wed, 30 Nov 2016 13:24:57 +0100 Subject: [PATCH 14/34] [9.0][FIX] custom_connector_prestashop_catalog_feature: Remove default_combination from product template export mapper --- .../models/product_template/exporter.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/connector_prestashop_catalog_manager/models/product_template/exporter.py b/connector_prestashop_catalog_manager/models/product_template/exporter.py index 8cbfbbf34..5997ddcc3 100644 --- a/connector_prestashop_catalog_manager/models/product_template/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_template/exporter.py @@ -241,14 +241,6 @@ def associations(self, record): } } - @mapping - def default_combination(self, record): - if record.product_variant_count > 1: - default_variant = record.product_variant_ids.filtered('default_on') - binder = self.binder_for('prestashop.product.combination') - ps_variant_id = binder.to_backend(default_variant.id, wrap=True) - return {'id_default_combination': ps_variant_id} - @mapping def tax_ids(self, record): binder = self.binder_for('prestashop.account.tax.group') From d6c788e2b538fe3efc02dc4c28ec11e70a6fe316 Mon Sep 17 00:00:00 2001 From: Sergio Teruel Albert Date: Thu, 1 Dec 2016 08:53:51 +0100 Subject: [PATCH 15/34] [9.0][FIX] connector_prestashop_catalog_manager: Fix tax group in product_template export mapper --- .../models/product_template/exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector_prestashop_catalog_manager/models/product_template/exporter.py b/connector_prestashop_catalog_manager/models/product_template/exporter.py index 5997ddcc3..7e36d8207 100644 --- a/connector_prestashop_catalog_manager/models/product_template/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_template/exporter.py @@ -244,7 +244,7 @@ def associations(self, record): @mapping def tax_ids(self, record): binder = self.binder_for('prestashop.account.tax.group') - ext_id = binder.to_backend(record.tax_group_id.id, wrap=True) + ext_id = binder.to_backend(record.taxes_id[:1].tax_group_id, wrap=True) return {'id_tax_rules_group': ext_id} @mapping From 1f218d53346ac7056e804b538e7eb321c56b068a Mon Sep 17 00:00:00 2001 From: Sergio Teruel Albert Date: Thu, 1 Dec 2016 13:16:36 +0100 Subject: [PATCH 16/34] [9.0][FIX] connector_prestashop_catalog_manager: Fix error when export a product without taxes --- .../models/product_template/exporter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/connector_prestashop_catalog_manager/models/product_template/exporter.py b/connector_prestashop_catalog_manager/models/product_template/exporter.py index 7e36d8207..4f712b1a9 100644 --- a/connector_prestashop_catalog_manager/models/product_template/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_template/exporter.py @@ -243,6 +243,8 @@ def associations(self, record): @mapping def tax_ids(self, record): + if not record.taxes_id: + return binder = self.binder_for('prestashop.account.tax.group') ext_id = binder.to_backend(record.taxes_id[:1].tax_group_id, wrap=True) return {'id_tax_rules_group': ext_id} From cba839c72734eca61e6d1bc069153a23608499b5 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 5 Dec 2016 13:49:56 +0100 Subject: [PATCH 17/34] Move 'get_environment' to 'prestashop.backend' model --- connector_prestashop_catalog_manager/consumer.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/connector_prestashop_catalog_manager/consumer.py b/connector_prestashop_catalog_manager/consumer.py index 9bb01710c..76a461e8f 100644 --- a/connector_prestashop_catalog_manager/consumer.py +++ b/connector_prestashop_catalog_manager/consumer.py @@ -8,7 +8,6 @@ ) from openerp.addons.connector.connector import Binder from openerp.addons.connector_prestashop.unit.exporter import export_record -from openerp.addons.connector_prestashop.connector import get_environment from openerp.addons.connector_prestashop.unit.deleter import ( export_delete_record ) @@ -103,20 +102,22 @@ def product_image_unlink(session, model_name, record_id): model = session.env[model_name] record = model.browse(record_id) for binding in record.prestashop_bind_ids: + backend = binding.backend_id product = session.env[record.owner_model].browse(record.owner_id) if product.exists(): product_template = product.prestashop_bind_ids.filtered( lambda x: x.backend_id == binding.backend_id) if not product_template: return - env_product = get_environment( - session, 'prestashop.product.template', binding.backend_id.id) + env_product = backend.get_environment( + 'prestashop.product.template', + session=session, + ) binder_product = env_product.get_connector_unit(Binder) external_product_id = binder_product.to_backend( product_template.id) - env = get_environment( - session, binding._name, binding.backend_id.id) + env = backend.get_environment(binding._name, session=session) binder = env.get_connector_unit(Binder) external_id = binder.to_backend(binding.id) resource = 'images/products/%s' % (external_product_id) From 0cfedfbc8005aa6912a37b44db27e4416e172896 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 5 Dec 2016 15:01:44 +0100 Subject: [PATCH 18/34] fixup! [9.0][MIG] connector_prestashop_catalog_manager: Migration and new structure --- .../wizard/__init__.py | 7 - .../wizard/active_deactive_products.py | 28 ---- .../wizard/active_deactive_products_view.xml | 61 -------- .../wizard/export_category.py | 64 -------- .../wizard/export_category_view.xml | 35 ----- .../wizard/export_multiple_products.py | 138 ------------------ .../wizard/export_multiple_products_view.xml | 57 -------- .../wizard/sync_products.py | 28 ---- .../wizard/sync_products_view.xml | 30 ---- 9 files changed, 448 deletions(-) delete mode 100644 connector_prestashop_catalog_manager/wizard/__init__.py delete mode 100644 connector_prestashop_catalog_manager/wizard/active_deactive_products.py delete mode 100644 connector_prestashop_catalog_manager/wizard/active_deactive_products_view.xml delete mode 100644 connector_prestashop_catalog_manager/wizard/export_category.py delete mode 100644 connector_prestashop_catalog_manager/wizard/export_category_view.xml delete mode 100644 connector_prestashop_catalog_manager/wizard/export_multiple_products.py delete mode 100644 connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml delete mode 100644 connector_prestashop_catalog_manager/wizard/sync_products.py delete mode 100644 connector_prestashop_catalog_manager/wizard/sync_products_view.xml diff --git a/connector_prestashop_catalog_manager/wizard/__init__.py b/connector_prestashop_catalog_manager/wizard/__init__.py deleted file mode 100644 index c10f7b14a..000000000 --- a/connector_prestashop_catalog_manager/wizard/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import export_multiple_products -from . import sync_products -from . import active_deactive_products -from . import export_category diff --git a/connector_prestashop_catalog_manager/wizard/active_deactive_products.py b/connector_prestashop_catalog_manager/wizard/active_deactive_products.py deleted file mode 100644 index 59eee4d41..000000000 --- a/connector_prestashop_catalog_manager/wizard/active_deactive_products.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from openerp import models, fields, api - - -class SyncProducts(models.TransientModel): - _name = 'active.deactive.products' - - force_status = fields.Boolean( - string='Force Status', - help='Check this option to force active product in prestashop') - - def _change_status(self, status): - self.ensure_one() - product_obj = self.env['product.template'] - for product in product_obj.browse(self.env.context['active_ids']): - for bind in product.prestashop_bind_ids: - if bind.always_available != status or self.force_status: - bind.always_available = status - - @api.multi - def active_products(self): - self._change_status(True) - - @api.multi - def deactive_products(self): - self._change_status(False) diff --git a/connector_prestashop_catalog_manager/wizard/active_deactive_products_view.xml b/connector_prestashop_catalog_manager/wizard/active_deactive_products_view.xml deleted file mode 100644 index d03b12bd0..000000000 --- a/connector_prestashop_catalog_manager/wizard/active_deactive_products_view.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - active.product.form - active.deactive.products - -
- - - - - -
-
-
-
-
- - - - deactive.product.form - active.deactive.products - -
- - -
-
-
-
-
- -
-
\ No newline at end of file diff --git a/connector_prestashop_catalog_manager/wizard/export_category.py b/connector_prestashop_catalog_manager/wizard/export_category.py deleted file mode 100644 index e8c6993b6..000000000 --- a/connector_prestashop_catalog_manager/wizard/export_category.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from openerp import models, fields, api -import unicodedata -import re - -try: - import slugify as slugify_lib -except ImportError: - slugify_lib = None - - -def get_slug(name): - if slugify_lib: - try: - return slugify_lib.slugify(name) - except TypeError: - pass - uni = unicodedata.normalize('NFKD', name).encode( - 'ascii', 'ignore').decode('ascii') - slug = re.sub(r'[\W_]', ' ', uni).strip().lower() - slug = re.sub(r'[-\s]+', '-', slug) - return slug - - -class PrestashopExportCategory(models.TransientModel): - _name = 'wiz.prestashop.export.category' - - def _default_backend(self): - return self.env['prestashop.backend'].search([], limit=1).id - - def _default_shop(self): - return self.env['prestashop.shop'].search([], limit=1).id - - backend_id = fields.Many2one( - comodel_name='prestashop.backend', - default=_default_backend, - string='Backend', - ) - shop_id = fields.Many2one( - comodel_name='prestashop.shop', - default=_default_shop, - string='Shop', - ) - - @api.multi - def export_categories(self): - self.ensure_one() - category_obj = self.env['product.category'] - ps_category_obj = self.env['prestashop.product.category'] - for category in category_obj.browse(self.env.context['active_ids']): - ps_category = ps_category_obj.search([ - ('odoo_id', '=', category.id), - ('backend_id', '=', self.backend_id.id), - ('default_shop_id', '=', self.shop_id.id), - ]) - if not ps_category: - ps_category_obj.create({ - 'backend_id': self.backend_id.id, - 'default_shop_id': self.shop_id.id, - 'link_rewrite': get_slug(category.name), - 'odoo_id': category.id, - }) diff --git a/connector_prestashop_catalog_manager/wizard/export_category_view.xml b/connector_prestashop_catalog_manager/wizard/export_category_view.xml deleted file mode 100644 index 402962ff8..000000000 --- a/connector_prestashop_catalog_manager/wizard/export_category_view.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - wiz.prestashop.export.category.form - wiz.prestashop.export.category - -
- - - - -
-
-
-
-
- - - -
-
diff --git a/connector_prestashop_catalog_manager/wizard/export_multiple_products.py b/connector_prestashop_catalog_manager/wizard/export_multiple_products.py deleted file mode 100644 index 275d29c49..000000000 --- a/connector_prestashop_catalog_manager/wizard/export_multiple_products.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from openerp import models, fields, api -import unicodedata -import re - -try: - import slugify as slugify_lib -except ImportError: - slugify_lib = None - - -def get_slug(name): - if slugify_lib: - try: - return slugify_lib.slugify(name) - except TypeError: - pass - uni = unicodedata.normalize('NFKD', name).encode( - 'ascii', 'ignore').decode('ascii') - slug = re.sub(r'[\W_]', ' ', uni).strip().lower() - slug = re.sub(r'[-\s]+', '-', slug) - return slug - - -class ExportMultipleProducts(models.TransientModel): - _name = 'export.multiple.products' - - def _default_backend(self): - return self.env['prestashop.backend'].search([], limit=1).id - - def _default_shop(self): - return self.env['prestashop.shop'].search([], limit=1).id - - name = fields.Many2one( - comodel_name='prestashop.backend', - default=_default_backend, - string='Backend', - ) - shop = fields.Many2one( - comodel_name='prestashop.shop', - default=_default_shop, - string='Shop', - ) - - def _parent_length(self, categ): - if not categ.parent_id: - return 1 - else: - return 1 + self._parent_length(categ.parent_id) - - def _set_main_category(self, product): - if product.categ_ids and product.categ_id.parent_id: - max_parent = {'length': 0} - for categ in product.categ_ids: - parent_length = self._parent_length(categ.parent_id) - if parent_length > max_parent['length']: - max_parent = {'categ_id': categ.id, - 'length': parent_length} - categ_length = self._parent_length(product.categ_id.parent_id) - if categ_length < parent_length: - if product.categ_id.id not in product.categ_ids.ids: - product.write({ - 'categ_ids': [(4, product.categ_id.id)], - }) - product.write({ - 'categ_id': max_parent['categ_id'], - 'categ_ids': [(3, max_parent['categ_id'])] - }) - else: - product.write({ - 'categ_id': max_parent['categ_id'], - 'categ_ids': [(3, max_parent['categ_id'])], - }) - - @api.multi - def set_category(self): - product_obj = self.env['product.template'] - for product in product_obj.browse(self.env.context['active_ids']): - self._set_main_category(product) - - def _check_images(self, product): - for variant in product.product_variant_ids: - for image in variant.image_ids: - if image.owner_id != product.id: - image.product_id = product - - def _check_category(self, product): - if not (product.categ_id and product.categ_id.parent_id): - return False - return True - - def _check_variants(self, product): - if len(product.product_variant_ids) == 1: - return True - if (len(product.product_variant_ids) > 1 and - not product.attribute_line_ids): - check_count = reduce( - lambda x, y: x * y, map(lambda x: len(x.value_ids), - product.attribute_line_ids)) - if check_count < len(product.product_variant_ids): - return False - return True - - @api.multi - def export_variant_stock(self): - template_obj = self.env['product.template'] - products = template_obj.browse(self.env.context['active_ids']) - products.update_prestashop_quantities() - - @api.multi - def export_products(self): - self.ensure_one() - product_obj = self.env['product.template'] - presta_tmpl_obj = self.env['prestashop.product.template'] - for product in product_obj.browse(self.env.context['active_ids']): - presta_tmpl = presta_tmpl_obj.search([ - ('odoo_id', '=', product.id), - ('backend_id', '=', self.name.id), - ('default_shop_id', '=', self.shop.id), - ]) - if not presta_tmpl: - self._check_images(product) - cat = self._check_category(product) - var = self._check_variants(product) - if not(var and cat): - continue - presta_tmpl_obj.create({ - 'backend_id': self.name.id, - 'default_shop_id': self.shop.id, - 'link_rewrite': get_slug(product.name), - 'odoo_id': product.id, - }) - else: - for tmpl in presta_tmpl: - if ' ' in tmpl.link_rewrite: - tmpl.link_rewrite = get_slug(tmpl.link_rewrite) diff --git a/connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml b/connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml deleted file mode 100644 index d40083d9c..000000000 --- a/connector_prestashop_catalog_manager/wizard/export_multiple_products_view.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - export.multiple.products.form - export.multiple.products - -
- - - - -
-
-
-
-
- - - - export.variant.stock.form - export.multiple.products - -
-
-
-
-
-
- - -
-
diff --git a/connector_prestashop_catalog_manager/wizard/sync_products.py b/connector_prestashop_catalog_manager/wizard/sync_products.py deleted file mode 100644 index fe895f1f9..000000000 --- a/connector_prestashop_catalog_manager/wizard/sync_products.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from openerp import models, api -import logging - -_logger = logging.getLogger(__name__) - - -class SyncProducts(models.TransientModel): - _name = 'sync.products' - - def _bind_resync(self, product_ids): - products = self.env['product.template'].browse(product_ids) - for product in products: - try: - for bind in product.prestashop_bind_ids: - bind.resync() - except Exception, e: - _logger.info('id %s, attributes %s\n', str(product.id), e) - - @api.multi - def sync_products(self): - self._bind_resync(self.env.context['active_ids']) - - @api.multi - def sync_all_products(self): - self._bind_resync([]) diff --git a/connector_prestashop_catalog_manager/wizard/sync_products_view.xml b/connector_prestashop_catalog_manager/wizard/sync_products_view.xml deleted file mode 100644 index 6684559f6..000000000 --- a/connector_prestashop_catalog_manager/wizard/sync_products_view.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - sync.products.form - sync.products - -
- - -
-
-
-
-
- -
-
From f6764704419686ce65b8c0e0c65dd2e46ecc76dc Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 5 Dec 2016 15:43:25 +0100 Subject: [PATCH 19/34] Remove the shop_url * Passing arguments between ConnectorUnits should be done using **kwargs, passing values in context should be discouraged * This shop_url attribute/arg is not used anywhere but a specific addon for a specific use case, it should be done in a custom extension addon. Changes that aim to make the extension more easy should be encouraged. In this regard, I extracted some methods in ProductTemplateImporter to allow the forward of custom **kwargs --- .../consumer.py | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/connector_prestashop_catalog_manager/consumer.py b/connector_prestashop_catalog_manager/consumer.py index 76a461e8f..7a7a66268 100644 --- a/connector_prestashop_catalog_manager/consumer.py +++ b/connector_prestashop_catalog_manager/consumer.py @@ -49,16 +49,11 @@ def get_slug(name): EXCLUDE_FIELDS = ['list_price'] -def _get_shop_url(session): - return session.context.get('shop_url', None) - - @on_record_create(model_names='prestashop.product.category') def prestashop_product_category_create(session, model_name, record_id, fields): if session.context.get('connector_no_export'): return - export_record.delay(session, model_name, record_id, priority=20, - shop_url=_get_shop_url(session)) + export_record.delay(session, model_name, record_id, priority=20) @on_record_write(model_names='product.category') @@ -71,7 +66,7 @@ def product_category_write(session, model_name, record_id, fields): for binding in record.prestashop_bind_ids: export_record.delay( session, binding._model._name, binding.id, fields=fields, - priority=20,shop_url=_get_shop_url(session)) + priority=20) @on_record_write(model_names='prestashop.product.category') @@ -79,8 +74,7 @@ def prestashop_product_category_write(session, model_name, record_id, fields): if session.context.get('connector_no_export'): return if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): - export_record.delay(session, model_name, record_id, fields, - shop_url=_get_shop_url(session)) + export_record.delay(session, model_name, record_id, fields) @on_record_write(model_names='base_multi_image.image') @@ -92,7 +86,7 @@ def product_image_write(session, model_name, record_id, fields): for binding in record.prestashop_bind_ids: export_record.delay(session, 'prestashop.product.image', binding.id, record.file_db_store, - priority=20, shop_url=_get_shop_url(session)) + priority=20) @on_record_unlink(model_names='base_multi_image.image') @@ -131,9 +125,7 @@ def product_image_unlink(session, model_name, record_id): def prestashop_product_template_create(session, model_name, record_id, fields): if session.context.get('connector_no_export'): return - export_record.delay( - session, model_name, record_id, priority=20, - shop_url=_get_shop_url(session)) + export_record.delay(session, model_name, record_id, priority=20) @on_record_write(model_names='prestashop.product.template') @@ -143,8 +135,8 @@ def prestashop_product_template_write(session, model_name, record_id, fields): fields = list(set(fields).difference(set(INVENTORY_FIELDS))) if fields: export_record.delay( - session, model_name, record_id, fields, priority=20, - shop_url=_get_shop_url(session)) + session, model_name, record_id, fields, priority=20 + ) # Propagate minimal_quantity from template to variants if 'minimal_quantity' in fields: ps_template = session.env[model_name].browse(record_id) @@ -164,7 +156,8 @@ def product_template_write(session, model_name, record_id, fields): for binding in record.prestashop_bind_ids: export_record.delay( session, 'prestashop.product.template', binding.id, fields, - priority=20, shop_url=_get_shop_url(session)) + priority=20, + ) @on_record_create(model_names='prestashop.product.combination') @@ -172,8 +165,7 @@ def prestashop_product_combination_create(session, model_name, record_id, fields=None): if session.context.get('connector_no_export'): return - export_record.delay(session, model_name, record_id, priority=20, - shop_url=_get_shop_url(session)) + export_record.delay(session, model_name, record_id, priority=20) @on_record_write(model_names='prestashop.product.combination') @@ -186,7 +178,7 @@ def prestashop_product_combination_write(session, model_name, if fields: export_record.delay( session, model_name, record_id, fields, priority=20, - shop_url=_get_shop_url(session)) + ) def prestashop_product_combination_unlink(session, record_id): @@ -232,7 +224,6 @@ def product_product_write(session, model_name, record_id, fields): binding.id, fields, priority=priority, - shop_url=_get_shop_url(session) ) @@ -241,8 +232,7 @@ def prestashop_product_attribute_created( session, model_name, record_id, fields=None): if session.context.get('connector_no_export'): return - export_record.delay(session, model_name, record_id, priority=20, - shop_url=_get_shop_url(session)) + export_record.delay(session, model_name, record_id, priority=20) @on_record_create(model_names='prestashop.product.combination.option.value') @@ -250,8 +240,7 @@ def prestashop_product_atrribute_value_created( session, model_name, record_id, fields=None): if session.context.get('connector_no_export'): return - export_record.delay(session, model_name, record_id, priority=20, - shop_url=_get_shop_url(session)) + export_record.delay(session, model_name, record_id, priority=20) @on_record_write(model_names='prestashop.product.combination.option') @@ -259,8 +248,7 @@ def prestashop_product_attribute_written(session, model_name, record_id, fields=None): if session.context.get('connector_no_export'): return - export_record.delay(session, model_name, record_id, priority=20, - shop_url=_get_shop_url(session)) + export_record.delay(session, model_name, record_id, priority=20) @on_record_write(model_names='prestashop.product.combination.option.value') @@ -268,8 +256,7 @@ def prestashop_attribute_option_written(session, model_name, record_id, fields=None): if session.context.get('connector_no_export'): return - export_record.delay(session, model_name, record_id, priority=20, - shop_url=_get_shop_url(session)) + export_record.delay(session, model_name, record_id, priority=20) @on_record_write(model_names='product.attribute.value') @@ -281,8 +268,7 @@ def product_attribute_written(session, model_name, record_id, fields=None): record_id, context=session.context) for binding in record.prestashop_bind_ids: export_record.delay(session, 'prestashop.product.combination.option', - binding.id, fields, priority=20, - shop_url=_get_shop_url(session)) + binding.id, fields, priority=20) @on_record_write(model_names='produc.attribute.value') @@ -295,5 +281,4 @@ def attribute_option_written(session, model_name, record_id, fields=None): for binding in record.prestashop_bind_ids: export_record.delay(session, 'prestashop.product.combination.option.value', - binding.id, fields, priority=20, - shop_url=_get_shop_url(session)) + binding.id, fields, priority=20) From b715f78a4ddcd080c6f1921c8e4ef4d4ac99bccf Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 7 Dec 2016 11:41:25 +0100 Subject: [PATCH 20/34] pep8 --- connector_prestashop_catalog_manager/consumer.py | 1 + .../models/product_template/exporter.py | 10 +++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/connector_prestashop_catalog_manager/consumer.py b/connector_prestashop_catalog_manager/consumer.py index 7a7a66268..e44fd5c5f 100644 --- a/connector_prestashop_catalog_manager/consumer.py +++ b/connector_prestashop_catalog_manager/consumer.py @@ -34,6 +34,7 @@ def get_slug(name): slug = re.sub(r'[-\s]+', '-', slug) return slug + # TODO: attach this to a model to ease override CATEGORY_EXPORT_FIELDS = [ 'name', diff --git a/connector_prestashop_catalog_manager/models/product_template/exporter.py b/connector_prestashop_catalog_manager/models/product_template/exporter.py index 4f712b1a9..e24014cb6 100644 --- a/connector_prestashop_catalog_manager/models/product_template/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_template/exporter.py @@ -18,8 +18,6 @@ from openerp.addons.connector_prestashop.backend import prestashop from ...consumer import get_slug -import re - @prestashop class ProductTemplateExport(TranslationPrestashopExporter): @@ -90,9 +88,7 @@ def _export_dependencies(self): 'prestashop.product.combination.option') option_binder = self.binder_for( 'prestashop.product.combination.option.value') - attribute_obj = self.session.env[ - 'prestashop.product.combination.option'] - + for category in self.binding.categ_ids: self.export_categories(category) @@ -108,7 +104,7 @@ def _export_dependencies(self): if not value_ext_id: self._export_dependency( value, 'prestashop.product.combination.option.value') - + def export_variants(self): combination_obj = self.session.env['prestashop.product.combination'] for product in self.binding.product_variant_ids: @@ -267,4 +263,4 @@ def default_image(self, record): binder = self.binder_for('prestashop.product.image') ps_image_id = binder.to_backend(default_image, wrap=True) if ps_image_id: - return {'id_default_image': ps_image_id} \ No newline at end of file + return {'id_default_image': ps_image_id} From 3ad0ac32c11fab72ad1bbf89edafcc3c6aee04b7 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 7 Dec 2016 14:39:25 +0100 Subject: [PATCH 21/34] Fix pylint errors/warnings --- connector_prestashop_catalog_manager/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector_prestashop_catalog_manager/__openerp__.py b/connector_prestashop_catalog_manager/__openerp__.py index 3e03964ad..2db156cd5 100644 --- a/connector_prestashop_catalog_manager/__openerp__.py +++ b/connector_prestashop_catalog_manager/__openerp__.py @@ -7,7 +7,7 @@ { "name": "Prestashop-Odoo Catalog Manager", - "version": "8.0.1.0.2", + "version": "9.0.1.0.2", "license": "AGPL-3", "depends": [ "connector_prestashop" From 534ec028001d0a3c12665e12ff8d7ec3cab22533 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 1 Feb 2017 11:17:23 +0100 Subject: [PATCH 22/34] [mig][9.0] connector_prestashop_manufacturer --- .../models/product_template/exporter.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/connector_prestashop_catalog_manager/models/product_template/exporter.py b/connector_prestashop_catalog_manager/models/product_template/exporter.py index e24014cb6..5a282d39a 100644 --- a/connector_prestashop_catalog_manager/models/product_template/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_template/exporter.py @@ -141,7 +141,13 @@ def check_images(self): image_binder = self.binder_for('prestashop.product.image') for image in self.binding.image_ids: image_ext_id = image_binder.to_backend(image.id, wrap=True) - if not image_ext_id: + # `image_ext_id` is ZERO as long as the image is not exported. + # Here we delay the export so, + # if we don't check this we create 2 records to be sync'ed + # and this leads to: + # ValueError: + # Expected singleton: prestashop.product.image(x, y) + if image_ext_id is None: image_ext_id = self.session.env[ 'prestashop.product.image'].with_context( connector_no_export=True).create({ From d26ab29e0b49eff977c4c320582d5a7ff65c0e05 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 10 Feb 2017 14:48:10 +0100 Subject: [PATCH 23/34] update contribs --- connector_prestashop_catalog_manager/README.rst | 1 + connector_prestashop_catalog_manager/__openerp__.py | 1 + 2 files changed, 2 insertions(+) diff --git a/connector_prestashop_catalog_manager/README.rst b/connector_prestashop_catalog_manager/README.rst index 788ff77d5..ba1c61318 100644 --- a/connector_prestashop_catalog_manager/README.rst +++ b/connector_prestashop_catalog_manager/README.rst @@ -54,6 +54,7 @@ Contributors * Mikel Arregi * Sergio Teruel * Pedro M. Baeza +* Simone Orsi Maintainer ---------- diff --git a/connector_prestashop_catalog_manager/__openerp__.py b/connector_prestashop_catalog_manager/__openerp__.py index 2db156cd5..d2cdd4f9b 100644 --- a/connector_prestashop_catalog_manager/__openerp__.py +++ b/connector_prestashop_catalog_manager/__openerp__.py @@ -15,6 +15,7 @@ "author": "Akretion," "AvanzOSC," "Tecnativa," + 'Camptocamp SA', "Odoo Community Association (OCA)", "website": "https://github.com/OCA/connector-prestashop", "category": "Connector", From 02566d48b5b929a8f7562a0e051f628845998329 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 10 Feb 2017 16:55:19 +0100 Subject: [PATCH 24/34] [add] base adapter: add _export_node_name_res attr to handle results This allows to drop all the "_create" overrides. --- .../models/product_category/exporter.py | 4 ---- .../models/product_image/exporter.py | 8 -------- 2 files changed, 12 deletions(-) diff --git a/connector_prestashop_catalog_manager/models/product_category/exporter.py b/connector_prestashop_catalog_manager/models/product_category/exporter.py index 75243f952..5ddb1b321 100644 --- a/connector_prestashop_catalog_manager/models/product_category/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_category/exporter.py @@ -18,10 +18,6 @@ class ProductCategoryExporter(PrestashopExporter): _model_name = 'prestashop.product.category' - def _create(self, record): - res = super(ProductCategoryExporter, self)._create(record) - return res['prestashop']['category']['id'] - def _export_dependencies(self): """ Export the dependencies for the category""" category_binder = self.binder_for('prestashop.product.category') diff --git a/connector_prestashop_catalog_manager/models/product_image/exporter.py b/connector_prestashop_catalog_manager/models/product_image/exporter.py index f0f885648..78d2f5500 100644 --- a/connector_prestashop_catalog_manager/models/product_image/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_image/exporter.py @@ -27,14 +27,6 @@ class ProductImage(models.Model): class ProductImageExport(PrestashopExporter): _model_name = 'prestashop.product.image' - def _create(self, record): - res = super(ProductImageExport, self)._create(record) - return res['prestashop']['image']['id'] - - def _update(self, record): - res = super(ProductImageExport, self)._update(record) - return res['prestashop']['image']['id'] - def _run(self, fields=None): """ Flow of the synchronization, implemented in inherited classes""" assert self.binding_id From 7bfd2ac1311b13863c5ff5e56013f14414fbb527 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 10 Feb 2017 16:56:13 +0100 Subject: [PATCH 25/34] fix author --- connector_prestashop_catalog_manager/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector_prestashop_catalog_manager/__openerp__.py b/connector_prestashop_catalog_manager/__openerp__.py index d2cdd4f9b..fd9d7171b 100644 --- a/connector_prestashop_catalog_manager/__openerp__.py +++ b/connector_prestashop_catalog_manager/__openerp__.py @@ -15,7 +15,7 @@ "author": "Akretion," "AvanzOSC," "Tecnativa," - 'Camptocamp SA', + 'Camptocamp SA,' "Odoo Community Association (OCA)", "website": "https://github.com/OCA/connector-prestashop", "category": "Connector", From 7f03b65313d3360c902ecdf3c18522ca142c7eb9 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 14 Feb 2017 18:14:38 +0100 Subject: [PATCH 26/34] [add] manufacturer export --- .../models/product_template/exporter.py | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/connector_prestashop_catalog_manager/models/product_template/exporter.py b/connector_prestashop_catalog_manager/models/product_template/exporter.py index 5a282d39a..5c0af7ed3 100644 --- a/connector_prestashop_catalog_manager/models/product_template/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_template/exporter.py @@ -20,11 +20,11 @@ @prestashop -class ProductTemplateExport(TranslationPrestashopExporter): +class ProductTemplateExporter(TranslationPrestashopExporter): _model_name = 'prestashop.product.template' def _create(self, record): - res = super(ProductTemplateExport, self)._create(record) + res = super(ProductTemplateExporter, self)._create(record) self.write_binging_vals(self.binding, record) return res['prestashop']['product']['id'] @@ -83,7 +83,7 @@ def _parent_length(self, categ): def _export_dependencies(self): """ Export the dependencies for the product""" - super(ProductTemplateExport, self)._export_dependencies() + super(ProductTemplateExporter, self)._export_dependencies() attribute_binder = self.binder_for( 'prestashop.product.combination.option') option_binder = self.binder_for( @@ -270,3 +270,22 @@ def default_image(self, record): ps_image_id = binder.to_backend(default_image, wrap=True) if ps_image_id: return {'id_default_image': ps_image_id} + + @mapping + def extras_manufacturer(self, record): + mapper = self.unit_for(ManufacturerExportMapper) + return mapper.map_record(record).values(**self.options) + + +@prestashop +class ManufacturerExportMapper(TranslationPrestashopExportMapper): + # To extend in connector_prestashop_manufacturer module + _model_name = 'prestashop.product.template' + + _translatable_fields = [ + ('name', 'name'), + ] + + @mapping + def manufacturer(self, record): + return {} From 8fb51ff840a540fe9dd3769e9db7d57dbd837dd7 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 3 Mar 2017 13:45:14 +0100 Subject: [PATCH 27/34] Rise the digit number for exported prices rounding The default value of the 'Product Price' rounding is 2, which is insufficient to get back and forth from the tax included and tax excluded prices. 6 should be sufficient (e.g. from 250.00 and a tax of 8%, we get 231.481481 and from the latter we can get back our 250.00) --- .../models/product_product/exporter.py | 7 ++++--- .../models/product_template/exporter.py | 11 +++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/connector_prestashop_catalog_manager/models/product_product/exporter.py b/connector_prestashop_catalog_manager/models/product_product/exporter.py index 4db4230a9..86c65a17d 100644 --- a/connector_prestashop_catalog_manager/models/product_product/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_product/exporter.py @@ -126,13 +126,14 @@ def main_template_id(self, record): @mapping def _unit_price_impact(self, record): - dp_obj = self.env['decimal.precision'] - precision = dp_obj.precision_get('Product Price') tax = record.taxes_id[:1] if tax.price_include and tax.amount_type == 'percent': + # 6 is the rounding precision used by PrestaShop for the + # tax excluded price. we can get back a 2 digits tax included + # price from the 6 digits rounded value return { 'price': round( - record.impact_price / self._get_factor_tax(tax), precision) + record.impact_price / self._get_factor_tax(tax), 6) } else: return {'price': record.impact_price} diff --git a/connector_prestashop_catalog_manager/models/product_template/exporter.py b/connector_prestashop_catalog_manager/models/product_template/exporter.py index 5c0af7ed3..3842442b4 100644 --- a/connector_prestashop_catalog_manager/models/product_template/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_template/exporter.py @@ -205,19 +205,18 @@ class ProductTemplateExportMapper(TranslationPrestashopExportMapper): ] def _get_factor_tax(self, tax): - factor_tax = tax.price_include and (1 + tax.amount / 100) or 1.0 - return factor_tax + return (1 + tax.amount / 100) if tax.price_include else 1.0 @mapping def list_price(self, record): - dp_obj = self.env['decimal.precision'] - precision = dp_obj.precision_get('Product Price') tax = record.taxes_id if tax.price_include and tax.amount_type == 'percent': + # 6 is the rounding precision used by PrestaShop for the + # tax excluded price. we can get back a 2 digits tax included + # price from the 6 digits rounded value return { 'price': str( - round(record.list_price / - self._get_factor_tax(tax), precision)) + round(record.list_price / self._get_factor_tax(tax), 6)) } else: return {'price': str(record.list_price)} From c7ee68beeb4fa5038ded5e2e31bc375e68e42672 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Sun, 2 Apr 2017 23:09:39 +0200 Subject: [PATCH 28/34] [MIG] Make modules uninstallable --- connector_prestashop_catalog_manager/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector_prestashop_catalog_manager/__openerp__.py b/connector_prestashop_catalog_manager/__openerp__.py index fd9d7171b..dac07bd78 100644 --- a/connector_prestashop_catalog_manager/__openerp__.py +++ b/connector_prestashop_catalog_manager/__openerp__.py @@ -28,5 +28,5 @@ 'wizards/active_deactive_products_view.xml', 'views/product_image_view.xml', ], - "installable": True, + 'installable': False, } From e31bf9cc4ad8681954a5812c165c9d9e59e1f4e4 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Sun, 2 Apr 2017 23:09:41 +0200 Subject: [PATCH 29/34] [MIG] Rename manifest files --- .../{__openerp__.py => __manifest__.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename connector_prestashop_catalog_manager/{__openerp__.py => __manifest__.py} (100%) diff --git a/connector_prestashop_catalog_manager/__openerp__.py b/connector_prestashop_catalog_manager/__manifest__.py similarity index 100% rename from connector_prestashop_catalog_manager/__openerp__.py rename to connector_prestashop_catalog_manager/__manifest__.py From eb2ad55d75fff3a33112dd8227166e7dd161ca4e Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 8 May 2017 16:51:59 +0200 Subject: [PATCH 30/34] [FIX+IMP] connector_prestashop: Several things: * [FIX] manufacturer export on template Export of manufacturer on template was broken due to a probable typo. `id_manufacturer` value was taken using `to_odoo` instead of `to_backend`. * [IMP] filter of importable orders by state If `backend_record.importable_order_state_ids` is valued we check if current order is in the list. If not, the job fails gracefully. * [FIX] combination image: do not default to `False` when missing --- .../models/product_product/exporter.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/connector_prestashop_catalog_manager/models/product_product/exporter.py b/connector_prestashop_catalog_manager/models/product_product/exporter.py index 86c65a17d..926446822 100644 --- a/connector_prestashop_catalog_manager/models/product_product/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_product/exporter.py @@ -167,8 +167,12 @@ def associations(self, record): ('product_option_values', {'product_option_value': self._get_product_option_value(record)}), - ('images', {'image': self._get_combination_image(record) or False}) ]) + image = self._get_combination_image(record) + if image: + associations['images'] = { + 'image': self._get_combination_image(record) + } return {'associations': associations} From 2fc592b95efc220878b3a2290c3ec50bf23ce782 Mon Sep 17 00:00:00 2001 From: Sergio Teruel Albert Date: Sun, 25 Jun 2017 20:37:28 +0200 Subject: [PATCH 31/34] [9.0][IMP] connector_prestashop_catalog_manager: Change image from database store to linked to ps image after export (cherry picked from commit 8e3b16b) --- .../models/product_image/exporter.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/connector_prestashop_catalog_manager/models/product_image/exporter.py b/connector_prestashop_catalog_manager/models/product_image/exporter.py index 78d2f5500..5c1858a92 100644 --- a/connector_prestashop_catalog_manager/models/product_image/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_image/exporter.py @@ -7,7 +7,9 @@ from openerp.addons.connector_prestashop.unit.mapper import ( PrestashopExportMapper ) - +from openerp.addons.connector_prestashop.unit.backend_adapter import ( + PrestaShopWebServiceImage +) from openerp.addons.connector_prestashop.backend import prestashop from openerp import models, fields @@ -54,9 +56,26 @@ def _run(self, fields=None): self._validate_data(record) self.prestashop_id = self._create(record) self._after_export() + self._link_image_to_url() message = _('Record exported with ID %s on Prestashop.') return message % self.prestashop_id + def _link_image_to_url(self): + """Change image storage to a url linked to product prestashop image""" + api = PrestaShopWebServiceImage( + api_url=self.backend_record.location, + api_key=self.backend_record.webservice_key) + full_public_url = api.get_image_public_url({ + 'id_image': str(self.prestashop_id), + 'type': 'image/jpeg', + }) + if self.binding.url != full_public_url: + self.binding.with_context(connector_no_export=True).write({ + 'url': full_public_url, + 'file_db_store': False, + 'storage': 'url', + }) + @prestashop class ProductImageExportMapper(PrestashopExportMapper): From 7d5556a1acfac1eefd5edf269e767b006dc9ecba Mon Sep 17 00:00:00 2001 From: "Nicolas Bessi (nbessi)" Date: Wed, 13 Sep 2017 11:27:44 +0200 Subject: [PATCH 32/34] [FIX] connector_prestashop: Fix atomicity design (#81) That may pass the prestashop id insted of the binding id to the exporter That can cause the following problems: * A job Error: Key (id_attribute_group)=(4) is not present in table "prestashop_product_combination_option" * Wrong combination or combination values export or updated * Combination or combination value never exported * Faulty value in fields 'id_attribute_group' leading to data incoherence (cherry picked from commit 767ba36) --- .../models/product_product/exporter.py | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/connector_prestashop_catalog_manager/models/product_product/exporter.py b/connector_prestashop_catalog_manager/models/product_product/exporter.py index 926446822..235b27f3c 100644 --- a/connector_prestashop_catalog_manager/models/product_product/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_product/exporter.py @@ -58,34 +58,43 @@ def _export_dependencies(self): 'prestashop.product.combination.option') option_binder = self.binder_for( 'prestashop.product.combination.option.value') + Option = self.env['prestashop.product.combination.option'] + OptionValue = self.env['prestashop.product.combination.option.value'] for value in self.binding.attribute_value_ids: - attribute_ext_id = attribute_binder.to_backend( + prestashop_option_id = attribute_binder.to_backend( value.attribute_id.id, wrap=True) - if not attribute_ext_id: - attribute_ext_id = self.session.env[ - 'prestashop.product.combination.option'].with_context( - connector_no_export=True).create({ - 'backend_id': self.backend_record.id, - 'odoo_id': value.attribute_id.id, - }) - export_record( - self.session, - 'prestashop.product.combination.option', - attribute_ext_id + if not prestashop_option_id: + option_binding = Option.search( + [('backend_id', '=', self.backend_record.id), + ('odoo_id', '=', value.attribute_id.id)]) + if not option_binding: + option_binding = Option.with_context( + connector_no_export=True).create({ + 'backend_id': self.backend_record.id, + 'odoo_id': value.attribute_id.id}) + export_record(self.session, + 'prestashop.product.combination.option', + option_binding.id) + prestashop_value_id = option_binder.to_backend( + value.id, wrap=True) + if not prestashop_value_id: + value_binding = OptionValue.search( + [('backend_id', '=', self.backend_record.id), + ('odoo_id', '=', value.id)] ) - value_ext_id = option_binder.to_backend(value.id, wrap=True) - if not value_ext_id: - value_ext_id = self.session.env[ - 'prestashop.product.combination.option.value']\ - .with_context(connector_no_export=True).create({ - 'backend_id': self.backend_record.id, - 'odoo_id': value.val_id.id, - 'id_attribute_group': attribute_ext_id - }) + if not value_binding: + option_binding = Option.search( + [('backend_id', '=', self.backend_record.id), + ('odoo_id', '=', value.attribute_id.id)]) + value_binding = OptionValue.with_context( + connector_no_export=True).create({ + 'backend_id': self.backend_record.id, + 'odoo_id': value.id, + 'id_attribute_group': option_binding.id}) export_record( self.session, 'prestashop.product.combination.option.value', - value_ext_id) + value_binding.id) # self._export_images() def update_quantities(self): From f6229ebf8ccf13d2a0383dd60d6af1d53b0bf919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurelija=20Vitkauskien=C4=97?= Date: Wed, 26 Jun 2024 16:11:48 +0300 Subject: [PATCH 33/34] [IMP] connector_prestashop_catalog_manager: pre-commit stuff --- .pre-commit-config.yaml | 1 + .../__init__.py | 1 - .../__manifest__.py | 29 +-- .../consumer.py | 228 +++++++++-------- .../models/__init__.py | 1 - .../models/product_category/__init__.py | 2 - .../models/product_category/exporter.py | 63 ++--- .../models/product_image/__init__.py | 2 - .../models/product_image/exporter.py | 115 +++++---- .../models/product_product/__init__.py | 2 - .../models/product_product/common.py | 9 +- .../models/product_product/exporter.py | 235 ++++++++++-------- .../models/product_template/__init__.py | 2 - .../models/product_template/common.py | 48 ++-- .../models/product_template/exporter.py | 219 ++++++++-------- .../views/product_attribute_view.xml | 24 +- .../views/product_image_view.xml | 6 +- .../views/product_view.xml | 28 ++- .../wizards/__init__.py | 1 - .../wizards/active_deactive_products.py | 14 +- .../wizards/active_deactive_products_view.xml | 70 +++--- .../wizards/export_category.py | 59 ++--- .../wizards/export_category_view.xml | 35 +-- .../wizards/export_multiple_products.py | 116 +++++---- .../wizards/export_multiple_products_view.xml | 67 +++-- .../wizards/sync_products.py | 4 +- .../wizards/sync_products_view.xml | 31 ++- 27 files changed, 732 insertions(+), 680 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 060e6e193..6c85f8d30 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,7 @@ exclude: | (?x) # NOT INSTALLABLE ADDONS + ^connector_prestashop_catalog_manager/| # END NOT INSTALLABLE ADDONS # Files and folders generated by bots, to avoid loops ^setup/|/static/description/index\.html$| diff --git a/connector_prestashop_catalog_manager/__init__.py b/connector_prestashop_catalog_manager/__init__.py index a6847e3b1..d549a2b6e 100644 --- a/connector_prestashop_catalog_manager/__init__.py +++ b/connector_prestashop_catalog_manager/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import consumer diff --git a/connector_prestashop_catalog_manager/__manifest__.py b/connector_prestashop_catalog_manager/__manifest__.py index dac07bd78..9913bb7ac 100644 --- a/connector_prestashop_catalog_manager/__manifest__.py +++ b/connector_prestashop_catalog_manager/__manifest__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2011-2013 Camptocamp # Copyright 2011-2013 Akretion # Copyright 2015 AvanzOSC @@ -9,24 +8,22 @@ "name": "Prestashop-Odoo Catalog Manager", "version": "9.0.1.0.2", "license": "AGPL-3", - "depends": [ - "connector_prestashop" - ], + "depends": ["connector_prestashop"], "author": "Akretion," - "AvanzOSC," - "Tecnativa," - 'Camptocamp SA,' - "Odoo Community Association (OCA)", + "AvanzOSC," + "Tecnativa," + "Camptocamp SA," + "Odoo Community Association (OCA)", "website": "https://github.com/OCA/connector-prestashop", "category": "Connector", "data": [ - 'views/product_attribute_view.xml', - 'views/product_view.xml', - 'wizards/export_category_view.xml', - 'wizards/export_multiple_products_view.xml', - 'wizards/sync_products_view.xml', - 'wizards/active_deactive_products_view.xml', - 'views/product_image_view.xml', + "views/product_attribute_view.xml", + "views/product_view.xml", + "wizards/export_category_view.xml", + "wizards/export_multiple_products_view.xml", + "wizards/sync_products_view.xml", + "wizards/active_deactive_products_view.xml", + "views/product_image_view.xml", ], - 'installable': False, + "installable": False, } diff --git a/connector_prestashop_catalog_manager/consumer.py b/connector_prestashop_catalog_manager/consumer.py index e44fd5c5f..5d68862d6 100644 --- a/connector_prestashop_catalog_manager/consumer.py +++ b/connector_prestashop_catalog_manager/consumer.py @@ -1,20 +1,17 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import re +import unicodedata + +from openerp.addons.connector.connector import Binder from openerp.addons.connector.event import ( on_record_create, - on_record_write, on_record_unlink, -) -from openerp.addons.connector.connector import Binder -from openerp.addons.connector_prestashop.unit.exporter import export_record -from openerp.addons.connector_prestashop.unit.deleter import ( - export_delete_record + on_record_write, ) from openerp.addons.connector_prestashop.consumer import INVENTORY_FIELDS - -import unicodedata -import re +from openerp.addons.connector_prestashop.unit.deleter import export_delete_record +from openerp.addons.connector_prestashop.unit.exporter import export_record try: import slugify as slugify_lib @@ -28,71 +25,74 @@ def get_slug(name): return slugify_lib.slugify(name) except TypeError: pass - uni = unicodedata.normalize('NFKD', name).encode( - 'ascii', 'ignore').decode('ascii') - slug = re.sub(r'[\W_]', ' ', uni).strip().lower() - slug = re.sub(r'[-\s]+', '-', slug) + uni = unicodedata.normalize("NFKD", name).encode("ascii", "ignore").decode("ascii") + slug = re.sub(r"[\W_]", " ", uni).strip().lower() + slug = re.sub(r"[-\s]+", "-", slug) return slug # TODO: attach this to a model to ease override CATEGORY_EXPORT_FIELDS = [ - 'name', - 'parent_id', - 'description', - 'link_rewrite', - 'meta_description', - 'meta_keywords', - 'meta_title', - 'position' + "name", + "parent_id", + "description", + "link_rewrite", + "meta_description", + "meta_keywords", + "meta_title", + "position", ] -EXCLUDE_FIELDS = ['list_price'] +EXCLUDE_FIELDS = ["list_price"] -@on_record_create(model_names='prestashop.product.category') +@on_record_create(model_names="prestashop.product.category") def prestashop_product_category_create(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): + if session.context.get("connector_no_export"): return export_record.delay(session, model_name, record_id, priority=20) -@on_record_write(model_names='product.category') +@on_record_write(model_names="product.category") def product_category_write(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): + if session.context.get("connector_no_export"): return if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): model = session.env[model_name] record = model.browse(record_id) for binding in record.prestashop_bind_ids: export_record.delay( - session, binding._model._name, binding.id, fields=fields, - priority=20) + session, binding._model._name, binding.id, fields=fields, priority=20 + ) -@on_record_write(model_names='prestashop.product.category') +@on_record_write(model_names="prestashop.product.category") def prestashop_product_category_write(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): + if session.context.get("connector_no_export"): return if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): export_record.delay(session, model_name, record_id, fields) -@on_record_write(model_names='base_multi_image.image') +@on_record_write(model_names="base_multi_image.image") def product_image_write(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): + if session.context.get("connector_no_export"): return model = session.env[model_name] record = model.browse(record_id) for binding in record.prestashop_bind_ids: - export_record.delay(session, 'prestashop.product.image', - binding.id, record.file_db_store, - priority=20) + export_record.delay( + session, + "prestashop.product.image", + binding.id, + record.file_db_store, + priority=20, + ) -@on_record_unlink(model_names='base_multi_image.image') +@on_record_unlink(model_names="base_multi_image.image") def product_image_unlink(session, model_name, record_id): - if session.context.get('connector_no_export'): + if session.context.get("connector_no_export"): return model = session.env[model_name] record = model.browse(record_id) @@ -101,104 +101,109 @@ def product_image_unlink(session, model_name, record_id): product = session.env[record.owner_model].browse(record.owner_id) if product.exists(): product_template = product.prestashop_bind_ids.filtered( - lambda x: x.backend_id == binding.backend_id) + lambda x: x.backend_id == binding.backend_id + ) if not product_template: return env_product = backend.get_environment( - 'prestashop.product.template', + "prestashop.product.template", session=session, ) binder_product = env_product.get_connector_unit(Binder) - external_product_id = binder_product.to_backend( - product_template.id) + external_product_id = binder_product.to_backend(product_template.id) env = backend.get_environment(binding._name, session=session) binder = env.get_connector_unit(Binder) external_id = binder.to_backend(binding.id) - resource = 'images/products/%s' % (external_product_id) + resource = "images/products/%s" % (external_product_id) if external_id: export_delete_record.delay( - session, binding._name, binding.backend_id.id, - external_id, resource) + session, binding._name, binding.backend_id.id, external_id, resource + ) -@on_record_create(model_names='prestashop.product.template') +@on_record_create(model_names="prestashop.product.template") def prestashop_product_template_create(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): + if session.context.get("connector_no_export"): return export_record.delay(session, model_name, record_id, priority=20) -@on_record_write(model_names='prestashop.product.template') +@on_record_write(model_names="prestashop.product.template") def prestashop_product_template_write(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): + if session.context.get("connector_no_export"): return fields = list(set(fields).difference(set(INVENTORY_FIELDS))) if fields: - export_record.delay( - session, model_name, record_id, fields, priority=20 - ) + export_record.delay(session, model_name, record_id, fields, priority=20) # Propagate minimal_quantity from template to variants - if 'minimal_quantity' in fields: + if "minimal_quantity" in fields: ps_template = session.env[model_name].browse(record_id) for binding in ps_template.prestashop_bind_ids: - binding.odoo_id.mapped( - 'product_variant_ids.prestashop_bind_ids').write({ - 'minimal_quantity': binding.minimal_quantity - }) + binding.odoo_id.mapped("product_variant_ids.prestashop_bind_ids").write( + {"minimal_quantity": binding.minimal_quantity} + ) -@on_record_write(model_names='product.template') +@on_record_write(model_names="product.template") def product_template_write(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): + if session.context.get("connector_no_export"): return model = session.env[model_name] record = model.browse(record_id) for binding in record.prestashop_bind_ids: export_record.delay( - session, 'prestashop.product.template', binding.id, fields, + session, + "prestashop.product.template", + binding.id, + fields, priority=20, ) -@on_record_create(model_names='prestashop.product.combination') -def prestashop_product_combination_create(session, model_name, record_id, - fields=None): - if session.context.get('connector_no_export'): +@on_record_create(model_names="prestashop.product.combination") +def prestashop_product_combination_create(session, model_name, record_id, fields=None): + if session.context.get("connector_no_export"): return export_record.delay(session, model_name, record_id, priority=20) -@on_record_write(model_names='prestashop.product.combination') -def prestashop_product_combination_write(session, model_name, - record_id, fields): - if session.context.get('connector_no_export'): +@on_record_write(model_names="prestashop.product.combination") +def prestashop_product_combination_write(session, model_name, record_id, fields): + if session.context.get("connector_no_export"): return fields = list(set(fields).difference(set(INVENTORY_FIELDS))) if fields: export_record.delay( - session, model_name, record_id, fields, priority=20, + session, + model_name, + record_id, + fields, + priority=20, ) def prestashop_product_combination_unlink(session, record_id): # binding is deactivate when deactive a product variant - ps_binding_product = session.env['prestashop.product.combination'].search([ - ('active', '=', False), - ('odoo_id', '=', record_id) - ]) + ps_binding_product = session.env["prestashop.product.combination"].search( + [("active", "=", False), ("odoo_id", "=", record_id)] + ) for binding in ps_binding_product: - resource = 'combinations/%s' % (binding.prestashop_id) + resource = "combinations/%s" % (binding.prestashop_id) export_delete_record.delay( - session, 'prestashop.product.combination', binding.backend_id.id, - binding.prestashop_id, resource) + session, + "prestashop.product.combination", + binding.backend_id.id, + binding.prestashop_id, + resource, + ) ps_binding_product.unlink() -@on_record_write(model_names='product.product') +@on_record_write(model_names="product.product") def product_product_write(session, model_name, record_id, fields): - if session.context.get('connector_no_export'): + if session.context.get("connector_no_export"): return for field in EXCLUDE_FIELDS: @@ -209,77 +214,82 @@ def product_product_write(session, model_name, record_id, fields): if not record.is_product_variant: return - if 'active' in fields and not fields['active']: + if "active" in fields and not fields["active"]: prestashop_product_combination_unlink(session, record_id) return if fields: for binding in record.prestashop_bind_ids: priority = 20 - if 'default_on' in fields and fields['default_on']: + if "default_on" in fields and fields["default_on"]: # PS has to uncheck actual default combination first priority = 99 export_record.delay( session, - 'prestashop.product.combination', + "prestashop.product.combination", binding.id, fields, priority=priority, ) -@on_record_create(model_names='prestashop.product.combination.option') -def prestashop_product_attribute_created( - session, model_name, record_id, fields=None): - if session.context.get('connector_no_export'): +@on_record_create(model_names="prestashop.product.combination.option") +def prestashop_product_attribute_created(session, model_name, record_id, fields=None): + if session.context.get("connector_no_export"): return export_record.delay(session, model_name, record_id, priority=20) -@on_record_create(model_names='prestashop.product.combination.option.value') +@on_record_create(model_names="prestashop.product.combination.option.value") def prestashop_product_atrribute_value_created( - session, model_name, record_id, fields=None): - if session.context.get('connector_no_export'): + session, model_name, record_id, fields=None +): + if session.context.get("connector_no_export"): return export_record.delay(session, model_name, record_id, priority=20) -@on_record_write(model_names='prestashop.product.combination.option') -def prestashop_product_attribute_written(session, model_name, record_id, - fields=None): - if session.context.get('connector_no_export'): +@on_record_write(model_names="prestashop.product.combination.option") +def prestashop_product_attribute_written(session, model_name, record_id, fields=None): + if session.context.get("connector_no_export"): return export_record.delay(session, model_name, record_id, priority=20) -@on_record_write(model_names='prestashop.product.combination.option.value') -def prestashop_attribute_option_written(session, model_name, record_id, - fields=None): - if session.context.get('connector_no_export'): +@on_record_write(model_names="prestashop.product.combination.option.value") +def prestashop_attribute_option_written(session, model_name, record_id, fields=None): + if session.context.get("connector_no_export"): return export_record.delay(session, model_name, record_id, priority=20) -@on_record_write(model_names='product.attribute.value') +@on_record_write(model_names="product.attribute.value") def product_attribute_written(session, model_name, record_id, fields=None): - if session.context.get('connector_no_export'): + if session.context.get("connector_no_export"): return model = session.pool.get(model_name) - record = model.browse(session.cr, session.uid, - record_id, context=session.context) + record = model.browse(session.cr, session.uid, record_id, context=session.context) for binding in record.prestashop_bind_ids: - export_record.delay(session, 'prestashop.product.combination.option', - binding.id, fields, priority=20) + export_record.delay( + session, + "prestashop.product.combination.option", + binding.id, + fields, + priority=20, + ) -@on_record_write(model_names='produc.attribute.value') +@on_record_write(model_names="produc.attribute.value") def attribute_option_written(session, model_name, record_id, fields=None): - if session.context.get('connector_no_export'): + if session.context.get("connector_no_export"): return model = session.pool.get(model_name) - record = model.browse(session.cr, session.uid, - record_id, context=session.context) + record = model.browse(session.cr, session.uid, record_id, context=session.context) for binding in record.prestashop_bind_ids: - export_record.delay(session, - 'prestashop.product.combination.option.value', - binding.id, fields, priority=20) + export_record.delay( + session, + "prestashop.product.combination.option.value", + binding.id, + fields, + priority=20, + ) diff --git a/connector_prestashop_catalog_manager/models/__init__.py b/connector_prestashop_catalog_manager/models/__init__.py index 058e6d810..1fccf4fc5 100644 --- a/connector_prestashop_catalog_manager/models/__init__.py +++ b/connector_prestashop_catalog_manager/models/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # © 2016 Sergio Teruel # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html diff --git a/connector_prestashop_catalog_manager/models/product_category/__init__.py b/connector_prestashop_catalog_manager/models/product_category/__init__.py index d8ad335f9..24472e23d 100644 --- a/connector_prestashop_catalog_manager/models/product_category/__init__.py +++ b/connector_prestashop_catalog_manager/models/product_category/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - from . import exporter diff --git a/connector_prestashop_catalog_manager/models/product_category/exporter.py b/connector_prestashop_catalog_manager/models/product_category/exporter.py index 5ddb1b321..10686a0b9 100644 --- a/connector_prestashop_catalog_manager/models/product_category/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_category/exporter.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openerp.addons.connector.unit.mapper import mapping - +from openerp.addons.connector_prestashop.backend import prestashop from openerp.addons.connector_prestashop.unit.exporter import ( PrestashopExporter, export_record, @@ -10,21 +9,22 @@ from openerp.addons.connector_prestashop.unit.mapper import ( TranslationPrestashopExportMapper, ) + from ...consumer import get_slug -from openerp.addons.connector_prestashop.backend import prestashop @prestashop class ProductCategoryExporter(PrestashopExporter): - _model_name = 'prestashop.product.category' + _model_name = "prestashop.product.category" def _export_dependencies(self): - """ Export the dependencies for the category""" - category_binder = self.binder_for('prestashop.product.category') - categories_obj = self.session.env['prestashop.product.category'] + """Export the dependencies for the category""" + category_binder = self.binder_for("prestashop.product.category") + categories_obj = self.session.env["prestashop.product.category"] for category in self.binding: self.export_parent_category( - category.odoo_id.parent_id, category_binder, categories_obj) + category.odoo_id.parent_id, category_binder, categories_obj + ) def export_parent_category(self, category, binder, ps_categ_obj): if not category: @@ -33,42 +33,43 @@ def export_parent_category(self, category, binder, ps_categ_obj): if ext_id: return ext_id res = { - 'backend_id': self.backend_record.id, - 'odoo_id': category.id, - 'link_rewrite': get_slug(category.name), + "backend_id": self.backend_record.id, + "odoo_id": category.id, + "link_rewrite": get_slug(category.name), } - category_ext_id = ps_categ_obj.with_context( - connector_no_export=True).create(res) + category_ext_id = ps_categ_obj.with_context(connector_no_export=True).create( + res + ) parent_cat_id = export_record( - self.session, 'prestashop.product.category', category_ext_id.id) + self.session, "prestashop.product.category", category_ext_id.id + ) return parent_cat_id @prestashop class ProductCategoryExportMapper(TranslationPrestashopExportMapper): - _model_name = 'prestashop.product.category' + _model_name = "prestashop.product.category" direct = [ - ('sequence', 'position'), - ('default_shop_id', 'id_shop_default'), - ('active', 'active'), - ('position', 'position') + ("sequence", "position"), + ("default_shop_id", "id_shop_default"), + ("active", "active"), + ("position", "position"), ] # handled by base mapping `translatable_fields` _translatable_fields = [ - ('name', 'name'), - ('link_rewrite', 'link_rewrite'), - ('description', 'description'), - ('meta_description', 'meta_description'), - ('meta_keywords', 'meta_keywords'), - ('meta_title', 'meta_title'), + ("name", "name"), + ("link_rewrite", "link_rewrite"), + ("description", "description"), + ("meta_description", "meta_description"), + ("meta_keywords", "meta_keywords"), + ("meta_title", "meta_title"), ] @mapping def parent_id(self, record): - if not record['parent_id']: - return {'id_parent': 2} - category_binder = self.binder_for('prestashop.product.category') - ext_categ_id = category_binder.to_backend( - record.parent_id.id, wrap=True) - return {'id_parent': ext_categ_id} + if not record["parent_id"]: + return {"id_parent": 2} + category_binder = self.binder_for("prestashop.product.category") + ext_categ_id = category_binder.to_backend(record.parent_id.id, wrap=True) + return {"id_parent": ext_categ_id} diff --git a/connector_prestashop_catalog_manager/models/product_image/__init__.py b/connector_prestashop_catalog_manager/models/product_image/__init__.py index d8ad335f9..24472e23d 100644 --- a/connector_prestashop_catalog_manager/models/product_image/__init__.py +++ b/connector_prestashop_catalog_manager/models/product_image/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - from . import exporter diff --git a/connector_prestashop_catalog_manager/models/product_image/exporter.py b/connector_prestashop_catalog_manager/models/product_image/exporter.py index 5c1858a92..4be5757b4 100644 --- a/connector_prestashop_catalog_manager/models/product_image/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_image/exporter.py @@ -1,36 +1,31 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import os +import os.path + +from openerp import fields, models from openerp.addons.connector.unit.mapper import mapping -from openerp.addons.connector_prestashop.unit.exporter import \ - PrestashopExporter -from openerp.addons.connector_prestashop.unit.mapper import ( - PrestashopExportMapper -) +from openerp.addons.connector_prestashop.backend import prestashop from openerp.addons.connector_prestashop.unit.backend_adapter import ( - PrestaShopWebServiceImage + PrestaShopWebServiceImage, ) -from openerp.addons.connector_prestashop.backend import prestashop - -from openerp import models, fields +from openerp.addons.connector_prestashop.unit.exporter import PrestashopExporter +from openerp.addons.connector_prestashop.unit.mapper import PrestashopExportMapper from openerp.tools.translate import _ -import os -import os.path - class ProductImage(models.Model): - _inherit = 'base_multi_image.image' + _inherit = "base_multi_image.image" - front_image = fields.Boolean(string='Front image') + front_image = fields.Boolean(string="Front image") @prestashop class ProductImageExport(PrestashopExporter): - _model_name = 'prestashop.product.image' + _model_name = "prestashop.product.image" def _run(self, fields=None): - """ Flow of the synchronization, implemented in inherited classes""" + """Flow of the synchronization, implemented in inherited classes""" assert self.binding_id assert self.binding @@ -44,45 +39,50 @@ def _run(self, fields=None): if self.prestashop_id: record = map_record.values() if not record: - return _('Nothing to export.') + return _("Nothing to export.") # special check on data before export self._validate_data(record) self.prestashop_id = self._update(record) else: record = map_record.values(for_create=True) if not record: - return _('Nothing to export.') + return _("Nothing to export.") # special check on data before export self._validate_data(record) self.prestashop_id = self._create(record) self._after_export() self._link_image_to_url() - message = _('Record exported with ID %s on Prestashop.') + message = _("Record exported with ID %s on Prestashop.") return message % self.prestashop_id def _link_image_to_url(self): """Change image storage to a url linked to product prestashop image""" api = PrestaShopWebServiceImage( api_url=self.backend_record.location, - api_key=self.backend_record.webservice_key) - full_public_url = api.get_image_public_url({ - 'id_image': str(self.prestashop_id), - 'type': 'image/jpeg', - }) + api_key=self.backend_record.webservice_key, + ) + full_public_url = api.get_image_public_url( + { + "id_image": str(self.prestashop_id), + "type": "image/jpeg", + } + ) if self.binding.url != full_public_url: - self.binding.with_context(connector_no_export=True).write({ - 'url': full_public_url, - 'file_db_store': False, - 'storage': 'url', - }) + self.binding.with_context(connector_no_export=True).write( + { + "url": full_public_url, + "file_db_store": False, + "storage": "url", + } + ) @prestashop class ProductImageExportMapper(PrestashopExportMapper): - _model_name = 'prestashop.product.image' + _model_name = "prestashop.product.image" direct = [ - ('name', 'name'), + ("name", "name"), ] def _get_file_name(self, record): @@ -94,50 +94,55 @@ def _get_file_name(self, record): file_name = record.odoo_id.filename if not file_name: storage = record.odoo_id.storage - if storage == 'url': - file_name = os.path.splitext( - os.path.basename(record.odoo_id.url)) - elif storage == 'db': + if storage == "url": + file_name = os.path.splitext(os.path.basename(record.odoo_id.url)) + elif storage == "db": if not record.odoo_id.filename: - file_name = '%s_%s.jpg' % ( + file_name = "%s_%s.jpg" % ( record.odoo_id.owner_model, - record.odoo_id.owner_id) - file_name = os.path.splitext( - os.path.basename(record.odoo_id.filename or file_name)) - elif storage == 'file': + record.odoo_id.owner_id, + ) file_name = os.path.splitext( - os.path.basename(record.odoo_id.path)) + os.path.basename(record.odoo_id.filename or file_name) + ) + elif storage == "file": + file_name = os.path.splitext(os.path.basename(record.odoo_id.path)) return file_name @mapping def source_image(self, record): content = getattr( - record.odoo_id, "_get_image_from_%s" % record.odoo_id.storage)() - return {'content': content} + record.odoo_id, "_get_image_from_%s" % record.odoo_id.storage + )() + return {"content": content} @mapping def product_id(self, record): - if record.odoo_id.owner_model == u'product.product': - product_tmpl = record.env['product.product'].browse( - record.odoo_id.owner_id).product_tmpl_id + if record.odoo_id.owner_model == "product.product": + product_tmpl = ( + record.env["product.product"] + .browse(record.odoo_id.owner_id) + .product_tmpl_id + ) else: - product_tmpl = record.env['product.template'].browse( - record.odoo_id.owner_id) - binder = self.binder_for('prestashop.product.template') + product_tmpl = record.env["product.template"].browse( + record.odoo_id.owner_id + ) + binder = self.binder_for("prestashop.product.template") ps_product_id = binder.to_backend(product_tmpl, wrap=True) - return {'id_product': ps_product_id} + return {"id_product": ps_product_id} @mapping def extension(self, record): - return {'extension': self._get_file_name(record)[1]} + return {"extension": self._get_file_name(record)[1]} @mapping def legend(self, record): - return {'legend': record.name} + return {"legend": record.name} @mapping def filename(self, record): file_name = record.filename if not file_name: - file_name = '.'.join(self._get_file_name(record)) - return {'filename': file_name} + file_name = ".".join(self._get_file_name(record)) + return {"filename": file_name} diff --git a/connector_prestashop_catalog_manager/models/product_product/__init__.py b/connector_prestashop_catalog_manager/models/product_product/__init__.py index 6d964d9a7..f43c99d95 100644 --- a/connector_prestashop_catalog_manager/models/product_product/__init__.py +++ b/connector_prestashop_catalog_manager/models/product_product/__init__.py @@ -1,4 +1,2 @@ -# -*- coding: utf-8 -*- - from . import exporter from . import common diff --git a/connector_prestashop_catalog_manager/models/product_product/common.py b/connector_prestashop_catalog_manager/models/product_product/common.py index fc71a2031..36046b9b5 100644 --- a/connector_prestashop_catalog_manager/models/product_product/common.py +++ b/connector_prestashop_catalog_manager/models/product_product/common.py @@ -1,14 +1,13 @@ -# -*- coding: utf-8 -*- # © 2016 Sergio Teruel # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html -from openerp import models, fields +from openerp import fields, models class PrestashopProductCombination(models.Model): - _inherit = 'prestashop.product.combination' + _inherit = "prestashop.product.combination" minimal_quantity = fields.Integer( - string='Minimal Quantity', + string="Minimal Quantity", default=1, - help='Minimal Sale quantity', + help="Minimal Sale quantity", ) diff --git a/connector_prestashop_catalog_manager/models/product_product/exporter.py b/connector_prestashop_catalog_manager/models/product_product/exporter.py index 235b27f3c..ab91dba05 100644 --- a/connector_prestashop_catalog_manager/models/product_product/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_product/exporter.py @@ -1,26 +1,25 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp.addons.connector.unit.mapper import mapping +import logging +from collections import OrderedDict +from openerp.addons.connector.unit.mapper import mapping +from openerp.addons.connector_prestashop.backend import prestashop from openerp.addons.connector_prestashop.unit.exporter import ( + PrestashopExporter, TranslationPrestashopExporter, export_record, - PrestashopExporter, ) -from openerp.addons.connector_prestashop.unit.mapper import \ - TranslationPrestashopExportMapper -from openerp.addons.connector_prestashop.backend import prestashop -from collections import OrderedDict -import logging - +from openerp.addons.connector_prestashop.unit.mapper import ( + TranslationPrestashopExportMapper, +) _logger = logging.getLogger(__name__) @prestashop class ProductCombinationExport(TranslationPrestashopExporter): - _model_name = 'prestashop.product.combination' + _model_name = "prestashop.product.combination" def _create(self, record): """ @@ -28,78 +27,100 @@ def _create(self, record): :return integer: Prestashop record id """ res = super(ProductCombinationExport, self)._create(record) - return res['prestashop']['combination']['id'] + return res["prestashop"]["combination"]["id"] def _export_images(self): if self.binding.image_ids: - image_binder = self.binder_for('prestashop.product.image') + image_binder = self.binder_for("prestashop.product.image") for image_line in self.binding.image_ids: - image_ext_id = image_binder.to_backend( - image_line.id, wrap=True) + image_ext_id = image_binder.to_backend(image_line.id, wrap=True) if not image_ext_id: - image_ext_id = \ - self.session.env['prestashop.product.image']\ - .with_context(connector_no_export=True).create({ - 'backend_id': self.backend_record.id, - 'odoo_id': image_line.id, - }).id - image_content = getattr(image_line, "_get_image_from_%s" % - image_line.storage)() + image_ext_id = ( + self.session.env["prestashop.product.image"] + .with_context(connector_no_export=True) + .create( + { + "backend_id": self.backend_record.id, + "odoo_id": image_line.id, + } + ) + .id + ) + image_content = getattr( + image_line, "_get_image_from_%s" % image_line.storage + )() export_record( self.session, - 'prestashop.product.image', + "prestashop.product.image", image_ext_id, - image_content) + image_content, + ) def _export_dependencies(self): - """ Export the dependencies for the product""" + """Export the dependencies for the product""" # TODO add export of category - attribute_binder = self.binder_for( - 'prestashop.product.combination.option') - option_binder = self.binder_for( - 'prestashop.product.combination.option.value') - Option = self.env['prestashop.product.combination.option'] - OptionValue = self.env['prestashop.product.combination.option.value'] + attribute_binder = self.binder_for("prestashop.product.combination.option") + option_binder = self.binder_for("prestashop.product.combination.option.value") + Option = self.env["prestashop.product.combination.option"] + OptionValue = self.env["prestashop.product.combination.option.value"] for value in self.binding.attribute_value_ids: prestashop_option_id = attribute_binder.to_backend( - value.attribute_id.id, wrap=True) + value.attribute_id.id, wrap=True + ) if not prestashop_option_id: option_binding = Option.search( - [('backend_id', '=', self.backend_record.id), - ('odoo_id', '=', value.attribute_id.id)]) + [ + ("backend_id", "=", self.backend_record.id), + ("odoo_id", "=", value.attribute_id.id), + ] + ) if not option_binding: option_binding = Option.with_context( - connector_no_export=True).create({ - 'backend_id': self.backend_record.id, - 'odoo_id': value.attribute_id.id}) - export_record(self.session, - 'prestashop.product.combination.option', - option_binding.id) - prestashop_value_id = option_binder.to_backend( - value.id, wrap=True) + connector_no_export=True + ).create( + { + "backend_id": self.backend_record.id, + "odoo_id": value.attribute_id.id, + } + ) + export_record( + self.session, + "prestashop.product.combination.option", + option_binding.id, + ) + prestashop_value_id = option_binder.to_backend(value.id, wrap=True) if not prestashop_value_id: value_binding = OptionValue.search( - [('backend_id', '=', self.backend_record.id), - ('odoo_id', '=', value.id)] + [ + ("backend_id", "=", self.backend_record.id), + ("odoo_id", "=", value.id), + ] ) if not value_binding: option_binding = Option.search( - [('backend_id', '=', self.backend_record.id), - ('odoo_id', '=', value.attribute_id.id)]) + [ + ("backend_id", "=", self.backend_record.id), + ("odoo_id", "=", value.attribute_id.id), + ] + ) value_binding = OptionValue.with_context( - connector_no_export=True).create({ - 'backend_id': self.backend_record.id, - 'odoo_id': value.id, - 'id_attribute_group': option_binding.id}) + connector_no_export=True + ).create( + { + "backend_id": self.backend_record.id, + "odoo_id": value.id, + "id_attribute_group": option_binding.id, + } + ) export_record( self.session, - 'prestashop.product.combination.option.value', - value_binding.id) + "prestashop.product.combination.option.value", + value_binding.id, + ) # self._export_images() def update_quantities(self): - self.binding.odoo_id.with_context( - self.session.context).update_prestashop_qty() + self.binding.odoo_id.with_context(self.session.context).update_prestashop_qty() def _after_export(self): self.update_quantities() @@ -107,14 +128,14 @@ def _after_export(self): @prestashop class ProductCombinationExportMapper(TranslationPrestashopExportMapper): - _model_name = 'prestashop.product.combination' + _model_name = "prestashop.product.combination" direct = [ - ('default_code', 'reference'), - ('active', 'active'), - ('barcode', 'ean13'), - ('minimal_quantity', 'minimal_quantity'), - ('weight', 'weight'), + ("default_code", "reference"), + ("active", "active"), + ("barcode", "ean13"), + ("minimal_quantity", "minimal_quantity"), + ("weight", "weight"), ] def _get_factor_tax(self, tax): @@ -123,138 +144,134 @@ def _get_factor_tax(self, tax): @mapping def combination_default(self, record): - return {'default_on': int(record['default_on'])} + return {"default_on": int(record["default_on"])} def get_main_template_id(self, record): - template_binder = self.binder_for('prestashop.product.template') + template_binder = self.binder_for("prestashop.product.template") return template_binder.to_backend(record.main_template_id.id) @mapping def main_template_id(self, record): - return {'id_product': self.get_main_template_id(record)} + return {"id_product": self.get_main_template_id(record)} @mapping def _unit_price_impact(self, record): tax = record.taxes_id[:1] - if tax.price_include and tax.amount_type == 'percent': + if tax.price_include and tax.amount_type == "percent": # 6 is the rounding precision used by PrestaShop for the # tax excluded price. we can get back a 2 digits tax included # price from the 6 digits rounded value - return { - 'price': round( - record.impact_price / self._get_factor_tax(tax), 6) - } + return {"price": round(record.impact_price / self._get_factor_tax(tax), 6)} else: - return {'price': record.impact_price} + return {"price": record.impact_price} @mapping def cost_price(self, record): - return {'wholesale_price': record.standard_price} + return {"wholesale_price": record.standard_price} def _get_product_option_value(self, record): option_value = [] - option_binder = self.binder_for( - 'prestashop.product.combination.option.value') + option_binder = self.binder_for("prestashop.product.combination.option.value") for value in record.attribute_value_ids: value_ext_id = option_binder.to_backend(value.id, wrap=True) if value_ext_id: - option_value.append({'id': value_ext_id}) + option_value.append({"id": value_ext_id}) return option_value def _get_combination_image(self, record): images = [] - image_binder = self.binder_for('prestashop.product.image') + image_binder = self.binder_for("prestashop.product.image") for image in record.image_ids: image_ext_id = image_binder.to_backend(image.id, wrap=True) if image_ext_id: - images.append({'id': image_ext_id}) + images.append({"id": image_ext_id}) return images @mapping def associations(self, record): - associations = OrderedDict([ - ('product_option_values', - {'product_option_value': - self._get_product_option_value(record)}), - ]) + associations = OrderedDict( + [ + ( + "product_option_values", + {"product_option_value": self._get_product_option_value(record)}, + ), + ] + ) image = self._get_combination_image(record) if image: - associations['images'] = { - 'image': self._get_combination_image(record) - } - return {'associations': associations} + associations["images"] = {"image": self._get_combination_image(record)} + return {"associations": associations} @prestashop class ProductCombinationOptionExport(PrestashopExporter): - _model_name = 'prestashop.product.combination.option' + _model_name = "prestashop.product.combination.option" def _create(self, record): res = super(ProductCombinationOptionExport, self)._create(record) - return res['prestashop']['product_option']['id'] + return res["prestashop"]["product_option"]["id"] @prestashop class ProductCombinationOptionExportMapper(TranslationPrestashopExportMapper): - _model_name = 'prestashop.product.combination.option' + _model_name = "prestashop.product.combination.option" direct = [ - ('prestashop_position', 'position'), - ('group_type', 'group_type'), + ("prestashop_position", "position"), + ("group_type", "group_type"), ] _translatable_fields = [ - ('name', 'name'), - ('name', 'public_name'), + ("name", "name"), + ("name", "public_name"), ] @prestashop class ProductCombinationOptionValueExport(PrestashopExporter): - _model_name = 'prestashop.product.combination.option.value' + _model_name = "prestashop.product.combination.option.value" def _create(self, record): res = super(ProductCombinationOptionValueExport, self)._create(record) - return res['prestashop']['product_option_value']['id'] + return res["prestashop"]["product_option_value"]["id"] def _export_dependencies(self): - """ Export the dependencies for the record""" + """Export the dependencies for the record""" attribute_id = self.binding.attribute_id.id # export product attribute - binder = self.binder_for('prestashop.product.combination.option') + binder = self.binder_for("prestashop.product.combination.option") if not binder.to_backend(attribute_id, wrap=True): exporter = self.get_connector_unit_for_model( - TranslationPrestashopExporter, - 'prestashop.product.combination.option') + TranslationPrestashopExporter, "prestashop.product.combination.option" + ) exporter.run(attribute_id) return @prestashop -class ProductCombinationOptionValueExportMapper( - TranslationPrestashopExportMapper): - _model_name = 'prestashop.product.combination.option.value' +class ProductCombinationOptionValueExportMapper(TranslationPrestashopExportMapper): + _model_name = "prestashop.product.combination.option.value" - direct = [('name', 'value')] + direct = [("name", "value")] # handled by base mapping `translatable_fields` _translatable_fields = [ - ('name', 'name'), + ("name", "name"), ] @mapping def prestashop_product_attribute_id(self, record): attribute_binder = self.binder_for( - 'prestashop.product.combination.option.value') + "prestashop.product.combination.option.value" + ) return { - 'id_feature': attribute_binder.to_backend( - record.attribute_id.id, wrap=True) + "id_feature": attribute_binder.to_backend(record.attribute_id.id, wrap=True) } @mapping def prestashop_product_group_attribute_id(self, record): - attribute_binder = self.binder_for( - 'prestashop.product.combination.option') + attribute_binder = self.binder_for("prestashop.product.combination.option") return { - 'id_attribute_group': attribute_binder.to_backend( - record.attribute_id.id, wrap=True), + "id_attribute_group": attribute_binder.to_backend( + record.attribute_id.id, wrap=True + ), } diff --git a/connector_prestashop_catalog_manager/models/product_template/__init__.py b/connector_prestashop_catalog_manager/models/product_template/__init__.py index 6d964d9a7..f43c99d95 100644 --- a/connector_prestashop_catalog_manager/models/product_template/__init__.py +++ b/connector_prestashop_catalog_manager/models/product_template/__init__.py @@ -1,4 +1,2 @@ -# -*- coding: utf-8 -*- - from . import exporter from . import common diff --git a/connector_prestashop_catalog_manager/models/product_template/common.py b/connector_prestashop_catalog_manager/models/product_template/common.py index b4cca65c4..8eac058bb 100644 --- a/connector_prestashop_catalog_manager/models/product_template/common.py +++ b/connector_prestashop_catalog_manager/models/product_template/common.py @@ -1,46 +1,28 @@ -# -*- coding: utf-8 -*- # © 2016 Sergio Teruel # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html -from openerp import models, fields import openerp.addons.decimal_precision as dp +from openerp import fields, models class PrestashopProductTemplate(models.Model): - _inherit = 'prestashop.product.template' + _inherit = "prestashop.product.template" - meta_title = fields.Char( - string='Meta Title', - translate=True - ) - meta_description = fields.Char( - string='Meta Description', - translate=True - ) - meta_keywords = fields.Char( - string='Meta Keywords', - translate=True - ) - tags = fields.Char( - string='Tags', - translate=True - ) - online_only = fields.Boolean(string='Online Only') + meta_title = fields.Char(string="Meta Title", translate=True) + meta_description = fields.Char(string="Meta Description", translate=True) + meta_keywords = fields.Char(string="Meta Keywords", translate=True) + tags = fields.Char(string="Tags", translate=True) + online_only = fields.Boolean(string="Online Only") additional_shipping_cost = fields.Float( - string='Additional Shipping Price', - digits_compute=dp.get_precision('Product Price'), - help="Additionnal Shipping Price for the product on Prestashop") - available_now = fields.Char( - string='Available Now', - translate=True - ) - available_later = fields.Char( - string='Available Later', - translate=True + string="Additional Shipping Price", + digits_compute=dp.get_precision("Product Price"), + help="Additionnal Shipping Price for the product on Prestashop", ) - available_date = fields.Date(string='Available Date') + available_now = fields.Char(string="Available Now", translate=True) + available_later = fields.Char(string="Available Later", translate=True) + available_date = fields.Date(string="Available Date") minimal_quantity = fields.Integer( - string='Minimal Quantity', - help='Minimal Sale quantity', + string="Minimal Quantity", + help="Minimal Sale quantity", default=1, ) diff --git a/connector_prestashop_catalog_manager/models/product_template/exporter.py b/connector_prestashop_catalog_manager/models/product_template/exporter.py index 3842442b4..4210f9c98 100644 --- a/connector_prestashop_catalog_manager/models/product_template/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_template/exporter.py @@ -1,35 +1,34 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import timedelta -from openerp.addons.connector.unit.mapper import mapping, m2o_to_backend - -from openerp.addons.connector_prestashop.\ - models.product_template.importer import ProductTemplateImporter - +from openerp.addons.connector.unit.mapper import m2o_to_backend, mapping +from openerp.addons.connector_prestashop.backend import prestashop +from openerp.addons.connector_prestashop.models.product_template.importer import ( + ProductTemplateImporter, +) from openerp.addons.connector_prestashop.unit.exporter import ( + TranslationPrestashopExporter, export_record, - TranslationPrestashopExporter ) from openerp.addons.connector_prestashop.unit.mapper import ( TranslationPrestashopExportMapper, ) -from openerp.addons.connector_prestashop.backend import prestashop + from ...consumer import get_slug @prestashop class ProductTemplateExporter(TranslationPrestashopExporter): - _model_name = 'prestashop.product.template' + _model_name = "prestashop.product.template" def _create(self, record): res = super(ProductTemplateExporter, self)._create(record) self.write_binging_vals(self.binding, record) - return res['prestashop']['product']['id'] + return res["prestashop"]["product"]["id"] def _update(self, data): - """ Update an Prestashop record """ + """Update an Prestashop record""" assert self.prestashop_id self.export_variants() self.check_images() @@ -37,8 +36,8 @@ def _update(self, data): def write_binging_vals(self, erp_record, ps_record): keys_to_update = [ - ('description_short_html', 'description_short'), - ('description_html', 'description'), + ("description_short_html", "description_short"), + ("description_html", "description"), ] trans = ProductTemplateImporter(self.connector_env) splitted_record = trans._split_per_language(ps_record) @@ -46,34 +45,29 @@ def write_binging_vals(self, erp_record, ps_record): vals = {} for key in keys_to_update: vals[key[0]] = prestashop_record[key[1]] - erp_record.with_context( - connector_no_export=True, - lang=lang_code).write(vals) + erp_record.with_context(connector_no_export=True, lang=lang_code).write( + vals + ) def export_categories(self, category): if not category: return - category_binder = self.binder_for('prestashop.product.category') + category_binder = self.binder_for("prestashop.product.category") ext_id = category_binder.to_backend(category.id, wrap=True) if ext_id: return ext_id - ps_categ_obj = self.session.env['prestashop.product.category'] - position_cat_id = ps_categ_obj.search( - [], order='position desc', limit=1) + ps_categ_obj = self.session.env["prestashop.product.category"] + position_cat_id = ps_categ_obj.search([], order="position desc", limit=1) obj_position = position_cat_id.position + 1 res = { - 'backend_id': self.backend_record.id, - 'odoo_id': category.id, - 'link_rewrite': get_slug(category.name), - 'position': obj_position, + "backend_id": self.backend_record.id, + "odoo_id": category.id, + "link_rewrite": get_slug(category.name), + "position": obj_position, } - binding = ps_categ_obj.with_context( - connector_no_export=True).create(res) - export_record( - self.session, - 'prestashop.product.category', - binding.id) + binding = ps_categ_obj.with_context(connector_no_export=True).create(res) + export_record(self.session, "prestashop.product.category", binding.id) def _parent_length(self, categ): if not categ.parent_id: @@ -82,52 +76,59 @@ def _parent_length(self, categ): return 1 + self._parent_length(categ.parent_id) def _export_dependencies(self): - """ Export the dependencies for the product""" + """Export the dependencies for the product""" super(ProductTemplateExporter, self)._export_dependencies() - attribute_binder = self.binder_for( - 'prestashop.product.combination.option') - option_binder = self.binder_for( - 'prestashop.product.combination.option.value') + attribute_binder = self.binder_for("prestashop.product.combination.option") + option_binder = self.binder_for("prestashop.product.combination.option.value") for category in self.binding.categ_ids: self.export_categories(category) for line in self.binding.attribute_line_ids: attribute_ext_id = attribute_binder.to_backend( - line.attribute_id.id, wrap=True) + line.attribute_id.id, wrap=True + ) if not attribute_ext_id: self._export_dependency( - line.attribute_id, - 'prestashop.product.combination.option') + line.attribute_id, "prestashop.product.combination.option" + ) for value in line.value_ids: value_ext_id = option_binder.to_backend(value.id, wrap=True) if not value_ext_id: self._export_dependency( - value, 'prestashop.product.combination.option.value') + value, "prestashop.product.combination.option.value" + ) def export_variants(self): - combination_obj = self.session.env['prestashop.product.combination'] + combination_obj = self.session.env["prestashop.product.combination"] for product in self.binding.product_variant_ids: if not product.attribute_value_ids: continue - combination_ext_id = combination_obj.search([ - ('backend_id', '=', self.backend_record.id), - ('odoo_id', '=', product.id), - ]) + combination_ext_id = combination_obj.search( + [ + ("backend_id", "=", self.backend_record.id), + ("odoo_id", "=", product.id), + ] + ) if not combination_ext_id: combination_ext_id = combination_obj.with_context( - connector_no_export=True).create({ - 'backend_id': self.backend_record.id, - 'odoo_id': product.id, - 'main_template_id': self.binding_id, - }) + connector_no_export=True + ).create( + { + "backend_id": self.backend_record.id, + "odoo_id": product.id, + "main_template_id": self.binding_id, + } + ) # If a template has been modified then always update PrestaShop # combinations export_record.delay( self.session, - 'prestashop.product.combination', - combination_ext_id.id, priority=50, - eta=timedelta(seconds=20)) + "prestashop.product.combination", + combination_ext_id.id, + priority=50, + eta=timedelta(seconds=20), + ) def _not_in_variant_images(self, image): images = [] @@ -138,7 +139,7 @@ def _not_in_variant_images(self, image): def check_images(self): if self.binding.image_ids: - image_binder = self.binder_for('prestashop.product.image') + image_binder = self.binder_for("prestashop.product.image") for image in self.binding.image_ids: image_ext_id = image_binder.to_backend(image.id, wrap=True) # `image_ext_id` is ZERO as long as the image is not exported. @@ -148,16 +149,22 @@ def check_images(self): # ValueError: # Expected singleton: prestashop.product.image(x, y) if image_ext_id is None: - image_ext_id = self.session.env[ - 'prestashop.product.image'].with_context( - connector_no_export=True).create({ - 'backend_id': self.backend_record.id, - 'odoo_id': image.id, - }) + image_ext_id = ( + self.session.env["prestashop.product.image"] + .with_context(connector_no_export=True) + .create( + { + "backend_id": self.backend_record.id, + "odoo_id": image.id, + } + ) + ) export_record.delay( self.session, - 'prestashop.product.image', - image_ext_id.id, priority=15) + "prestashop.product.image", + image_ext_id.id, + priority=15, + ) def update_quantities(self): if len(self.binding.product_variant_ids) == 1: @@ -172,36 +179,39 @@ def _after_export(self): @prestashop class ProductTemplateExportMapper(TranslationPrestashopExportMapper): - _model_name = 'prestashop.product.template' + _model_name = "prestashop.product.template" direct = [ - ('available_for_order', 'available_for_order'), - ('show_price', 'show_price'), - ('online_only', 'online_only'), - ('weight', 'weight'), - ('standard_price', 'wholesale_price'), - (m2o_to_backend('default_shop_id'), 'id_shop_default'), - ('always_available', 'active'), - ('barcode', 'barcode'), - ('additional_shipping_cost', 'additional_shipping_cost'), - ('minimal_quantity', 'minimal_quantity'), - ('on_sale', 'on_sale'), - (m2o_to_backend( - 'prestashop_default_category_id', - binding='prestashop.product.category'), 'id_category_default'), + ("available_for_order", "available_for_order"), + ("show_price", "show_price"), + ("online_only", "online_only"), + ("weight", "weight"), + ("standard_price", "wholesale_price"), + (m2o_to_backend("default_shop_id"), "id_shop_default"), + ("always_available", "active"), + ("barcode", "barcode"), + ("additional_shipping_cost", "additional_shipping_cost"), + ("minimal_quantity", "minimal_quantity"), + ("on_sale", "on_sale"), + ( + m2o_to_backend( + "prestashop_default_category_id", binding="prestashop.product.category" + ), + "id_category_default", + ), ] # handled by base mapping `translatable_fields` _translatable_fields = [ - ('name', 'name'), - ('link_rewrite', 'link_rewrite'), - ('meta_title', 'meta_title'), - ('meta_description', 'meta_description'), - ('meta_keywords', 'meta_keywords'), - ('tags', 'tags'), - ('available_now', 'available_now'), - ('available_later', 'available_later'), - ('description_short_html', 'description_short'), - ('description_html', 'description'), + ("name", "name"), + ("link_rewrite", "link_rewrite"), + ("meta_title", "meta_title"), + ("meta_description", "meta_description"), + ("meta_keywords", "meta_keywords"), + ("tags", "tags"), + ("available_now", "available_now"), + ("available_later", "available_later"), + ("description_short_html", "description_short"), + ("description_html", "description"), ] def _get_factor_tax(self, tax): @@ -210,35 +220,32 @@ def _get_factor_tax(self, tax): @mapping def list_price(self, record): tax = record.taxes_id - if tax.price_include and tax.amount_type == 'percent': + if tax.price_include and tax.amount_type == "percent": # 6 is the rounding precision used by PrestaShop for the # tax excluded price. we can get back a 2 digits tax included # price from the 6 digits rounded value return { - 'price': str( - round(record.list_price / self._get_factor_tax(tax), 6)) + "price": str(round(record.list_price / self._get_factor_tax(tax), 6)) } else: - return {'price': str(record.list_price)} + return {"price": str(record.list_price)} @mapping def reference(self, record): - return {'reference': record.reference or record.default_code or ''} + return {"reference": record.reference or record.default_code or ""} def _get_product_category(self, record): ext_categ_ids = [] - binder = self.binder_for('prestashop.product.category') + binder = self.binder_for("prestashop.product.category") for category in record.categ_ids: - ext_categ_ids.append( - {'id': binder.to_backend(category.id, wrap=True)}) + ext_categ_ids.append({"id": binder.to_backend(category.id, wrap=True)}) return ext_categ_ids @mapping def associations(self, record): return { - 'associations': { - 'categories': { - 'category_id': self._get_product_category(record)}, + "associations": { + "categories": {"category_id": self._get_product_category(record)}, } } @@ -246,29 +253,29 @@ def associations(self, record): def tax_ids(self, record): if not record.taxes_id: return - binder = self.binder_for('prestashop.account.tax.group') + binder = self.binder_for("prestashop.account.tax.group") ext_id = binder.to_backend(record.taxes_id[:1].tax_group_id, wrap=True) - return {'id_tax_rules_group': ext_id} + return {"id_tax_rules_group": ext_id} @mapping def available_date(self, record): if record.available_date: - return {'available_date': record.available_date} + return {"available_date": record.available_date} return {} @mapping def date_add(self, record): # When export a record the date_add in PS is null. - return {'date_add': record.create_date} + return {"date_add": record.create_date} @mapping def default_image(self, record): - default_image = record.image_ids.filtered('front_image')[:1] + default_image = record.image_ids.filtered("front_image")[:1] if default_image: - binder = self.binder_for('prestashop.product.image') + binder = self.binder_for("prestashop.product.image") ps_image_id = binder.to_backend(default_image, wrap=True) if ps_image_id: - return {'id_default_image': ps_image_id} + return {"id_default_image": ps_image_id} @mapping def extras_manufacturer(self, record): @@ -279,10 +286,10 @@ def extras_manufacturer(self, record): @prestashop class ManufacturerExportMapper(TranslationPrestashopExportMapper): # To extend in connector_prestashop_manufacturer module - _model_name = 'prestashop.product.template' + _model_name = "prestashop.product.template" _translatable_fields = [ - ('name', 'name'), + ("name", "name"), ] @mapping diff --git a/connector_prestashop_catalog_manager/views/product_attribute_view.xml b/connector_prestashop_catalog_manager/views/product_attribute_view.xml index f052fba84..b05f0783e 100644 --- a/connector_prestashop_catalog_manager/views/product_attribute_view.xml +++ b/connector_prestashop_catalog_manager/views/product_attribute_view.xml @@ -1,4 +1,4 @@ - + @@ -11,35 +11,33 @@
- - + +
- + prestashop.product.combination.option
- - - + + +
- + prestashop.product.combination.option - - - + + + diff --git a/connector_prestashop_catalog_manager/views/product_image_view.xml b/connector_prestashop_catalog_manager/views/product_image_view.xml index 8c2dcc919..bbefd0109 100644 --- a/connector_prestashop_catalog_manager/views/product_image_view.xml +++ b/connector_prestashop_catalog_manager/views/product_image_view.xml @@ -1,13 +1,13 @@ - + connector_prestashop.product.image.form base_multi_image.image - + - + diff --git a/connector_prestashop_catalog_manager/views/product_view.xml b/connector_prestashop_catalog_manager/views/product_view.xml index f390e073b..27995ef8f 100644 --- a/connector_prestashop_catalog_manager/views/product_view.xml +++ b/connector_prestashop_catalog_manager/views/product_view.xml @@ -1,26 +1,28 @@ - + connector_prestashop.product.template.form prestashop.product.template - + form - - - - - - - - - - + + + + + + + + + + diff --git a/connector_prestashop_catalog_manager/wizards/__init__.py b/connector_prestashop_catalog_manager/wizards/__init__.py index c10f7b14a..d45436ab9 100644 --- a/connector_prestashop_catalog_manager/wizards/__init__.py +++ b/connector_prestashop_catalog_manager/wizards/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import export_multiple_products diff --git a/connector_prestashop_catalog_manager/wizards/active_deactive_products.py b/connector_prestashop_catalog_manager/wizards/active_deactive_products.py index 59eee4d41..c4d935ad5 100644 --- a/connector_prestashop_catalog_manager/wizards/active_deactive_products.py +++ b/connector_prestashop_catalog_manager/wizards/active_deactive_products.py @@ -1,20 +1,20 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields, api +from openerp import api, fields, models class SyncProducts(models.TransientModel): - _name = 'active.deactive.products' + _name = "active.deactive.products" force_status = fields.Boolean( - string='Force Status', - help='Check this option to force active product in prestashop') + string="Force Status", + help="Check this option to force active product in prestashop", + ) def _change_status(self, status): self.ensure_one() - product_obj = self.env['product.template'] - for product in product_obj.browse(self.env.context['active_ids']): + product_obj = self.env["product.template"] + for product in product_obj.browse(self.env.context["active_ids"]): for bind in product.prestashop_bind_ids: if bind.always_available != status or self.force_status: bind.always_available = status diff --git a/connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml b/connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml index d03b12bd0..7bd42b0ad 100644 --- a/connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml +++ b/connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml @@ -1,4 +1,4 @@ - + @@ -7,55 +7,65 @@
- - +
-
- - + + deactive.product.form active.deactive.products
-
-
- +
-
\ No newline at end of file +
diff --git a/connector_prestashop_catalog_manager/wizards/export_category.py b/connector_prestashop_catalog_manager/wizards/export_category.py index e8c6993b6..e6fac1114 100644 --- a/connector_prestashop_catalog_manager/wizards/export_category.py +++ b/connector_prestashop_catalog_manager/wizards/export_category.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields, api -import unicodedata import re +import unicodedata + +from openerp import api, fields, models try: import slugify as slugify_lib @@ -17,48 +17,51 @@ def get_slug(name): return slugify_lib.slugify(name) except TypeError: pass - uni = unicodedata.normalize('NFKD', name).encode( - 'ascii', 'ignore').decode('ascii') - slug = re.sub(r'[\W_]', ' ', uni).strip().lower() - slug = re.sub(r'[-\s]+', '-', slug) + uni = unicodedata.normalize("NFKD", name).encode("ascii", "ignore").decode("ascii") + slug = re.sub(r"[\W_]", " ", uni).strip().lower() + slug = re.sub(r"[-\s]+", "-", slug) return slug class PrestashopExportCategory(models.TransientModel): - _name = 'wiz.prestashop.export.category' + _name = "wiz.prestashop.export.category" def _default_backend(self): - return self.env['prestashop.backend'].search([], limit=1).id + return self.env["prestashop.backend"].search([], limit=1).id def _default_shop(self): - return self.env['prestashop.shop'].search([], limit=1).id + return self.env["prestashop.shop"].search([], limit=1).id backend_id = fields.Many2one( - comodel_name='prestashop.backend', + comodel_name="prestashop.backend", default=_default_backend, - string='Backend', + string="Backend", ) shop_id = fields.Many2one( - comodel_name='prestashop.shop', + comodel_name="prestashop.shop", default=_default_shop, - string='Shop', + string="Shop", ) @api.multi def export_categories(self): self.ensure_one() - category_obj = self.env['product.category'] - ps_category_obj = self.env['prestashop.product.category'] - for category in category_obj.browse(self.env.context['active_ids']): - ps_category = ps_category_obj.search([ - ('odoo_id', '=', category.id), - ('backend_id', '=', self.backend_id.id), - ('default_shop_id', '=', self.shop_id.id), - ]) + category_obj = self.env["product.category"] + ps_category_obj = self.env["prestashop.product.category"] + for category in category_obj.browse(self.env.context["active_ids"]): + ps_category = ps_category_obj.search( + [ + ("odoo_id", "=", category.id), + ("backend_id", "=", self.backend_id.id), + ("default_shop_id", "=", self.shop_id.id), + ] + ) if not ps_category: - ps_category_obj.create({ - 'backend_id': self.backend_id.id, - 'default_shop_id': self.shop_id.id, - 'link_rewrite': get_slug(category.name), - 'odoo_id': category.id, - }) + ps_category_obj.create( + { + "backend_id": self.backend_id.id, + "default_shop_id": self.shop_id.id, + "link_rewrite": get_slug(category.name), + "odoo_id": category.id, + } + ) diff --git a/connector_prestashop_catalog_manager/wizards/export_category_view.xml b/connector_prestashop_catalog_manager/wizards/export_category_view.xml index 44dd26c7b..79d8286a0 100644 --- a/connector_prestashop_catalog_manager/wizards/export_category_view.xml +++ b/connector_prestashop_catalog_manager/wizards/export_category_view.xml @@ -1,4 +1,4 @@ - + @@ -8,28 +8,33 @@
- - + +
-
- +
diff --git a/connector_prestashop_catalog_manager/wizards/export_multiple_products.py b/connector_prestashop_catalog_manager/wizards/export_multiple_products.py index beb5dbff2..db41ba98f 100644 --- a/connector_prestashop_catalog_manager/wizards/export_multiple_products.py +++ b/connector_prestashop_catalog_manager/wizards/export_multiple_products.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields, api -import unicodedata import re +import unicodedata + +from openerp import api, fields, models try: import slugify as slugify_lib @@ -17,33 +17,32 @@ def get_slug(name): return slugify_lib.slugify(name) except TypeError: pass - uni = unicodedata.normalize('NFKD', name).encode( - 'ascii', 'ignore').decode('ascii') - slug = re.sub(r'[\W_]', ' ', uni).strip().lower() - slug = re.sub(r'[-\s]+', '-', slug) + uni = unicodedata.normalize("NFKD", name).encode("ascii", "ignore").decode("ascii") + slug = re.sub(r"[\W_]", " ", uni).strip().lower() + slug = re.sub(r"[-\s]+", "-", slug) return slug class ExportMultipleProducts(models.TransientModel): - _name = 'export.multiple.products' + _name = "export.multiple.products" @api.multi def _default_backend(self): - return self.env['prestashop.backend'].search([], limit=1).id + return self.env["prestashop.backend"].search([], limit=1).id @api.multi def _default_shop(self): - return self.env['prestashop.shop'].search([], limit=1).id + return self.env["prestashop.shop"].search([], limit=1).id backend_id = fields.Many2one( - comodel_name='prestashop.backend', + comodel_name="prestashop.backend", default=_default_backend, - string='Backend', + string="Backend", ) shop_id = fields.Many2one( - comodel_name='prestashop.shop', + comodel_name="prestashop.shop", default=_default_shop, - string='Default Shop', + string="Default Shop", ) def _parent_length(self, categ): @@ -54,32 +53,37 @@ def _parent_length(self, categ): def _set_main_category(self, product): if product.categ_ids and product.categ_id.parent_id: - max_parent = {'length': 0} + max_parent = {"length": 0} for categ in product.categ_ids: parent_length = self._parent_length(categ.parent_id) - if parent_length > max_parent['length']: - max_parent = {'categ_id': categ.id, - 'length': parent_length} + if parent_length > max_parent["length"]: + max_parent = {"categ_id": categ.id, "length": parent_length} categ_length = self._parent_length(product.categ_id.parent_id) if categ_length < parent_length: if product.categ_id.id not in product.categ_ids.ids: - product.write({ - 'categ_ids': [(4, product.categ_id.id)], - }) - product.write({ - 'categ_id': max_parent['categ_id'], - 'categ_ids': [(3, max_parent['categ_id'])] - }) + product.write( + { + "categ_ids": [(4, product.categ_id.id)], + } + ) + product.write( + { + "categ_id": max_parent["categ_id"], + "categ_ids": [(3, max_parent["categ_id"])], + } + ) else: - product.write({ - 'categ_id': max_parent['categ_id'], - 'categ_ids': [(3, max_parent['categ_id'])], - }) + product.write( + { + "categ_id": max_parent["categ_id"], + "categ_ids": [(3, max_parent["categ_id"])], + } + ) @api.multi def set_category(self): - product_obj = self.env['product.template'] - for product in product_obj.browse(self.env.context['active_ids']): + product_obj = self.env["product.template"] + for product in product_obj.browse(self.env.context["active_ids"]): self._set_main_category(product) def _check_images(self, product): @@ -96,50 +100,54 @@ def _check_category(self, product): def _check_variants(self, product): if len(product.product_variant_ids) == 1: return True - if (len(product.product_variant_ids) > 1 and - not product.attribute_line_ids): + if len(product.product_variant_ids) > 1 and not product.attribute_line_ids: check_count = reduce( - lambda x, y: x * y, map(lambda x: len(x.value_ids), - product.attribute_line_ids)) + lambda x, y: x * y, + map(lambda x: len(x.value_ids), product.attribute_line_ids), + ) if check_count < len(product.product_variant_ids): return False return True @api.multi def export_variant_stock(self): - template_obj = self.env['product.template'] - products = template_obj.browse(self.env.context['active_ids']) + template_obj = self.env["product.template"] + products = template_obj.browse(self.env.context["active_ids"]) products.update_prestashop_quantities() @api.multi def create_prestashop_template(self, product): - presta_tmpl_obj = self.env['prestashop.product.template'] - return presta_tmpl_obj.create({ - 'backend_id': self.backend_id.id, - 'default_shop_id': self.shop_id.id, - 'link_rewrite': get_slug(product.name), - 'odoo_id': product.id, - }) + presta_tmpl_obj = self.env["prestashop.product.template"] + return presta_tmpl_obj.create( + { + "backend_id": self.backend_id.id, + "default_shop_id": self.shop_id.id, + "link_rewrite": get_slug(product.name), + "odoo_id": product.id, + } + ) @api.multi def export_products(self): self.ensure_one() - product_obj = self.env['product.template'] - presta_tmpl_obj = self.env['prestashop.product.template'] - for product in product_obj.browse(self.env.context['active_ids']): - presta_tmpl = presta_tmpl_obj.search([ - ('odoo_id', '=', product.id), - ('backend_id', '=', self.backend_id.id), - ('default_shop_id', '=', self.shop_id.id), - ]) + product_obj = self.env["product.template"] + presta_tmpl_obj = self.env["prestashop.product.template"] + for product in product_obj.browse(self.env.context["active_ids"]): + presta_tmpl = presta_tmpl_obj.search( + [ + ("odoo_id", "=", product.id), + ("backend_id", "=", self.backend_id.id), + ("default_shop_id", "=", self.shop_id.id), + ] + ) if not presta_tmpl: self._check_images(product) cat = self._check_category(product) var = self._check_variants(product) - if not(var and cat): + if not (var and cat): continue self.create_prestashop_template(product) else: for tmpl in presta_tmpl: - if ' ' in tmpl.link_rewrite: + if " " in tmpl.link_rewrite: tmpl.link_rewrite = get_slug(tmpl.link_rewrite) diff --git a/connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml b/connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml index bffd78867..aa4a1b787 100644 --- a/connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml +++ b/connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml @@ -1,4 +1,4 @@ - + @@ -7,27 +7,35 @@
- - + +
-
- + export.variant.stock.form @@ -35,23 +43,28 @@
-
- +
diff --git a/connector_prestashop_catalog_manager/wizards/sync_products.py b/connector_prestashop_catalog_manager/wizards/sync_products.py index fe895f1f9..f306bafbd 100644 --- a/connector_prestashop_catalog_manager/wizards/sync_products.py +++ b/connector_prestashop_catalog_manager/wizards/sync_products.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, api import logging +from openerp import api, models + _logger = logging.getLogger(__name__) diff --git a/connector_prestashop_catalog_manager/wizards/sync_products_view.xml b/connector_prestashop_catalog_manager/wizards/sync_products_view.xml index 6684559f6..03b322dae 100644 --- a/connector_prestashop_catalog_manager/wizards/sync_products_view.xml +++ b/connector_prestashop_catalog_manager/wizards/sync_products_view.xml @@ -1,4 +1,4 @@ - + @@ -7,24 +7,29 @@
-
-
- +
From b7060828ac3b522253056311cba3cc59d98ab800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurelija=20Vitkauskien=C4=97?= Date: Wed, 26 Jun 2024 16:15:31 +0300 Subject: [PATCH 34/34] [MIG] connector_prestashop_catalog_manager Migration to 16.0 --- .../__init__.py | 1 - .../__manifest__.py | 12 +- .../consumer.py | 295 ------------------ .../models/product_category/__init__.py | 2 + .../models/product_category/common.py | 141 +++++++++ .../models/product_category/deleter.py | 12 + .../models/product_category/exporter.py | 76 +++-- .../models/product_image/__init__.py | 2 + .../models/product_image/common.py | 51 +++ .../models/product_image/deleter.py | 10 + .../models/product_image/exporter.py | 103 ++---- .../models/product_product/__init__.py | 1 + .../models/product_product/common.py | 150 ++++++++- .../models/product_product/deleter.py | 19 ++ .../models/product_product/exporter.py | 188 +++++------ .../models/product_template/__init__.py | 3 +- .../models/product_template/common.py | 118 ++++++- .../models/product_template/deleter.py | 13 + .../models/product_template/exporter.py | 187 ++++++----- .../security/ir.model.access.csv | 7 + .../tests/__init__.py | 5 + .../tests/common.py | 30 ++ .../test_export_product_attribute.yaml | 50 +++ .../test_export_product_attribute_value.yaml | 46 +++ .../test_export_product_category.yaml | 69 ++++ .../fixtures/test_export_product_image.yaml | 225 +++++++++++++ .../fixtures/test_export_product_product.yaml | 77 +++++ .../test_export_product_template.yaml | 110 +++++++ .../tests/test_export_product_attribute.py | 192 ++++++++++++ .../tests/test_export_product_category.py | 126 ++++++++ .../tests/test_export_product_image.py | 137 ++++++++ .../tests/test_export_product_product.py | 231 ++++++++++++++ .../tests/test_export_product_template.py | 261 ++++++++++++++++ .../views/product_attribute_view.xml | 73 ++--- .../views/product_category_view.xml | 18 ++ .../views/product_image_view.xml | 10 +- .../views/product_view.xml | 75 +++-- .../wizards/active_deactive_products.py | 12 +- .../wizards/active_deactive_products_view.xml | 73 ++--- .../wizards/export_category.py | 24 +- .../wizards/export_category_view.xml | 37 +-- .../wizards/export_multiple_products.py | 30 +- .../wizards/export_multiple_products_view.xml | 127 ++++---- .../wizards/sync_products.py | 19 +- .../wizards/sync_products_view.xml | 67 ++-- 45 files changed, 2656 insertions(+), 859 deletions(-) delete mode 100644 connector_prestashop_catalog_manager/consumer.py create mode 100644 connector_prestashop_catalog_manager/models/product_category/common.py create mode 100644 connector_prestashop_catalog_manager/models/product_category/deleter.py create mode 100644 connector_prestashop_catalog_manager/models/product_image/common.py create mode 100644 connector_prestashop_catalog_manager/models/product_image/deleter.py create mode 100644 connector_prestashop_catalog_manager/models/product_product/deleter.py create mode 100644 connector_prestashop_catalog_manager/models/product_template/deleter.py create mode 100644 connector_prestashop_catalog_manager/security/ir.model.access.csv create mode 100644 connector_prestashop_catalog_manager/tests/__init__.py create mode 100644 connector_prestashop_catalog_manager/tests/common.py create mode 100644 connector_prestashop_catalog_manager/tests/fixtures/test_export_product_attribute.yaml create mode 100644 connector_prestashop_catalog_manager/tests/fixtures/test_export_product_attribute_value.yaml create mode 100644 connector_prestashop_catalog_manager/tests/fixtures/test_export_product_category.yaml create mode 100644 connector_prestashop_catalog_manager/tests/fixtures/test_export_product_image.yaml create mode 100644 connector_prestashop_catalog_manager/tests/fixtures/test_export_product_product.yaml create mode 100644 connector_prestashop_catalog_manager/tests/fixtures/test_export_product_template.yaml create mode 100644 connector_prestashop_catalog_manager/tests/test_export_product_attribute.py create mode 100644 connector_prestashop_catalog_manager/tests/test_export_product_category.py create mode 100644 connector_prestashop_catalog_manager/tests/test_export_product_image.py create mode 100644 connector_prestashop_catalog_manager/tests/test_export_product_product.py create mode 100644 connector_prestashop_catalog_manager/tests/test_export_product_template.py create mode 100644 connector_prestashop_catalog_manager/views/product_category_view.xml diff --git a/connector_prestashop_catalog_manager/__init__.py b/connector_prestashop_catalog_manager/__init__.py index d549a2b6e..7588e52c8 100644 --- a/connector_prestashop_catalog_manager/__init__.py +++ b/connector_prestashop_catalog_manager/__init__.py @@ -1,5 +1,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import consumer from . import models from . import wizards diff --git a/connector_prestashop_catalog_manager/__manifest__.py b/connector_prestashop_catalog_manager/__manifest__.py index 9913bb7ac..15143a453 100644 --- a/connector_prestashop_catalog_manager/__manifest__.py +++ b/connector_prestashop_catalog_manager/__manifest__.py @@ -6,9 +6,13 @@ { "name": "Prestashop-Odoo Catalog Manager", - "version": "9.0.1.0.2", + "version": "16.0.1.0.0", "license": "AGPL-3", - "depends": ["connector_prestashop"], + "depends": [ + "connector_prestashop", + "product_categ_image", + "product_multi_image", + ], "author": "Akretion," "AvanzOSC," "Tecnativa," @@ -24,6 +28,8 @@ "wizards/sync_products_view.xml", "wizards/active_deactive_products_view.xml", "views/product_image_view.xml", + "views/product_category_view.xml", + "security/ir.model.access.csv", ], - "installable": False, + "installable": True, } diff --git a/connector_prestashop_catalog_manager/consumer.py b/connector_prestashop_catalog_manager/consumer.py deleted file mode 100644 index 5d68862d6..000000000 --- a/connector_prestashop_catalog_manager/consumer.py +++ /dev/null @@ -1,295 +0,0 @@ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) - -import re -import unicodedata - -from openerp.addons.connector.connector import Binder -from openerp.addons.connector.event import ( - on_record_create, - on_record_unlink, - on_record_write, -) -from openerp.addons.connector_prestashop.consumer import INVENTORY_FIELDS -from openerp.addons.connector_prestashop.unit.deleter import export_delete_record -from openerp.addons.connector_prestashop.unit.exporter import export_record - -try: - import slugify as slugify_lib -except ImportError: - slugify_lib = None - - -def get_slug(name): - if slugify_lib: - try: - return slugify_lib.slugify(name) - except TypeError: - pass - uni = unicodedata.normalize("NFKD", name).encode("ascii", "ignore").decode("ascii") - slug = re.sub(r"[\W_]", " ", uni).strip().lower() - slug = re.sub(r"[-\s]+", "-", slug) - return slug - - -# TODO: attach this to a model to ease override -CATEGORY_EXPORT_FIELDS = [ - "name", - "parent_id", - "description", - "link_rewrite", - "meta_description", - "meta_keywords", - "meta_title", - "position", -] - -EXCLUDE_FIELDS = ["list_price"] - - -@on_record_create(model_names="prestashop.product.category") -def prestashop_product_category_create(session, model_name, record_id, fields): - if session.context.get("connector_no_export"): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names="product.category") -def product_category_write(session, model_name, record_id, fields): - if session.context.get("connector_no_export"): - return - if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): - model = session.env[model_name] - record = model.browse(record_id) - for binding in record.prestashop_bind_ids: - export_record.delay( - session, binding._model._name, binding.id, fields=fields, priority=20 - ) - - -@on_record_write(model_names="prestashop.product.category") -def prestashop_product_category_write(session, model_name, record_id, fields): - if session.context.get("connector_no_export"): - return - if set(fields.keys()) <= set(CATEGORY_EXPORT_FIELDS): - export_record.delay(session, model_name, record_id, fields) - - -@on_record_write(model_names="base_multi_image.image") -def product_image_write(session, model_name, record_id, fields): - if session.context.get("connector_no_export"): - return - model = session.env[model_name] - record = model.browse(record_id) - for binding in record.prestashop_bind_ids: - export_record.delay( - session, - "prestashop.product.image", - binding.id, - record.file_db_store, - priority=20, - ) - - -@on_record_unlink(model_names="base_multi_image.image") -def product_image_unlink(session, model_name, record_id): - if session.context.get("connector_no_export"): - return - model = session.env[model_name] - record = model.browse(record_id) - for binding in record.prestashop_bind_ids: - backend = binding.backend_id - product = session.env[record.owner_model].browse(record.owner_id) - if product.exists(): - product_template = product.prestashop_bind_ids.filtered( - lambda x: x.backend_id == binding.backend_id - ) - if not product_template: - return - env_product = backend.get_environment( - "prestashop.product.template", - session=session, - ) - binder_product = env_product.get_connector_unit(Binder) - external_product_id = binder_product.to_backend(product_template.id) - - env = backend.get_environment(binding._name, session=session) - binder = env.get_connector_unit(Binder) - external_id = binder.to_backend(binding.id) - resource = "images/products/%s" % (external_product_id) - if external_id: - export_delete_record.delay( - session, binding._name, binding.backend_id.id, external_id, resource - ) - - -@on_record_create(model_names="prestashop.product.template") -def prestashop_product_template_create(session, model_name, record_id, fields): - if session.context.get("connector_no_export"): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names="prestashop.product.template") -def prestashop_product_template_write(session, model_name, record_id, fields): - if session.context.get("connector_no_export"): - return - fields = list(set(fields).difference(set(INVENTORY_FIELDS))) - if fields: - export_record.delay(session, model_name, record_id, fields, priority=20) - # Propagate minimal_quantity from template to variants - if "minimal_quantity" in fields: - ps_template = session.env[model_name].browse(record_id) - for binding in ps_template.prestashop_bind_ids: - binding.odoo_id.mapped("product_variant_ids.prestashop_bind_ids").write( - {"minimal_quantity": binding.minimal_quantity} - ) - - -@on_record_write(model_names="product.template") -def product_template_write(session, model_name, record_id, fields): - if session.context.get("connector_no_export"): - return - model = session.env[model_name] - record = model.browse(record_id) - for binding in record.prestashop_bind_ids: - export_record.delay( - session, - "prestashop.product.template", - binding.id, - fields, - priority=20, - ) - - -@on_record_create(model_names="prestashop.product.combination") -def prestashop_product_combination_create(session, model_name, record_id, fields=None): - if session.context.get("connector_no_export"): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names="prestashop.product.combination") -def prestashop_product_combination_write(session, model_name, record_id, fields): - if session.context.get("connector_no_export"): - return - fields = list(set(fields).difference(set(INVENTORY_FIELDS))) - - if fields: - export_record.delay( - session, - model_name, - record_id, - fields, - priority=20, - ) - - -def prestashop_product_combination_unlink(session, record_id): - # binding is deactivate when deactive a product variant - ps_binding_product = session.env["prestashop.product.combination"].search( - [("active", "=", False), ("odoo_id", "=", record_id)] - ) - for binding in ps_binding_product: - resource = "combinations/%s" % (binding.prestashop_id) - export_delete_record.delay( - session, - "prestashop.product.combination", - binding.backend_id.id, - binding.prestashop_id, - resource, - ) - ps_binding_product.unlink() - - -@on_record_write(model_names="product.product") -def product_product_write(session, model_name, record_id, fields): - if session.context.get("connector_no_export"): - return - - for field in EXCLUDE_FIELDS: - fields.pop(field, None) - - model = session.env[model_name] - record = model.browse(record_id) - if not record.is_product_variant: - return - - if "active" in fields and not fields["active"]: - prestashop_product_combination_unlink(session, record_id) - return - - if fields: - for binding in record.prestashop_bind_ids: - priority = 20 - if "default_on" in fields and fields["default_on"]: - # PS has to uncheck actual default combination first - priority = 99 - export_record.delay( - session, - "prestashop.product.combination", - binding.id, - fields, - priority=priority, - ) - - -@on_record_create(model_names="prestashop.product.combination.option") -def prestashop_product_attribute_created(session, model_name, record_id, fields=None): - if session.context.get("connector_no_export"): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_create(model_names="prestashop.product.combination.option.value") -def prestashop_product_atrribute_value_created( - session, model_name, record_id, fields=None -): - if session.context.get("connector_no_export"): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names="prestashop.product.combination.option") -def prestashop_product_attribute_written(session, model_name, record_id, fields=None): - if session.context.get("connector_no_export"): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names="prestashop.product.combination.option.value") -def prestashop_attribute_option_written(session, model_name, record_id, fields=None): - if session.context.get("connector_no_export"): - return - export_record.delay(session, model_name, record_id, priority=20) - - -@on_record_write(model_names="product.attribute.value") -def product_attribute_written(session, model_name, record_id, fields=None): - if session.context.get("connector_no_export"): - return - model = session.pool.get(model_name) - record = model.browse(session.cr, session.uid, record_id, context=session.context) - for binding in record.prestashop_bind_ids: - export_record.delay( - session, - "prestashop.product.combination.option", - binding.id, - fields, - priority=20, - ) - - -@on_record_write(model_names="produc.attribute.value") -def attribute_option_written(session, model_name, record_id, fields=None): - if session.context.get("connector_no_export"): - return - model = session.pool.get(model_name) - record = model.browse(session.cr, session.uid, record_id, context=session.context) - for binding in record.prestashop_bind_ids: - export_record.delay( - session, - "prestashop.product.combination.option.value", - binding.id, - fields, - priority=20, - ) diff --git a/connector_prestashop_catalog_manager/models/product_category/__init__.py b/connector_prestashop_catalog_manager/models/product_category/__init__.py index 24472e23d..2c2fc9544 100644 --- a/connector_prestashop_catalog_manager/models/product_category/__init__.py +++ b/connector_prestashop_catalog_manager/models/product_category/__init__.py @@ -1 +1,3 @@ +from . import common from . import exporter +from . import deleter diff --git a/connector_prestashop_catalog_manager/models/product_category/common.py b/connector_prestashop_catalog_manager/models/product_category/common.py new file mode 100644 index 000000000..cfeacef54 --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_category/common.py @@ -0,0 +1,141 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from odoo import fields, models +from odoo.tools import config + +from odoo.addons.component.core import Component +from odoo.addons.component_event import skip_if +from odoo.addons.connector_prestashop.components.backend_adapter import ( + PrestaShopWebServiceImage, +) + + +class ProductCategory(models.Model): + _inherit = "product.category" + + prestashop_image_bind_ids = fields.One2many( + comodel_name="prestashop.categ.image", + inverse_name="odoo_id", + copy=False, + string="PrestaShop Image Bindings", + ) + + +class PrestashopCategImage(models.Model): + _name = "prestashop.categ.image" + _inherit = "prestashop.binding.odoo" + _inherits = {"product.category": "odoo_id"} + _description = "Prestashop Category Image" + + odoo_id = fields.Many2one( + comodel_name="product.category", + string="Product", + required=True, + ondelete="cascade", + ) + + +class PrestashopCategImageModelBinder(Component): + _name = "prestashop.categ.image.binder" + _inherit = "prestashop.binder" + _apply_on = "prestashop.categ.image" + + +class CategImageAdapter(Component): + _name = "prestashop.categ.image.adapter" + _inherit = "prestashop.crud.adapter" + _apply_on = "prestashop.categ.image" + _prestashop_image_model = "categories" + + def connect(self): + debug = False + if config["log_level"] == "debug": + debug = True + return PrestaShopWebServiceImage( + self.prestashop.api_url, self.prestashop.webservice_key, debug=debug + ) + + def read(self, category_id, image_id, options=None): + # pylint: disable=method-required-super + api = self.connect() + return api.get_image( + self._prestashop_image_model, category_id, image_id, options=options + ) + + def create(self, attributes=None): + # pylint: disable=method-required-super + api = self.connect() + image_binary = attributes["image"] + img_filename = attributes["name"] + image_url = "images/{}/{}".format( + self._prestashop_image_model, + str(attributes["categ_id"]), + ) + return api.add(image_url, files=[("image", img_filename, image_binary)]) + + def write(self, id_, attributes=None): + # pylint: disable=method-required-super + api = self.connect() + image_binary = attributes["image"] + img_filename = attributes["name"] + delete_url = "images/%s" % (self._prestashop_image_model) + api.delete(delete_url, str(attributes["categ_id"])) + image_url = "images/{}/{}".format( + self._prestashop_image_model, + str(attributes["categ_id"]), + ) + return api.add(image_url, files=[("image", img_filename, image_binary)]) + + +class PrestashopProductCategoryListener(Component): + _name = "prestashop.product.category.event.listener" + _inherit = "prestashop.connector.listener" + _apply_on = "prestashop.product.category" + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_create(self, record, fields=None): + """Called when a record is created""" + record.with_delay().export_record(fields=fields) + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + @skip_if(lambda self, record, **kwargs: self.need_to_export(record, **kwargs)) + def on_record_write(self, record, fields=None): + """Called when a record is written""" + record.with_delay().export_record(fields=fields) + + +class ProductCategoryListener(Component): + _name = "product.category.event.listener" + _inherit = "prestashop.connector.listener" + _apply_on = "product.category" + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + """Called when a record is written""" + for binding in record.prestashop_bind_ids: + if not self.need_to_export(binding, fields): + binding.with_delay().export_record(fields=fields) + if "image" in fields: + if record.prestashop_image_bind_ids: + for image in record.prestashop_image_bind_ids: + image.with_delay().export_record(fields=fields) + else: + for presta_categ in record.prestashop_bind_ids: + image = self.env["prestashop.categ.image"].create( + {"backend_id": presta_categ.backend_id.id, "odoo_id": record.id} + ) + image.with_delay().export_record(fields=fields) + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_unlink(self, record, fields=None): + """Called when a record is deleted""" + for binding in record.prestashop_bind_ids: + work = self.work.work_on(collection=binding.backend_id) + binder = work.component( + usage="binder", model_name="prestashop.product.category" + ) + prestashop_id = binder.to_external(binding) + if prestashop_id: + self.env[ + "prestashop.product.category" + ].with_delay().export_delete_record(binding.backend_id, prestashop_id) diff --git a/connector_prestashop_catalog_manager/models/product_category/deleter.py b/connector_prestashop_catalog_manager/models/product_category/deleter.py new file mode 100644 index 000000000..c43456fb8 --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_category/deleter.py @@ -0,0 +1,12 @@ +# Copyright 2018 PlanetaTIC - Marc Poch +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.component.core import Component + + +class ProductCategoryDeleter(Component): + _name = "prestashop.product.category.deleter" + _inherit = "prestashop.deleter" + _apply_on = [ + "prestashop.product.category", + ] diff --git a/connector_prestashop_catalog_manager/models/product_category/exporter.py b/connector_prestashop_catalog_manager/models/product_category/exporter.py index 10686a0b9..08c22a9fa 100644 --- a/connector_prestashop_catalog_manager/models/product_category/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_category/exporter.py @@ -1,26 +1,20 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp.addons.connector.unit.mapper import mapping -from openerp.addons.connector_prestashop.backend import prestashop -from openerp.addons.connector_prestashop.unit.exporter import ( - PrestashopExporter, - export_record, -) -from openerp.addons.connector_prestashop.unit.mapper import ( - TranslationPrestashopExportMapper, -) +from odoo.addons.component.core import Component +from odoo.addons.connector.components.mapper import changed_by, mapping -from ...consumer import get_slug +from ..product_template.exporter import get_slug -@prestashop -class ProductCategoryExporter(PrestashopExporter): - _model_name = "prestashop.product.category" +class ProductCategoryExporter(Component): + _name = "prestashop.product.category.exporter" + _inherit = "prestashop.exporter" + _apply_on = "prestashop.product.category" def _export_dependencies(self): """Export the dependencies for the category""" category_binder = self.binder_for("prestashop.product.category") - categories_obj = self.session.env["prestashop.product.category"] + categories_obj = self.env["prestashop.product.category"] for category in self.binding: self.export_parent_category( category.odoo_id.parent_id, category_binder, categories_obj @@ -29,7 +23,7 @@ def _export_dependencies(self): def export_parent_category(self, category, binder, ps_categ_obj): if not category: return - ext_id = binder.to_backend(category.id, wrap=True) + ext_id = binder.to_external(category.id, wrap=True) if ext_id: return ext_id res = { @@ -37,21 +31,17 @@ def export_parent_category(self, category, binder, ps_categ_obj): "odoo_id": category.id, "link_rewrite": get_slug(category.name), } - category_ext_id = ps_categ_obj.with_context(connector_no_export=True).create( - res - ) - parent_cat_id = export_record( - self.session, "prestashop.product.category", category_ext_id.id - ) + category_ext = ps_categ_obj.with_context(connector_no_export=True).create(res) + parent_cat_id = category_ext.export_record() return parent_cat_id -@prestashop -class ProductCategoryExportMapper(TranslationPrestashopExportMapper): - _model_name = "prestashop.product.category" +class ProductCategoryExportMapper(Component): + _name = "prestashop.product.category.export.mapper" + _inherit = "translation.prestashop.export.mapper" + _apply_on = "prestashop.product.category" direct = [ - ("sequence", "position"), ("default_shop_id", "id_shop_default"), ("active", "active"), ("position", "position"), @@ -66,10 +56,44 @@ class ProductCategoryExportMapper(TranslationPrestashopExportMapper): ("meta_title", "meta_title"), ] + @changed_by("parent_id") @mapping def parent_id(self, record): if not record["parent_id"]: return {"id_parent": 2} category_binder = self.binder_for("prestashop.product.category") - ext_categ_id = category_binder.to_backend(record.parent_id.id, wrap=True) + ext_categ_id = category_binder.to_external(record.parent_id.id, wrap=True) return {"id_parent": ext_categ_id} + + +class CategImageExporter(Component): + _name = "prestashop.product.category.image.exporter" + _inherit = "prestashop.exporter" + _apply_on = "prestashop.categ.image" + + def _create(self, data): + """Create the Prestashop record""" + if self.backend_adapter.create(data): + return 1 + + def _update(self, data): + return 1 + + +class CategImageExportMapper(Component): + _name = "prestashop.product.category.image.mapper" + _inherit = "prestashop.export.mapper" + _apply_on = "prestashop.categ.image" + + @changed_by("image") + @mapping + def image(self, record): + name = record.name.lower() + ".jpg" + return {"image": record["image"], "name": name} + + @changed_by("odoo_id") + @mapping + def odoo_id(self, record): + binder = self.binder_for("prestashop.product.category") + ext_categ_id = binder.to_external(record.odoo_id.id, wrap=True) + return {"categ_id": ext_categ_id} diff --git a/connector_prestashop_catalog_manager/models/product_image/__init__.py b/connector_prestashop_catalog_manager/models/product_image/__init__.py index 24472e23d..2c2fc9544 100644 --- a/connector_prestashop_catalog_manager/models/product_image/__init__.py +++ b/connector_prestashop_catalog_manager/models/product_image/__init__.py @@ -1 +1,3 @@ +from . import common from . import exporter +from . import deleter diff --git a/connector_prestashop_catalog_manager/models/product_image/common.py b/connector_prestashop_catalog_manager/models/product_image/common.py new file mode 100644 index 000000000..2c0b27e5b --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_image/common.py @@ -0,0 +1,51 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + +from odoo.addons.component.core import Component +from odoo.addons.component_event import skip_if + + +class ProductImage(models.Model): + _inherit = "base_multi_image.image" + + front_image = fields.Boolean() + + +class PrestashopProductImageListener(Component): + _name = "prestashop.product.image.event.listener" + _inherit = "base.connector.listener" + _apply_on = "base_multi_image.image" + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + """Called when a record is written""" + for binding in record.prestashop_bind_ids: + binding.with_delay().export_record(fields=fields) + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_unlink(self, record, fields=None): + """Called when a record is deleted""" + for binding in record.prestashop_bind_ids: + product = self.env[record.owner_model].browse(record.owner_id) + if product.exists(): + template = product.prestashop_bind_ids.filtered( + lambda x: x.backend_id == binding.backend_id + ) + if not template: + return + + work = self.work.work_on(collection=binding.backend_id) + binder = work.component( + usage="binder", model_name="prestashop.product.image" + ) + prestashop_id = binder.to_external(binding) + attributes = { + "id_product": template.prestashop_id, + } + if prestashop_id: + self.env[ + "prestashop.product.image" + ].with_delay().export_delete_record( + binding.backend_id, prestashop_id, attributes + ) diff --git a/connector_prestashop_catalog_manager/models/product_image/deleter.py b/connector_prestashop_catalog_manager/models/product_image/deleter.py new file mode 100644 index 000000000..0449a19e0 --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_image/deleter.py @@ -0,0 +1,10 @@ +# Copyright 2018 PlanetaTIC - Marc Poch +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.component.core import Component + + +class ProductImageDeleter(Component): + _name = "prestashop.product.image.deleter" + _inherit = "prestashop.deleter" + _apply_on = "prestashop.product.image" diff --git a/connector_prestashop_catalog_manager/models/product_image/exporter.py b/connector_prestashop_catalog_manager/models/product_image/exporter.py index 4be5757b4..f9bbd6a89 100644 --- a/connector_prestashop_catalog_manager/models/product_image/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_image/exporter.py @@ -1,28 +1,19 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import os -import os.path -from openerp import fields, models -from openerp.addons.connector.unit.mapper import mapping -from openerp.addons.connector_prestashop.backend import prestashop -from openerp.addons.connector_prestashop.unit.backend_adapter import ( +from odoo.tools.translate import _ + +from odoo.addons.component.core import Component +from odoo.addons.connector.components.mapper import mapping +from odoo.addons.connector_prestashop.components.backend_adapter import ( PrestaShopWebServiceImage, ) -from openerp.addons.connector_prestashop.unit.exporter import PrestashopExporter -from openerp.addons.connector_prestashop.unit.mapper import PrestashopExportMapper -from openerp.tools.translate import _ - - -class ProductImage(models.Model): - _inherit = "base_multi_image.image" - front_image = fields.Boolean(string="Front image") - -@prestashop -class ProductImageExport(PrestashopExporter): - _model_name = "prestashop.product.image" +class ProductImageExporter(Component): + _name = "prestashop.product.image.exporter" + _inherit = "prestashop.exporter" + _apply_on = "prestashop.product.image" def _run(self, fields=None): """Flow of the synchronization, implemented in inherited classes""" @@ -37,20 +28,27 @@ def _run(self, fields=None): map_record = self.mapper.map_record(self.binding) if self.prestashop_id: - record = map_record.values() + record = list(map_record.values()) if not record: return _("Nothing to export.") # special check on data before export self._validate_data(record) - self.prestashop_id = self._update(record) + exported_vals = self._update(record) else: record = map_record.values(for_create=True) if not record: return _("Nothing to export.") # special check on data before export self._validate_data(record) - self.prestashop_id = self._create(record) - self._after_export() + exported_vals = self._create(record) + self._after_export() + if ( + exported_vals + and exported_vals.get("prestashop") + and exported_vals["prestashop"].get("image") + ): + self.prestashop_id = int(exported_vals["prestashop"]["image"].get("id")) + self._link_image_to_url() message = _("Record exported with ID %s on Prestashop.") return message % self.prestashop_id @@ -67,55 +65,23 @@ def _link_image_to_url(self): "type": "image/jpeg", } ) - if self.binding.url != full_public_url: + if self.binding.load_from != full_public_url: self.binding.with_context(connector_no_export=True).write( { - "url": full_public_url, - "file_db_store": False, - "storage": "url", + "load_from": full_public_url, } ) -@prestashop -class ProductImageExportMapper(PrestashopExportMapper): - _model_name = "prestashop.product.image" +class ProductImageExportMapper(Component): + _name = "prestashop.product.image.mapper" + _inherit = "prestashop.export.mapper" + _apply_on = "prestashop.product.image" direct = [ ("name", "name"), ] - def _get_file_name(self, record): - """ - Get file name with extension from depending storage. - :param record: browse record - :return: string: file name.extension. - """ - file_name = record.odoo_id.filename - if not file_name: - storage = record.odoo_id.storage - if storage == "url": - file_name = os.path.splitext(os.path.basename(record.odoo_id.url)) - elif storage == "db": - if not record.odoo_id.filename: - file_name = "%s_%s.jpg" % ( - record.odoo_id.owner_model, - record.odoo_id.owner_id, - ) - file_name = os.path.splitext( - os.path.basename(record.odoo_id.filename or file_name) - ) - elif storage == "file": - file_name = os.path.splitext(os.path.basename(record.odoo_id.path)) - return file_name - - @mapping - def source_image(self, record): - content = getattr( - record.odoo_id, "_get_image_from_%s" % record.odoo_id.storage - )() - return {"content": content} - @mapping def product_id(self, record): if record.odoo_id.owner_model == "product.product": @@ -129,20 +95,17 @@ def product_id(self, record): record.odoo_id.owner_id ) binder = self.binder_for("prestashop.product.template") - ps_product_id = binder.to_backend(product_tmpl, wrap=True) + ps_product_id = binder.to_external(product_tmpl, wrap=True) return {"id_product": ps_product_id} - @mapping - def extension(self, record): - return {"extension": self._get_file_name(record)[1]} - @mapping def legend(self, record): return {"legend": record.name} @mapping - def filename(self, record): - file_name = record.filename - if not file_name: - file_name = ".".join(self._get_file_name(record)) - return {"filename": file_name} + def load_from(self, record): + return {"load_from": record.load_from} + + @mapping + def image_1920(self, record): + return {"image_1920": record.image_1920} diff --git a/connector_prestashop_catalog_manager/models/product_product/__init__.py b/connector_prestashop_catalog_manager/models/product_product/__init__.py index f43c99d95..bc9719d7f 100644 --- a/connector_prestashop_catalog_manager/models/product_product/__init__.py +++ b/connector_prestashop_catalog_manager/models/product_product/__init__.py @@ -1,2 +1,3 @@ from . import exporter from . import common +from . import deleter diff --git a/connector_prestashop_catalog_manager/models/product_product/common.py b/connector_prestashop_catalog_manager/models/product_product/common.py index 36046b9b5..ab0eb8f27 100644 --- a/connector_prestashop_catalog_manager/models/product_product/common.py +++ b/connector_prestashop_catalog_manager/models/product_product/common.py @@ -1,13 +1,159 @@ # © 2016 Sergio Teruel # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html -from openerp import fields, models +from odoo import fields, models + +from odoo.addons.component.core import Component +from odoo.addons.component_event import skip_if +from odoo.addons.connector_prestashop.models.product_template.common import ( + PrestashopProductQuantityListener, +) class PrestashopProductCombination(models.Model): _inherit = "prestashop.product.combination" minimal_quantity = fields.Integer( - string="Minimal Quantity", default=1, help="Minimal Sale quantity", ) + + +class PrestashopProductProductListener(Component): + _name = "prestashop.product.product.event.listener" + _inherit = "prestashop.connector.listener" + _apply_on = "prestashop.product.combination" + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_create(self, record, fields=None): + """Called when a record is created""" + record.with_delay().export_record(fields=fields) + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + @skip_if(lambda self, record, **kwargs: self.need_to_export(record, **kwargs)) + def on_record_write(self, record, fields=None): + """Called when a record is written""" + inventory_fields = PrestashopProductQuantityListener._get_inventory_fields(self) + fields = list(set(fields).difference(set(inventory_fields))) + if fields: + record.with_delay().export_record(fields=fields) + + +class ProductProductListener(Component): + _name = "product.product.event.listener" + _inherit = "prestashop.connector.listener" + _apply_on = "product.product" + + EXCLUDE_FIELDS = ["list_price"] + + def prestashop_product_combination_unlink(self, record): + # binding is deactivate when deactive a product variant + for binding in record.prestashop_combinations_bind_ids: + work = self.work.work_on(collection=binding.backend_id) + binder = work.component( + usage="binder", model_name="prestashop.product.combination" + ) + prestashop_id = binder.to_external(binding) + binding.with_delay().export_delete_record(binding.backend_id, prestashop_id) + record.prestashop_combinations_bind_ids.unlink() + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + """Called when a record is written""" + for field in self.EXCLUDE_FIELDS: + if field in fields: + fields.remove(field) + if "active" in fields and not record["active"]: + self.prestashop_product_combination_unlink(record) + return + if fields: + priority = 20 + if "default_on" in fields and record["default_on"]: + # PS has to uncheck actual default combination first + priority = 99 + for binding in record.prestashop_combinations_bind_ids: + if not self.need_to_export(binding, fields): + binding.with_delay(priority=priority).export_record(fields=fields) + + def on_product_price_changed(self, record): + fields = ["standard_price", "impact_price", "lst_price", "list_price"] + for binding in record.prestashop_combinations_bind_ids: + if not self.need_to_export(binding, fields): + binding.with_delay(priority=20).export_record(fields=fields) + + +class PrestashopAttributeListener(Component): + _name = "prestashop.attribute.event.listener" + _inherit = "prestashop.connector.listener" + _apply_on = [ + "prestashop.product.combination.option", + "prestashop.product.combination.option.value", + ] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_create(self, record, fields=None): + """Called when a record is created""" + record.with_delay().export_record(fields=fields) + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + @skip_if(lambda self, record, **kwargs: self.need_to_export(record, **kwargs)) + def on_record_write(self, record, fields=None): + """Called when a record is written""" + record.with_delay().export_record(fields=fields) + + +class AttributeListener(Component): + _name = "attribute.event.listener" + _inherit = "prestashop.connector.listener" + _apply_on = [ + "product.attribute", + ] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + """Called when a record is written""" + for binding in record.prestashop_bind_ids: + if not self.need_to_export(binding, fields): + binding.with_delay().export_record(fields=fields) + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_unlink(self, record, fields=None): + """Called when a record is deleted""" + for binding in record.prestashop_bind_ids: + work = self.work.work_on(collection=binding.backend_id) + binder = work.component( + usage="binder", model_name="prestashop.product.combination.option" + ) + prestashop_id = binder.to_external(binding) + if prestashop_id: + self.env[ + "prestashop.product.combination.option" + ].with_delay().export_delete_record(binding.backend_id, prestashop_id) + + +class AttributeValueListener(Component): + _name = "attribute.value.event.listener" + _inherit = "prestashop.connector.listener" + _apply_on = [ + "product.attribute.value", + ] + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + """Called when a record is written""" + for binding in record.prestashop_bind_ids: + if not self.need_to_export(binding, fields): + binding.with_delay().export_record(fields=fields) + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_unlink(self, record, fields=None): + """Called when a record is deleted""" + for binding in record.prestashop_bind_ids: + work = self.work.work_on(collection=binding.backend_id) + binder = work.component( + usage="binder", model_name="prestashop.product.combination.option.value" + ) + prestashop_id = binder.to_external(binding) + if prestashop_id: + self.env[ + "prestashop.product.combination.option.value" + ].with_delay().export_delete_record(binding.backend_id, prestashop_id) diff --git a/connector_prestashop_catalog_manager/models/product_product/deleter.py b/connector_prestashop_catalog_manager/models/product_product/deleter.py new file mode 100644 index 000000000..148a29d6c --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_product/deleter.py @@ -0,0 +1,19 @@ +# Copyright 2018 PlanetaTIC - Marc Poch +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.component.core import Component + + +class ProductCombinationDeleter(Component): + _name = "prestashop.product.combination.deleter" + _inherit = "prestashop.deleter" + _apply_on = "prestashop.product.combination" + + +class ProductCombinationOptionDeleter(Component): + _name = "prestashop.product.combination.option.deleter" + _inherit = "prestashop.deleter" + _apply_on = [ + "prestashop.product.combination.option", + "prestashop.product.combination.option.value", + ] diff --git a/connector_prestashop_catalog_manager/models/product_product/exporter.py b/connector_prestashop_catalog_manager/models/product_product/exporter.py index ab91dba05..0a16f59b8 100644 --- a/connector_prestashop_catalog_manager/models/product_product/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_product/exporter.py @@ -1,42 +1,34 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -from collections import OrderedDict - -from openerp.addons.connector.unit.mapper import mapping -from openerp.addons.connector_prestashop.backend import prestashop -from openerp.addons.connector_prestashop.unit.exporter import ( - PrestashopExporter, - TranslationPrestashopExporter, - export_record, -) -from openerp.addons.connector_prestashop.unit.mapper import ( - TranslationPrestashopExportMapper, -) + +from odoo.addons.component.core import Component +from odoo.addons.connector.components.mapper import changed_by, mapping _logger = logging.getLogger(__name__) -@prestashop -class ProductCombinationExport(TranslationPrestashopExporter): - _model_name = "prestashop.product.combination" +class ProductCombinationExporter(Component): + _name = "prestashop.product.combination.exporter" + _inherit = "translation.prestashop.exporter" + _apply_on = "prestashop.product.combination" def _create(self, record): """ :param record: browse record to create in prestashop :return integer: Prestashop record id """ - res = super(ProductCombinationExport, self)._create(record) + res = super()._create(record) return res["prestashop"]["combination"]["id"] def _export_images(self): if self.binding.image_ids: image_binder = self.binder_for("prestashop.product.image") for image_line in self.binding.image_ids: - image_ext_id = image_binder.to_backend(image_line.id, wrap=True) + image_ext_id = image_binder.to_external(image_line.id, wrap=True) if not image_ext_id: - image_ext_id = ( - self.session.env["prestashop.product.image"] + image_ext = ( + self.env["prestashop.product.image"] .with_context(connector_no_export=True) .create( { @@ -47,14 +39,9 @@ def _export_images(self): .id ) image_content = getattr( - image_line, "_get_image_from_%s" % image_line.storage + image_line, "_get_image_from_%s" % image_line.load_from )() - export_record( - self.session, - "prestashop.product.image", - image_ext_id, - image_content, - ) + image_ext.export_record(image_content) def _export_dependencies(self): """Export the dependencies for the product""" @@ -63,8 +50,8 @@ def _export_dependencies(self): option_binder = self.binder_for("prestashop.product.combination.option.value") Option = self.env["prestashop.product.combination.option"] OptionValue = self.env["prestashop.product.combination.option.value"] - for value in self.binding.attribute_value_ids: - prestashop_option_id = attribute_binder.to_backend( + for value in self.binding.product_template_attribute_value_ids: + prestashop_option_id = attribute_binder.to_external( value.attribute_id.id, wrap=True ) if not prestashop_option_id: @@ -83,12 +70,10 @@ def _export_dependencies(self): "odoo_id": value.attribute_id.id, } ) - export_record( - self.session, - "prestashop.product.combination.option", - option_binding.id, - ) - prestashop_value_id = option_binder.to_backend(value.id, wrap=True) + option_binding.export_record() + prestashop_value_id = option_binder.to_external( + value.product_attribute_value_id.id, wrap=True + ) if not prestashop_value_id: value_binding = OptionValue.search( [ @@ -108,27 +93,24 @@ def _export_dependencies(self): ).create( { "backend_id": self.backend_record.id, - "odoo_id": value.id, + "odoo_id": value.product_attribute_value_id.id, "id_attribute_group": option_binding.id, } ) - export_record( - self.session, - "prestashop.product.combination.option.value", - value_binding.id, - ) + value_binding.export_record() # self._export_images() def update_quantities(self): - self.binding.odoo_id.with_context(self.session.context).update_prestashop_qty() + self.binding.odoo_id.with_context(**self.env.context).update_prestashop_qty() def _after_export(self): self.update_quantities() -@prestashop -class ProductCombinationExportMapper(TranslationPrestashopExportMapper): - _model_name = "prestashop.product.combination" +class ProductCombinationExportMapper(Component): + _name = "prestashop.product.combination.export.mapper" + _inherit = "translation.prestashop.export.mapper" + _apply_on = "prestashop.product.combination" direct = [ ("default_code", "reference"), @@ -148,32 +130,48 @@ def combination_default(self, record): def get_main_template_id(self, record): template_binder = self.binder_for("prestashop.product.template") - return template_binder.to_backend(record.main_template_id.id) + return template_binder.to_external(record.main_template_id.id) @mapping def main_template_id(self, record): return {"id_product": self.get_main_template_id(record)} + @changed_by("impact_price") @mapping def _unit_price_impact(self, record): + pricelist = record.backend_id.pricelist_id + if pricelist: + tmpl_prices = pricelist._get_products_price( + record.odoo_id.product_tmpl_id, 1.0 + ) + tmpl_price = tmpl_prices.get(record.odoo_id.product_tmpl_id.id) + product_prices = pricelist._get_products_price(record.odoo_id, 1.0) + product_price = product_prices.get(record.odoo_id.id) + extra_to_export = product_price - tmpl_price + else: + extra_to_export = record.impact_price tax = record.taxes_id[:1] if tax.price_include and tax.amount_type == "percent": # 6 is the rounding precision used by PrestaShop for the # tax excluded price. we can get back a 2 digits tax included # price from the 6 digits rounded value - return {"price": round(record.impact_price / self._get_factor_tax(tax), 6)} + return {"price": round(extra_to_export / self._get_factor_tax(tax), 6)} else: - return {"price": record.impact_price} + return {"price": extra_to_export} + @changed_by("standard_price") @mapping def cost_price(self, record): - return {"wholesale_price": record.standard_price} + wholesale_price = float(f"{record.standard_price:.2f}") + return {"wholesale_price": wholesale_price} def _get_product_option_value(self, record): option_value = [] option_binder = self.binder_for("prestashop.product.combination.option.value") - for value in record.attribute_value_ids: - value_ext_id = option_binder.to_backend(value.id, wrap=True) + for value in record.product_template_attribute_value_ids: + value_ext_id = option_binder.to_external( + value.product_attribute_value_id.id, wrap=True + ) if value_ext_id: option_value.append({"id": value_ext_id}) return option_value @@ -182,43 +180,52 @@ def _get_combination_image(self, record): images = [] image_binder = self.binder_for("prestashop.product.image") for image in record.image_ids: - image_ext_id = image_binder.to_backend(image.id, wrap=True) + image_ext_id = image_binder.to_external(image.id, wrap=True) if image_ext_id: images.append({"id": image_ext_id}) return images + @changed_by("product_template_attribute_value_ids", "image_ids") @mapping def associations(self, record): - associations = OrderedDict( - [ - ( - "product_option_values", - {"product_option_value": self._get_product_option_value(record)}, - ), - ] - ) - image = self._get_combination_image(record) - if image: - associations["images"] = {"image": self._get_combination_image(record)} - return {"associations": associations} + return { + "associations": { + "product_option_values": { + "product_option_value": self._get_product_option_value(record) + }, + "images": {"image": self._get_combination_image(record)}, + } + } + + @mapping + def low_stock_alert(self, record): + low_stock_alert = False + if record.product_tmpl_id.prestashop_bind_ids: + for presta_prod_tmpl in record.product_tmpl_id.prestashop_bind_ids: + if presta_prod_tmpl.low_stock_alert: + low_stock_alert = True + break + return {"low_stock_alert": "1" if low_stock_alert else "0"} -@prestashop -class ProductCombinationOptionExport(PrestashopExporter): - _model_name = "prestashop.product.combination.option" +class ProductCombinationOptionExporter(Component): + _name = "prestashop.product.combination.option.exporter" + _inherit = "prestashop.exporter" + _apply_on = "prestashop.product.combination.option" def _create(self, record): - res = super(ProductCombinationOptionExport, self)._create(record) + res = super()._create(record) return res["prestashop"]["product_option"]["id"] -@prestashop -class ProductCombinationOptionExportMapper(TranslationPrestashopExportMapper): - _model_name = "prestashop.product.combination.option" +class ProductCombinationOptionExportMapper(Component): + _name = "prestashop.product.combination.option.export.mapper" + _inherit = "translation.prestashop.export.mapper" + _apply_on = "prestashop.product.combination.option" direct = [ ("prestashop_position", "position"), - ("group_type", "group_type"), + ("display_type", "group_type"), ] _translatable_fields = [ @@ -227,32 +234,37 @@ class ProductCombinationOptionExportMapper(TranslationPrestashopExportMapper): ] -@prestashop -class ProductCombinationOptionValueExport(PrestashopExporter): - _model_name = "prestashop.product.combination.option.value" +class ProductCombinationOptionValueExporter(Component): + _name = "prestashop.product.combination.option.value.exporter" + _inherit = "prestashop.exporter" + _apply_on = "prestashop.product.combination.option.value" def _create(self, record): - res = super(ProductCombinationOptionValueExport, self)._create(record) + res = super()._create(record) return res["prestashop"]["product_option_value"]["id"] def _export_dependencies(self): """Export the dependencies for the record""" attribute_id = self.binding.attribute_id.id # export product attribute - binder = self.binder_for("prestashop.product.combination.option") - if not binder.to_backend(attribute_id, wrap=True): - exporter = self.get_connector_unit_for_model( - TranslationPrestashopExporter, "prestashop.product.combination.option" - ) - exporter.run(attribute_id) + attr_model = "prestashop.product.combination.option" + binder = self.binder_for(attr_model) + if not binder.to_external(attribute_id, wrap=True): + with self.backend_id.work_on(attr_model) as work: + exporter = work.component(usage="record.exporter") + exporter.run(attribute_id) return -@prestashop -class ProductCombinationOptionValueExportMapper(TranslationPrestashopExportMapper): - _model_name = "prestashop.product.combination.option.value" +class ProductCombinationOptionValueExportMapper(Component): + _name = "prestashop.product.combination.option.value.export.mapper" + _inherit = "translation.prestashop.export.mapper" + _apply_on = "prestashop.product.combination.option.value" - direct = [("name", "value")] + direct = [ + ("name", "value"), + ("prestashop_position", "position"), + ] # handled by base mapping `translatable_fields` _translatable_fields = [ ("name", "name"), @@ -264,14 +276,16 @@ def prestashop_product_attribute_id(self, record): "prestashop.product.combination.option.value" ) return { - "id_feature": attribute_binder.to_backend(record.attribute_id.id, wrap=True) + "id_feature": attribute_binder.to_external( + record.attribute_id.id, wrap=True + ) } @mapping def prestashop_product_group_attribute_id(self, record): attribute_binder = self.binder_for("prestashop.product.combination.option") return { - "id_attribute_group": attribute_binder.to_backend( + "id_attribute_group": attribute_binder.to_external( record.attribute_id.id, wrap=True ), } diff --git a/connector_prestashop_catalog_manager/models/product_template/__init__.py b/connector_prestashop_catalog_manager/models/product_template/__init__.py index f43c99d95..2c2fc9544 100644 --- a/connector_prestashop_catalog_manager/models/product_template/__init__.py +++ b/connector_prestashop_catalog_manager/models/product_template/__init__.py @@ -1,2 +1,3 @@ -from . import exporter from . import common +from . import exporter +from . import deleter diff --git a/connector_prestashop_catalog_manager/models/product_template/common.py b/connector_prestashop_catalog_manager/models/product_template/common.py index 8eac058bb..36b9bccce 100644 --- a/connector_prestashop_catalog_manager/models/product_template/common.py +++ b/connector_prestashop_catalog_manager/models/product_template/common.py @@ -1,28 +1,122 @@ # © 2016 Sergio Teruel # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html -import openerp.addons.decimal_precision as dp -from openerp import fields, models +from odoo import fields, models + +from odoo.addons.component.core import Component +from odoo.addons.component_event import skip_if class PrestashopProductTemplate(models.Model): _inherit = "prestashop.product.template" - meta_title = fields.Char(string="Meta Title", translate=True) - meta_description = fields.Char(string="Meta Description", translate=True) - meta_keywords = fields.Char(string="Meta Keywords", translate=True) - tags = fields.Char(string="Tags", translate=True) - online_only = fields.Boolean(string="Online Only") + meta_title = fields.Char(translate=True) + meta_description = fields.Char(translate=True) + meta_keywords = fields.Char(translate=True) + tags = fields.Char(translate=True) + online_only = fields.Boolean() additional_shipping_cost = fields.Float( string="Additional Shipping Price", - digits_compute=dp.get_precision("Product Price"), + digits="Product Price", help="Additionnal Shipping Price for the product on Prestashop", ) - available_now = fields.Char(string="Available Now", translate=True) - available_later = fields.Char(string="Available Later", translate=True) - available_date = fields.Date(string="Available Date") + available_now = fields.Char(translate=True) + available_later = fields.Char(translate=True) + available_date = fields.Date() minimal_quantity = fields.Integer( - string="Minimal Quantity", help="Minimal Sale quantity", default=1, ) + state = fields.Boolean(default=True) + visibility = fields.Char(translate=True) + low_stock_threshold = fields.Integer( + help="Low Stock Threshold", + default=0, + ) + low_stock_alert = fields.Integer( + help="Low Stock Alert", + default=0, + ) + + +class PrestashopProductTemplateListener(Component): + _name = "prestashop.product.template.event.listener" + _inherit = "prestashop.connector.listener" + _apply_on = "prestashop.product.template" + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_create(self, record, fields=None): + """Called when a record is created""" + record.with_delay().export_record(fields=fields) + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + @skip_if(lambda self, record, **kwargs: self.need_to_export(record, **kwargs)) + def on_record_write(self, record, fields=None): + """Called when a record is written""" + record.with_delay().export_record(fields=fields) + if "minimal_quantity" in fields: + record.product_variant_ids.mapped( + "prestashop_combinations_bind_ids" + ).filtered(lambda cb: cb.backend_id == record.backend_id).write( + {"minimal_quantity": record.minimal_quantity} + ) + + +class ProductTemplateListener(Component): + _name = "product.template.event.listener" + _inherit = "prestashop.connector.listener" + _apply_on = "product.template" + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_write(self, record, fields=None): + """Called when a record is written""" + for binding in record.prestashop_bind_ids: + # when assigning a product.attribute to a product.template, + # write is called 2 times. + # To avoid duplicates entries on Prestashop, ignore the empty write + if fields and not self.need_to_export(binding, fields): + binding.with_delay().export_record(fields=fields) + + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) + def on_record_unlink(self, record, fields=None): + """Called when a record is deleted""" + for binding in record.prestashop_bind_ids: + work = self.work.work_on(collection=binding.backend_id) + binder = work.component( + usage="binder", model_name="prestashop.product.template" + ) + prestashop_id = binder.to_external(binding) + if prestashop_id: + self.env[ + "prestashop.product.template" + ].with_delay().export_delete_record(binding.backend_id, prestashop_id) + + +class TemplateAdapter(Component): + _inherit = "prestashop.product.template.adapter" + + def write(self, id_, attributes=None): + # Prestashop wants all product data: + prestashop_data = self.client.get(self._prestashop_model, id_) + + # Remove read-only fields: + prestashop_data["product"].pop("manufacturer_name", False) + prestashop_data["product"].pop("quantity", False) + + # Remove position_in_category to avoid these PrestaShop issues: + # https://github.com/PrestaShop/PrestaShop/issues/14903 + # https://github.com/PrestaShop/PrestaShop/issues/15380 + prestashop_data["product"].pop("position_in_category", False) + + full_attributes = prestashop_data["product"].copy() + fa_assoc = full_attributes["associations"] + for field in attributes: + if field != "associations": + full_attributes[field] = attributes[field] + continue + for association, value in attributes["associations"].items(): + fa_assoc[association] = value + + res = super().write(id_, full_attributes) + + return res diff --git a/connector_prestashop_catalog_manager/models/product_template/deleter.py b/connector_prestashop_catalog_manager/models/product_template/deleter.py new file mode 100644 index 000000000..f5201652b --- /dev/null +++ b/connector_prestashop_catalog_manager/models/product_template/deleter.py @@ -0,0 +1,13 @@ +# Copyright 2018 PlanetaTIC - Marc Poch +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# + +from odoo.addons.component.core import Component + + +class ProductCombinationOptionDeleter(Component): + _name = "prestashop.product.template.deleter" + _inherit = "prestashop.deleter" + _apply_on = [ + "prestashop.product.template", + ] diff --git a/connector_prestashop_catalog_manager/models/product_template/exporter.py b/connector_prestashop_catalog_manager/models/product_template/exporter.py index 4210f9c98..04490516a 100644 --- a/connector_prestashop_catalog_manager/models/product_template/exporter.py +++ b/connector_prestashop_catalog_manager/models/product_template/exporter.py @@ -1,36 +1,49 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging +import re +import unicodedata from datetime import timedelta -from openerp.addons.connector.unit.mapper import m2o_to_backend, mapping -from openerp.addons.connector_prestashop.backend import prestashop -from openerp.addons.connector_prestashop.models.product_template.importer import ( - ProductTemplateImporter, -) -from openerp.addons.connector_prestashop.unit.exporter import ( - TranslationPrestashopExporter, - export_record, -) -from openerp.addons.connector_prestashop.unit.mapper import ( - TranslationPrestashopExportMapper, -) +from odoo import fields +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT -from ...consumer import get_slug +from odoo.addons.component.core import Component +from odoo.addons.connector.components.mapper import changed_by, m2o_to_external, mapping +try: + import slugify as slugify_lib +except ImportError: + slugify_lib = None -@prestashop -class ProductTemplateExporter(TranslationPrestashopExporter): - _model_name = "prestashop.product.template" +_logger = logging.getLogger(__name__) + + +def get_slug(name): + if slugify_lib: + try: + return slugify_lib.slugify(name) + except TypeError as e: + _logger.info("get_slug TypeError: %s", e) + uni = unicodedata.normalize("NFKD", name).encode("ascii", "ignore").decode("ascii") + slug = re.sub(r"[\W_]", " ", uni).strip().lower() + slug = re.sub(r"[-\s]+", "-", slug) + return slug + + +class ProductTemplateExporter(Component): + _name = "prestashop.product.template.exporter" + _inherit = "translation.prestashop.exporter" + _apply_on = "prestashop.product.template" def _create(self, record): - res = super(ProductTemplateExporter, self)._create(record) + res = super()._create(record) self.write_binging_vals(self.binding, record) return res["prestashop"]["product"]["id"] def _update(self, data): """Update an Prestashop record""" assert self.prestashop_id - self.export_variants() self.check_images() self.backend_adapter.write(self.prestashop_id, data) @@ -39,9 +52,9 @@ def write_binging_vals(self, erp_record, ps_record): ("description_short_html", "description_short"), ("description_html", "description"), ] - trans = ProductTemplateImporter(self.connector_env) + trans = self.component(usage="record.importer") splitted_record = trans._split_per_language(ps_record) - for lang_code, prestashop_record in splitted_record.items(): + for lang_code, prestashop_record in list(splitted_record.items()): vals = {} for key in keys_to_update: vals[key[0]] = prestashop_record[key[1]] @@ -53,11 +66,11 @@ def export_categories(self, category): if not category: return category_binder = self.binder_for("prestashop.product.category") - ext_id = category_binder.to_backend(category.id, wrap=True) + ext_id = category_binder.to_external(category, wrap=True) if ext_id: return ext_id - ps_categ_obj = self.session.env["prestashop.product.category"] + ps_categ_obj = self.env["prestashop.product.category"] position_cat_id = ps_categ_obj.search([], order="position desc", limit=1) obj_position = position_cat_id.position + 1 res = { @@ -67,7 +80,7 @@ def export_categories(self, category): "position": obj_position, } binding = ps_categ_obj.with_context(connector_no_export=True).create(res) - export_record(self.session, "prestashop.product.category", binding.id) + binding.export_record() def _parent_length(self, categ): if not categ.parent_id: @@ -77,7 +90,7 @@ def _parent_length(self, categ): def _export_dependencies(self): """Export the dependencies for the product""" - super(ProductTemplateExporter, self)._export_dependencies() + res = super()._export_dependencies() attribute_binder = self.binder_for("prestashop.product.combination.option") option_binder = self.binder_for("prestashop.product.combination.option.value") @@ -85,33 +98,34 @@ def _export_dependencies(self): self.export_categories(category) for line in self.binding.attribute_line_ids: - attribute_ext_id = attribute_binder.to_backend( - line.attribute_id.id, wrap=True + attribute_ext_id = attribute_binder.to_external( + line.attribute_id, wrap=True ) if not attribute_ext_id: self._export_dependency( line.attribute_id, "prestashop.product.combination.option" ) for value in line.value_ids: - value_ext_id = option_binder.to_backend(value.id, wrap=True) + value_ext_id = option_binder.to_external(value, wrap=True) if not value_ext_id: self._export_dependency( value, "prestashop.product.combination.option.value" ) + return res def export_variants(self): - combination_obj = self.session.env["prestashop.product.combination"] + combination_obj = self.env["prestashop.product.combination"] for product in self.binding.product_variant_ids: - if not product.attribute_value_ids: + if not product.product_template_attribute_value_ids: continue - combination_ext_id = combination_obj.search( + combination_ext = combination_obj.search( [ ("backend_id", "=", self.backend_record.id), ("odoo_id", "=", product.id), ] ) - if not combination_ext_id: - combination_ext_id = combination_obj.with_context( + if not combination_ext: + combination_ext = combination_obj.with_context( connector_no_export=True ).create( { @@ -122,13 +136,9 @@ def export_variants(self): ) # If a template has been modified then always update PrestaShop # combinations - export_record.delay( - self.session, - "prestashop.product.combination", - combination_ext_id.id, - priority=50, - eta=timedelta(seconds=20), - ) + combination_ext.with_delay( + priority=50, eta=timedelta(seconds=20) + ).export_record() def _not_in_variant_images(self, image): images = [] @@ -141,7 +151,7 @@ def check_images(self): if self.binding.image_ids: image_binder = self.binder_for("prestashop.product.image") for image in self.binding.image_ids: - image_ext_id = image_binder.to_backend(image.id, wrap=True) + image_ext_id = image_binder.to_external(image, wrap=True) # `image_ext_id` is ZERO as long as the image is not exported. # Here we delay the export so, # if we don't check this we create 2 records to be sync'ed @@ -149,8 +159,8 @@ def check_images(self): # ValueError: # Expected singleton: prestashop.product.image(x, y) if image_ext_id is None: - image_ext_id = ( - self.session.env["prestashop.product.image"] + image_ext = ( + self.env["prestashop.product.image"] .with_context(connector_no_export=True) .create( { @@ -159,12 +169,7 @@ def check_images(self): } ) ) - export_record.delay( - self.session, - "prestashop.product.image", - image_ext_id.id, - priority=15, - ) + image_ext.with_delay(priority=5).export_record() def update_quantities(self): if len(self.binding.product_variant_ids) == 1: @@ -175,30 +180,40 @@ def _after_export(self): self.check_images() self.export_variants() self.update_quantities() + if not self.binding.date_add: + self.binding.with_context( + connector_no_export=True + ).date_add = fields.Datetime.now() -@prestashop -class ProductTemplateExportMapper(TranslationPrestashopExportMapper): - _model_name = "prestashop.product.template" +class ProductTemplateExportMapper(Component): + _name = "prestashop.product.template.export.mapper" + _inherit = "translation.prestashop.export.mapper" + _apply_on = "prestashop.product.template" direct = [ ("available_for_order", "available_for_order"), ("show_price", "show_price"), ("online_only", "online_only"), ("weight", "weight"), - ("standard_price", "wholesale_price"), - (m2o_to_backend("default_shop_id"), "id_shop_default"), + (m2o_to_external("default_shop_id"), "id_shop_default"), ("always_available", "active"), ("barcode", "barcode"), ("additional_shipping_cost", "additional_shipping_cost"), ("minimal_quantity", "minimal_quantity"), ("on_sale", "on_sale"), + ("date_add", "date_add"), + ("barcode", "ean13"), ( - m2o_to_backend( + m2o_to_external( "prestashop_default_category_id", binding="prestashop.product.category" ), "id_category_default", ), + ("state", "state"), + ("low_stock_threshold", "low_stock_threshold"), + ("default_code", "reference"), + ("visibility", "visibility"), ] # handled by base mapping `translatable_fields` _translatable_fields = [ @@ -217,81 +232,89 @@ class ProductTemplateExportMapper(TranslationPrestashopExportMapper): def _get_factor_tax(self, tax): return (1 + tax.amount / 100) if tax.price_include else 1.0 + @changed_by("taxes_id", "list_price") @mapping def list_price(self, record): tax = record.taxes_id + pricelist = record.backend_id.pricelist_id + if pricelist: + prices = pricelist._get_products_price(record.odoo_id, 1.0) + price_to_export = prices.get(record.odoo_id.id) + else: + price_to_export = record.list_price if tax.price_include and tax.amount_type == "percent": # 6 is the rounding precision used by PrestaShop for the # tax excluded price. we can get back a 2 digits tax included # price from the 6 digits rounded value - return { - "price": str(round(record.list_price / self._get_factor_tax(tax), 6)) - } + return {"price": str(round(price_to_export / self._get_factor_tax(tax), 6))} else: - return {"price": str(record.list_price)} + return {"price": str(price_to_export)} + @changed_by("standard_price") @mapping - def reference(self, record): - return {"reference": record.reference or record.default_code or ""} + def cost_price(self, record): + wholesale_price = float(f"{record.standard_price:.2f}") + return {"wholesale_price": wholesale_price} def _get_product_category(self, record): ext_categ_ids = [] binder = self.binder_for("prestashop.product.category") for category in record.categ_ids: - ext_categ_ids.append({"id": binder.to_backend(category.id, wrap=True)}) + ext_categ_ids.append({"id": binder.to_external(category, wrap=True)}) return ext_categ_ids + def _get_product_image(self, record): + ext_image_ids = [] + binder = self.binder_for("prestashop.product.image") + for image in record.image_ids: + ext_image_ids.append({"id": binder.to_external(image, wrap=True)}) + return ext_image_ids + + @changed_by( + "attribute_line_ids", + "categ_ids", + "categ_id", + "image_ids", + ) @mapping def associations(self, record): return { "associations": { "categories": {"category_id": self._get_product_category(record)}, + "images": {"image": self._get_product_image(record)}, } } + @changed_by("taxes_id") @mapping def tax_ids(self, record): if not record.taxes_id: return binder = self.binder_for("prestashop.account.tax.group") - ext_id = binder.to_backend(record.taxes_id[:1].tax_group_id, wrap=True) + ext_id = binder.to_external(record.taxes_id[:1].tax_group_id, wrap=True) return {"id_tax_rules_group": ext_id} + @changed_by("available_date") @mapping def available_date(self, record): if record.available_date: - return {"available_date": record.available_date} + return {"available_date": record.available_date.strftime("%Y-%m-%d")} return {} @mapping def date_add(self, record): # When export a record the date_add in PS is null. - return {"date_add": record.create_date} + return {"date_add": record.create_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)} @mapping def default_image(self, record): default_image = record.image_ids.filtered("front_image")[:1] if default_image: binder = self.binder_for("prestashop.product.image") - ps_image_id = binder.to_backend(default_image, wrap=True) + ps_image_id = binder.to_external(default_image, wrap=True) if ps_image_id: return {"id_default_image": ps_image_id} @mapping - def extras_manufacturer(self, record): - mapper = self.unit_for(ManufacturerExportMapper) - return mapper.map_record(record).values(**self.options) - - -@prestashop -class ManufacturerExportMapper(TranslationPrestashopExportMapper): - # To extend in connector_prestashop_manufacturer module - _model_name = "prestashop.product.template" - - _translatable_fields = [ - ("name", "name"), - ] - - @mapping - def manufacturer(self, record): - return {} + def low_stock_alert(self, record): + return {"low_stock_alert": "1" if record.low_stock_alert else "0"} diff --git a/connector_prestashop_catalog_manager/security/ir.model.access.csv b/connector_prestashop_catalog_manager/security/ir.model.access.csv new file mode 100644 index 000000000..bc98bca0d --- /dev/null +++ b/connector_prestashop_catalog_manager/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_prestashop_product_category_image_user,User access on prestashop.categ.image,model_prestashop_categ_image,base.group_user,1,0,0,0 +access_prestashop_product_category_image_full,Full access on prestashop.categ.image,model_prestashop_categ_image,connector.group_connector_manager,1,1,1,1 +access_prestashop_export_multiple_products,Export access on product template,model_export_multiple_products,connector.group_connector_manager,1,1,1,1 +access_sync_products,access_sync_products,model_sync_products,connector.group_connector_manager,1,1,1,1 +access_active_deactive_products,access_active_deactive_products,model_active_deactive_products,connector.group_connector_manager,1,1,1,1 +access_wiz_prestashop_export_category,access_wiz_prestashop_export_category,model_wiz_prestashop_export_category,connector.group_connector_manager,1,1,1,1 diff --git a/connector_prestashop_catalog_manager/tests/__init__.py b/connector_prestashop_catalog_manager/tests/__init__.py new file mode 100644 index 000000000..130e754a4 --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/__init__.py @@ -0,0 +1,5 @@ +from . import test_export_product_attribute +from . import test_export_product_category +from . import test_export_product_image +from . import test_export_product_product +from . import test_export_product_template diff --git a/connector_prestashop_catalog_manager/tests/common.py b/connector_prestashop_catalog_manager/tests/common.py new file mode 100644 index 000000000..6de40552f --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/common.py @@ -0,0 +1,30 @@ +# © 2018 PlanetaTIC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from os.path import dirname, join +from unittest import mock + +from odoo.addons.connector_prestashop.tests.common import PrestashopTransactionCase + + +class CatalogManagerTransactionCase(PrestashopTransactionCase): + def setUp(self): + super().setUp() + self.sync_metadata() + self.base_mapping() + self.shop_group = self.env["prestashop.shop.group"].search([]) + self.shop = self.env["prestashop.shop"].search([]) + + mock_delay_record = mock.MagicMock() + self.instance_delay_record = mock_delay_record.return_value + self.patch_delay_record = mock.patch( + "odoo.addons.queue_job.models.base.DelayableRecordset", + new=mock_delay_record, + ) + self.patch_delay_record.start() + + self.cassette_library_dir = join(dirname(__file__), "fixtures") + + def tearDown(self): + super().tearDown() + self.patch_delay_record.stop() diff --git a/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_attribute.yaml b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_attribute.yaml new file mode 100644 index 000000000..63f824e6f --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_attribute.yaml @@ -0,0 +1,50 @@ +interactions: + - request: + body: + !!python/unicode New + attribute4New + attributeselect + headers: + Accept: ["*/*"] + Accept-Encoding: ["gzip, deflate"] + Connection: [keep-alive] + Content-Length: ["273"] + Content-Type: [text/xml] + User-Agent: [python-requests/2.11.1] + method: POST + uri: http://localhost:8080/api/product_options + response: + body: + { + string: + !!python/unicode "\n\n\n\t\n\t\n\t\n\t\n\t\n\t\n\n\n\n\n\n", + } + headers: + access-time: ["1544009921"] + connection: [keep-alive] + content-length: ["652"] + content-sha1: [4290e789a9ce1823ce4ac91b063ca96ff36f04ea] + content-type: [text/xml;charset=utf-8] + date: ["Wed, 05 Dec 2018 11:38:41 GMT"] + execution-time: ["0.02"] + psws-version: [1.6.1.22] + server: [nginx/1.10.3 (Ubuntu)] + set-cookie: + [ + "PrestaShop-ac85e42aef73418ac8f31f6687398719=785d98e9bd28108f77b731d15480adab26832755ccb4f95be30db352aeea3ac5%3Ay9dT%2FUvu1KNqFoEFFplK6NS2kY3JMur8CJSWpxVtPnDlJXtgJWHexZZTRsadIcbiwDNh4vUR3%2B5XjBpOfoNRUDLN4zMfjjUQnwJD8t04vsM%3D; + expires=Tue, 25-Dec-2018 11:38:41 GMT; Max-Age=1728000; path=/; HttpOnly", + ] + transfer-encoding: [chunked] + vary: [Accept-Encoding] + x-powered-by: [PrestaShop Webservice] + status: {code: 201, message: Created} +version: 1 diff --git a/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_attribute_value.yaml b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_attribute_value.yaml new file mode 100644 index 000000000..7d91119b7 --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_attribute_value.yaml @@ -0,0 +1,46 @@ +interactions: + - request: + body: + !!python/unicode 11New valueNew + value + headers: + Accept: ["*/*"] + Accept-Encoding: ["gzip, deflate"] + Connection: [keep-alive] + Content-Length: ["271"] + Content-Type: [text/xml] + User-Agent: [python-requests/2.11.1] + method: POST + uri: http://localhost:8080/api/product_option_values + response: + body: + { + string: + !!python/unicode "\n\n\n\t\n\t\n\t\n\t\n\t\n\n\n", + } + headers: + access-time: ["1544009922"] + connection: [keep-alive] + content-length: ["462"] + content-sha1: [680b0fdf6f597b19907a853063d1118004e618e6] + content-type: [text/xml;charset=utf-8] + date: ["Wed, 05 Dec 2018 11:38:42 GMT"] + execution-time: ["0.015"] + psws-version: [1.6.1.22] + server: [nginx/1.10.3 (Ubuntu)] + set-cookie: + [ + "PrestaShop-ac85e42aef73418ac8f31f6687398719=a8a4d05d822236e10fc6b55aa29d11faa3716e1e99c5fb6f90388ddcc869bf44%3Ay9dT%2FUvu1KNqFoEFFplK6MdWNmB2dGM8e7QF71k0oQ%2FPRFEuTpUHkyA1D38AfRaBcdttFxJhxpUDel%2FmoANR4Bx32suWjACFgxh1quGCHB4%3D; + expires=Tue, 25-Dec-2018 11:38:42 GMT; Max-Age=1728000; path=/; HttpOnly", + ] + transfer-encoding: [chunked] + vary: [Accept-Encoding] + x-powered-by: [PrestaShop Webservice] + status: {code: 201, message: Created} +version: 1 diff --git a/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_category.yaml b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_category.yaml new file mode 100644 index 000000000..29183ec08 --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_category.yaml @@ -0,0 +1,69 @@ +interactions: + - request: + body: + !!python/unicode New + category meta descriptionnew-categoryNew + category meta titleNew + category keywordsNew + category2111<p>New category + description</p> + headers: + Accept: ["*/*"] + Accept-Encoding: ["gzip, deflate"] + Connection: [keep-alive] + Content-Length: ["654"] + Content-Type: [text/xml] + User-Agent: [python-requests/2.11.1] + method: POST + uri: http://localhost:8080/api/categories + response: + body: + { + string: + !!python/unicode "\n\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\tNew + category + description

]]>
\n\t\n\t\n\t\n\n\n\n\n
\n
\n", + } + headers: + access-time: ["1544010311"] + connection: [keep-alive] + content-length: ["1614"] + content-sha1: [73b504e1f3e7d44f8511f02cc8b1e8764b3380da] + content-type: [text/xml;charset=utf-8] + date: ["Wed, 05 Dec 2018 11:45:11 GMT"] + execution-time: ["0.044"] + psws-version: [1.6.1.22] + server: [nginx/1.10.3 (Ubuntu)] + set-cookie: + [ + "PrestaShop-ac85e42aef73418ac8f31f6687398719=8a23211feb6747fd92d7117800b697fe81b83282d61b7482c14fd1156239b1ef%3Ay9dT%2FUvu1KNqFoEFFplK6GUHezpItVFXBKxry0vskUErKLdJXddpHQxmwbDxzWLGiJLOUqe2aMh%2Bsa40YPqEWMWyRAoyMLZ3CDMF1J8HvEQ%3D; + expires=Tue, 25-Dec-2018 11:45:11 GMT; Max-Age=1728000; path=/; HttpOnly", + ] + transfer-encoding: [chunked] + vary: [Accept-Encoding] + x-powered-by: [PrestaShop Webservice] + status: {code: 201, message: Created} +version: 1 diff --git a/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_image.yaml b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_image.yaml new file mode 100644 index 000000000..8c8b5f16f --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_image.yaml @@ -0,0 +1,225 @@ +interactions: + - request: + body: !!binary | + LS0tLS0tLS0tLS0tVGhJc19Jc190SGVfYm91TmRhUllfJA0KQ29udGVudC1EaXNwb3NpdGlvbjog + Zm9ybS1kYXRhOyAgICAgICAgICAgICAgICAgICAgIG5hbWU9ImltYWdlIjsgZmlsZW5hbWU9Imlj + b24uLnBuZyINCkNvbnRlbnQtVHlwZTogaW1hZ2UvcG5nDQoNColQTkcNChoKAAAADUlIRFIAAACA + AAAAgAgGAAAAwz5hywAAAARzQklUCAgICHwIZIgAAAAJcEhZcwAADdcAAA3XAUIom3gAAAAZdEVY + dFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAdbklEQVR4nO2deZgU1dW431PV3bOzM4AI + QcUtJERFRRR1QBgIJMbvi5C4gLjkMyZGAdklphP2AcElMWKiIhp/CcR8iRuyyIwQ9YtKXAgoRB0W + GZR9hmG6p7vrnt8fszAzDNBL9Sxh3uepB7q6zrlnuk7duvfce88VWkgKT147L8sOSG/L0m+i0hu0 + J9BJoYNAByACHAEOg+5F+FiQT1R1c6ph3cg1U4obwk5piEJOFZbkzuljYQ0VyFXoB3jjVOUovGsJ + qyPqPH/rqmmfuGlnTVocIEGW5s7PRswojN6GyNeTUYbAmwZ5yoTKnr+1wB90WXcL8bDk23lnWY5O + BUYBvoYoU6EIZK4Jlf3OLUdocYAYWTpobndEZiLcAHgaxwrdhTBp9MopzyeqqcUBomRxn8Xe9HbF + P1HRGUBWY9tTyRsOzo8TaSO0OEAUPDN0fm8x5nmgV2PbUg9lItw9auXkp+MRbnGAk/DskLmjVeW3 + QHpj23JClGcR667RqyYeiUWsxQGOw7IRfl95cfqTit7c2LZEjcq7EU9k+G0rpu2NVqTFAephae78 + DDB/BoY2ti1x8JmxZciYFZM+i+biFgeow9M5i9p4fOE1ivZpbFsS4AvbY/e/6dUJ2092odUQ1jQX + lvVbmGb7Qi8285sPcLoTcdY8f82sTie7sMUBKsnP8XuCmeHlwJWNbYtL9IzYnhcXf9d/wsZriwNU + sjMlfRbC8Ma2w2UuTStPe+REF7S0AYBnh8wdriov4f7vcUTQNxVrPZZuxlhbHStS6rMpJmR3MBad + FXOeqFyg0B/o7XL5AIjoLaNWTlla73fJKLA58fSwvM52RP8FtHdLpwqvC/pUwBf8650v+cuilXtm + 4MKu2KEfCNzu8sDSEduY3jetmfp53S8aKZbddLAjugC3br6w0qi5f8zKqRviEb9l7fhdwEJFFz03 + OO+7CL9UuMAFyzIcy3oUjn3FndI1wLND5+SosdaS+O+wB4u7Rr82+S9u2FXFshHL7GBJ4c9QZgIZ + ieoTketHrZz0Qq1ziSptrigqz+bmvQ98K0FVb4hEbhi18v7dbthVH0uGzO1lqbwAnJugqm2B/W3O + uXPDneGqE6dsL+C5wXnfJeGbry8cstOGJPPmA4xZOWWTE/JdBryVoKoeaR0Ojql54pR1ABXuT0yD + /C219Zk/uGfFPeXuWHRibi0YdwisXIE3E1KkMm1xn8XVU9VOSQdYOmhuf+DS+DXoP5xQ2Q9HLh/p + uGZUFIxeNfFIuXivBUlkjmCPtA4HR1R9OCUdQG25JQHxA7bH8wO35+ZFyx0rxx8wYq4Hou5e1kWR + 26r+f8o5wLJ+C9NEGXHyK+tHYXw0gyzJZMzKKZsUJsYrL8qAPwxb8DU4BR0gkOkMBVrHKb5+9KpJ + 9UbUGprCywOPA+/FKW45kcgNcAo6AJiBcUui0wVRN62JF7/fb8BMjl+DNRiaYRzgiSeeOMcYc4WI + xLXoIu0vh+5BY5/bJ/DmqFWT+8dTZjJZmjv3/0D6xiEaTD3sbdesQsFPP/30ZGAWYNf9TvXkD6aU + KyhxPcGq8vt45JKNiCxWJR4HSA1lhi9vNg7wwgsvDLcsa+7xvo/GAZzdAQLx1Xqh1HDZn+OQSzqh + FP7sDfIYkBqrrCLfaBavAFWVgoKCE4Zto3GAIx/u5eCKbTGXL7Bu1KrJV8cs2EAszZ27GmRQ7JLy + 22ZRA6xfv/4S27a/BSASv8+a4vDJL6oHRRINwSYZWQvE4QB6XqM7wE033dRq586dncrLy1sFg8FW + qtrOGGNERFU1bNt22ZYtW27s3r17QjcfIFQciE9Q2ZJQwUlG0Q8kvvb8aQ3qADk5Oan79u0bVl5e + fqExppfjOOetW7eup+M4J2zRb9u2jezs7ITLjwTiqwHEdrYlXHgSMZZ+bJvYHUAhq0EcoG/fvp0O + HDgwrbCwcGQ4HO4cq7zP5yMQiPPp5ehrwymPzwFQuyTuwhuAjKzyomBxmhJjA1cawgF69er1w927 + dy+IRCJd49URDofjcoC6rwwJOXFVlGJZjRL3j5aRy/2hpblzD4O0ilE0PakO0KtXr7uLi4sfMsYc + 02+vwuPxFNu2vUNESlT1sGVZJYAaY1IBbNuWSCRyWSAQiOkdUF97IVX02ABCFGilLU0bicfGQNIc + 4Pzzzx9VXFz8sDGmVrjZ5/Nt9fl8/2vb9vvt27f/pEuXLv9avnz5CYdVO3bsODcQCEQV9jxRQ9Er + TlwOYNB4xw4ahGX9FqYFCceTpOJwUhzgggsu6LF///6FNW++bdvFrVq1mj5ixIjHKuLY0RMIBE46 + CSKaHkI6Nr44XgKWkbOA/JgFG4hQRqhnXPEtSZIDHD58eLbjOB2qPtu2fbBNmzbXbdy4cd2mTZti + 1rd///416enpB4B29X0fbfewXFJIjyObi4pJSu4ft1Cb8+MJcIvypeujgX369DkvGAx+v+a5rKys + CRs3blwXr85FixYFAoHAwkAgQM0jGAwSDAape/54xxErvtlbgnVVvLY3CCpxRSkVtrheA5SUlNyp + qtWPWVpaWsHmzZufSlRvaWlpnm3b1wAD4g0IHRaIJ7uLohcuzZ2fPXrVxD1xFZxkFHLjkRN0q+sO + EIlEaq2pT01N/bUbep944onwmDFjhlmW9VNVHUg8mbkiTipkxzOkayHmRuChOGSTynOD5l1moGc8 + sgY2ueoAffv27b1z587zqj57vd59HTt2fMUt/UuWLAkCD1YecXFZ7rztQPeYBZU7FH24qUwIqcJY + 3BGnaCSSKn93tQ1w6NChb9b87PV63ygoKGhiQRQtiFOw19Ih865105JEeSr3wW5U5CmMh3duf3Hy + YVcdwHGcujNtNrqp3w1EpCBuWZVf5ef4G30ArQoPkV8QZ5JKEV4Hl+cEGmO61Pzs8Xj+7aZ+N1C1 + XgHiHBSg905f2k/ctCdelubO7wvcGq+8WPYfwf1JobVW2fp8vgMu60+Y0asm7lFhRQIqZj+dO/u8 + k1+WPCqTWD1N/PfvnZtXTNhMAgrqRVVrxaNt24578UIyEeGZBMQzbOw/VtyERkLNb4Hz4xbn6N/v + dg1QK8Srqk1y2nlqVuBlYGcCKr4FZlljtAeeyZ3nR+Ju+AHsFaykOUCtUJvjOE0yu+bI5f4QsCBB + NcN2pKS98HSOv8FGCp8dMm+ywC8S06ILa2YTddUBRKRWhspwOJz4NJ4kkXrY+zvgy0R0iHKt7Ut7 + 9alvz+7okln1smyE37c0N+8xVY47KzpK9odT5Tc1T7jtALXWyUcikdgDLg3EyLfHBxRmuKBqgMex + 3n9u8LxrXNB1DM9cM/ucYHHaetC7EtWlqvff/uLkwzXPueoAtm1vrVNgU8yuXU1a6zMWE//6uhpI + VyOsWZqb94enh87pkbg+WDZobuulufNmim1/REJL2at5p/CK4O/qnnR1XcBll1120Y4dO6oTJPl8 + vs+2bdsWV5y6oVg6eN7FCP9HPauN4iSsynNYPH7LysnvxCpcuRPJrcBPgTZu2WQw/casOjZ5lasO + kJOT4yksLNxV893ftWvXC999990P6rve7/dbe/uvb+tDWofQ4t8Men2/m/ZEy9LcuTNApruvWT4B + fU2UfMcjm9Ize2yrm1Ti/w2ed5oD5xv0KhXJFeiL22s2VSePXj0lr14LXS0I6Nmz55/KyspGVn3O + zMyctnXr1jlVnyfm53R28NyuMASIgHUAtBeQIuiTCsWqFD00YM3/IvGt44uVZSOW2cFD29YgmpPk + osJAKXCQinHpTCAtmQUKrLh51aThxxvEcr2f7vV6V9f8HA6HfwCAIuMLBv0ogu/XinWhHbKuXzRg + zcBFA1Zdv2jA6vMdiyGKfAusAJbuHvfGoMd/uuYa15I3noiRy0c6YoVvrNiUKal4gbbAmUBHknzz + Ff5th+zRJxrBdL0GGDJkSLvNmzd/7jhO9UTK7Ozs7wxc1PEbiPWOUeeqh3LW/Krep1uR8QW5eSoU + mYjzvGXbiyO2/aNHr1oR9QYIiVCZjm0dx5l61szYa6tccdPqSSccj3G9Bli5cuUBn8/315rnThto + PxwJyRpUhzkB79zjVu2CLhywaqKo6ePx2L2NWpM9TmQx2jB5DMasnLJJLes7JJB/p4lwSESGnuzm + Q5IyhGRlZT0kIhGA9PZewqHIWZ+vLhmD6DuPDltx0ol5Hm/6XUb1FyYo2wTdMLYg93vJsLM+bnlt + 4tsiMpSK93Rz5EsRzRm1ctI/o7k4KQ7wwQcffJCenv4ngHOHt+ez1w+S0tbcuXbi3l3RyOf1f/Gw + qjzlSYv8OEjkYRH9UTLsPB6jVk5ab0SvJLHxgsbgM2NL/1Erp3wYrUDSBmvatWs3JTXdu8+XYeOE + lHDQePft3vfqeeed90BOTs5J4+dt9rV+DvR7jw0oKMXIron5OTGvKUyEMSunbNKItx/wRkOWGz/y + Uki8l0a7V1C1VLLMAbh24mUzivcdmVa8M2hlZvv4vKCiVk1JSXkvLS1tfnZ29l8KCgoix5Mft3bw + U4isQs1FKrJdVT+1xOosmNMV6QzSDbQTcDpo60U5a1q73XWsSNi87X5Uf07TzK4eVrh/9KpJC+KZ + r5hUBxibP2jChl8fOC2sgbFlB8Kyb2tF28r2CimtPLTtmrGz47np+d37tdmali22CNkKpwHZQCeg + KzF0lSzCHR8cULAvGX/Lc0PnXGKM9WvcCcu6gsA6R/QnY1ZOiX21zVEd7uNfNsJX0qm4E8bMMVjb + ggfC3y8viZxj+cRKa+PFm56cN4/ChQ8NWF1v1NEN/H6/ddbbabeoMgvoclKB5LFDRH9+88rJzyY6 + SzlhBxhbMPgGUYYIdDbQVSqe3KQOjx4PQb67cMCql5NdzrIRfl95SeoPVa2poA03PUzZLsiiSLhs + sVupahN+p0lFirJbYs5OkAQUPb0hyqmcULLU7/c/d8Zb6cMQvVGU75Gc7WWDCC+h+my3cHDFgAL/ + cdtM8ZCwA6hSlGDqHhfRuJNQxEPlKueXgZeX5fgzA7704aKaA3oVIucT/zNRCJovyFrLa1696ZWp + SYtJJOwAllDUVJbKKNIgNUB9jCzwlwJ/qjx4PmdBh5Av8nXB6iloT0S7gGSgtAF8oGGgGJUysdir + 8G/UbNVIypbKvYMahMS7NWLtQmNa7p80pBEdoC43FkzYB6yrPJosCTfHHSPJHkGLgYZpA/wnkbAD + hCXYYNVVFHRrbAOaGwk7wGMDCkpBD5/8ygYhY2x+jlvTqE4JXIrISJOpBQy+ltdADLjjAJL0mTRR + Y+O0OEAMuOMA2nQcQLFaHCAG3ArKN5lXgNWEuoLNAbccIKk7Z8ZGw0YDmzuuOIA2oUZgY0YDmyOu + OIBlmlIboCUYFAuuOICxrCbjAAItDhADrjhAm71ZRdAwq3iioM1P8nMyG9uI5oIrDuAfuTwEJGUq + Vjx4NbWlIRglbs7Nivk1IAi9OvRjSI9RnN/uEtcMaQkGRY+bs1yLOMG2bvVx89encmF2DiYUwerh + YUXhElZvfz5hQ1RaegLR4loNIDEGg07POpsLs3PYtuR13r1lEXsLNtK/qzuJOAXT4gBR4poDaIzj + Ae1TKybVHvhHxY5sBzd8RpavHVm+xNdlGrVa2gBR4l4NEON4QMhUTGoVT0VijkBRRW6ILhk9ErdF + WmIB0eJiDRDbzKB9gYrL07q0BaD8q0NoxKFzxtfcMKfFAaLENQeINRp4IPAlRh1SO1dU+eoYgl8d + anGABsY9B5BQTI1ARyMcDH5FamUNABDYtZ/O6T3olnUO3znzdnK6XU+aJ5M0TyaXdB5M745XYltR + dVw6+PNPvgC1BRe7gRlv5OwpvvrNSCw69wZ20blL9d5SBHYdoPsllzC2z6OUf1WMt2M6/bteS7on + i1Q7o6KrUfopb+56qcIZxGbV9j/w2aGP6qqWI8bqCsS0UvZUxLUaoHKRREyZN/cFikg77WgNECza + jyU2xe8X8uG437FxyhLaSHvkYIR//vg3bJzyDJ3t7ow8dxw9QmfRwzqHH/eey9daHZs3OSJ2y2sg + CtxdpRljV3BfoAhfh9ZY3sqewK6KnsD+tz4GhfI9xRR/tI1DHxQSLimjbPse9qz9CBQ2TnmGD+55 + grJPv+L6c352jO6WYFB0uJsqVjUmB9gb2IVYQkqniom8gaIDoBAurs5lTPDLg6BHx5kOb/kCBMQS + TCjCrj+/RdfMnnTLOqeWbqtlWDgqXE54cPyJIT1afZ3rzr6Ljmld2R/YzauFS6q7gqmd2xH4Yj+m + PMz+f2whXHJ0o+gvV2xAvEeTeJYVfsWe1z9ErArfLdm8AycQ4vz2l7LzcI1MtSotwaAocNkBdHd9 + 6yG7ZvbkrgvmEfz3XvZu2ECrXt25o/cMVhQuqegK1ugJfPrwi7VkQwdLa30u31dC4e9XHS3RMZR+ + upuuXWpnpNWWYFBUNEgNcEH2VUhA+WT2ckwowu6X3+H0EVcy/LrbQCCtS2Lh3+CXB2ndo05OSaGl + BogCd7eMOU4wqLB4E1a6jzN+NASxBBS+WLaezx5/FROuXQPEQ7j4CB3STtsnwkgLvRJHz2qt4SsS + UnqK4GoN4FhSZNczMWjz/n+wZNOvGHX5VM403+azx1eAKvvWbcLbOoMuwy9OrGBjSPdkHVqYs3p5 + YopOPVytAdI93uM1AoP/2v/W50s3zf5Xm/7nOD1uPbq3QvhgKd7WGdjpKfEXXJGh4sjJLmvhWFyt + AeZe+crB8WsH34ewR5WdlshXEmLngiGrKm/Oam7XXw7qNPjCl8LFZam7XniLI9v28NXq97FTvThl + 8e3u7WuXBfCVa3/IKUSjJHdR1euAF7Y9tdr6yoWkXudNHUHr3j2eFZHRiVt3atEo27qJyF+ByT1u + HUT7y85NWF9a1/YAnyas6BSk0fb1E5EFiDx+5k+G06pX/HtL2ekpVa+AT1wz7hSisTd2vMfy2mvP + HnstKR1axaUgrUu7qhdZk9unuDnQqA4gImHgek9m2qdnj/1e9aBQLHjbVu/g2twyezcJGrsGQEQO + AiMzzuoc7D5qYMzynqw0qNiytlE2nGruNLoDAIjI+8B9nQZfQIervhGTrO3zAgRFEsuZe6rSJBwA + QEQeA5afcdsgUjudOM+T2BapndrQ5ltnkHluV2iJAcRNk0nyCqCqrYENpf8uOmuz/3k8rdJJ79aB + lOw2pGS3JjW7DSmd2pDWtT2WrzqGVQT8tLJr2UKMNCkHAFDVC4ACNdparGrzSqlo5VcdW6sOETnQ + KIb+h9DkHABAVXsCVwA7qLjJTSYDyX8asmzZMntzYWEnT9grkUjJ7srJnbXwz5+fnRKJpGdkZOy+ + 5557agXsH3nkkZTS0tJanfiQz6f+CRNqLRf3L1jQwReiQyhU+rnf7w/5/f50n8+XQT307NnzwMiR + FVus+v2PtALH8vvHHTreHzH+jdxuGjbeLw603b585HIHYPLqQa0lNcWae+Ur1Zm2/+e9Pt6Usuw2 + 7b/MLK5c0l7NvasHdbc8kk1K6aZFl78dqPmd3++3Dg18u5PjWHb7N/oW1fcbNVesrZ9uK/E4sgsr + 8oXHl142c07eizNnPlidcnXm7PmvecL6laN2YUlpsGzm7LyPZs06ulV6yZHgdQbPnpqHJ2S+qPp+ + 9uwFX581O+8dT8jsNZiPPb70ooULF6Z5fOnj68pVHZ98/vk3AFRVPL5goccXPjhr1qxOdY0flz9o + 0Lj8wYVqdAe2fHZ6x+L1Vd+Ve+Tl8khofc3rs0rb53icyJ5DHQ5+p+qcPz8ndXz+oOcsj2wD3qU8 + c/v4giEDasoVX/3mPnFMkYfIzuKr3ywblz/41bH5Q3sk9Ms3ESwgXZA/G7EuRnQ8KgOwnJf9fr8F + oGg6whZjmW9g5DoEn4q8NGfOnDNrKhLRaRi5FiPXKvw3wPz58zMM5iUV2iBcryLXqDJj/PjxAQvr + L6Jyg6jcAAQU3qz6bIIp2wFm5uWdR8UO2kEjnr41yxu3ZtCZiPwN2G5Uh6rIMFHzeKw/QDHeuYqM + VJU7UHIQ9qqa5ffl53SocVk6yN8slYsF7gXtLziv+vNzmuImUjHhAVBM0QNTJ28ANsyaMy9LVeZ6 + PGkXAe8BoFr2wNQpm4BNs2fnHTDwd2Os/wIerFJksN7++fSJBTWVl0fIBc5ErKunT5lQlTZ9LcC0 + aRM2A5sBZs7OewLYfv/9E/9YyzrH6g/mU0GKQfoB1RMGxbZGq6qPcuu/Hx66Mq6G4Nh1Q7pgzF0o + jz00cNVTAGPXDv4fEf5u8NwBzD16te5+cODqDcCGcQW56aguLDG+S4G34im7qXBMHEBU/g4gYp1z + 7OUQCpVV7EEvcsbJlKvR7gAaNnE14kT0cpAPVdmo0K+WbtXuoIFFcd58AMs430HxKVrdhXxo4Oo3 + gb3AsOPJGZy/VyjQen+j5sQxDuAIWQBGtKQ+gdTU1GwARE4afLHQjwAsj14fp339RGQjIhsFLvb7 + /dVVroh+BJJ179pBQ+LUjapchGJ83vQNNc8L/BOss48nJ45d8Rtp/b9Rc6K+GmA4EHFsrXemRlit + e4CIqNSafydox5kzH+w2c+aD3WbPnt0RYNq0SQXAWlWZNWNW3rHLd06Af8GCDijnGNUNFvo+kOH1 + pveu+j5I5PfAdkvkT+PXDj7O06qe8W/kdqs6jDF1djOTHliU5vV/sVa6e1WKQDtUtYOOwdbhgCOG + pG1R11BUtgGkw6/mLOgjxvkv0B+DPO6fPKm6JS9Ip5mz86YDV6BcBXrbtGkTao+/K8uwHAAMnnxg + oIjonDlzrnfUfk2ER2bMybu0dUbqHXW7kvUaFtErAHG88p5z5EiZx5duTMVr4J9QsU/BvWuHDrHE + Wa3Cy+Pycx9YlLNqVu2dQ+VcNbrj6MfaYQ9FM0U59ikWSgDP4asL2nE0+1n7sa8PuUhs8z2UnyH6 + 5KJBaz4/2d/R1LEABG601LwnIj8F8iKhI+NqXqQVLfERwNXA5ohNfl1FqtyNMVdgzBXGMtVP+9Sp + Uw+2yky9CvRJUW4uPhL8fVSWGb1cYYd/4sQ9fr+/FGULFpfVvOThga9tsQhfBFoAOmNswaD76mjZ + KcLIqgN0Zq0/XjDAsWPQol4AU55Ss78/QiyzAeVeERYdyTx4d1R/RxOn8p0qz3gs54G2bdvuvvPO + O8PHXqZbpk+bfNHMmQu+hpj3PIYnqNtIsmTT9KlT6m0RVz7xd8yYk5ciys0zZ857dPr0ye+cyDCF + fhZ4Zs7KW1xhIumitRuCAA8OKNjnz88ZVox3vYj8csr64U/WCP6U1Jwqfl9B7iFTY52hqpaAHDsT + xUgWYI502Hf01SDyvOVEph1uXVz0xMUb6vmNmieV7zhTPGXKlB313/yjTJ8+YTvC71EZMmfOnJhX + c9hqz6goVU448O/3+30CFyvsQWiL0JaKlvlZ/vnzs4+5fkBBENX5KOlBJ3zZsRrrR9QqBDLuXXNN + 7SCTxVkIO2vdaNWSB69Zu/0/6eZDHMPBCusByxiJeRPlkE8PAKjqCbN3+HwZfYA0hfunT5s0cvq0 + SSOxrPsA7LCp/wYLByr+ObHumhjhHwC2ZVUHmfz5OR6U3sC70eppzsTsAGI8H1ZIykWxytoh/X5F + ofr2ia5TNZcD6vis6tdEJFj6HhCRioBQfZZ9H3Ai6on6xqV6vK8CR4zF7VXnDol3BJApwrJo9TRn + Yg5lTp8+ftfM2Xl71UifWl+ouWvGnLzqGLtXzCMRY1+vmL6WWP9S5WzQG4E106ZNXnX//VOOX4hY + lyP6ac0BJb/fXzZz9ryNWukAY/MHPy7gUWF75f7Fw1VY+GjOii+Oq7cOc6985eDYgsHzRPnVuILc + Pyj6iSj3gby9c0/rv0T9ozRjLOBzkOPOpxMoOmbVr8pyEXr4/X5LoRjlY1H5phiGVR0haI3oTgs5 + Q1XvBu0loj+PhMqG152+JfCJQPWNM2gnVFfUtUXVek2gvd/vt1A+By4VuBtoI6q3PHT16gk1dG5T + qZ0jyMEpBT4WOdr1a1NwxSwVHkD1GlGdCLwUcpzvVo0qVlII2mSSYbvJ/wf4lzkq0/tU8wAAAABJ + RU5ErkJggg0KLS0tLS0tLS0tLS0tVGhJc19Jc190SGVfYm91TmRhUllfJC0tDQo= + headers: + Accept: ["*/*"] + Accept-Encoding: ["gzip, deflate"] + Connection: [keep-alive] + Content-Length: ["7856"] + Content-Type: [multipart/form-data; boundary=----------ThIs_Is_tHe_bouNdaRY_$] + User-Agent: [python-requests/2.11.1] + method: POST + uri: http://localhost:8080/api/images/products/1 + response: + body: {string: !!python/unicode ' + + + + + + + + + + + + + + + + + + + + + + '} + headers: + access-time: ["1544010333"] + connection: [keep-alive] + content-length: ["6609"] + content-sha1: [3a308e3ae28e34ab2eef6c8bedeacd54ba5250f3] + content-type: [text/xml;charset=utf-8] + date: ["Wed, 05 Dec 2018 11:45:33 GMT"] + execution-time: ["0.104"] + psws-version: [1.6.1.22] + server: [nginx/1.10.3 (Ubuntu)] + set-cookie: + [ + "PrestaShop-ac85e42aef73418ac8f31f6687398719=955af223f6180571e5e5e318e1a81079ca7d43406aa0e6cdc920b78d05165d99%3Ay9dT%2FUvu1KNqFoEFFplK6KgmFvX9MCUWzxfVdGR2sctbyzMZQ%2FuHKRHmpCgQ6rfMv8tpY%2FD%2BCaCDSw627GwpbrPaMx1unqas4i0VMPWUblE%3D; + expires=Tue, 25-Dec-2018 11:45:33 GMT; Max-Age=1728000; path=/; HttpOnly", + ] + transfer-encoding: [chunked] + vary: [Accept-Encoding] + x-powered-by: [PrestaShop Webservice] + status: {code: 200, message: OK} + - request: + body: null + headers: + Accept: ["*/*"] + Accept-Encoding: ["gzip, deflate"] + Connection: [keep-alive] + Content-Length: ["0"] + User-Agent: [python-requests/2.11.1] + method: DELETE + uri: http://localhost:8080/api/images/products/1/68 + response: + body: {string: !!python/unicode ""} + headers: + access-time: ["1544010333"] + connection: [keep-alive] + content-length: ["0"] + date: ["Wed, 05 Dec 2018 11:45:33 GMT"] + execution-time: ["0.01"] + psws-version: [1.6.1.22] + server: [nginx/1.10.3 (Ubuntu)] + set-cookie: + [ + "PrestaShop-ac85e42aef73418ac8f31f6687398719=955af223f6180571e5e5e318e1a81079ca7d43406aa0e6cdc920b78d05165d99%3Ay9dT%2FUvu1KNqFoEFFplK6KgmFvX9MCUWzxfVdGR2sctbyzMZQ%2FuHKRHmpCgQ6rfMv8tpY%2FD%2BCaCDSw627GwpbrPaMx1unqas4i0VMPWUblE%3D; + expires=Tue, 25-Dec-2018 11:45:33 GMT; Max-Age=1728000; path=/; HttpOnly", + ] + transfer-encoding: [chunked] + vary: [Accept-Encoding] + x-powered-by: [PrestaShop Webservice] + status: {code: 200, message: OK} +version: 1 diff --git a/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_product.yaml b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_product.yaml new file mode 100644 index 000000000..42bfa2ddc --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_product.yaml @@ -0,0 +1,77 @@ +interactions: + - request: + body: + !!python/unicode 4138411788010150demo_3_OS0.10110.020.023 + headers: + Accept: ["*/*"] + Accept-Encoding: ["gzip, deflate"] + Connection: [keep-alive] + Content-Length: ["532"] + Content-Type: [text/xml] + User-Agent: [python-requests/2.11.1] + method: POST + uri: http://localhost:8080/api/combinations + response: + body: + { + string: + !!python/unicode "\n\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\n\n\t\n\t\n\t\n\t\n\t\n\t\n\n\n\n\n\n", + } + headers: + access-time: ["1544010356"] + connection: [keep-alive] + content-length: ["1200"] + content-sha1: [7a46cf797b3653f5c063bf16e0a898a08fc411cb] + content-type: [text/xml;charset=utf-8] + date: ["Wed, 05 Dec 2018 11:45:56 GMT"] + execution-time: ["0.026"] + psws-version: [1.6.1.22] + server: [nginx/1.10.3 (Ubuntu)] + set-cookie: + [ + "PrestaShop-ac85e42aef73418ac8f31f6687398719=a6ba7c94454759752a139fc6fdae9b327c99c9b1c42f846b56f69caf29139e71%3Ay9dT%2FUvu1KNqFoEFFplK6Otra9jGVczMTxQ0zVLeniD4jwMTT5sp%2F2lnJQCK1O3fW7QmPITjvETXHqyribPUUOy6ImdWABG%2F43xhrzi%2BccQ%3D; + expires=Tue, 25-Dec-2018 11:45:56 GMT; Max-Age=1728000; path=/; HttpOnly", + ] + transfer-encoding: [chunked] + vary: [Accept-Encoding] + x-powered-by: [PrestaShop Webservice] + status: {code: 201, message: Created} + - request: + body: null + headers: + Accept: ["*/*"] + Accept-Encoding: ["gzip, deflate"] + Connection: [keep-alive] + Content-Length: ["0"] + User-Agent: [python-requests/2.11.1] + method: DELETE + uri: http://localhost:8080/api/combinations/60 + response: + body: {string: !!python/unicode ""} + headers: + access-time: ["1544010356"] + connection: [keep-alive] + content-length: ["0"] + date: ["Wed, 05 Dec 2018 11:45:56 GMT"] + execution-time: ["0.018"] + psws-version: [1.6.1.22] + server: [nginx/1.10.3 (Ubuntu)] + set-cookie: + [ + "PrestaShop-ac85e42aef73418ac8f31f6687398719=a6ba7c94454759752a139fc6fdae9b327c99c9b1c42f846b56f69caf29139e71%3Ay9dT%2FUvu1KNqFoEFFplK6Otra9jGVczMTxQ0zVLeniD4jwMTT5sp%2F2lnJQCK1O3fW7QmPITjvETXHqyribPUUOy6ImdWABG%2F43xhrzi%2BccQ%3D; + expires=Tue, 25-Dec-2018 11:45:56 GMT; Max-Age=1728000; path=/; HttpOnly", + ] + transfer-encoding: [chunked] + vary: [Accept-Encoding] + x-powered-by: [PrestaShop Webservice] + status: {code: 200, message: OK} +version: 1 diff --git a/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_template.yaml b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_template.yaml new file mode 100644 index 000000000..eb9b33fcb --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/fixtures/test_export_product_template.yaml @@ -0,0 +1,110 @@ +interactions: + - request: + body: + !!python/unicode 2020-02-06 + 16:57:248411788010150New + product meta + title2new-product<p>New product + description</p>1110.020.084117880101505New product meta + keywords2016-08-2911NEW_PRODUCTNew productNew + product meta + description01.00.11111New product + tags2345New product available + now<p>New + product description + short</p>New product available + later + headers: + Accept: ["*/*"] + Accept-Encoding: ["gzip, deflate"] + Connection: [keep-alive] + Content-Length: ["1845"] + Content-Type: [text/xml] + User-Agent: [python-requests/2.11.1] + method: POST + uri: http://localhost:8080/api/products + response: + body: + { + string: + !!python/unicode "\n\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\tNew product + description

]]>
\n\tNew product + description + short

]]>
\n\t\n\t\n\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\n\n\n\n\n\n\n\t\n\t\n\t\n\t\n\n\n\n\n
\n
\n", + } + headers: + access-time: ["1581008244"] + connection: [keep-alive] + content-length: ["5218"] + content-sha1: [c70fd4a619a4a4bf71229b57e465b3aace21f256] + content-type: [text/xml;charset=utf-8] + date: ["Thu, 06 Feb 2020 16:57:24 GMT"] + execution-time: ["0.051"] + psws-version: [1.6.1.22] + server: [nginx/1.10.3 (Ubuntu)] + set-cookie: + [ + "PrestaShop-6afe1be0fe41e536d378c33ccb8576a7=99ef58a58fe325ebc994812ea04fec000e7d9e4532b86e166abaa438d78e6711%3AERwbwgjnbPhegaRWNyAjEDGs9Ou1AWI7j6aOPwsre8LNHD2QLBTUTdzjB3g7IWQeicISDrhEhOu4WTKS9MpUFEgN1N3HM7BH145oU1%2FGeuo%3D; + expires=Wed, 26-Feb-2020 16:57:24 GMT; Max-Age=1728000; path=/; + domain=localhost:8080; HttpOnly", + ] + transfer-encoding: [chunked] + vary: [Accept-Encoding] + x-powered-by: [PrestaShop Webservice] + status: {code: 201, message: Created} +version: 1 diff --git a/connector_prestashop_catalog_manager/tests/test_export_product_attribute.py b/connector_prestashop_catalog_manager/tests/test_export_product_attribute.py new file mode 100644 index 000000000..6726aa9ce --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/test_export_product_attribute.py @@ -0,0 +1,192 @@ +# © 2018 PlanetaTIC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from odoo.tests import tagged + +from odoo.addons.connector_prestashop.tests.common import ( + assert_no_job_delayed, + recorder, +) + +from .common import CatalogManagerTransactionCase + + +@tagged("post_install", "-at_install") +class TestExportProductAttribute(CatalogManagerTransactionCase): + def setUp(self): + super().setUp() + + # create and bind attribute + attribute_size = self.env["product.attribute"].create( + { + "name": "Size", + } + ) + self.create_binding_no_export( + "prestashop.product.combination.option", attribute_size.id, 1 + ) + + # create attribute and value + self.attribute = self.env["product.attribute"].create( + { + "name": "New attribute", + } + ) + self.value = self.env["product.attribute.value"].create( + { + "attribute_id": attribute_size.id, + "name": "New value", + } + ) + + def _bind_attribute(self): + return self.create_binding_no_export( + "prestashop.product.combination.option", self.attribute.id, 4 + ).with_context(connector_no_export=False) + + def _bind_value(self): + return self.create_binding_no_export( + "prestashop.product.combination.option.value", self.value.id, 25 + ).with_context(connector_no_export=False) + + @assert_no_job_delayed + def test_01_export_product_attribute_onbind(self): + # create attribute binding + self.env["prestashop.product.combination.option"].create( + { + "backend_id": self.backend_record.id, + "odoo_id": self.attribute.id, + } + ) + # check export delayed + self.assertEqual(1, self.instance_delay_record.export_record.call_count) + + @assert_no_job_delayed + def test_02_export_product_attribute_value_onbind(self): + # bind attribute + self._bind_attribute() + # create value binding + self.env["prestashop.product.combination.option.value"].create( + { + "backend_id": self.backend_record.id, + "odoo_id": self.value.id, + } + ) + # check export delayed + self.assertEqual(1, self.instance_delay_record.export_record.call_count) + + @assert_no_job_delayed + def test_03_export_product_attribute_onwrite(self): + # bind attribute + self._bind_attribute() + # check no export delayed + self.assertEqual(0, self.instance_delay_record.export_record.call_count) + # write in value + self.attribute.name = "New attribute updated" + # check export delayed + self.assertEqual(1, self.instance_delay_record.export_record.call_count) + # write in binding + # binding.display_type = "radio" --> This triggered below 2 events + # attribute.event.listener.on_record_write calling export_record + # prestashop.attribute.event.listener.on_record_write calling export_record + self.attribute.display_type = "radio" + # check export delayed again + self.assertEqual(2, self.instance_delay_record.export_record.call_count) + + @assert_no_job_delayed + def test_04_export_product_attribute_value_onwrite(self): + # bind attribute and value + self._bind_attribute() + binding = self._bind_value() + # check no export delayed + self.assertEqual(0, self.instance_delay_record.export_record.call_count) + # write in value + self.value.name = "New value updated" + # check export delayed + self.assertEqual(1, self.instance_delay_record.export_record.call_count) + # write in binding + binding.prestashop_position = 2 + # check export delayed again + self.assertEqual(2, self.instance_delay_record.export_record.call_count) + + @assert_no_job_delayed + def test_05_export_product_attribute_job(self): + # create attribute binding + binding = self.env["prestashop.product.combination.option"].create( + { + "backend_id": self.backend_record.id, + "odoo_id": self.attribute.id, + "group_type": "select", + "prestashop_position": 4, + } + ) + # export attribute + with recorder.use_cassette( + "test_export_product_attribute", + cassette_library_dir=self.cassette_library_dir, + ) as cassette: + binding.export_record() + + # check request + self.assertEqual(1, len(cassette.requests)) + request = cassette.requests[0] + self.assertEqual("POST", request.method) + self.assertEqual("/api/product_options", self.parse_path(request.uri)) + self.assertDictEqual({}, self.parse_qs(request.uri)) + body = self.xmltodict(request.body) + ps_option = body["prestashop"]["product_options"] + # check basic fields + for field, value in list( + { + "group_type": "select", + "position": "4", + }.items() + ): + self.assertEqual(value, ps_option[field]) + # check translatable fields + for field, value in list( + { + "name": "New attribute", + "public_name": "New attribute", + }.items() + ): + self.assertEqual(value, ps_option[field]["language"]["value"]) + + @assert_no_job_delayed + def test_06_export_product_attribute_value_job(self): + # create value binding + binding = self.env["prestashop.product.combination.option.value"].create( + { + "backend_id": self.backend_record.id, + "odoo_id": self.value.id, + } + ) + # export value + with recorder.use_cassette( + "test_export_product_attribute_value", + cassette_library_dir=self.cassette_library_dir, + ) as cassette: + binding.export_record() + + # check request + self.assertEqual(1, len(cassette.requests)) + request = cassette.requests[0] + self.assertEqual("POST", request.method) + self.assertEqual("/api/product_option_values", self.parse_path(request.uri)) + self.assertDictEqual({}, self.parse_qs(request.uri)) + body = self.xmltodict(request.body) + ps_option = body["prestashop"]["product_option_value"] + # check basic fields + for field, value in list( + { + "id_attribute_group": "1", + "value": "New value", + }.items() + ): + self.assertEqual(value, ps_option[field]) + # check translatable fields + for field, value in list( + { + "name": "New value", + }.items() + ): + self.assertEqual(value, ps_option[field]["language"]["value"]) diff --git a/connector_prestashop_catalog_manager/tests/test_export_product_category.py b/connector_prestashop_catalog_manager/tests/test_export_product_category.py new file mode 100644 index 000000000..dcb8de17a --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/test_export_product_category.py @@ -0,0 +1,126 @@ +# © 2018 PlanetaTIC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from odoo.tests import tagged + +from odoo.addons.connector_prestashop.tests.common import ( + assert_no_job_delayed, + recorder, +) + +from ..models.product_template.exporter import get_slug +from .common import CatalogManagerTransactionCase + + +@tagged("post_install", "-at_install") +class TestExportProductCategory(CatalogManagerTransactionCase): + def setUp(self): + super().setUp() + + # create and bind parent + parent = self.env["product.category"].create({"name": "Home"}) + self.create_binding_no_export("prestashop.product.category", parent.id, 2) + + # Create a product category to export: + self.category = self.env["product.category"].create( + { + "name": "New category", + "parent_id": parent.id, + } + ) + + def _bind_category(self): + return self.create_binding_no_export( + "prestashop.product.category", self.category.id, 12 + ).with_context(connector_no_export=False) + + @assert_no_job_delayed + def test_01_export_product_category_wizard(self): + # export from wizard + wizard = ( + self.env["wiz.prestashop.export.category"] + .with_context(active_ids=[self.category.id]) + .create({}) + ) + wizard.export_categories() + + # check binding created + bindings = self.env["prestashop.product.category"].search( + [("odoo_id", "=", self.category.id)] + ) + self.assertEqual(1, len(bindings)) + # check export delayed + # sequence of fields is from ./wizards/export_category.py + # > def export_categories + self.instance_delay_record.export_record.assert_called_once_with( + fields=["backend_id", "default_shop_id", "link_rewrite", "odoo_id"] + ) + + @assert_no_job_delayed + def test_02_export_product_category_onwrite(self): + # bind category + binding = self._bind_category() + # check no export delayed + self.assertEqual(0, self.instance_delay_record.export_record.call_count) + + # write in category + self.category.name = "New category updated" + # check export delayed + self.assertEqual(1, self.instance_delay_record.export_record.call_count) + # write in binding + binding.description = "New category description updated" + # check export delayed again + self.assertEqual(2, self.instance_delay_record.export_record.call_count) + + @assert_no_job_delayed + def test_03_export_product_category_job(self): + # create binding + binding = self.env["prestashop.product.category"].create( + { + "backend_id": self.backend_record.id, + "odoo_id": self.category.id, + "default_shop_id": self.shop.id, + "description": "New category description", + "link_rewrite": get_slug(self.category.name), + "meta_description": "New category meta description", + "meta_keywords": "New category keywords", + "meta_title": "New category meta title", + "position": 1, + } + ) + # export category + with recorder.use_cassette( + "test_export_product_category", + cassette_library_dir=self.cassette_library_dir, + ) as cassette: + binding.export_record() + + # check request + self.assertEqual(1, len(cassette.requests)) + request = cassette.requests[0] + self.assertEqual("POST", request.method) + self.assertEqual("/api/categories", self.parse_path(request.uri)) + self.assertDictEqual({}, self.parse_qs(request.uri)) + body = self.xmltodict(request.body) + ps_category = body["prestashop"]["category"] + # check basic fields + for field, value in list( + { + "active": "1", + "id_parent": "2", + "id_shop_default": "1", + "position": "1", + }.items() + ): + self.assertEqual(value, ps_category[field]) + # check translatable fields + for field, value in list( + { + "description": "

New category description

", + "link_rewrite": "new-category", + "meta_description": "New category meta description", + "meta_keywords": "New category keywords", + "meta_title": "New category meta title", + "name": "New category", + }.items() + ): + self.assertEqual(value, ps_category[field]["language"]["value"]) diff --git a/connector_prestashop_catalog_manager/tests/test_export_product_image.py b/connector_prestashop_catalog_manager/tests/test_export_product_image.py new file mode 100644 index 000000000..94683c4e9 --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/test_export_product_image.py @@ -0,0 +1,137 @@ +# © 2018 PlanetaTIC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from odoo.tests import tagged + +from odoo.addons.connector_prestashop.tests.common import ( + assert_no_job_delayed, + recorder, +) + +from .common import CatalogManagerTransactionCase + + +@tagged("post_install", "-at_install") +class TestExportProductImage(CatalogManagerTransactionCase): + def setUp(self): + super().setUp() + + # create and bind template + template = self.env["product.template"].create( + { + "name": "Faded Short Sleeves T-shirt", + } + ) + self.create_binding_no_export( + "prestashop.product.template", + template.id, + 1, + **{ + "default_shop_id": self.shop.id, + "link_rewrite": "faded-short-sleaves-t-shirt", + } + ) + self.transparent_image = ( # 1x1 Transparent GIF + b"R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" + ) + self.grey_image = ( # 1x1 Grey GIF + b"R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw ==" + ) + # create image and binding + self.image = self.env["base_multi_image.image"].create( + { + "owner_id": template.id, + "owner_model": "product.template", + "image_1920": self.transparent_image, + } + ) + self.image._onchange_load_from() + self.binding = self.create_binding_no_export( + "prestashop.product.image", self.image.id, None + ) + + @assert_no_job_delayed + def test_01_export_product_image_onwrite(self): + # write in image + self.image.write( + { + "image_1920": self.grey_image, + } + ) + # check export delayed + self.instance_delay_record.export_record.assert_called_once_with( + fields=[ + "load_from", + ] + ) + + @assert_no_job_delayed + def test_02_export_product_image_ondelete(self): + # bind image + self.binding.prestashop_id = 24 + + # delete image + self.image.unlink() + # check export delete delayed + self.instance_delay_record.export_delete_record.assert_called_once_with( + self.backend_record, 24, {"id_product": 1} + ) + + @assert_no_job_delayed + def test_03_export_product_image_jobs(self): + with recorder.use_cassette( + "test_export_product_image", cassette_library_dir=self.cassette_library_dir + ) as cassette: + + # create image in PS + self.binding.export_record() + + # check POST request + request = cassette.requests[0] + self.assertEqual("POST", request.method) + self.assertEqual("/api/images/products/1", self.parse_path(request.uri)) + self.assertDictEqual({}, self.parse_qs(request.uri)) + + # VCR.py does not support urllib v1 request in + # OCA/server-tools/base_multi_image/models/image.py: + # to get image from URL so... + + # ...update test is avoided + # update image in PS + # prestashop_id = self.binding.prestashop_id + # self.binding.export_record() + # + # # check DELETE requests + # request = cassette.requests[1] + # self.assertEqual('DELETE', request.method) + # self.assertEqual( + # '/api/images/products/1/%s' % prestashop_id, + # self.parse_path(request.uri)) + # self.assertDictEqual({}, self.parse_qs(request.uri)) + # + # # check POST request + # request = cassette.requests[2] + # self.assertEqual('POST', request.method) + # self.assertEqual('/api/images/products/1', + # self.parse_path(request.uri)) + # self.assertDictEqual({}, self.parse_qs(request.uri)) + + # ...and delete test is hacked + + self.image.write({"image_1920": self.grey_image}) + + # delete image in PS + attributes = {"id_product": 1} + self.env["prestashop.product.image"].export_delete_record( + self.backend_record, + self.binding.prestashop_id, + attributes, + ) + + # check DELETE requests + request = cassette.requests[1] + self.assertEqual("DELETE", request.method) + self.assertEqual( + "/api/images/products/1/%s" % self.binding.prestashop_id, + self.parse_path(request.uri), + ) + self.assertDictEqual({}, self.parse_qs(request.uri)) diff --git a/connector_prestashop_catalog_manager/tests/test_export_product_product.py b/connector_prestashop_catalog_manager/tests/test_export_product_product.py new file mode 100644 index 000000000..553a7483d --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/test_export_product_product.py @@ -0,0 +1,231 @@ +# © 2018 PlanetaTIC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from unittest import mock + +from odoo.tests import tagged + +from odoo.addons.connector_prestashop.tests.common import ( + assert_no_job_delayed, + recorder, +) + +from .common import CatalogManagerTransactionCase + + +@tagged("post_install", "-at_install") +class TestExportProductProduct(CatalogManagerTransactionCase): + def setUp(self): + super().setUp() + + # create and bind color attribute + color_attribute = self.env["product.attribute"].create( + { + "name": "Color", + } + ) + self.create_binding_no_export( + "prestashop.product.combination.option", color_attribute.id, 3 + ) + + # create and bind color value + color_value = self.env["product.attribute.value"].create( + { + "attribute_id": color_attribute.id, + "name": "Orange", + } + ) + self.create_binding_no_export( + "prestashop.product.combination.option.value", color_value.id, 13 + ) + + # create and bind size attribute + size_attribute = self.env["product.attribute"].create( + { + "name": "Size", + } + ) + self.create_binding_no_export( + "prestashop.product.combination.option", size_attribute.id, 1 + ) + + # create and bind size value + size_value = self.env["product.attribute.value"].create( + { + "attribute_id": size_attribute.id, + "name": "One size", + } + ) + self.create_binding_no_export( + "prestashop.product.combination.option.value", size_value.id, 4 + ) + + # create and bind template + template = self.env["product.template"].create( + { + "name": "Printed Dress", + } + ) + self.main_template_id = self.create_binding_no_export( + "prestashop.product.template", + template.id, + 3, + **{ + "default_shop_id": self.shop.id, + "link_rewrite": "printed-dress", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": color_attribute.id, + "value_ids": [(6, 0, [color_value.id])], + }, + ), + ( + 0, + 0, + { + "attribute_id": size_attribute.id, + "value_ids": [(6, 0, [size_value.id])], + }, + ), + ], + }, + ) + + # update product + self.product = template.product_variant_ids[0] + self.product.write( + { + "barcode": "8411788010150", + "default_code": "demo_3_OS", + "default_on": False, + "impact_price": 20.0, + "product_tmpl_id": template.id, + "standard_price": 10.0, + "weight": 0.1, + } + ) + + def _bind_product(self): + return self.create_binding_no_export( + "prestashop.product.combination", + self.product.id, + None, + **{ + "main_template_id": self.main_template_id.id, + "minimal_quantity": 2, + }, + ).with_context(connector_no_export=False) + + def test_01_export_product_product_oncreate(self): + # create binding + self.env["prestashop.product.combination"].create( + { + "backend_id": self.backend_record.id, + "odoo_id": self.product.id, + "main_template_id": self.main_template_id.id, + } + ) + # check export delayed + # The sequence of fields should follow above create + self.instance_delay_record.export_record.assert_called_once_with( + fields=["backend_id", "odoo_id", "main_template_id"] + ) + + def test_02_export_product_product_onwrite(self): + # reset mock: + self.patch_delay_record.stop() + mock_delay_record = mock.MagicMock() + self.instance_delay_record = mock_delay_record.return_value + self.patch_delay_record = mock.patch( + "odoo.addons.queue_job.models.base.DelayableRecordset", + new=mock_delay_record, + ) + self.patch_delay_record.start() + + # bind product + binding = self._bind_product() + # check no export delayed + self.assertEqual(0, self.instance_delay_record.export_record.call_count) + # write in product + self.product.default_code = "demo_3_OS" + # check export delayed + self.assertEqual(1, self.instance_delay_record.export_record.call_count) + # write in binding + binding.minimal_quantity = 2 + # check export delayed + self.assertEqual(2, self.instance_delay_record.export_record.call_count) + + @assert_no_job_delayed + def test_03_export_product_product_ondelete(self): + # bind product + binding = self._bind_product() + binding.prestashop_id = 46 + backend_id = binding.backend_id + # delete product + self.product.unlink() + # check export delete delayed + self.instance_delay_record.export_delete_record.assert_any_call(backend_id, 46) + self.instance_delay_record.export_delete_record.assert_called_with( + backend_id, 3 + ) + assert self.instance_delay_record.export_delete_record.call_count == 2 + + @assert_no_job_delayed + def test_04_export_product_product_jobs(self): + # bind product + binding = self._bind_product() + + with recorder.use_cassette( + "test_export_product_product", + cassette_library_dir=self.cassette_library_dir, + ) as cassette: + + # create combination in PS + binding.export_record() + + # check POST request + request = cassette.requests[0] + self.assertEqual("POST", request.method) + self.assertEqual("/api/combinations", self.parse_path(request.uri)) + self.assertDictEqual({}, self.parse_qs(request.uri)) + body = self.xmltodict(request.body) + ps_product = body["prestashop"]["combination"] + # check basic fields + for field, value in list( + { + "active": "1", + "default_on": "0", + "ean13": "8411788010150", + "id_product": "3", + "minimal_quantity": "2", + "price": "20.0", + "reference": "demo_3_OS", + "weight": "0.1", + "wholesale_price": "10.0", + }.items() + ): + self.assertEqual(value, ps_product[field]) + # check option values + ps_product_option_values = ps_product["associations"][ + "product_option_values" + ]["product_option_value"] + self.assertIn({"id": "4"}, ps_product_option_values) + self.assertIn({"id": "13"}, ps_product_option_values) + + # delete combination in PS + self.env["prestashop.product.combination"].export_delete_record( + self.backend_record, + binding.prestashop_id, + ) + + # check DELETE requests + request = cassette.requests[1] + self.assertEqual("DELETE", request.method) + self.assertEqual( + f"/api/combinations/{binding.prestashop_id}", + self.parse_path(request.uri), + ) + self.assertDictEqual({}, self.parse_qs(request.uri)) diff --git a/connector_prestashop_catalog_manager/tests/test_export_product_template.py b/connector_prestashop_catalog_manager/tests/test_export_product_template.py new file mode 100644 index 000000000..198f97aa5 --- /dev/null +++ b/connector_prestashop_catalog_manager/tests/test_export_product_template.py @@ -0,0 +1,261 @@ +# © 2018 PlanetaTIC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from odoo.tests import tagged + +from odoo.addons.connector_prestashop.tests.common import ( + assert_no_job_delayed, + recorder, +) + +from ..models.product_template.exporter import get_slug +from .common import CatalogManagerTransactionCase + + +@tagged("post_install", "-at_install") +class TestExportProduct(CatalogManagerTransactionCase): + def setUp(self): + super().setUp() + + # create and bind category + category_home = self.env["product.category"].create( + { + "name": "Home", + } + ) + self.create_binding_no_export( + "prestashop.product.category", category_home.id, 2 + ) + category_women = self.env["product.category"].create( + { + "name": "Women", + "parent_id": category_home.id, + } + ) + self.create_binding_no_export( + "prestashop.product.category", category_women.id, 3 + ) + category_tops = self.env["product.category"].create( + { + "name": "Tops", + "parent_id": category_women.id, + } + ) + self.create_binding_no_export( + "prestashop.product.category", category_tops.id, 4 + ) + category_tshirts = self.env["product.category"].create( + { + "name": "T-shirts", + "parent_id": category_tops.id, + } + ) + self.create_binding_no_export( + "prestashop.product.category", category_tshirts.id, 5 + ) + + # create template + self.template = self.env["product.template"].create( + { + "barcode": "8411788010150", + "categ_ids": [ + ( + 6, + False, + [ + category_home.id, + category_women.id, + category_tops.id, + category_tshirts.id, + ], + ) + ], + "default_code": "NEW_PRODUCT", + "list_price": 20.0, + "name": "New product", + "prestashop_default_category_id": category_tshirts.id, + "standard_price": 10.0, + "weight": 0.1, + } + ) + + def _bind_template(self): + return self.create_binding_no_export( + "prestashop.product.template", + self.template.id, + 8, + **{ + "default_shop_id": self.shop.id, + "link_rewrite": "new-product", + } + ).with_context(connector_no_export=False) + + @assert_no_job_delayed + def test_01_export_product_template_wizard_export(self): + # export from wizard + wizard = ( + self.env["export.multiple.products"] + .with_context(active_ids=[self.template.id]) + .create({}) + ) + wizard.export_products() + + # check binding created + binding_model = "prestashop.product.template" + bindings = self.env[binding_model].search([("odoo_id", "=", self.template.id)]) + self.assertEqual(1, len(bindings)) + # check export delayed + # sequence of fields is from ./wizards/export_multiple_products.py + # > def create_prestashop_template + self.instance_delay_record.export_record.assert_called_once_with( + fields=["backend_id", "default_shop_id", "link_rewrite", "odoo_id"] + ) + + @assert_no_job_delayed + def test_02_export_product_template_wizard_active(self): + # bind template + self._bind_template() + # check no export delayed + self.assertEqual(0, self.instance_delay_record.export_record.call_count) + # deactivate from wizard + wizard = ( + self.env["active.deactive.products"] + .with_context(active_ids=[self.template.id]) + .create({}) + ) + wizard.deactive_products() + # check export delayed + self.assertEqual(1, self.instance_delay_record.export_record.call_count) + # deactivate again + wizard.deactive_products() + # check no export delayed + self.assertEqual(1, self.instance_delay_record.export_record.call_count) + # force deactivate + wizard.force_status = True + wizard.deactive_products() + # check export delayed + self.assertEqual(2, self.instance_delay_record.export_record.call_count) + # activate from wizard + wizard.force_status = False + wizard.active_products() + # check export delayed + self.assertEqual(3, self.instance_delay_record.export_record.call_count) + # activate again + wizard.active_products() + # check no export delayed + self.assertEqual(3, self.instance_delay_record.export_record.call_count) + # force activate + wizard.force_status = True + wizard.active_products() + # check export delayed + self.assertEqual(4, self.instance_delay_record.export_record.call_count) + + @assert_no_job_delayed + def test_03_export_product_template_wizard_resync(self): + # bind template + self._bind_template() + # resync from wizard + wizard = ( + self.env["sync.products"] + .with_context(active_ids=[self.template.id], connector_delay=True) + .create({}) + ) + wizard.sync_products() + # check import done + self.instance_delay_record.import_record.assert_called_once_with( + self.backend_record, 8 + ) + + @assert_no_job_delayed + def test_04_export_product_template_onwrite(self): + # bind template + binding = self._bind_template() + # check no export delayed + self.assertEqual(0, self.instance_delay_record.export_record.call_count) + # write in template + self.template.name = "New product updated" + # check export delayed + self.assertEqual(1, self.instance_delay_record.export_record.call_count) + # write in binding + binding.meta_title = "New product meta title updated" + # check export delayed + self.assertEqual(2, self.instance_delay_record.export_record.call_count) + + @assert_no_job_delayed + def test_05_export_product_template_job(self): + # create binding + binding = self.env["prestashop.product.template"].create( + { + "backend_id": self.backend_record.id, + "odoo_id": self.template.id, + "additional_shipping_cost": 1.0, + "always_available": True, + "available_date": "2016-08-29", + "available_later": "New product available later", + "available_now": "New product available now", + "default_shop_id": self.shop.id, + "description_html": "New product description", + "description_short_html": "New product description short", + "link_rewrite": get_slug(self.template.name), + "meta_title": "New product meta title", + "meta_description": "New product meta description", + "meta_keywords": "New product meta keywords", + "minimal_quantity": 2, + "on_sale": True, + "online_only": True, + "tags": "New product tags", + } + ) + # export template + with recorder.use_cassette( + "test_export_product_template", + cassette_library_dir=self.cassette_library_dir, + ) as cassette: + binding.export_record() + + # check request + self.assertEqual(1, len(cassette.requests)) + request = cassette.requests[0] + self.assertEqual("POST", request.method) + self.assertEqual("/api/products", self.parse_path(request.uri)) + self.assertDictEqual({}, self.parse_qs(request.uri)) + body = self.xmltodict(request.body) + ps_product = body["prestashop"]["product"] + # check basic fields + for field, value in list( + { + "active": "1", + "additional_shipping_cost": "1.0", + "available_date": "2016-08-29", + "available_for_order": "1", + "barcode": "8411788010150", + "id_category_default": "5", + "id_shop_default": "1", + "minimal_quantity": "2", + "on_sale": "1", + "online_only": "1", + "price": "20.0", + "reference": "NEW_PRODUCT", + "show_price": "1", + "weight": "0.1", + "wholesale_price": "10.0", + "id_manufacturer": "1", + }.items() + ): + self.assertEqual(value, ps_product[field]) + # check translatable fields + for field, value in list( + { + "available_later": "New product available later", + "available_now": "New product available now", + "description": "

New product description

", + "description_short": "

New product description short" "

", + "link_rewrite": "new-product", + "meta_description": "New product meta description", + "meta_keywords": "New product meta keywords", + "meta_title": "New product meta title", + "name": "New product", + "tags": "New product tags", + }.items() + ): + self.assertEqual(value, ps_product[field]["language"]["value"]) diff --git a/connector_prestashop_catalog_manager/views/product_attribute_view.xml b/connector_prestashop_catalog_manager/views/product_attribute_view.xml index b05f0783e..cdd4f03ca 100644 --- a/connector_prestashop_catalog_manager/views/product_attribute_view.xml +++ b/connector_prestashop_catalog_manager/views/product_attribute_view.xml @@ -1,46 +1,41 @@ - - - + + + + product.attribute.form + product.attribute + +
+ + + + +
+
+
- - - - product.attribute.form - product.attribute - + + prestashop.product.combination.option +
- - - + + + + - -
+
+
- - prestashop.product.combination.option - -
- - - - - -
-
-
- - - prestashop.product.combination.option - - - - - - - - + + prestashop.product.combination.option + + + + + + + + -
-
+ diff --git a/connector_prestashop_catalog_manager/views/product_category_view.xml b/connector_prestashop_catalog_manager/views/product_category_view.xml new file mode 100644 index 000000000..62f69711a --- /dev/null +++ b/connector_prestashop_catalog_manager/views/product_category_view.xml @@ -0,0 +1,18 @@ + + + + + connector_prestashop.product.category.tree + prestashop.product.category + + + + 1 + + + + + diff --git a/connector_prestashop_catalog_manager/views/product_image_view.xml b/connector_prestashop_catalog_manager/views/product_image_view.xml index bbefd0109..e1443ff79 100644 --- a/connector_prestashop_catalog_manager/views/product_image_view.xml +++ b/connector_prestashop_catalog_manager/views/product_image_view.xml @@ -1,15 +1,13 @@ - - + connector_prestashop.product.image.form base_multi_image.image - + - + - - + diff --git a/connector_prestashop_catalog_manager/views/product_view.xml b/connector_prestashop_catalog_manager/views/product_view.xml index 27995ef8f..ca8d5d131 100644 --- a/connector_prestashop_catalog_manager/views/product_view.xml +++ b/connector_prestashop_catalog_manager/views/product_view.xml @@ -1,31 +1,46 @@ - - - - connector_prestashop.product.template.form - - prestashop.product.template - - form - - - - - - - - - - - - - - - - - - - + + + connector_prestashop.product.template.form + + prestashop.product.template + + form + + + + + + + + + + + + + + + + + + + + + connector_prestashop.product.template.tree + + prestashop.product.template + + + + 1 + + + + + diff --git a/connector_prestashop_catalog_manager/wizards/active_deactive_products.py b/connector_prestashop_catalog_manager/wizards/active_deactive_products.py index c4d935ad5..5a6458537 100644 --- a/connector_prestashop_catalog_manager/wizards/active_deactive_products.py +++ b/connector_prestashop_catalog_manager/wizards/active_deactive_products.py @@ -1,13 +1,13 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import api, fields, models +from odoo import fields, models class SyncProducts(models.TransientModel): _name = "active.deactive.products" + _description = "Activate/Deactivate Products" force_status = fields.Boolean( - string="Force Status", help="Check this option to force active product in prestashop", ) @@ -19,10 +19,10 @@ def _change_status(self, status): if bind.always_available != status or self.force_status: bind.always_available = status - @api.multi def active_products(self): - self._change_status(True) + for product in self: + product._change_status(True) - @api.multi def deactive_products(self): - self._change_status(False) + for product in self: + product._change_status(False) diff --git a/connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml b/connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml index 7bd42b0ad..4da7b2838 100644 --- a/connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml +++ b/connector_prestashop_catalog_manager/wizards/active_deactive_products_view.xml @@ -1,40 +1,40 @@ - - + active.product.form active.deactive.products
-
- + + Active Products + active.deactive.products + + form + form + new + + deactive.product.form @@ -42,30 +42,31 @@
-
- -
-
+ + Deactive Products + active.deactive.products + + form + form + new + + + + diff --git a/connector_prestashop_catalog_manager/wizards/export_category.py b/connector_prestashop_catalog_manager/wizards/export_category.py index e6fac1114..bde658155 100644 --- a/connector_prestashop_catalog_manager/wizards/export_category.py +++ b/connector_prestashop_catalog_manager/wizards/export_category.py @@ -1,30 +1,13 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import re -import unicodedata +from odoo import fields, models -from openerp import api, fields, models - -try: - import slugify as slugify_lib -except ImportError: - slugify_lib = None - - -def get_slug(name): - if slugify_lib: - try: - return slugify_lib.slugify(name) - except TypeError: - pass - uni = unicodedata.normalize("NFKD", name).encode("ascii", "ignore").decode("ascii") - slug = re.sub(r"[\W_]", " ", uni).strip().lower() - slug = re.sub(r"[-\s]+", "-", slug) - return slug +from ..models.product_template.exporter import get_slug class PrestashopExportCategory(models.TransientModel): _name = "wiz.prestashop.export.category" + _description = "Prestashop Export Category" def _default_backend(self): return self.env["prestashop.backend"].search([], limit=1).id @@ -43,7 +26,6 @@ def _default_shop(self): string="Shop", ) - @api.multi def export_categories(self): self.ensure_one() category_obj = self.env["product.category"] diff --git a/connector_prestashop_catalog_manager/wizards/export_category_view.xml b/connector_prestashop_catalog_manager/wizards/export_category_view.xml index 79d8286a0..d45d45375 100644 --- a/connector_prestashop_catalog_manager/wizards/export_category_view.xml +++ b/connector_prestashop_catalog_manager/wizards/export_category_view.xml @@ -1,6 +1,5 @@ - - + wiz.prestashop.export.category.form @@ -11,30 +10,26 @@ -
+
+
- + + Export To PrestaShop + wiz.prestashop.export.category + form + new + + -
-
+ diff --git a/connector_prestashop_catalog_manager/wizards/export_multiple_products.py b/connector_prestashop_catalog_manager/wizards/export_multiple_products.py index db41ba98f..b0eda5702 100644 --- a/connector_prestashop_catalog_manager/wizards/export_multiple_products.py +++ b/connector_prestashop_catalog_manager/wizards/export_multiple_products.py @@ -1,9 +1,15 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging import re import unicodedata +from functools import reduce + +from odoo import _, fields, models +from odoo.exceptions import ValidationError + +_logger = logging.getLogger(__name__) -from openerp import api, fields, models try: import slugify as slugify_lib @@ -15,8 +21,8 @@ def get_slug(name): if slugify_lib: try: return slugify_lib.slugify(name) - except TypeError: - pass + except TypeError as e: + _logger.info("get_slug TypeError: %s", e) uni = unicodedata.normalize("NFKD", name).encode("ascii", "ignore").decode("ascii") slug = re.sub(r"[\W_]", " ", uni).strip().lower() slug = re.sub(r"[-\s]+", "-", slug) @@ -25,12 +31,11 @@ def get_slug(name): class ExportMultipleProducts(models.TransientModel): _name = "export.multiple.products" + _description = "Export Multiple Products" - @api.multi def _default_backend(self): return self.env["prestashop.backend"].search([], limit=1).id - @api.multi def _default_shop(self): return self.env["prestashop.shop"].search([], limit=1).id @@ -80,7 +85,6 @@ def _set_main_category(self, product): } ) - @api.multi def set_category(self): product_obj = self.env["product.template"] for product in product_obj.browse(self.env.context["active_ids"]): @@ -103,19 +107,17 @@ def _check_variants(self, product): if len(product.product_variant_ids) > 1 and not product.attribute_line_ids: check_count = reduce( lambda x, y: x * y, - map(lambda x: len(x.value_ids), product.attribute_line_ids), + [len(x.value_ids) for x in product.attribute_line_ids], ) if check_count < len(product.product_variant_ids): return False return True - @api.multi def export_variant_stock(self): template_obj = self.env["product.template"] products = template_obj.browse(self.env.context["active_ids"]) products.update_prestashop_quantities() - @api.multi def create_prestashop_template(self, product): presta_tmpl_obj = self.env["prestashop.product.template"] return presta_tmpl_obj.create( @@ -127,7 +129,6 @@ def create_prestashop_template(self, product): } ) - @api.multi def export_products(self): self.ensure_one() product_obj = self.env["product.template"] @@ -145,7 +146,14 @@ def export_products(self): cat = self._check_category(product) var = self._check_variants(product) if not (var and cat): - continue + raise ValidationError( + _( + """Product "%s" cannot be exported to Prestashop \ +because is not assigned to any Prestashop category or \ +has not any Product Variant.""" + ) + % product.name + ) self.create_prestashop_template(product) else: for tmpl in presta_tmpl: diff --git a/connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml b/connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml index aa4a1b787..59bbe91f6 100644 --- a/connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml +++ b/connector_prestashop_catalog_manager/wizards/export_multiple_products_view.xml @@ -1,70 +1,63 @@ - - - - export.multiple.products.form - export.multiple.products - -
- - - - -
-
-
-
-
- + + + export.multiple.products.form + export.multiple.products + +
+ + + + +
+
+
+
+
+ + Export Products to Prestashop + export.multiple.products + + list,form + form + new + + - - export.variant.stock.form - export.multiple.products - -
-
-
-
-
-
- + + export.variant.stock.form + export.multiple.products + +
+
+
+
+
+
+ + Export Products Stock + export.multiple.products + + form + form + new + + -
-
+ diff --git a/connector_prestashop_catalog_manager/wizards/sync_products.py b/connector_prestashop_catalog_manager/wizards/sync_products.py index f306bafbd..932c3e8a4 100644 --- a/connector_prestashop_catalog_manager/wizards/sync_products.py +++ b/connector_prestashop_catalog_manager/wizards/sync_products.py @@ -2,27 +2,28 @@ import logging -from openerp import api, models +from odoo import models _logger = logging.getLogger(__name__) class SyncProducts(models.TransientModel): - _name = 'sync.products' + _name = "sync.products" + _description = "Synchronize Products" def _bind_resync(self, product_ids): - products = self.env['product.template'].browse(product_ids) + products = self.env["product.template"].browse(product_ids) for product in products: try: for bind in product.prestashop_bind_ids: bind.resync() - except Exception, e: - _logger.info('id %s, attributes %s\n', str(product.id), e) + except Exception as e: + _logger.info("id %s, attributes %s\n", str(product.id), e) - @api.multi def sync_products(self): - self._bind_resync(self.env.context['active_ids']) + for product in self: + product._bind_resync(product.env.context["active_ids"]) - @api.multi def sync_all_products(self): - self._bind_resync([]) + for product in self: + product._bind_resync([]) diff --git a/connector_prestashop_catalog_manager/wizards/sync_products_view.xml b/connector_prestashop_catalog_manager/wizards/sync_products_view.xml index 03b322dae..c24e3f7af 100644 --- a/connector_prestashop_catalog_manager/wizards/sync_products_view.xml +++ b/connector_prestashop_catalog_manager/wizards/sync_products_view.xml @@ -1,35 +1,34 @@ - - - - sync.products.form - sync.products - -
- - -
-
-
-
-
- -
-
+ + + sync.products.form + sync.products + +
+ +
+

sync selected products

+
+
+
+
+
+
+
+ + Sync Products + sync.products + + form + form + new + + +