102 lines
4.4 KiB
Python

# -*- coding: utf-8 -*-
import json
from xml.sax.saxutils import escape
from odoo import http
from odoo.http import request, Response
class MetaCatalogFeedController(http.Controller):
@http.route('/social/meta/catalog_feed.xml', type='http', auth='public', methods=['GET'], csrf=False)
def catalog_feed_xml(self, **kwargs):
"""
Generates standard Meta Commerce / Google Merchant RSS 2.0 product feed XML.
Meta Commerce Manager can be scheduled to fetch this URL hourly or daily.
"""
company = request.env.company
base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url', '').rstrip('/')
products = request.env['product.product'].sudo().search([
('is_published', '=', True),
('sale_ok', '=', True),
('company_id', 'in', (False, company.id))
])
xml_lines = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">',
'<channel>',
f'<title>{escape(company.name)} Meta Commerce Catalog</title>',
f'<link>{escape(base_url)}</link>',
f'<description>Product feed for Meta Commerce and Instagram Shopping</description>',
]
currency = company.currency_id.name or 'USD'
for product in products:
tmpl = product.product_tmpl_id
retailer_id = product.default_code or f"odoo_prod_{product.id}"
title = product.display_name or tmpl.name
description = tmpl.description_sale or tmpl.description or title
product_url = f"{base_url}{product.website_url}" if hasattr(product, 'website_url') and product.website_url else f"{base_url}/shop"
image_url = f"{base_url}/web/image/product.product/{product.id}/image_1920"
# Availability
if tmpl.detailed_type in ('consu', 'service'):
avail = 'in stock'
elif hasattr(product, 'qty_available') and product.qty_available <= 0:
avail = 'out of stock'
else:
avail = 'in stock'
price_str = f"{product.lst_price:.2f} {currency}"
item_group_id = f"odoo_tmpl_{tmpl.id}"
brand = tmpl.company_id.name or company.name
xml_lines.append('<item>')
xml_lines.append(f'<g:id>{escape(str(retailer_id))}</g:id>')
xml_lines.append(f'<g:title>{escape(str(title))}</g:title>')
xml_lines.append(f'<g:description>{escape(str(description[:4900]))}</g:description>')
xml_lines.append(f'<g:link>{escape(product_url)}</g:link>')
xml_lines.append(f'<g:image_link>{escape(image_url)}</g:image_link>')
xml_lines.append(f'<g:brand>{escape(str(brand))}</g:brand>')
xml_lines.append(f'<g:condition>new</g:condition>')
xml_lines.append(f'<g:availability>{avail}</g:availability>')
xml_lines.append(f'<g:price>{price_str}</g:price>')
xml_lines.append(f'<g:item_group_id>{escape(item_group_id)}</g:item_group_id>')
xml_lines.append('</item>')
xml_lines.append('</channel>')
xml_lines.append('</rss>')
feed_xml = "\n".join(xml_lines)
return request.make_response(feed_xml, [
('Content-Type', 'application/xml; charset=utf-8'),
('Cache-Control', 'public, max-age=900')
])
@http.route('/social/meta/catalog_feed.json', type='http', auth='public', methods=['GET'], csrf=False)
def catalog_feed_json(self, **kwargs):
"""JSON catalog feed endpoint."""
company = request.env.company
base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url', '').rstrip('/')
products = request.env['product.product'].sudo().search([
('is_published', '=', True),
('sale_ok', '=', True),
('company_id', 'in', (False, company.id))
])
items = []
meta_api = request.env['social.meta.catalog'].sudo()
for product in products:
try:
payload = meta_api.build_item_payload(product, method='UPDATE')
items.append(payload.get('data'))
except Exception:
continue
return request.make_response(
json.dumps({'catalog': items, 'total_items': len(items)}, indent=2),
[('Content-Type', 'application/json; charset=utf-8')]
)