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) <noreply@anthropic.com>
This commit is contained in:
@@ -246,30 +246,17 @@ class WooProductMap(models.Model):
|
|||||||
if wc_tax_class:
|
if wc_tax_class:
|
||||||
var_data['tax_class'] = 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:
|
if variant.image_variant_1920:
|
||||||
# Upload image
|
odoo_base = inst.env['ir.config_parameter'].sudo().get_param('web.base.url', '')
|
||||||
try:
|
filename = (variant.default_code or 'variant') + '.jpg'
|
||||||
import base64
|
if odoo_base:
|
||||||
import requests as req
|
img_url = f"{odoo_base}/web/image/product.product/{variant.id}/image_1920"
|
||||||
img_bytes = base64.b64decode(variant.image_variant_1920)
|
var_data['image'] = {
|
||||||
wp_url = inst.url.rstrip('/')
|
'src': img_url,
|
||||||
filename = (variant.default_code or 'variant') + '.jpg'
|
'name': filename,
|
||||||
resp = req.post(
|
'alt': variant.display_name,
|
||||||
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)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wc_variation = client.create_product_variation(self.woo_product_id, var_data)
|
wc_variation = client.create_product_variation(self.woo_product_id, var_data)
|
||||||
|
|||||||
@@ -472,59 +472,27 @@ class WooProductCreateWizard(models.TransientModel):
|
|||||||
except (json.JSONDecodeError, TypeError):
|
except (json.JSONDecodeError, TypeError):
|
||||||
pass
|
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 = []
|
wc_images = []
|
||||||
for i in range(1, 6):
|
odoo_base = inst.env['ir.config_parameter'].sudo().get_param('web.base.url', '')
|
||||||
img_data = getattr(self, f'image_{i}', None)
|
if odoo_base and self.product_template_id:
|
||||||
if not img_data:
|
tmpl_id = self.product_template_id.id
|
||||||
continue
|
for i in range(1, 6):
|
||||||
filename = getattr(self, f'image_{i}_filename', '') or f'product_image_{i}.jpg'
|
img_data = getattr(self, f'image_{i}', None)
|
||||||
|
if not img_data:
|
||||||
# Find AI metadata for this image
|
continue
|
||||||
img_meta = next((m for m in image_metadata if m.get('index') == i), {})
|
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), {})
|
||||||
# Upload image via WordPress REST API
|
img_url = f"{odoo_base}/web/image/product.template/{tmpl_id}/image_1920"
|
||||||
try:
|
wc_img = {
|
||||||
img_bytes = base64.b64decode(
|
'src': img_url,
|
||||||
img_data if isinstance(img_data, str) else img_data.decode('utf-8')
|
'name': img_meta.get('title', filename),
|
||||||
)
|
'alt': img_meta.get('alt_text', ''),
|
||||||
|
|
||||||
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',
|
|
||||||
}
|
}
|
||||||
|
if i == 1:
|
||||||
resp = requests.post(
|
wc_img['position'] = 0
|
||||||
upload_url,
|
wc_images.append(wc_img)
|
||||||
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 wc_images:
|
if wc_images:
|
||||||
wc_data['images'] = wc_images
|
wc_data['images'] = wc_images
|
||||||
@@ -615,32 +583,17 @@ class WooProductCreateWizard(models.TransientModel):
|
|||||||
'stock_quantity': int(variant.qty_available),
|
'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:
|
if line.image:
|
||||||
try:
|
odoo_base = inst.env['ir.config_parameter'].sudo().get_param('web.base.url', '')
|
||||||
img_bytes = base64.b64decode(
|
if odoo_base and variant.id:
|
||||||
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"
|
|
||||||
var_filename = f"variant_{line.sku or variant.id}.jpg"
|
var_filename = f"variant_{line.sku or variant.id}.jpg"
|
||||||
headers = {
|
img_url = f"{odoo_base}/web/image/product.product/{variant.id}/image_1920"
|
||||||
'Content-Disposition': f'attachment; filename="{var_filename}"',
|
var_data['image'] = {
|
||||||
'Content-Type': 'image/jpeg',
|
'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
|
# Tax class
|
||||||
if self.wc_tax_class:
|
if self.wc_tax_class:
|
||||||
|
|||||||
@@ -171,43 +171,17 @@ class WooVariantPushWizard(models.TransientModel):
|
|||||||
if wc_tax_class:
|
if wc_tax_class:
|
||||||
var_data['tax_class'] = wc_tax_class
|
var_data['tax_class'] = wc_tax_class
|
||||||
|
|
||||||
# Upload variant image
|
# Set variant image via Odoo's public image URL
|
||||||
if line.image:
|
if variant.id and (variant.image_variant_1920 or variant.image_1920):
|
||||||
try:
|
odoo_base = inst.env['ir.config_parameter'].sudo().get_param('web.base.url', '')
|
||||||
img_data = line.image
|
if odoo_base:
|
||||||
if isinstance(img_data, bytes):
|
img_url = f"{odoo_base}/web/image/product.product/{variant.id}/image_1920"
|
||||||
img_data = img_data.decode('utf-8')
|
img_name = (line.sku or variant.default_code or 'variant') + '.jpg'
|
||||||
|
var_data['image'] = {
|
||||||
# Geo-tag if configured
|
'src': img_url,
|
||||||
if inst.geo_lat and inst.geo_lng:
|
'name': img_name,
|
||||||
img_data = ImageProcessor.geo_tag_image(
|
'alt': line.variant_name or '',
|
||||||
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)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wc_variation = client.create_product_variation(pm.woo_product_id, var_data)
|
wc_variation = client.create_product_variation(pm.woo_product_id, var_data)
|
||||||
@@ -270,35 +244,20 @@ class WooVariantPushWizard(models.TransientModel):
|
|||||||
if wc_tax_class:
|
if wc_tax_class:
|
||||||
var_data['tax_class'] = wc_tax_class
|
var_data['tax_class'] = wc_tax_class
|
||||||
|
|
||||||
# Upload new image if changed
|
# Set variant image via Odoo's public image URL
|
||||||
if line.image:
|
# WC downloads the image from this URL directly
|
||||||
try:
|
if variant.id and (variant.image_variant_1920 or variant.image_1920):
|
||||||
img_data = line.image
|
# Build the public Odoo image URL
|
||||||
if isinstance(img_data, bytes):
|
odoo_base = inst.env['ir.config_parameter'].sudo().get_param('web.base.url', '')
|
||||||
img_data = img_data.decode('utf-8')
|
if odoo_base:
|
||||||
if inst.geo_lat and inst.geo_lng:
|
img_url = f"{odoo_base}/web/image/product.product/{variant.id}/image_1920"
|
||||||
img_data = ImageProcessor.geo_tag_image(
|
img_name = (line.sku or variant.default_code or 'variant') + '.jpg'
|
||||||
img_data, inst.geo_company_name,
|
var_data['image'] = {
|
||||||
inst.geo_company_address, inst.geo_company_phone,
|
'src': img_url,
|
||||||
inst.geo_lat, inst.geo_lng,
|
'name': img_name,
|
||||||
)
|
'alt': line.variant_name or '',
|
||||||
img_bytes = base64.b64decode(img_data)
|
}
|
||||||
import requests as req
|
_logger.info("Variant image URL: %s", img_url)
|
||||||
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)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client.update_product_variation(pm.woo_product_id, wc_var_id, var_data)
|
client.update_product_variation(pm.woo_product_id, wc_var_id, var_data)
|
||||||
|
|||||||
Reference in New Issue
Block a user