Purpose

The goal is to refresh product information dynamically when there are changes.

Implementation

sequenceDiagram
    autonumber
    participant DB as Odoo Backend
    participant WS as WebSocket (bus.bus)
    participant PY as Python Controller
    participant JS as JavaScript (Frontend)
    participant HTML as Website UI
    
    DB->>WS: Notify WebSocket on Product Update
    WS->>JS: Send Notification with Product ID
    JS->>PY: Call /product/tree/sync with Product ID
    PY->>DB: Fetch Updated Product Data
    DB->>PY: Return Product Data
    PY->>JS: Send Rendered HTML
    JS->>HTML: Replace Product Section in DOM

1. Python Controller Backend

Render Website Products

This method is responsible for rendering the product information using the specified template.

def _render_website_products(self, product_obj):
    html_return = request.env["ir.ui.view"]._render_template(
        'website_sale.inherited_products_description_website_sale2', {
            'website': request.env['website'].browse(1),
            'product': product_obj,
        }
    )
    return {
        'html': html_return,
    }

Product Sync Endpoint

This method is exposed as a public route to allow the JavaScript code to request updated product information.

@http.route(['/product/tree/sync'], type='json', auth="public", website=True, csrf=False)
def product_tree_sync(self, product_id):
    product_obj = request.env['product.template'].sudo().browse(int(product_id))
    return self._render_website_products(product_obj)

2. Python Backend

This method sends a notification to the WebSocket service when there is an update in the product status.

def notify_websocket(self):
    self.ensure_one()
    self.env['bus.bus']._sendone('product_updates', 'notification', {
        'product_tmpl_id': self.product_tmpl_id.id,
    })

3. JavaScript Frontend

Define a JavaScript module to handle WebSocket connections and update the DOM when there are notifications.

/** @odoo-module */

import { registry } from "@web/core/registry";
import websiteSaleList from "website_sale.website_sale_list";

export const websiteSaleTreeWs = {
    dependencies: ['bus_service', 'rpc'],

    start(env, { bus_service, rpc }) {
        if (!document.querySelector('.oe_website_sale') || !document.querySelector('.oe_product_cart')) {
            return;
        }
        this.product_ids = Array.from(document.querySelectorAll('div[data-product_id]')).map(product => parseInt(product.getAttribute('data-product_id')));

        this.rpc = rpc;

        this.busService = bus_service;
        this.busService.addChannel('product_updates');
        this.busService.addEventListener('notification', this.onMessage.bind(this));
        this.busService.start();
    },
    destroy() {
        this.busService.removeEventListener('notification', this.onMessage.bind(this));
        this.busService.stop();
    },
    async onMessage({ detail: notifications }) {
        for (const notification of notifications) {
            if (this.product_ids.includes(notification['payload']['product_tmpl_id'])) {
                const result = await this.rpc('/product/tree/sync', {
                    product_id: notification['payload']['product_tmpl_id'],
                });
                if (result) {
                    let product = document.querySelector(`.oe_product_cart[data-product_id="${notification['payload']['product_tmpl_id']}"]`);
                    product = product && product.querySelector('.js_replace_me_please');
                    if (product) {
                        product.parentElement.innerHTML = result.html;
                    }
                }
            }
        }
    }
}

registry.category("services").add("websiteSaleTreeWs", websiteSaleTreeWs);

4. XML Templates

Product Description Template

This template contains the structure for rendering product information.

<template id="inherited_products_description_website_sale2" name="Website Sale Products 2">
    <div> <!-- this div is important for js replacement -->
        <div class="js_replace_me_please">
            <div>
                <h6>
                    <a t-field="product.name"/>
                </h6>
            </div>
        </div>
    </div>
</template>

Inherited Product Template

This template extends the existing product template to include data attributes.

<template id="inherited_products_description_website_sale" name="Website Sale Products" inherit_id="website_sale.products_item">
    <xpath expr="//form" position="replace">
        <div t-att-data-product_id="product.id">
            <t t-call="website_sale.inherited_products_description_website_sale2"/>
        </div>
    </xpath>
</template>