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>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;\n&lt;cfdi:Comprobante xmlns:cfdi=&quot;h....;</pre>", "type": "ir.actions.act_window_close" } }
问题分析
- 多余的模态框操作干扰Dialog渲染:JS中
setTimeout里移除modal-backdrop和.o_technical_modal的代码完全多余,Dialog组件会自行管理遮罩和模态框容器,手动移除这些元素会破坏Dialog的正常渲染流程,导致对话框无法显示。 - HTML内容双重转义:Python代码中生成
html_content时使用了<、"等转义字符,导致返回的HTML被双重转义,JS中$(result.html_content)无法正确解析为DOM元素,即使Dialog弹出,内容也会显示异常。 - RPC调用缺少记录ID:Python方法是实例方法(依赖
self.uri),但JS的RPC调用未传入目标记录ID,虽然当前返回了结果,但可能在其他场景下导致方法执行异常。 - 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'<{tag} src="...">'改为f'<{tag} src="...">' - 把
f'<div style="...">'改为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
相关产品推荐
相关产品推荐

