You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Odoo11中JS调用RPC方法后Dialog弹窗不显示问题排查

问题:JS调用Python RPC返回成功,但Dialog对话框未弹出

我通过JS调用Python端的RPC方法,网络请求已返回正确结果,但表单视图中并未弹出预期的Dialog对话框。

JS代码

odoo.define('alicuam.show_modal_on_load', function (require) {
    "use strict";

    console.log("js loaded");
    var FormController = require('web.FormController');
    var Dialog = require('web.Dialog');

    FormController.include({
        events: _.extend({}, FormController.prototype.events, {
            'click .preview_btn': '_onPreviewBtnClick',
        }),

        _onPreviewBtnClick: function () {
            var self = this; // Guardar el contexto
            this._rpc({
                model: 'crm.files',
                method: 'open_file_preview_dialog',
            }).then(function (result) {
                if (result && result.html_content !== null) { // Revisar resultado
                    var dialog = new Dialog(self, {  // Usar el contexto guardado
                        title: 'Preview',
                        size: 'large',
                        $content: $(result.html_content)
                    });
                    dialog.open();
                    setTimeout(function() {
                        $('.modal-backdrop').addClass('fade_out');
                        $('.o_act_window').parents('.o_technical_modal').first().addClass('slide_up');
                        setTimeout(function() {
                            $('.modal-backdrop').remove();
                            $('.o_act_window').parents('.o_technical_modal').first().remove();
                        }, 350);
                        let $targetModal = dialog.$el.parents('.o_technical_modal').first();
                        $targetModal.addClass("dialog_preview");
                        console.log("Added class to: ", $targetModal.length, " elements");
                        $('.dialog_preview').css('z-index', 9999);
                        console.log("Z-index applied");
                    }, 50);
                } else {
                    console.log("Resultado es null o undefined");
                }
            }).catch(function (error) {  // Manejar errores
                console.error("Error en la solicitud RPC:", error);
            });
        },
    });
});

Python代码

def open_file_preview_dialog(self):
    _logger.info('El método open_file_preview_dialog ha sido llamado.')
    if self.uri:
        uri_split = self.uri.split('.')
        file_format = uri_split[-1]

        # Verificar las dos últimas extensiones
        last_two_ext = uri_split[-2:]

        # Si la última extensión es 'jpeg' y la penúltima es una extensión problemática
        if last_two_ext[-1].lower() == 'jpeg' and last_two_ext[-2].lower() in ['gif', 'webp', 'tif', 'tiff']:
            file_format = last_two_ext[-2]

        response = requests.get(self.uri)
    else:
        return

    if response.ok:
        file_content = response.content
        file_data = base64.decodebytes(file_content)

        if file_format in ['png', 'jpg', 'jpeg', 'webp', 'tiff', 'tif']:
            mime_types = {
                'png': 'image/png',
                'jpg': 'image/jpeg',
                'jpeg': 'image/jpeg',
                'webp': 'image/jpeg',
                'tiff': 'image/tiff',
                'tif': 'image/tif'
            }

            try:
                img = Image.open(io.BytesIO(file_data))

                if file_format == 'webp':
                    img = img.convert("RGB")
                    file_format = 'jpg'

                img_temp = io.BytesIO()
                img.save(img_temp, format=f'{file_format.upper()}')
            except Exception:
                try:
                    # Intentar con imageio
                    if file_format == 'tiff' or file_format == 'tif':
                        img_array = imageio.imread(io.BytesIO(file_data), format='tifffile')
                    elif file_format == 'webp':
                        img_array = imageio.imread(io.BytesIO(file_data))

                    img_temp = io.BytesIO()
                    imageio.imsave(img_temp, img_array, format='JPEG')
                    file_format = 'jpeg'
                except Exception:
                    # Intentar con OpenCV
                    img_array = cv2.imdecode(np.frombuffer(file_data, np.uint8), -1)

                    # Convertir a RGB si es necesario
                    if len(img_array.shape) == 3 and img_array.shape[2] == 4:
                        img_array = cv2.cvtColor(img_array, cv2.COLOR_BGRA2BGR)

                    _, buffer = cv2.imencode('.jpg', img_array)
                    img_temp = io.BytesIO(buffer)
                    file_format = 'jpeg'

            file = img_temp
            content_type = mime_types.get(file_format, 'image')
            tag = 'img'

            content = f'<{tag} src="data:{content_type};base64,{base64.b64encode(file.getvalue()).decode("utf-8")}" style="border: 0; width: {img.size[0] if "img_array" not in locals() else img_array.shape[1]}px; height: {img.size[1] if "img_array" not in locals() else img_array.shape[0]}px;" />'
            html_content = f'''
                    <div style="display: flex; justify-content: center; align-items: center; height: 100vh;">
                        {content}
                    </div>'''

        # PROCESSING PDFs
        elif file_format == 'pdf':
            content_type = 'application/pdf'
            pdf_temp = io.BytesIO(file_data)
            file = pdf_temp
            tag = 'iframe'

            html_content = f'<{tag} src="data:{content_type};base64,{base64.b64encode(file.getvalue()).decode("utf-8")}" style="border: 0; width: 100%; height: 100vh; allowfullscreen" />'

        # PROCESSING XMLs
        elif file_format == 'xml':
            xml_content = file_data.decode('utf-8')
            formatted_xml = html.escape(xml_content)
            html_content = f'<pre>{formatted_xml}</pre>'

        else:
            raise Warning(f"Lo sentimos el formato {file_format} no esta actualmente soportado para previsualizacion")

    _logger.info(f'Esto voy a retornar: {html_content}')
    return {'html_content': html_content }

