From 78a12fede8de28a9f7e746f7861084fa07cc9c70 Mon Sep 17 00:00:00 2001 From: Yogesh Mahera <yogesh.mahera.serpentcs@gmail.com> Date: Mon, 1 Oct 2018 12:24:51 +0530 Subject: [PATCH] Migrated WooCommerce module and added Export functions --- connector_woocommerce/__manifest__.py | 1 - connector_woocommerce/backend.py | 28 -- connector_woocommerce/connector.py | 85 ---- connector_woocommerce/model/__init__.py | 26 -- connector_woocommerce/model/backend.py | 192 --------- connector_woocommerce/model/customer.py | 229 ----------- connector_woocommerce/model/product.py | 330 --------------- .../model/product_category.py | 197 --------- connector_woocommerce/model/sale.py | 377 ------------------ connector_woocommerce/related_action.py | 56 --- connector_woocommerce/unit/__init__.py | 25 -- connector_woocommerce/unit/backend_adapter.py | 227 ----------- connector_woocommerce/unit/binder.py | 163 -------- .../unit/import_synchronizer.py | 284 ------------- connector_woocommerce/unit/mapper.py | 32 -- 15 files changed, 2252 deletions(-) delete mode 100755 connector_woocommerce/backend.py delete mode 100755 connector_woocommerce/connector.py delete mode 100755 connector_woocommerce/model/__init__.py delete mode 100755 connector_woocommerce/model/backend.py delete mode 100755 connector_woocommerce/model/customer.py delete mode 100755 connector_woocommerce/model/product.py delete mode 100755 connector_woocommerce/model/product_category.py delete mode 100755 connector_woocommerce/model/sale.py delete mode 100755 connector_woocommerce/related_action.py delete mode 100755 connector_woocommerce/unit/__init__.py delete mode 100755 connector_woocommerce/unit/backend_adapter.py delete mode 100755 connector_woocommerce/unit/binder.py delete mode 100755 connector_woocommerce/unit/import_synchronizer.py delete mode 100755 connector_woocommerce/unit/mapper.py diff --git a/connector_woocommerce/__manifest__.py b/connector_woocommerce/__manifest__.py index 0e0c639..4794eb8 100644 --- a/connector_woocommerce/__manifest__.py +++ b/connector_woocommerce/__manifest__.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD # © 2009 Tech-Receptives Solutions Pvt. Ltd. # © 2018 Serpent Consulting Services Pvt. Ltd. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/connector_woocommerce/backend.py b/connector_woocommerce/backend.py deleted file mode 100755 index b86e7a1..0000000 --- a/connector_woocommerce/backend.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -import openerp.addons.connector.backend as backend - -woo = backend.Backend('woo') -""" Generic woo Backend """ - -woov2 = backend.Backend(parent=woo, version='v2') -""" WooCommerce Backend for version v2 """ diff --git a/connector_woocommerce/connector.py b/connector_woocommerce/connector.py deleted file mode 100755 index 960a8d9..0000000 --- a/connector_woocommerce/connector.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -from openerp import models, fields -from openerp.addons.connector.connector import (ConnectorEnvironment, - install_in_connector) -from openerp.addons.connector.checkpoint import checkpoint - -install_in_connector() - - -def get_environment(session, model_name, backend_id): - """ Create an environment to work with. """ - backend_record = session.env['wc.backend'].browse(backend_id) - env = ConnectorEnvironment(backend_record, session, model_name) - lang = backend_record.default_lang_id - lang_code = lang.code if lang else 'en_US' - if lang_code == session.context.get('lang'): - return env - else: - with env.session.change_context(lang=lang_code): - return env - - -class WooBinding(models.AbstractModel): - - """ Abstract Model for the Bindigs. - - All the models used as bindings between WooCommerce and OpenERP - (``woo.res.partner``, ``woo.product.product``, ...) should - ``_inherit`` it. - """ - _name = 'woo.binding' - _inherit = 'external.binding' - _description = 'Woo Binding (abstract)' - - # openerp_id = openerp-side id must be declared in concrete model - backend_id = fields.Many2one( - comodel_name='wc.backend', - string='Woo Backend', - required=True, - ondelete='restrict', - ) - # fields.Char because 0 is a valid WooCommerce ID - woo_id = fields.Char(string='ID on Woo') - - _sql_constraints = [ - ('woo_uniq', 'unique(backend_id, woo_id)', - 'A binding already exists with the same Woo ID.'), - ] - - -def add_checkpoint(session, model_name, record_id, backend_id): - """ Add a row in the model ``connector.checkpoint`` for a record, - meaning it has to be reviewed by a user. - - :param session: current session - :type session: :class:`openerp.addons.connector.session.ConnectorSession` - :param model_name: name of the model of the record to be reviewed - :type model_name: str - :param record_id: ID of the record to be reviewed - :type record_id: int - :param backend_id: ID of the WooCommerce Backend - :type backend_id: int - """ - return checkpoint.add_checkpoint(session, model_name, record_id, - 'wc.backend', backend_id) diff --git a/connector_woocommerce/model/__init__.py b/connector_woocommerce/model/__init__.py deleted file mode 100755 index d87e529..0000000 --- a/connector_woocommerce/model/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -from . import backend -from . import product_category -from . import product -from . import customer -from . import sale diff --git a/connector_woocommerce/model/backend.py b/connector_woocommerce/model/backend.py deleted file mode 100755 index 996c7c5..0000000 --- a/connector_woocommerce/model/backend.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -from openerp import models, api, fields, _ -from woocommerce import API -from openerp.exceptions import Warning -from openerp.addons.connector.session import ConnectorSession -from datetime import datetime -from .product_category import category_import_batch -from .product import product_import_batch -from .customer import customer_import_batch -from .sale import sale_order_import_batch - - -class wc_backend(models.Model): - _name = 'wc.backend' - _inherit = 'connector.backend' - _description = 'WooCommerce Backend Configuration' - name = fields.Char(string='name') - _backend_type = 'woo' - location = fields.Char("Url") - consumer_key = fields.Char("Consumer key") - consumer_secret = fields.Char("Consumer Secret") - version = fields.Selection([('v2', 'V2')], 'Version') - verify_ssl = fields.Boolean("Verify SSL") - default_lang_id = fields.Many2one( - comodel_name='res.lang', - string='Default Language', - help="If a default language is selected, the records " - "will be imported in the translation of this language.\n" - "Note that a similar configuration exists " - "for each storeview.", - ) - - @api.multi - def get_product_ids(self, data): - product_ids = [x['id'] for x in data['products']] - product_ids = sorted(product_ids) - return product_ids - - @api.multi - def get_product_category_ids(self, data): - product_category_ids = [x['id'] for x in data['product_categories']] - product_category_ids = sorted(product_category_ids) - return product_category_ids - - @api.multi - def get_customer_ids(self, data): - customer_ids = [x['id'] for x in data['customers']] - customer_ids = sorted(customer_ids) - return customer_ids - - @api.multi - def get_order_ids(self, data): - order_ids = self.check_existing_order(data) - return order_ids - - @api.multi - def update_existing_order(self, woo_sale_order, data): - """ Enter Your logic for Existing Sale Order """ - return True - - @api.multi - def check_existing_order(self, data): - order_ids = [] - for val in data['orders']: - woo_sale_order = self.env['woo.sale.order'].search( - [('woo_id', '=', val['id'])]) - if woo_sale_order: - self.update_existing_order(woo_sale_order[0], val) - continue - order_ids.append(val['id']) - return order_ids - - @api.multi - def test_connection(self): - location = self.location - cons_key = self.consumer_key - sec_key = self.consumer_secret - version = 'v2' - - wcapi = API(url=location, consumer_key=cons_key, - consumer_secret=sec_key, version=version) - r = wcapi.get("products") - if r.status_code == 404: - raise Warning(_("Enter Valid url")) - val = r.json() - msg = '' - if 'errors' in r.json(): - msg = val['errors'][0]['message'] + '\n' + val['errors'][0]['code'] - raise Warning(_(msg)) - else: - raise Warning(_('Test Success')) - return True - - @api.multi - def import_category(self): - session = ConnectorSession(self.env.cr, self.env.uid, - context=self.env.context) - import_start_time = datetime.now() - backend_id = self.id - from_date = None - category_import_batch.delay( - session, 'woo.product.category', backend_id, - {'from_date': from_date, - 'to_date': import_start_time}, priority=1) - return True - - @api.multi - def import_product(self): - session = ConnectorSession(self.env.cr, self.env.uid, - context=self.env.context) - import_start_time = datetime.now() - backend_id = self.id - from_date = None - product_import_batch.delay( - session, 'woo.product.product', backend_id, - {'from_date': from_date, - 'to_date': import_start_time}, priority=2) - return True - - @api.multi - def import_customer(self): - session = ConnectorSession(self.env.cr, self.env.uid, - context=self.env.context) - import_start_time = datetime.now() - backend_id = self.id - from_date = None - customer_import_batch.delay( - session, 'woo.res.partner', backend_id, - {'from_date': from_date, - 'to_date': import_start_time}, priority=3) - return True - - @api.multi - def import_order(self): - session = ConnectorSession(self.env.cr, self.env.uid, - context=self.env.context) - import_start_time = datetime.now() - backend_id = self.id - from_date = None - sale_order_import_batch.delay( - session, 'woo.sale.order', backend_id, - {'from_date': from_date, - 'to_date': import_start_time}, priority=4) - return True - - @api.multi - def import_categories(self): - """ Import Product categories """ - for backend in self: - backend.import_category() - return True - - @api.multi - def import_products(self): - """ Import categories from all websites """ - for backend in self: - backend.import_product() - return True - - @api.multi - def import_customers(self): - """ Import categories from all websites """ - for backend in self: - backend.import_customer() - return True - - @api.multi - def import_orders(self): - """ Import Orders from all websites """ - for backend in self: - backend.import_order() - return True diff --git a/connector_woocommerce/model/customer.py b/connector_woocommerce/model/customer.py deleted file mode 100755 index 3f26f47..0000000 --- a/connector_woocommerce/model/customer.py +++ /dev/null @@ -1,229 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -import logging -import xmlrpclib -from openerp import models, fields -from openerp.addons.connector.queue.job import job -from openerp.addons.connector.unit.mapper import (mapping, - ImportMapper - ) -from openerp.addons.connector.exception import IDMissingInBackend -from ..unit.backend_adapter import (GenericAdapter) -from ..unit.import_synchronizer import (DelayedBatchImporter, WooImporter) -from ..connector import get_environment -from ..backend import woo - -_logger = logging.getLogger(__name__) - - -class WooResPartner(models.Model): - _name = 'woo.res.partner' - _inherit = 'woo.binding' - _inherits = {'res.partner': 'openerp_id'} - _description = 'woo res partner' - - _rec_name = 'name' - - openerp_id = fields.Many2one(comodel_name='res.partner', - string='Partner', - required=True, - ondelete='cascade') - backend_id = fields.Many2one( - comodel_name='wc.backend', - string='Woo Backend', - store=True, - readonly=False, - ) - - -@woo -class CustomerAdapter(GenericAdapter): - _model_name = 'woo.res.partner' - _woo_model = 'customers' - - def _call(self, method, arguments): - try: - return super(CustomerAdapter, self)._call(method, arguments) - except xmlrpclib.Fault as err: - # this is the error in the WooCommerce API - # when the customer does not exist - if err.faultCode == 102: - raise IDMissingInBackend - else: - raise - - def search(self, filters=None, from_date=None, to_date=None): - """ Search records according to some criteria and return a - list of ids - - :rtype: list - """ - if filters is None: - filters = {} - WOO_DATETIME_FORMAT = '%Y/%m/%d %H:%M:%S' - dt_fmt = WOO_DATETIME_FORMAT - if from_date is not None: - # updated_at include the created records - filters.setdefault('updated_at', {}) - filters['updated_at']['from'] = from_date.strftime(dt_fmt) - if to_date is not None: - filters.setdefault('updated_at', {}) - filters['updated_at']['to'] = to_date.strftime(dt_fmt) - # the search method is on ol_customer instead of customer - return self._call('customers/list', - [filters] if filters else [{}]) - - -@woo -class CustomerBatchImporter(DelayedBatchImporter): - - """ Import the WooCommerce Partners. - - For every partner in the list, a delayed job is created. - """ - _model_name = ['woo.res.partner'] - - def _import_record(self, woo_id, priority=None): - """ Delay a job for the import """ - super(CustomerBatchImporter, self)._import_record( - woo_id, priority=priority) - - def run(self, filters=None): - """ Run the synchronization """ - from_date = filters.pop('from_date', None) - to_date = filters.pop('to_date', None) - record_ids = self.backend_adapter.search( - filters, - from_date=from_date, - to_date=to_date, - ) -# record_ids = self.env['wc.backend'].get_customer_ids(record_ids) - _logger.info('search for woo partners %s returned %s', - filters, record_ids) - for record_id in record_ids: - self._import_record(record_id, 40) - - -CustomerBatchImporter = CustomerBatchImporter # deprecated - - -@woo -class CustomerImporter(WooImporter): - _model_name = ['woo.res.partner'] - - def _import_dependencies(self): - """ Import the dependencies for the record""" - return - - def _create(self, data): - openerp_binding = super(CustomerImporter, self)._create(data) - return openerp_binding - - def _after_import(self, binding): - """ Hook called at the end of the import """ - return - -CustomerImport = CustomerImporter # deprecated - - -@woo -class CustomerImportMapper(ImportMapper): - _model_name = 'woo.res.partner' - - @mapping - def name(self, record): - if record['customer']: - rec = record['customer'] - return {'name': rec['first_name'] + " " + rec['last_name']} - - @mapping - def email(self, record): - if record['customer']: - rec = record['customer'] - return {'email': rec['email'] or None} - - @mapping - def city(self, record): - if record['customer']: - rec = record['customer']['billing_address'] - return {'city': rec['city'] or None} - - @mapping - def zip(self, record): - if record['customer']: - rec = record['customer']['billing_address'] - return {'zip': rec['postcode'] or None} - - @mapping - def address(self, record): - if record['customer']: - rec = record['customer']['billing_address'] - return {'street': rec['address_1'] or None} - - @mapping - def address_2(self, record): - if record['customer']: - rec = record['customer']['billing_address'] - return {'street2': rec['address_2'] or None} - - @mapping - def country(self, record): - if record['customer']: - rec = record['customer']['billing_address'] - if rec['country']: - country_id = self.env['res.country'].search( - [('code', '=', rec['country'])]) - country_id = country_id.id - else: - country_id = False - return {'country_id': country_id} - - @mapping - def state(self, record): - if record['customer']: - rec = record['customer']['billing_address'] - if rec['state'] and rec['country']: - state_id = self.env['res.country.state'].search( - [('code', '=', rec['state'])]) - if not state_id: - country_id = self.env['res.country'].search( - [('code', '=', rec['country'])]) - state_id = self.env['res.country.state'].create( - {'name': rec['state'], - 'code': rec['state'], - 'country_id': country_id.id}) - state_id = state_id.id or False - else: - state_id = False - return {'state_id': state_id} - - @mapping - def backend_id(self, record): - return {'backend_id': self.backend_record.id} - - -@job(default_channel='root.woo') -def customer_import_batch(session, model_name, backend_id, filters=None): - """ Prepare the import of Customer """ - env = get_environment(session, model_name, backend_id) - importer = env.get_connector_unit(CustomerBatchImporter) - importer.run(filters=filters) diff --git a/connector_woocommerce/model/product.py b/connector_woocommerce/model/product.py deleted file mode 100755 index ffe8a88..0000000 --- a/connector_woocommerce/model/product.py +++ /dev/null @@ -1,330 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -import logging -import urllib2 -import xmlrpclib -import base64 -from openerp import models, fields -from openerp.addons.connector.queue.job import job -from openerp.addons.connector.exception import MappingError -from openerp.addons.connector.unit.synchronizer import (Importer, - ) -from openerp.addons.connector.unit.mapper import (mapping, - ImportMapper - ) -from openerp.addons.connector.exception import IDMissingInBackend -from ..unit.backend_adapter import (GenericAdapter) -from ..unit.import_synchronizer import (DelayedBatchImporter, WooImporter) -from ..connector import get_environment -from ..backend import woo - -_logger = logging.getLogger(__name__) - - -class WooProductProduct(models.Model): - _name = 'woo.product.product' - _inherit = 'woo.binding' - _inherits = {'product.product': 'openerp_id'} - _description = 'woo product product' - - _rec_name = 'name' - openerp_id = fields.Many2one(comodel_name='product.product', - string='product', - required=True, - ondelete='cascade') - backend_id = fields.Many2one( - comodel_name='wc.backend', - string='Woo Backend', - store=True, - readonly=False, - required=True, - ) - - slug = fields.Char('Slung Name') - credated_at = fields.Date('created_at') - weight = fields.Float('weight') - - -class ProductProduct(models.Model): - _inherit = 'product.product' - - woo_categ_ids = fields.Many2many( - comodel_name='product.category', - string='Woo product category', - ) - in_stock = fields.Boolean('In Stock') - - -@woo -class ProductProductAdapter(GenericAdapter): - _model_name = 'woo.product.product' - _woo_model = 'products/details' - - def _call(self, method, arguments): - try: - return super(ProductProductAdapter, self)._call(method, arguments) - except xmlrpclib.Fault as err: - # this is the error in the WooCommerce API - # when the customer does not exist - if err.faultCode == 102: - raise IDMissingInBackend - else: - raise - - def search(self, filters=None, from_date=None, to_date=None): - """ Search records according to some criteria and return a - list of ids - - :rtype: list - """ - if filters is None: - filters = {} - WOO_DATETIME_FORMAT = '%Y/%m/%d %H:%M:%S' - dt_fmt = WOO_DATETIME_FORMAT - if from_date is not None: - # updated_at include the created records - filters.setdefault('updated_at', {}) - filters['updated_at']['from'] = from_date.strftime(dt_fmt) - if to_date is not None: - filters.setdefault('updated_at', {}) - filters['updated_at']['to'] = to_date.strftime(dt_fmt) - - return self._call('products/list', - [filters] if filters else [{}]) - - def get_images(self, id, storeview_id=None): - return self._call('products/' + str(id), [int(id), storeview_id, 'id']) - - def read_image(self, id, image_name, storeview_id=None): - return self._call('products', - [int(id), image_name, storeview_id, 'id']) - - -@woo -class ProductBatchImporter(DelayedBatchImporter): - - """ Import the WooCommerce Partners. - - For every partner in the list, a delayed job is created. - """ - _model_name = ['woo.product.product'] - - def _import_record(self, woo_id, priority=None): - """ Delay a job for the import """ - super(ProductBatchImporter, self)._import_record( - woo_id, priority=priority) - - def run(self, filters=None): - """ Run the synchronization """ -# - from_date = filters.pop('from_date', None) - to_date = filters.pop('to_date', None) - record_ids = self.backend_adapter.search( - filters, - from_date=from_date, - to_date=to_date, - ) - _logger.info('search for woo Products %s returned %s', - filters, record_ids) - for record_id in record_ids: - self._import_record(record_id, 30) - - -ProductBatchImporter = ProductBatchImporter - - -@woo -class ProductProductImporter(WooImporter): - _model_name = ['woo.product.product'] - - def _import_dependencies(self): - """ Import the dependencies for the record""" - record = self.woo_record - record = record['product'] - for woo_category_id in record['categories']: - self._import_dependency(woo_category_id, - 'woo.product.category') - - def _create(self, data): - openerp_binding = super(ProductProductImporter, self)._create(data) - return openerp_binding - - def _after_import(self, binding): - """ Hook called at the end of the import """ - image_importer = self.unit_for(ProductImageImporter) - image_importer.run(self.woo_id, binding.id) - return - -ProductProductImport = ProductProductImporter - - -@woo -class ProductImageImporter(Importer): - - """ Import images for a record. - - Usually called from importers, in ``_after_import``. - For instance from the products importer. - """ - _model_name = ['woo.product.product', - ] - - def _get_images(self, storeview_id=None): - return self.backend_adapter.get_images(self.woo_id) - - def _sort_images(self, images): - """ Returns a list of images sorted by their priority. - An image with the 'image' type is the the primary one. - The other images are sorted by their position. - - The returned list is reversed, the items at the end - of the list have the higher priority. - """ - if not images: - return {} - # place the images where the type is 'image' first then - # sort them by the reverse priority (last item of the list has - # the the higher priority) - - def _get_binary_image(self, image_data): - url = image_data['src'].encode('utf8') - url = str(url).replace("\\", '') - try: - request = urllib2.Request(url) - binary = urllib2.urlopen(request) - except urllib2.HTTPError as err: - if err.code == 404: - # the image is just missing, we skip it - return - else: - # we don't know why we couldn't download the image - # so we propagate the error, the import will fail - # and we have to check why it couldn't be accessed - raise - else: - return binary.read() - - def run(self, woo_id, binding_id): - self.woo_id = woo_id - images = self._get_images() - images = images['product'] - images = images['images'] - binary = None - while not binary and images: - binary = self._get_binary_image(images.pop()) - if not binary: - return - model = self.model.with_context(connector_no_export=True) - binding = model.browse(binding_id) - binding.write({'image': base64.b64encode(binary)}) - - -@woo -class ProductProductImportMapper(ImportMapper): - _model_name = 'woo.product.product' - - direct = [ - ('description', 'description'), - ('weight', 'weight'), - ] - - @mapping - def is_active(self, record): - """Check if the product is active in Woo - and set active flag in OpenERP - status == 1 in Woo means active""" - if record['product']: - rec = record['product'] - return {'active': rec['visible']} - - @mapping - def in_stock(self, record): - if record['product']: - rec = record['product'] - return {'in_stock': rec['in_stock']} - - @mapping - def name(self, record): - if record['product']: - rec = record['product'] - return {'name': rec['title']} - - @mapping - def type(self, record): - if record['product']: - rec = record['product'] - if rec['type'] == 'simple': - return {'type': 'product'} - - @mapping - def categories(self, record): - if record['product']: - rec = record['product'] - woo_categories = rec['categories'] - binder = self.binder_for('woo.product.category') - category_ids = [] - main_categ_id = None - for woo_category_id in woo_categories: - cat_id = binder.to_openerp(woo_category_id, unwrap=True) - if cat_id is None: - raise MappingError("The product category with " - "woo id %s is not imported." % - woo_category_id) - category_ids.append(cat_id) - if category_ids: - main_categ_id = category_ids.pop(0) - result = {'woo_categ_ids': [(6, 0, category_ids)]} - if main_categ_id: # OpenERP assign 'All Products' if not specified - result['categ_id'] = main_categ_id - return result - - @mapping - def price(self, record): - """ The price is imported at the creation of - the product, then it is only modified and exported - from OpenERP """ - if record['product']: - rec = record['product'] - return {'list_price': rec and rec['price'] or 0.0} - - @mapping - def sale_price(self, record): - """ The price is imported at the creation of - the product, then it is only modified and exported - from OpenERP """ - if record['product']: - rec = record['product'] - return {'standard_price': rec and rec['sale_price'] or 0.0} - - @mapping - def backend_id(self, record): - return {'backend_id': self.backend_record.id} - - -@job(default_channel='root.woo') -def product_import_batch(session, model_name, backend_id, filters=None): - """ Prepare the import of product modified on Woo """ - if filters is None: - filters = {} - env = get_environment(session, model_name, backend_id) - importer = env.get_connector_unit(ProductBatchImporter) - importer.run(filters=filters) diff --git a/connector_woocommerce/model/product_category.py b/connector_woocommerce/model/product_category.py deleted file mode 100755 index c6fac06..0000000 --- a/connector_woocommerce/model/product_category.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -import logging -import xmlrpclib -from openerp import models, fields -from openerp.addons.connector.queue.job import job -from openerp.addons.connector.exception import MappingError -from openerp.addons.connector.unit.mapper import (mapping, - ImportMapper - ) -from openerp.addons.connector.exception import IDMissingInBackend -from ..unit.backend_adapter import (GenericAdapter) -from ..unit.import_synchronizer import (DelayedBatchImporter, WooImporter) -from ..connector import get_environment -from ..backend import woo -_logger = logging.getLogger(__name__) - - -class WooProductCategory(models.Model): - _name = 'woo.product.category' - _inherit = 'woo.binding' - _inherits = {'product.category': 'openerp_id'} - _description = 'woo product category' - - _rec_name = 'name' - - openerp_id = fields.Many2one(comodel_name='product.category', - string='category', - required=True, - ondelete='cascade') - backend_id = fields.Many2one( - comodel_name='wc.backend', - string='Woo Backend', - store=True, - readonly=False, - ) - - slug = fields.Char('Slung Name') - woo_parent_id = fields.Many2one( - comodel_name='woo.product.category', - string='Woo Parent Category', - ondelete='cascade',) - description = fields.Char('Description') - count = fields.Integer('count') - - -@woo -class CategoryAdapter(GenericAdapter): - _model_name = 'woo.product.category' - _woo_model = 'products/categories' - - def _call(self, method, arguments): - try: - return super(CategoryAdapter, self)._call(method, arguments) - except xmlrpclib.Fault as err: - # this is the error in the WooCommerce API - # when the customer does not exist - if err.faultCode == 102: - raise IDMissingInBackend - else: - raise - - def search(self, filters=None, from_date=None, to_date=None): - """ Search records according to some criteria and return a - list of ids - - :rtype: list - """ - if filters is None: - filters = {} - WOO_DATETIME_FORMAT = '%Y/%m/%d %H:%M:%S' - dt_fmt = WOO_DATETIME_FORMAT - if from_date is not None: - filters.setdefault('updated_at', {}) - filters['updated_at']['from'] = from_date.strftime(dt_fmt) - if to_date is not None: - filters.setdefault('updated_at', {}) - filters['updated_at']['to'] = to_date.strftime(dt_fmt) - return self._call('products/categories/list', - [filters] if filters else [{}]) - - -@woo -class CategoryBatchImporter(DelayedBatchImporter): - - """ Import the WooCommerce Partners. - - For every partner in the list, a delayed job is created. - """ - _model_name = ['woo.product.category'] - - def _import_record(self, woo_id, priority=None): - """ Delay a job for the import """ - - super(CategoryBatchImporter, self)._import_record( - woo_id, priority=priority) - - def run(self, filters=None): - """ Run the synchronization """ - from_date = filters.pop('from_date', None) - to_date = filters.pop('to_date', None) - record_ids = self.backend_adapter.search( - filters, - from_date=from_date, - to_date=to_date, - ) - _logger.info('search for woo Product Category %s returned %s', - filters, record_ids) - for record_id in record_ids: - self._import_record(record_id) -CategoryBatchImporter = CategoryBatchImporter - - -@woo -class ProductCategoryImporter(WooImporter): - _model_name = ['woo.product.category'] - - def _import_dependencies(self): - """ Import the dependencies for the record""" - record = self.woo_record - # import parent category - # the root category has a 0 parent_id - record = record['product_category'] - if record['parent']: - parent_id = record['parent'] - if self.binder.to_openerp(parent_id) is None: - importer = self.unit_for(WooImporter) - importer.run(parent_id) - return - - def _create(self, data): - openerp_binding = super(ProductCategoryImporter, self)._create(data) - return openerp_binding - - def _after_import(self, binding): - """ Hook called at the end of the import """ - return - -ProductCategoryImport = ProductCategoryImporter - - -@woo -class ProductCategoryImportMapper(ImportMapper): - _model_name = 'woo.product.category' - - @mapping - def name(self, record): - if record['product_category']: - rec = record['product_category'] - return {'name': rec['name']} - - @mapping - def backend_id(self, record): - return {'backend_id': self.backend_record.id} -# - - @mapping - def parent_id(self, record): - if record['product_category']: - rec = record['product_category'] - if not rec['parent']: - return - binder = self.binder_for() - category_id = binder.to_openerp(rec['parent'], unwrap=True) - woo_cat_id = binder.to_openerp(rec['parent']) - if category_id is None: - raise MappingError("The product category with " - "woo id %s is not imported." % - rec['parent']) - return {'parent_id': category_id, 'woo_parent_id': woo_cat_id} - - -@job(default_channel='root.woo') -def category_import_batch(session, model_name, backend_id, filters=None): - """ Prepare the import of category modified on WooCommerce """ - env = get_environment(session, model_name, backend_id) - importer = env.get_connector_unit(CategoryBatchImporter) - importer.run(filters=filters) diff --git a/connector_woocommerce/model/sale.py b/connector_woocommerce/model/sale.py deleted file mode 100755 index ae84aa8..0000000 --- a/connector_woocommerce/model/sale.py +++ /dev/null @@ -1,377 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -import logging -import xmlrpclib -from openerp import models, fields, api -from openerp.addons.connector.queue.job import job -from openerp.addons.connector.unit.mapper import (mapping, - ImportMapper - ) -from openerp.addons.connector.exception import IDMissingInBackend -from ..unit.backend_adapter import (GenericAdapter) -from ..unit.import_synchronizer import (DelayedBatchImporter, WooImporter) -from ..connector import get_environment -from ..backend import woo -_logger = logging.getLogger(__name__) - - -class woo_sale_order_status(models.Model): - _name = 'woo.sale.order.status' - _description = 'WooCommerce Sale Order Status' - - name = fields.Char('Name') - desc = fields.Text('Description') - - -class SaleOrder(models.Model): - - _inherit = 'sale.order' - status_id = fields.Many2one('woo.sale.order.status', - 'WooCommerce Order Status') - - -class WooSaleOrder(models.Model): - _name = 'woo.sale.order' - _inherit = 'woo.binding' - _inherits = {'sale.order': 'openerp_id'} - _description = 'Woo Sale Order' - - _rec_name = 'name' - - status_id = fields.Many2one('woo.sale.order.status', - 'WooCommerce Order Status') - - openerp_id = fields.Many2one(comodel_name='sale.order', - string='Sale Order', - required=True, - ondelete='cascade') - woo_order_line_ids = fields.One2many( - comodel_name='woo.sale.order.line', - inverse_name='woo_order_id', - string='Woo Order Lines' - ) - backend_id = fields.Many2one( - comodel_name='wc.backend', - string='Woo Backend', - store=True, - readonly=False, - required=True, - ) - - -class WooSaleOrderLine(models.Model): - _name = 'woo.sale.order.line' - _inherits = {'sale.order.line': 'openerp_id'} - - woo_order_id = fields.Many2one(comodel_name='woo.sale.order', - string='Woo Sale Order', - required=True, - ondelete='cascade', - select=True) - - openerp_id = fields.Many2one(comodel_name='sale.order.line', - string='Sale Order Line', - required=True, - ondelete='cascade') - - backend_id = fields.Many2one( - related='woo_order_id.backend_id', - string='Woo Backend', - readonly=True, - store=True, - required=False, - ) - - @api.model - def create(self, vals): - woo_order_id = vals['woo_order_id'] - binding = self.env['woo.sale.order'].browse(woo_order_id) - vals['order_id'] = binding.openerp_id.id - binding = super(WooSaleOrderLine, self).create(vals) - return binding - - -class SaleOrderLine(models.Model): - _inherit = 'sale.order.line' - - woo_bind_ids = fields.One2many( - comodel_name='woo.sale.order.line', - inverse_name='openerp_id', - string="WooCommerce Bindings", - ) - - -@woo -class SaleOrderLineImportMapper(ImportMapper): - _model_name = 'woo.sale.order.line' - - direct = [('quantity', 'product_uom_qty'), - ('quantity', 'product_uos_qty'), - ('name', 'name'), - ('price', 'price_unit') - ] - - @mapping - def product_id(self, record): - binder = self.binder_for('woo.product.product') - product_id = binder.to_openerp(record['product_id'], unwrap=True) - assert product_id is not None, ( - "product_id %s should have been imported in " - "SaleOrderImporter._import_dependencies" % record['product_id']) - return {'product_id': product_id} - - -@woo -class SaleOrderAdapter(GenericAdapter): - _model_name = 'woo.sale.order' - _woo_model = 'orders' - - def _call(self, method, arguments): - try: - return super(SaleOrderAdapter, self)._call(method, arguments) - except xmlrpclib.Fault as err: - # this is the error in the Woo API - # when the customer does not exist - if err.faultCode == 102: - raise IDMissingInBackend - else: - raise - - def search(self, filters=None, from_date=None, to_date=None): - """ Search records according to some criteria and return a - list of ids - - :rtype: list - """ - if filters is None: - filters = {} - WOO_DATETIME_FORMAT = '%Y/%m/%d %H:%M:%S' - dt_fmt = WOO_DATETIME_FORMAT - if from_date is not None: - # updated_at include the created records - filters.setdefault('updated_at', {}) - filters['updated_at']['from'] = from_date.strftime(dt_fmt) - if to_date is not None: - filters.setdefault('updated_at', {}) - filters['updated_at']['to'] = to_date.strftime(dt_fmt) - - return self._call('orders/list', - [filters] if filters else [{}]) - - -@woo -class SaleOrderBatchImporter(DelayedBatchImporter): - - """ Import the WooCommerce Partners. - - For every partner in the list, a delayed job is created. - """ - _model_name = ['woo.sale.order'] - - def _import_record(self, woo_id, priority=None): - """ Delay a job for the import """ - super(SaleOrderBatchImporter, self)._import_record( - woo_id, priority=priority) - - def update_existing_order(self, woo_sale_order, record_id): - """ Enter Your logic for Existing Sale Order """ - return True - - def run(self, filters=None): - """ Run the synchronization """ -# - from_date = filters.pop('from_date', None) - to_date = filters.pop('to_date', None) - record_ids = self.backend_adapter.search( - filters, - from_date=from_date, - to_date=to_date, - ) - order_ids = [] - for record_id in record_ids: - woo_sale_order = self.env['woo.sale.order'].search( - [('woo_id', '=', record_id)]) - if woo_sale_order: - self.update_existing_order(woo_sale_order[0], record_id) - else: - order_ids.append(record_id) - _logger.info('search for woo partners %s returned %s', - filters, record_ids) - for record_id in order_ids: - self._import_record(record_id, 50) - - -SaleOrderBatchImporter = SaleOrderBatchImporter -# - - -@woo -class SaleOrderImporter(WooImporter): - _model_name = ['woo.sale.order'] - - def _import_addresses(self): - record = self.woo_record - record = record['order'] - self._import_dependency(record['customer_id'], - 'woo.res.partner') - - def _import_dependencies(self): - """ Import the dependencies for the record""" - record = self.woo_record - - self._import_addresses() - record = record['items'] - for line in record: - _logger.debug('line: %s', line) - if 'product_id' in line: - self._import_dependency(line['product_id'], - 'woo.product.product') - - def _clean_woo_items(self, resource): - """ - Method that clean the sale order line given by WooCommerce before - importing it - - This method has to stay here because it allow to customize the - behavior of the sale order. - - """ - child_items = {} # key is the parent item id - top_items = [] - - # Group the childs with their parent - for item in resource['order']['line_items']: - if item.get('parent_item_id'): - child_items.setdefault(item['parent_item_id'], []).append(item) - else: - top_items.append(item) - - all_items = [] - for top_item in top_items: - all_items.append(top_item) - resource['items'] = all_items - return resource - - def _create(self, data): - openerp_binding = super(SaleOrderImporter, self)._create(data) - return openerp_binding - - def _after_import(self, binding): - """ Hook called at the end of the import """ - return - - def _get_woo_data(self): - """ Return the raw WooCommerce data for ``self.woo_id`` """ - record = super(SaleOrderImporter, self)._get_woo_data() - # sometimes we need to clean woo items (ex : configurable - # product in a sale) - record = self._clean_woo_items(record) - return record -SaleOrderImport = SaleOrderImporter - - -@woo -class SaleOrderImportMapper(ImportMapper): - _model_name = 'woo.sale.order' - - children = [('items', 'woo_order_line_ids', 'woo.sale.order.line'), - ] - - @mapping - def status(self, record): - if record['order']: - rec = record['order'] - if rec['status']: - status_id = self.env['woo.sale.order.status'].search( - [('name', '=', rec['status'])]) - if status_id: - return {'status_id': status_id[0].id} - else: - status_id = self.env['woo.sale.order.status'].create({ - 'name': rec['status'] - }) - return {'status_id': status_id.id} - else: - return {'status_id': False} - - @mapping - def customer_id(self, record): - if record['order']: - rec = record['order'] - binder = self.binder_for('woo.res.partner') - if rec['customer_id']: - partner_id = binder.to_openerp(rec['customer_id'], - unwrap=True) or False -# customer_id = str(rec['customer_id']) - assert partner_id, ("Please Check Customer Role \ - in WooCommerce") - result = {'partner_id': partner_id} - onchange_val = self.env['sale.order'].onchange_partner_id( - partner_id) - result.update(onchange_val['value']) - else: - customer = rec['customer']['billing_address'] - country_id = False - state_id = False - if customer['country']: - country_id = self.env['res.country'].search( - [('code', '=', customer['country'])]) - if country_id: - country_id = country_id.id - if customer['state']: - state_id = self.env['res.country.state'].search( - [('code', '=', customer['state'])]) - if state_id: - state_id = state_id.id - name = customer['first_name'] + ' ' + customer['last_name'] - partner_dict = { - 'name': name, - 'city': customer['city'], - 'phone': customer['phone'], - 'zip': customer['postcode'], - 'state_id': state_id, - 'country_id': country_id - } - partner_id = self.env['res.partner'].create(partner_dict) - partner_dict.update({ - 'backend_id': self.backend_record.id, - 'openerp_id': partner_id.id, - }) -# woo_partner_id = self.env['woo.res.partner'].create( -# partner_dict) - result = {'partner_id': partner_id.id} - onchange_val = self.env['sale.order'].onchange_partner_id( - partner_id.id) - result.update(onchange_val['value']) - return result - - @mapping - def backend_id(self, record): - return {'backend_id': self.backend_record.id} - - -@job(default_channel='root.woo') -def sale_order_import_batch(session, model_name, backend_id, filters=None): - """ Prepare the import of Sale Order modified on Woo """ - env = get_environment(session, model_name, backend_id) - importer = env.get_connector_unit(SaleOrderBatchImporter) - importer.run(filters=filters) diff --git a/connector_woocommerce/related_action.py b/connector_woocommerce/related_action.py deleted file mode 100755 index aaabb39..0000000 --- a/connector_woocommerce/related_action.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -import functools -from openerp import exceptions, _ -from openerp.addons.connector import related_action -from .connector import get_environment -from .unit.backend_adapter import GenericAdapter -from .unit.binder import WooBinder - -unwrap_binding = functools.partial(related_action.unwrap_binding, - binder_class=WooBinder) - - -def link(session, job, backend_id_pos=2, woo_id_pos=3): - """ Open a Woo URL on the admin page to view/edit the record - related to the job. - """ - binding_model = job.args[0] - # shift one to the left because session is not in job.args - backend_id = job.args[backend_id_pos - 1] - woo_id = job.args[woo_id_pos - 1] - env = get_environment(session, binding_model, backend_id) - adapter = env.get_connector_unit(GenericAdapter) - try: - url = adapter.admin_url(woo_id) - except ValueError: - raise exceptions.Warning( - _('No admin URL configured on the backend or ' - 'no admin path is defined for this record.') - ) - - action = { - 'type': 'ir.actions.act_url', - 'target': 'new', - 'url': url, - } - return action diff --git a/connector_woocommerce/unit/__init__.py b/connector_woocommerce/unit/__init__.py deleted file mode 100755 index bf30deb..0000000 --- a/connector_woocommerce/unit/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -from . import import_synchronizer -from . import backend_adapter -from . import mapper -from . import binder diff --git a/connector_woocommerce/unit/backend_adapter.py b/connector_woocommerce/unit/backend_adapter.py deleted file mode 100755 index 6073968..0000000 --- a/connector_woocommerce/unit/backend_adapter.py +++ /dev/null @@ -1,227 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -import socket -import logging -import xmlrpclib -from woocommerce import API - -from openerp.addons.connector.unit.backend_adapter import CRUDAdapter -from openerp.addons.connector.exception import (NetworkRetryableError, - RetryableJobError) -from openerp.tools.safe_eval import safe_eval - -from datetime import datetime -_logger = logging.getLogger(__name__) - -recorder = {} - - -def call_to_key(method, arguments): - """ Used to 'freeze' the method and arguments of a call to WooCommerce - so they can be hashable; they will be stored in a dict. - - Used in both the recorder and the tests. - """ - def freeze(arg): - if isinstance(arg, dict): - items = dict((key, freeze(value)) for key, value - in arg.iteritems()) - return frozenset(items.iteritems()) - elif isinstance(arg, list): - return tuple([freeze(item) for item in arg]) - else: - return arg - - new_args = [] - for arg in arguments: - new_args.append(freeze(arg)) - return (method, tuple(new_args)) - - -def record(method, arguments, result): - """ Utility function which can be used to record test data - during synchronisations. Call it from WooCRUDAdapter._call - - Then ``output_recorder`` can be used to write the data recorded - to a file. - """ - recorder[call_to_key(method, arguments)] = result - - -def output_recorder(filename): - import pprint - with open(filename, 'w') as f: - pprint.pprint(recorder, f) - _logger.debug('recorder written to file %s', filename) - - -class WooLocation(object): - - def __init__(self, location, consumer_key, consumre_secret): - self._location = location - self.consumer_key = consumer_key - self.consumer_secret = consumre_secret - - @property - def location(self): - location = self._location - return location - - -class WooCRUDAdapter(CRUDAdapter): - - """ External Records Adapter for woo """ - - def __init__(self, connector_env): - """ - - :param connector_env: current environment (backend, session, ...) - :type connector_env: :class:`connector.connector.ConnectorEnvironment` - """ - super(WooCRUDAdapter, self).__init__(connector_env) - backend = self.backend_record - woo = WooLocation( - backend.location, - backend.consumer_key, - backend.consumer_secret) - self.woo = woo - - def search(self, filters=None): - """ Search records according to some criterias - and returns a list of ids """ - raise NotImplementedError - - def read(self, id, attributes=None): - """ Returns the information of a record """ - raise NotImplementedError - - def search_read(self, filters=None): - """ Search records according to some criterias - and returns their information""" - raise NotImplementedError - - def create(self, data): - """ Create a record on the external system """ - raise NotImplementedError - - def write(self, id, data): - """ Update records on the external system """ - raise NotImplementedError - - def delete(self, id): - """ Delete a record on the external system """ - raise NotImplementedError - - def _call(self, method, arguments): - try: - _logger.debug("Start calling Woocommerce api %s", method) - api = API(url=self.woo.location, - consumer_key=self.woo.consumer_key, - consumer_secret=self.woo.consumer_secret, - version='v2') - if api: - if isinstance(arguments, list): - while arguments and arguments[-1] is None: - arguments.pop() - start = datetime.now() - try: - if 'false' or 'true' or 'null'in api.get(method).content: - result = api.get(method).content.replace( - 'false', 'False') - result = result.replace('true', 'True') - result = result.replace('null', 'False') - result = safe_eval(result) - else: - result = safe_eval(api.get(method).content) - except: - _logger.error("api.call(%s, %s) failed", method, arguments) - raise - else: - _logger.debug("api.call(%s, %s) returned %s in %s seconds", - method, arguments, result, - (datetime.now() - start).seconds) - return result - except (socket.gaierror, socket.error, socket.timeout) as err: - raise NetworkRetryableError( - 'A network error caused the failure of the job: ' - '%s' % err) - except xmlrpclib.ProtocolError as err: - if err.errcode in [502, # Bad gateway - 503, # Service unavailable - 504]: # Gateway timeout - raise RetryableJobError( - 'A protocol error caused the failure of the job:\n' - 'URL: %s\n' - 'HTTP/HTTPS headers: %s\n' - 'Error code: %d\n' - 'Error message: %s\n' % - (err.url, err.headers, err.errcode, err.errmsg)) - else: - raise - - -class GenericAdapter(WooCRUDAdapter): - - _model_name = None - _woo_model = None - - def search(self, filters=None): - """ Search records according to some criterias - and returns a list of ids - - :rtype: list - """ - return self._call('%s.search' % self._woo_model, - [filters] if filters else [{}]) - - def read(self, id, attributes=None): - """ Returns the information of a record - - :rtype: dict - """ - arguments = [] - if attributes: - # Avoid to pass Null values in attributes. Workaround for - # is not installed, calling info() with None in attributes - # would return a wrong result (almost empty list of - # attributes). The right correction is to install the - # compatibility patch on WooCommerce. - arguments.append(attributes) - return self._call('%s/' % self._woo_model + str(id), []) - - def search_read(self, filters=None): - """ Search records according to some criterias - and returns their information""" - return self._call('%s.list' % self._woo_model, [filters]) - - def create(self, data): - """ Create a record on the external system """ - return self._call('%s.create' % self._woo_model, [data]) - - def write(self, id, data): - """ Update records on the external system """ - return self._call('%s.update' % self._woo_model, - [int(id), data]) - - def delete(self, id): - """ Delete a record on the external system """ - return self._call('%s.delete' % self._woo_model, [int(id)]) diff --git a/connector_woocommerce/unit/binder.py b/connector_woocommerce/unit/binder.py deleted file mode 100755 index bf4cfc8..0000000 --- a/connector_woocommerce/unit/binder.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -import openerp -from openerp.addons.connector.connector import Binder -from ..backend import woo - - -class WooBinder(Binder): - - """ Generic Binder for WooCommerce """ - - -@woo -class WooModelBinder(WooBinder): - - """ - Bindings are done directly on the binding model.woo.product.category - - Binding models are models called ``woo.{normal_model}``, - like ``woo.res.partner`` or ``woo.product.product``. - They are ``_inherits`` of the normal models and contains - the Woo ID, the ID of the Woo Backend and the additional - fields belonging to the Woo instance. - """ - _model_name = [ - 'woo.res.partner', - 'woo.product.category', - 'woo.product.product', - 'woo.sale.order', - 'woo.sale.order.line', - ] - - def to_openerp(self, external_id, unwrap=False, browse=False): - """ Give the OpenERP ID for an external ID - - :param external_id: external ID for which we want the OpenERP ID - :param unwrap: if True, returns the normal record (the one - inherits'ed), else return the binding record - :param browse: if True, returns a recordset - :return: a recordset of one record, depending on the value of unwrap, - or an empty recordset if no binding is found - :rtype: recordset - """ - bindings = self.model.with_context(active_test=False).search( - [('woo_id', '=', str(external_id)), - ('backend_id', '=', self.backend_record.id)] - ) - if not bindings: - return self.model.browse() if browse else None - assert len(bindings) == 1, "Several records found: %s" % (bindings,) - if unwrap: - return bindings.openerp_id if browse else bindings.openerp_id.id - else: - return bindings if browse else bindings.id - - def to_backend(self, record_id, wrap=False): - """ Give the external ID for an OpenERP ID - - :param record_id: OpenERP ID for which we want the external id - or a recordset with one record - :param wrap: if False, record_id is the ID of the binding, - if True, record_id is the ID of the normal record, the - method will search the corresponding binding and returns - the backend id of the binding - :return: backend identifier of the record - """ - record = self.model.browse() - if isinstance(record_id, openerp.models.BaseModel): - record_id.ensure_one() - record = record_id - record_id = record_id.id - if wrap: - binding = self.model.with_context(active_test=False).search( - [('openerp_id', '=', record_id), - ('backend_id', '=', self.backend_record.id), - ] - ) - if binding: - binding.ensure_one() - return binding.woo_id - else: - return None - if not record: - record = self.model.browse(record_id) - assert record - return record.woo_id - - def bind(self, external_id, binding_id): - """ Create the link between an external ID and an OpenERP ID and - update the last synchronization date. - - :param external_id: External ID to bind - :param binding_id: OpenERP ID to bind - :type binding_id: int - """ - # the external ID can be 0 on Woo! Prevent False values - # like False, None, or "", but not 0. - assert (external_id or external_id == 0) and binding_id, ( - "external_id or binding_id missing, " - "got: %s, %s" % (external_id, binding_id) - ) - # avoid to trigger the export when we modify the `woo_id` - now_fmt = openerp.fields.Datetime.now() - if not isinstance(binding_id, openerp.models.BaseModel): - binding_id = self.model.browse(binding_id) - binding_id.with_context(connector_no_export=True).write( - {'woo_id': str(external_id), - 'sync_date': now_fmt, - }) - - def unwrap_binding(self, binding_id, browse=False): - """ For a binding record, gives the normal record. - - Example: when called with a ``woo.product.product`` id, - it will return the corresponding ``product.product`` id. - - :param browse: when True, returns a browse_record instance - rather than an ID - """ - if isinstance(binding_id, openerp.models.BaseModel): - binding = binding_id - else: - binding = self.model.browse(binding_id) - - openerp_record = binding.openerp_id - if browse: - return openerp_record - return openerp_record.id - - def unwrap_model(self): - """ For a binding model, gives the name of the normal model. - - Example: when called on a binder for ``woo.product.product``, - it will return ``product.product``. - - This binder assumes that the normal model lays in ``openerp_id`` since - this is the field we use in the ``_inherits`` bindings. - """ - try: - column = self.model._fields['openerp_id'] - except KeyError: - raise ValueError('Cannot unwrap model %s, because it has ' - 'no openerp_id field' % self.model._name) - return column.comodel_name diff --git a/connector_woocommerce/unit/import_synchronizer.py b/connector_woocommerce/unit/import_synchronizer.py deleted file mode 100755 index fb3535a..0000000 --- a/connector_woocommerce/unit/import_synchronizer.py +++ /dev/null @@ -1,284 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - -import logging -from openerp import fields, _ -from openerp.addons.connector.queue.job import job, related_action -from openerp.addons.connector.unit.synchronizer import Importer -from openerp.addons.connector.exception import IDMissingInBackend -from ..connector import get_environment -from ..related_action import link -from datetime import datetime -_logger = logging.getLogger(__name__) - - -class WooImporter(Importer): - - """ Base importer for WooCommerce """ - - def __init__(self, connector_env): - """ - :param connector_env: current environment (backend, session, ...) - :type connector_env: :class:`connector.connector.ConnectorEnvironment` - """ - super(WooImporter, self).__init__(connector_env) - self.woo_id = None - self.woo_record = None - - def _get_woo_data(self): - """ Return the raw WooCommerce data for ``self.woo_id`` """ - return self.backend_adapter.read(self.woo_id) - - def _before_import(self): - """ Hook called before the import, when we have the WooCommerce - data""" - - def _is_uptodate(self, binding): - """Return True if the import should be skipped because - it is already up-to-date in OpenERP""" - WOO_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' - dt_fmt = WOO_DATETIME_FORMAT - assert self.woo_record - if not self.woo_record: - return # no update date on WooCommerce, always import it. - if not binding: - return # it does not exist so it should not be skipped - sync = binding.sync_date - if not sync: - return - from_string = fields.Datetime.from_string - sync_date = from_string(sync) - self.woo_record['updated_at'] = {} - self.woo_record['updated_at'] = {'to': datetime.now().strftime(dt_fmt)} - woo_date = from_string(self.woo_record['updated_at']['to']) - # if the last synchronization date is greater than the last - # update in woo, we skip the import. - # Important: at the beginning of the exporters flows, we have to - # check if the woo_date is more recent than the sync_date - # and if so, schedule a new import. If we don't do that, we'll - # miss changes done in WooCommerce - return woo_date < sync_date - - def _import_dependency(self, woo_id, binding_model, - importer_class=None, always=False): - """ Import a dependency. - - The importer class is a class or subclass of - :class:`WooImporter`. A specific class can be defined. - - :param woo_id: id of the related binding to import - :param binding_model: name of the binding model for the relation - :type binding_model: str | unicode - :param importer_cls: :class:`openerp.addons.connector.\ - connector.ConnectorUnit` - class or parent class to use for the export. - By default: WooImporter - :type importer_cls: :class:`openerp.addons.connector.\ - connector.MetaConnectorUnit` - :param always: if True, the record is updated even if it already - exists, note that it is still skipped if it has - not been modified on WooCommerce since the last - update. When False, it will import it only when - it does not yet exist. - :type always: boolean - """ - if not woo_id: - return - if importer_class is None: - importer_class = WooImporter - binder = self.binder_for(binding_model) - if always or binder.to_openerp(woo_id) is None: - importer = self.unit_for(importer_class, model=binding_model) - importer.run(woo_id) - - def _import_dependencies(self): - """ Import the dependencies for the record - - Import of dependencies can be done manually or by calling - :meth:`_import_dependency` for each dependency. - """ - return - - def _map_data(self): - """ Returns an instance of - :py:class:`~openerp.addons.connector.unit.mapper.MapRecord` - - """ - return self.mapper.map_record(self.woo_record) - - def _validate_data(self, data): - """ Check if the values to import are correct - - Pro-actively check before the ``_create`` or - ``_update`` if some fields are missing or invalid. - - Raise `InvalidDataError` - """ - return - - def _must_skip(self): - """ Hook called right after we read the data from the backend. - - If the method returns a message giving a reason for the - skipping, the import will be interrupted and the message - recorded in the job (if the import is called directly by the - job, not by dependencies). - - If it returns None, the import will continue normally. - - :returns: None | str | unicode - """ - return - - def _get_binding(self): - return self.binder.to_openerp(self.woo_id, browse=True) - - def _create_data(self, map_record, **kwargs): - return map_record.values(for_create=True, **kwargs) - - def _create(self, data): - """ Create the OpenERP record """ - # special check on data before import - self._validate_data(data) - model = self.model.with_context(connector_no_export=True) - model = str(model).split('()')[0] - binding = self.env[model].create(data) - _logger.debug('%d created from woo %s', binding, self.woo_id) - return binding - - def _update_data(self, map_record, **kwargs): - return map_record.values(**kwargs) - - def _update(self, binding, data): - """ Update an OpenERP record """ - # special check on data before import - self._validate_data(data) - binding.with_context(connector_no_export=True).write(data) - _logger.debug('%d updated from woo %s', binding, self.woo_id) - return - - def _after_import(self, binding): - """ Hook called at the end of the import """ - return - - def run(self, woo_id, force=False): - """ Run the synchronization - - :param woo_id: identifier of the record on WooCommerce - """ - self.woo_id = woo_id - try: - self.woo_record = self._get_woo_data() - except IDMissingInBackend: - return _('Record does no longer exist in WooCommerce') - - skip = self._must_skip() - if skip: - return skip - - binding = self._get_binding() - if not force and self._is_uptodate(binding): - return _('Already up-to-date.') - self._before_import() - - # import the missing linked resources - self._import_dependencies() - - map_record = self._map_data() - - if binding: - record = self._update_data(map_record) - self._update(binding, record) - else: - record = self._create_data(map_record) - binding = self._create(record) - self.binder.bind(self.woo_id, binding) - - self._after_import(binding) - - -WooImportSynchronizer = WooImporter - - -class BatchImporter(Importer): - - """ The role of a BatchImporter is to search for a list of - items to import, then it can either import them directly or delay - the import of each item separately. - """ - - def run(self, filters=None): - """ Run the synchronization """ - record_ids = self.backend_adapter.search(filters) - for record_id in record_ids: - self._import_record(record_id) - - def _import_record(self, record_id): - """ Import a record directly or delay the import of the record. - - Method to implement in sub-classes. - """ - raise NotImplementedError - - -BatchImportSynchronizer = BatchImporter - - -class DirectBatchImporter(BatchImporter): - - """ Import the records directly, without delaying the jobs. """ - _model_name = None - - def _import_record(self, record_id): - """ Import the record directly """ - import_record(self.session, - self.model._name, - self.backend_record.id, - record_id) - - -DirectBatchImport = DirectBatchImporter - - -class DelayedBatchImporter(BatchImporter): - - """ Delay import of the records """ - _model_name = None - - def _import_record(self, record_id, **kwargs): - """ Delay the import of the records""" - import_record.delay(self.session, - self.model._name, - self.backend_record.id, - record_id, - **kwargs) - - -DelayedBatchImport = DelayedBatchImporter - - -@job(default_channel='root.woo') -@related_action(action=link) -def import_record(session, model_name, backend_id, woo_id, force=False): - """ Import a record from Woo """ - env = get_environment(session, model_name, backend_id) - importer = env.get_connector_unit(WooImporter) - importer.run(woo_id, force=force) diff --git a/connector_woocommerce/unit/mapper.py b/connector_woocommerce/unit/mapper.py deleted file mode 100755 index 1d148a7..0000000 --- a/connector_woocommerce/unit/mapper.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Tech-Receptives Solutions Pvt. Ltd. -# Copyright (C) 2009-TODAY Tech-Receptives(<http://www.techreceptives.com>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# - - -def normalize_datetime(field): - """Change a invalid date which comes from Woo, if - no real date is set to null for correct import to - OpenERP""" - - def modifier(self, record, to_attr): - if record[field] == '0000-00-00 00:00:00': - return None - return record[field] - return modifier