Odoo15开发:Controller实现附件Zip下载报KeyError问题
Odoo附件打包Zip下载功能KeyError报错问题修复
问题背景
需要实现将系统附件打包为Zip格式下载的功能,编写模型方法和Controller路由后运行异常,访问下载路由时触发报错。
相关代码截图

模型层代码(attachment.py)
from odoo import api, fields, models, _ from odoo.odoo.exceptions import UserError class IrAttachment(models.Model): _inherit = ["ir.attachment", "sale.order.line"] def download_product_attachments(self): attachment_id = self.datas if attachment_id: return {"type": "ir.actions.act_url", "url": "/download_attachments?attachment_id={}".format( ",".join([str(elem) for elem in attachment_id.ids])), "target": "new", "res_id": self.id, } else: raise UserError("Photos were not found")
控制层代码(controllers/main.py)
class DownloadZipFile(http.Controller): @http.route("/download_attachments", type="http", auth="user", website=True) def download_attachments_product_routes(self, **data): """Method compose data with attachments and send for download.""" attachments_ids = [int(x) for x in data["attachment_id"].split(',')] attachments_items = request.env["ir.attachment"].search([("id", "in", attachments_ids)]) in_memory = BytesIO() zip_archive = ZipFile(in_memory, "w") for attachment in attachments_items: zip_archive.writestr(attachment.filename, base64.b64decode(attachment.image)) zip_archive.close() res = http.send_file(in_memory, filename="attachments.zip", as_attachment=True) return res
报错信息
报错文件路径:
/home/user/PycharmProjects/Odoo14/custom/first_model/controllers/main.py
报错位置:第32行download_attachments_product_routes方法内的attachments_ids = [int(x) for x in data["attachment_id"].split(',')]语句
错误信息:KeyError: 'attachment_id'
问题原因
代码存在4处核心错误,直接或间接导致报错:
- 控制器参数接收逻辑不严谨:路由方法未显式声明
attachment_id形参,也未做参数存在性校验,当访问路径未携带attachment_id查询参数时,直接从data字典取不存在的键就会触发KeyError。 - 模型层继承逻辑错误:
_inherit = ["ir.attachment", "sale.order.line"]属于错误的多模型继承写法,该需求仅需扩展ir.attachment模型,不需要继承销售订单行模型。 - 模型层字段引用错误:
self.datas是ir.attachment模型存储二进制内容的base64字段,不是关联记录集,不存在.ids属性,点击按钮时模型层就会抛出属性错误,无法生成正确的带参下载链接,间接导致跳转时参数丢失。 - 控制层字段&导入缺失:
ir.attachment模型的文件名字段为name、二进制内容字段为datas,代码中错误引用了不存在的filename、image字段;同时代码中用到的BytesIO、ZipFile、base64模块均未显式导入,就算解决KeyError也会触发后续的字段不存在、名称未定义报错。
修复后代码
修复后的attachment.py
from odoo import api, fields, models, _ from odoo.exceptions import UserError class IrAttachment(models.Model): _inherit = "ir.attachment" def download_product_attachments(self): if not self: raise UserError("Photos were not found") return { "type": "ir.actions.act_url", "url": "/download_attachments?attachment_id={}".format(",".join(map(str, self.ids))), "target": "new", }
修复后的controllers/main.py
import base64 from io import BytesIO from zipfile import ZipFile from odoo import http from odoo.http import request class DownloadZipFile(http.Controller): @http.route("/download_attachments", type="http", auth="user", website=True) def download_attachments_product_routes(self, attachment_id=None, **kwargs): """组装附件内容并返回Zip压缩包下载响应""" if not attachment_id: return request.not_found() attachments_ids = [int(x) for x in attachment_id.split(',')] # 加sudo避免附件权限不足导致读取失败 attachments_items = request.env["ir.attachment"].sudo().search([("id", "in", attachments_ids)]) in_memory = BytesIO() with ZipFile(in_memory, "w") as zip_archive: for attachment in attachments_items: # 文件名加ID前缀避免重名文件被覆盖 file_name = f"{attachment.id}_{attachment.name}" file_content = base64.b64decode(attachment.datas or b'') zip_archive.writestr(file_name, file_content) # 将指针移到流开头,避免下载的压缩包损坏 in_memory.seek(0) return http.send_file(in_memory, filename="attachments.zip", as_attachment=True)
内容的提问来源于stack exchange,提问作者user14213623
相关产品推荐
相关产品推荐

