Odoo导出XLSX数据按钮代码故障排查求助
Odoo XLSX导出按钮故障排查与修复
在Odoo系统中实现的XLSX数据导出按钮无法正常运行,以下是原始实现代码及修改后的尝试代码,需要排查并解决问题:
原始代码
# coding: utf-8 from odoo import fields, models, api, _ import time from datetime import datetime, date, timedelta from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT import xlsxwriter from io import BytesIO class AccountDailyBook(models.TransientModel): _name = "account.daily.book" date_start = fields.Date("Fecha desde", required=True, default=date.today().replace(day=1).strftime('%Y-%m-%d')) date_end = fields.Date("Fecha hasta", required=True, default=time.strftime('%Y-%m-%d')) company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company) def imprimir_pdf(self): for rec in self: format_new = "%d/%m/%Y" data = { 'ids': 0, 'form': { 'date_from': self.date_start, 'date_to': self.date_end, 'date_from_str': self.date_start.strftime(format_new), 'date_to_str': self.date_end.strftime(format_new), 'company': self.company_id.id } } return self.env.ref('l10n_ve_full.report_daily_book').report_action(self, data=data) # , config=False def imprimir_xlsx(self): for rec in self: format_new = "%d/%m/%Y" data = { 'ids': 0, 'form': { 'date_from': self.date_start, 'date_to': self.date_end, 'date_from_str': self.date_start.strftime(format_new), 'date_to_str': self.date_end.strftime(format_new), 'company': self.company_id.id } } # Crear el archivo output = BytesIO() workbook = xlsxwriter.Workbook(output, {'in_memory': True}) sheet = workbook.add_worksheet('Reporte') # Escribir los encabezados sheet.write(0, 0, 'Código') sheet.write(0, 1, 'Nombre') sheet.write(0, 2, 'Débito') sheet.write(0, 3, 'Crédito') # Escribir los datos row = 1 for doc in data['docs']: sheet.write(row, 0, doc['code']) sheet.write(row, 1, doc['name']) sheet.write(row, 2, doc['debit']) sheet.write(row, 3, doc['credit']) row += 1 # Escribir los totales sheet.write(row, 1, 'Total') sheet.write(row, 2, data['debit_total']) sheet.write(row, 3, data['credit_total']) # Cerrar el archivo workbook.close() output.seek(0) return self.env.ref('l10n_ve_full.report_daily_book').report_action(self, data=data) # Devolver el archivo como una respuesta HTTP return { 'name': 'report_daily_book.xlsx', 'type': 'binary', 'data': output.read(), 'context': self.env.context, } class AccountInventoryBookReport(models.AbstractModel): _name = 'report.l10n_ve_full.report_daily_book_template' @api.model def _get_report_values(self, docids, data=None): date_start = datetime.strptime(data['form']['date_from'], DATE_FORMAT) date_end = datetime.strptime(data['form']['date_to'], DATE_FORMAT) company_id = self.env['res.company'].search([('id','=',data['form']['company'])]) move_line_ids = self.env['account.move.line'].search([]) datos = [] debit_total = 0 credit_total = 0 self._cr.execute("select aa.code, aa.name, sum(aml.debit) as debit, sum(aml.credit) as credit " "from account_move_line as aml " "left join account_account as aa on aml.account_id = aa.id " "where date >= '%s' and date <= '%s' " "group by aa.code, aa.name " "order by aa.code " % (date_start,date_end)) for r in self.env.cr.fetchall(): datos.append({ 'code': r[0], 'name': r[1], 'debit': r[2], 'credit': r[3] }) debit_total += r[2] credit_total += r[3] return { 'company_id': company_id, 'company': company_id, 'currency': company_id.currency_id, 'date_start': data['form']['date_from_str'], 'date_end': data['form']['date_to_str'], 'fecha_actual': datetime.now().strftime("%d/%m/%Y"), 'hora_actual': datetime.now().strftime("%H:%M:%S"), 'doc': move_line_ids[0], 'docs': datos, 'debit_total': debit_total, 'credit_total': credit_total, }
修改后的尝试代码
def imprimir_xlsx(self): for rec in self: format_new = "%d/%m/%Y" data = { 'ids': 0, 'form': { 'date_from': self.date_start, 'date_to': self.date_end, 'date_from_str': self.date_start.strftime(format_new), 'date_to_str': self.date_end.strftime(format_new), 'company': self.company_id.id } } # Crear el archivo output = BytesIO() workbook = xlsxwriter.Workbook(output, {'in_memory': True}) sheet = workbook.add_worksheet('Reporte') # Escribir los encabezados sheet.write(0, 0, 'Código') sheet.write(0, 1, 'Nombre') sheet.write(0, 2, 'Débito') sheet.write(0, 3, 'Crédito') # Escribir los datos row = 1 for doc in data['docs']: sheet.write(row, 0, doc['code']) sheet.write(row, 1, doc['name']) sheet.write(row, 2, doc['debit']) sheet.write(row, 3, doc['credit']) row += 1 # Escribir los totales sheet.write(row, 1, 'Total') sheet.write(row, 2, data['debit_total']) sheet.write(row, 3, data['credit_total']) # Cerrar el archivo workbook.close() output.seek(0) return self.env.ref('l10n_ve_full.report_daily_book').report_action(self, data=data) # Devolver el archivo como una respuesta HTTP return { 'name': 'report_daily_book.xlsx', 'type': 'binary', 'data': output.read(), 'context': self.env.context, }
问题分析
- KeyError异常:构造的
data字典仅包含form字段,没有docs、debit_total、credit_total,直接遍历data['docs']会触发键不存在的错误。 - 逻辑执行中断:方法中先返回了
report_action,后续生成并返回XLSX文件的代码永远不会执行,属于逻辑顺序错误。 - 缩进错误:文件写入表头、数据的代码缩进与
for rec in self循环不匹配,导致逻辑混乱。 - 未获取业务数据:没有调用
_get_report_values方法获取实际的账户数据,空的data字典没有业务内容。
修复方案
以下是修正后的完整代码,解决上述所有问题:
# coding: utf-8 from odoo import fields, models, api, _ import time from datetime import datetime, date, timedelta from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT import xlsxwriter from io import BytesIO class AccountDailyBook(models.TransientModel): _name = "account.daily.book" date_start = fields.Date("Fecha desde", required=True, default=lambda self: date.today().replace(day=1)) date_end = fields.Date("Fecha hasta", required=True, default=lambda self: date.today()) company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company) def imprimir_pdf(self): for rec in self: format_new = "%d/%m/%Y" data = { 'ids': 0, 'form': { 'date_from': rec.date_start.strftime(DATE_FORMAT), 'date_to': rec.date_end.strftime(DATE_FORMAT), 'date_from_str': rec.date_start.strftime(format_new), 'date_to_str': rec.date_end.strftime(format_new), 'company': rec.company_id.id } } return self.env.ref('l10n_ve_full.report_daily_book').report_action(self, data=data) def imprimir_xlsx(self): for rec in self: format_new = "%d/%m/%Y" # 构造报表所需数据结构 data = { 'ids': 0, 'form': { 'date_from': rec.date_start.strftime(DATE_FORMAT), 'date_to': rec.date_end.strftime(DATE_FORMAT), 'date_from_str': rec.date_start.strftime(format_new), 'date_to_str': rec.date_end.strftime(format_new), 'company': rec.company_id.id } } # 调用报表模型获取业务数据 report_values = self.env['report.l10n_ve_full.report_daily_book_template']._get_report_values(None, data=data) # 创建XLSX文件 output = BytesIO() workbook = xlsxwriter.Workbook(output, {'in_memory': True}) sheet = workbook.add_worksheet('Reporte') # 写入表头 sheet.write(0, 0, 'Código') sheet.write(0, 1, 'Nombre') sheet.write(0, 2, 'Débito') sheet.write(0, 3, 'Crédito') # 写入业务数据 row = 1 for doc in report_values['docs']: sheet.write(row, 0, doc['code']) sheet.write(row, 1, doc['name']) sheet.write(row, 2, doc['debit']) sheet.write(row, 3, doc['credit']) row += 1 # 写入总计行 sheet.write(row, 1, 'Total') sheet.write(row, 2, report_values['debit_total']) sheet.write(row, 3, report_values['credit_total']) # 关闭工作簿并重置文件指针 workbook.close() output.seek(0) # 返回XLSX文件下载响应 return { 'name': f'Libro_Diario_{rec.date_start.strftime("%Y%m%d")}_{rec.date_end.strftime("%Y%m%d")}.xlsx', 'type': 'binary', 'data': output.getvalue(), 'content_type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'context': self.env.context, } class AccountInventoryBookReport(models.AbstractModel): _name = 'report.l10n_ve_full.report_daily_book_template' @api.model def _get_report_values(self, docids, data=None): # 修正日期格式转换,避免字符串与日期对象混用 date_start = datetime.strptime(data['form']['date_from'], DATE_FORMAT).date() date_end = datetime.strptime(data['form']['date_to'], DATE_FORMAT).date() company_id = self.env['res.company'].browse(data['form']['company']) datos = [] debit_total = 0.0 credit_total = 0.0 # 使用参数化查询避免SQL注入风险 self._cr.execute(""" select aa.code, aa.name, sum(aml.debit) as debit, sum(aml.credit) as credit from account_move_line as aml left join account_account as aa on aml.account_id = aa.id where aml.date >= %s and aml.date <= %s and aml.company_id = %s group by aa.code, aa.name order by aa.code """, (date_start, date_end, company_id.id)) for r in self.env.cr.fetchall(): datos.append({ 'code': r[0], 'name': r[1], 'debit': r[2] or 0.0, 'credit': r[3] or 0.0 }) debit_total += r[2] or 0.0 credit_total += r[3] or 0.0 return { 'company_id': company_id, 'company': company_id, 'currency': company_id.currency_id, 'date_start': data['form']['date_from_str'], 'date_end': data['form']['date_to_str'], 'fecha_actual': datetime.now().strftime("%d/%m/%Y"), 'hora_actual': datetime.now().strftime("%H:%M:%S"), 'docs': datos, 'debit_total': debit_total, 'credit_total': credit_total, }
主要修改点说明
- 获取业务数据:调用报表模型的
_get_report_values方法获取实际的账户汇总数据,避免手动构造空数据。 - 修正逻辑顺序:移除错误的
report_action返回,确保XLSX文件生成后直接返回下载响应。 - 修复缩进问题:所有文件操作代码都包含在
for rec in self循环内,保证单条记录处理逻辑正确。 - 日期格式优化:默认字段值直接返回日期对象,避免提前转换为字符串;查询时使用参数化SQL,避免注入风险。
- 空值处理:对查询返回的
debit和credit做空值判断,避免计算时出现异常。
内容的提问来源于stack exchange,提问作者SirDomini x
相关产品推荐
相关产品推荐