RPC返回结果

{
  "jsonrpc": "2.0",
  "id": 905282351,
  "result": {
    "flags": {},
    "html_content": "<pre>&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;\n&amp;lt;cfdi:Comprobante xmlns:cfdi=&amp;quot;h....;&lt;/pre&gt;",
    "type": "ir.actions.act_window_close"
  }
}

问题分析

  1. 多余的模态框操作干扰Dialog渲染:JS中setTimeout里移除modal-backdrop和.o_technical_modal的代码完全多余,Dialog组件会自行管理遮罩和模态框容器,手动移除这些元素会破坏Dialog的正常渲染流程,导致对话框无法显示。
  2. HTML内容双重转义:Python代码中生成html_content时使用了&lt;、&quot;等转义字符,导致返回的HTML被双重转义,JS中$(result.html_content)无法正确解析为DOM元素,即使Dialog弹出,内容也会显示异常。
  3. RPC调用缺少记录ID:Python方法是实例方法(依赖self.uri),但JS的RPC调用未传入目标记录ID,虽然当前返回了结果,但可能在其他场景下导致方法执行异常。
  4. Odoo自动包装返回值:返回结果中的type: "ir.actions.act_window_close"是Odoo对模型方法返回值的自动包装,虽不会直接阻止Dialog弹出,但可能影响页面状态。

解决方案

1. 移除JS中干扰Dialog的代码

删除dialog.open()后的整个setTimeout代码块,让Dialog组件自行管理模态框和遮罩:

// 替换原有的dialog.open()及后续代码
dialog.open();
console.log("Dialog opened successfully");

2. 修复Python中的HTML转义问题

将Python代码中生成HTML的转义字符替换为原始HTML字符,比如:

  • 把f'&lt;{tag} src=&quot;...&quot;&gt;'改为f'<{tag} src="...">'
  • 把f'&lt;div style=&quot;...&quot;&gt;'改为f'<div style="...">'

以XML处理部分为例,修改后:

html_content = f'<pre>{formatted_xml}</pre>'

3. 完善RPC调用的记录ID传递

在JS的RPC调用中添加当前记录的ID,确保Python方法能获取到正确的实例数据:

this._rpc({
    model: 'crm.files',
    method: 'open_file_preview_dialog',
    args: [[this.model.get(this.handle).data.id]] // 传入当前记录ID,适配@api.multi装饰器
})

同时确保Python方法添加正确的装饰器:

from odoo import api, models

class CrmFiles(models.Model):
    _name = 'crm.files'

    @api.multi
    def open_file_preview_dialog(self):
        # 原方法代码

4. 处理Odoo的自动包装返回值

如果需要避免Odoo自动添加动作类型,可以让Python方法返回客户端动作格式,再在JS中监听处理:

return {
    'type': 'ir.actions.client',
    'tag': 'display_file_preview',
    'params': {'html_content': html_content}
}

然后在JS中注册对应的客户端动作:

odoo.define('alicuam.display_file_preview', function (require) {
    "use strict";
    var core = require('web.core');
    var Dialog = require('web.Dialog');

    core.action_registry.add('display_file_preview', function (action, options) {
        new Dialog(options.context, {
            title: 'Preview',
            size: 'large',
            $content: $(action.params.html_content)
        }).open();
        return {type: 'ir.actions.act_window_close'};
    });
});

内容的提问来源于stack exchange,提问作者Edgard Allan Pazos Inda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 19:22:03