本文详解如何在 Odoo 中通过 transient 模型安全、可靠地生成并下载 CSV 文件,重点解决因缺失 binary 字段导致的「field not found」错误,并提供完整可运行的代码示例与关键注意事项。
本文详解如何在 odoo 中通过 transient 模型安全、可靠地生成并下载 csv 文件,重点解决因缺失 `binary` 字段导致的「field not found」错误,并提供完整可运行的代码示例与关键注意事项。
在 Odoo 中实现 CSV 报表导出功能时,许多开发者会遇到类似 [Field 'file' not found] 的报错(如截图所示),其根本原因在于:ir.actions.act_url 路由中引用了不存在的字段 file,而当前 transient 模型并未定义该字段,也未将生成的 CSV 内容写入其中。
要正确实现可下载的 CSV 导出,必须满足三个核心条件:
- ✅ 定义一个 fields.Binary 字段用于存储编码后的文件内容;
- ✅ 在导出方法中将 CSV 字符串经 UTF-8 编码 + Base64 编码后写入该字段;
- ✅ 使用 web.content URL 正确关联模型、记录 ID、字段名与文件名。
以下是修正后的完整实现方案(适配 Odoo 15+,兼容新旧 API):
✅ 步骤一:更新 transient 模型(添加 binary 字段与逻辑增强)
import io
import csv
import base64
from odoo import models, fields, api
from odoo.exceptions import UserError
class ReportPatientWizard(models.TransientModel):
_name = "report.patient.wizard"
_description = "Patient Reports"
patient_id = fields.Many2one('hospital.patient', string='Patient', readonly=True)
gender_filter = fields.Selection([
('male', 'Male'),
('female', 'Female'),
('other', 'Other')
], string='Gender Filter')
# ? 关键:新增 binary 字段,用于存储 CSV 文件内容
file = fields.Binary(string="Download File", readonly=True, attachment=True)
filename = fields.Char(string="File Name", readonly=True)
def _prepare_csv_data(self, patients):
field_names = ['Name', 'Age', 'Gender']
output = io.StringIO()
writer = csv.DictWriter(output, fieldnames=field_names)
writer.writeheader()
for patient in patients:
writer.writerow({
'Name': patient.name or '',
'Age': str(patient.age) if patient.age else '',
'Gender': dict(patient._fields['gender'].selection).get(patient.gender, patient.gender),
})
return output.getvalue()
@api.model
def download_patient_report(self):
self.ensure_one() # ? 强制单记录上下文,避免批量调用异常
# 构建患者查询条件
domain = []
if self.gender_filter:
domain.append(('gender', '=', self.gender_filter))
patients = self.env['hospital.patient'].search(domain)
if not patients:
raise UserError(_('No patients match the selected criteria.'))
# 生成 CSV 内容并编码为 base64
csv_content = self._prepare_csv_data(patients)
self.file = base64.b64encode(csv_content.encode('utf-8'))
self.filename = f'patient_report_{fields.Date.today().strftime("%Y%m%d")}.csv'
# ? 返回标准 web.content 下载动作(注意:model、id、field、filename 必须严格对应)
return {
'type': 'ir.actions.act_url',
'url': f'/web/content/?model={self._name}&id={self.id}&field=file&filename={self.filename}&download=true',
'target': 'self',
}
def print_patient_report(self):
return self.download_patient_report()✅ 步骤二:确保视图中无需显示 file 字段(保持隐藏)
⚠️ 注意事项与最佳实践
- 编码一致性:务必使用 .encode('utf-8') 再 base64.b64encode(),否则中文等非 ASCII 字符将乱码;
- 文件名安全:避免在 filename 中包含用户可控特殊字符(如 /, \, ..),本例使用日期格式已规避风险;
- 附件存储:attachment=True 可选,但建议保留,便于 Odoo 自动清理临时附件(transient 模型记录删除时自动清理);
- 性能提示:若患者数据量极大(>10k 行),建议改用流式响应(http.Response)或后台任务(@api.background),避免内存溢出;
- 权限控制:确保当前用户对 hospital.patient 具有 read 权限,否则 search() 将静默返回空集;
- Odoo 版本差异:Odoo 17+ 已弃用 @api.multi,统一使用 @api.model 或实例方法(如上),并配合 self.ensure_one()。
✅ 最终效果验证
点击「Download」按钮后,浏览器将直接触发 CSV 文件下载,文件名形如 patient_report_20240520.csv,内容为按性别筛选后的结构化患者数据,无报错、无乱码、符合预期。
通过以上改造,你不仅修复了原始报错,还构建了一个健壮、可维护、符合 Odoo 官方推荐模式的 CSV 导出模块。后续如需扩展 Excel 支持,只需替换 _prepare_csv_data 为 xlsxwriter 实现即可复用相同架构。

















