From 768731da0fa5c56de281f22e9f79a308f66f9ae7 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Wed, 1 Apr 2026 19:10:47 -0400 Subject: [PATCH] fix: use Odoo public image URLs instead of broken WP media upload WC consumer key cannot auth against /wp/v2/media (401). Instead, pass Odoo's public image URL in the product/variation data and let WC download it directly from {odoo_base}/web/image/product.product/{id}/image_1920. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../models/woo_product_map.py | 33 ++---- .../wizard/woo_product_create.py | 101 +++++------------- .../wizard/woo_variant_push.py | 91 +++++----------- 3 files changed, 62 insertions(+), 163 deletions(-) diff --git a/fusion-woo-odoo/fusion_woocommerce/models/woo_product_map.py b/fusion-woo-odoo/fusion_woocommerce/models/woo_product_map.py index 6156d5b6..e9b31d96 100644 --- a/fusion-woo-odoo/fusion_woocommerce/models/woo_product_map.py +++ b/fusion-woo-odoo/fusion_woocommerce/models/woo_product_map.py @@ -246,30 +246,17 @@ class WooProductMap(models.Model): if wc_tax_class: var_data['tax_class'] = wc_tax_class - # Variant image + # Variant image — pass Odoo's public URL, WC downloads it directly if variant.image_variant_1920: - # Upload image - try: - import base64 - import requests as req - img_bytes = base64.b64decode(variant.image_variant_1920) - wp_url = inst.url.rstrip('/') - filename = (variant.default_code or 'variant') + '.jpg' - resp = req.post( - f"{wp_url}/wp-json/wp/v2/media", - auth=(inst.consumer_key, inst.consumer_secret), - headers={ - 'Content-Disposition': f'attachment; filename="{filename}"', - 'Content-Type': 'image/jpeg', - }, - data=img_bytes, - timeout=60, - ) - if resp.status_code in (200, 201): - media = resp.json() - var_data['image'] = {'id': media['id']} - except Exception as img_err: - _logger.warning("Variant image upload failed: %s", img_err) + odoo_base = inst.env['ir.config_parameter'].sudo().get_param('web.base.url', '') + filename = (variant.default_code or 'variant') + '.jpg' + if odoo_base: + img_url = f"{odoo_base}/web/image/product.product/{variant.id}/image_1920" + var_data['image'] = { + 'src': img_url, + 'name': filename, + 'alt': variant.display_name, + } try: wc_variation = client.create_product_variation(self.woo_product_id, var_data) diff --git a/fusion-woo-odoo/fusion_woocommerce/wizard/woo_product_create.py b/fusion-woo-odoo/fusion_woocommerce/wizard/woo_product_create.py index 2259e6df..19ed9088 100644 --- a/fusion-woo-odoo/fusion_woocommerce/wizard/woo_product_create.py +++ b/fusion-woo-odoo/fusion_woocommerce/wizard/woo_product_create.py @@ -472,59 +472,27 @@ class WooProductCreateWizard(models.TransientModel): except (json.JSONDecodeError, TypeError): pass + # Set product images via Odoo's public URL — WC downloads them directly + # WC consumer key/secret cannot authenticate against /wp/v2/media (401) wc_images = [] - for i in range(1, 6): - img_data = getattr(self, f'image_{i}', None) - if not img_data: - continue - filename = getattr(self, f'image_{i}_filename', '') or f'product_image_{i}.jpg' - - # Find AI metadata for this image - img_meta = next((m for m in image_metadata if m.get('index') == i), {}) - - # Upload image via WordPress REST API - try: - img_bytes = base64.b64decode( - img_data if isinstance(img_data, str) else img_data.decode('utf-8') - ) - - import requests - wp_url = inst.url.rstrip('/') - upload_url = f"{wp_url}/wp-json/wp/v2/media" - - headers = { - 'Content-Disposition': f'attachment; filename="{filename}"', - 'Content-Type': 'image/jpeg', + odoo_base = inst.env['ir.config_parameter'].sudo().get_param('web.base.url', '') + if odoo_base and self.product_template_id: + tmpl_id = self.product_template_id.id + for i in range(1, 6): + img_data = getattr(self, f'image_{i}', None) + if not img_data: + continue + filename = getattr(self, f'image_{i}_filename', '') or f'product_image_{i}.jpg' + img_meta = next((m for m in image_metadata if m.get('index') == i), {}) + img_url = f"{odoo_base}/web/image/product.template/{tmpl_id}/image_1920" + wc_img = { + 'src': img_url, + 'name': img_meta.get('title', filename), + 'alt': img_meta.get('alt_text', ''), } - - resp = requests.post( - upload_url, - auth=(inst.consumer_key, inst.consumer_secret), - headers=headers, - data=img_bytes, - timeout=60, - ) - - if resp.status_code in (200, 201): - media = resp.json() - wc_img = { - 'id': media['id'], - 'alt': img_meta.get('alt_text', ''), - 'name': img_meta.get('title', filename), - 'caption': img_meta.get('caption', ''), - 'description': img_meta.get('description', ''), - } - # First image is featured - if i == 1: - wc_img['position'] = 0 - wc_images.append(wc_img) - else: - _logger.warning( - "Image upload failed (HTTP %s): %s", - resp.status_code, resp.text[:200], - ) - except Exception as e: - _logger.error("Image upload error: %s", str(e)) + if i == 1: + wc_img['position'] = 0 + wc_images.append(wc_img) if wc_images: wc_data['images'] = wc_images @@ -615,32 +583,17 @@ class WooProductCreateWizard(models.TransientModel): 'stock_quantity': int(variant.qty_available), } - # Upload variant image if present + # Variant image — pass Odoo's public URL, WC downloads it directly if line.image: - try: - img_bytes = base64.b64decode( - line.image if isinstance(line.image, str) else line.image.decode('utf-8') - ) - import requests as req - wp_url = inst.url.rstrip('/') - upload_url = f"{wp_url}/wp-json/wp/v2/media" + odoo_base = inst.env['ir.config_parameter'].sudo().get_param('web.base.url', '') + if odoo_base and variant.id: var_filename = f"variant_{line.sku or variant.id}.jpg" - headers = { - 'Content-Disposition': f'attachment; filename="{var_filename}"', - 'Content-Type': 'image/jpeg', + img_url = f"{odoo_base}/web/image/product.product/{variant.id}/image_1920" + var_data['image'] = { + 'src': img_url, + 'name': var_filename, + 'alt': line.variant_name or '', } - resp = req.post( - upload_url, - auth=(inst.consumer_key, inst.consumer_secret), - headers=headers, - data=img_bytes, - timeout=60, - ) - if resp.status_code in (200, 201): - media = resp.json() - var_data['image'] = {'id': media['id']} - except Exception as img_e: - _logger.warning("Variant image upload failed for %s: %s", line.variant_name, img_e) # Tax class if self.wc_tax_class: diff --git a/fusion-woo-odoo/fusion_woocommerce/wizard/woo_variant_push.py b/fusion-woo-odoo/fusion_woocommerce/wizard/woo_variant_push.py index 62d757a3..2ef9a206 100644 --- a/fusion-woo-odoo/fusion_woocommerce/wizard/woo_variant_push.py +++ b/fusion-woo-odoo/fusion_woocommerce/wizard/woo_variant_push.py @@ -171,43 +171,17 @@ class WooVariantPushWizard(models.TransientModel): if wc_tax_class: var_data['tax_class'] = wc_tax_class - # Upload variant image - if line.image: - try: - img_data = line.image - if isinstance(img_data, bytes): - img_data = img_data.decode('utf-8') - - # Geo-tag if configured - if inst.geo_lat and inst.geo_lng: - img_data = ImageProcessor.geo_tag_image( - img_data, - inst.geo_company_name, - inst.geo_company_address, - inst.geo_company_phone, - inst.geo_lat, - inst.geo_lng, - ) - - img_bytes = base64.b64decode(img_data) - import requests as req - wp_url = inst.url.rstrip('/') - filename = (line.sku or 'variant_%d' % variant.id) + '.jpg' - resp = req.post( - f"{wp_url}/wp-json/wp/v2/media", - auth=(inst.consumer_key, inst.consumer_secret), - headers={ - 'Content-Disposition': f'attachment; filename="{filename}"', - 'Content-Type': 'image/jpeg', - }, - data=img_bytes, - timeout=60, - ) - if resp.status_code in (200, 201): - media = resp.json() - var_data['image'] = {'id': media['id']} - except Exception as img_err: - _logger.warning("Variant image upload failed: %s", img_err) + # Set variant image via Odoo's public image URL + if variant.id and (variant.image_variant_1920 or variant.image_1920): + odoo_base = inst.env['ir.config_parameter'].sudo().get_param('web.base.url', '') + if odoo_base: + img_url = f"{odoo_base}/web/image/product.product/{variant.id}/image_1920" + img_name = (line.sku or variant.default_code or 'variant') + '.jpg' + var_data['image'] = { + 'src': img_url, + 'name': img_name, + 'alt': line.variant_name or '', + } try: wc_variation = client.create_product_variation(pm.woo_product_id, var_data) @@ -270,35 +244,20 @@ class WooVariantPushWizard(models.TransientModel): if wc_tax_class: var_data['tax_class'] = wc_tax_class - # Upload new image if changed - if line.image: - try: - img_data = line.image - if isinstance(img_data, bytes): - img_data = img_data.decode('utf-8') - if inst.geo_lat and inst.geo_lng: - img_data = ImageProcessor.geo_tag_image( - img_data, inst.geo_company_name, - inst.geo_company_address, inst.geo_company_phone, - inst.geo_lat, inst.geo_lng, - ) - img_bytes = base64.b64decode(img_data) - import requests as req - wp_url = inst.url.rstrip('/') - filename = (line.sku or 'variant_%d' % variant.id) + '.jpg' - resp = req.post( - f"{wp_url}/wp-json/wp/v2/media", - auth=(inst.consumer_key, inst.consumer_secret), - headers={ - 'Content-Disposition': f'attachment; filename="{filename}"', - 'Content-Type': 'image/jpeg', - }, - data=img_bytes, timeout=60, - ) - if resp.status_code in (200, 201): - var_data['image'] = {'id': resp.json()['id']} - except Exception as img_err: - _logger.warning("Variant image upload failed: %s", img_err) + # Set variant image via Odoo's public image URL + # WC downloads the image from this URL directly + if variant.id and (variant.image_variant_1920 or variant.image_1920): + # Build the public Odoo image URL + odoo_base = inst.env['ir.config_parameter'].sudo().get_param('web.base.url', '') + if odoo_base: + img_url = f"{odoo_base}/web/image/product.product/{variant.id}/image_1920" + img_name = (line.sku or variant.default_code or 'variant') + '.jpg' + var_data['image'] = { + 'src': img_url, + 'name': img_name, + 'alt': line.variant_name or '', + } + _logger.info("Variant image URL: %s", img_url) try: client.update_product_variation(pm.woo_product_id, wc_var_id, var_data)