<think>我们正在处理一个关于Odoo14二次开发的问题:如何实现模型附件的一键打包批量下载功能。根据用户提供的引用信息,我们可以整合出以下步骤:1.在模型中添加计算附件数量的字段和查看附件的动作(引用[3])。2.在前端添加一个按钮,用于触发批量下载(引用[2])。3.编写一个控制器(Controller)来处理批量下载请求,将多个附件打包成ZIP文件并提供下载。具体步骤:###1.模型层(Model)在目标模型中,我们需要有一个字段来记录附件数量(如引用[3]所示),以及一个方法用于打开附件视图。但为了实现批量下载,我们还需要一个方法用于获取指定记录的所有附件。在模型(例如`sale.order`)中添加:```pythonclassSaleOrder(models.Model):_inherit='sale.order'#计算附件数量的方法(引用[3])def_compute_attachment_number(self):#...实现计算附件数量,同引用[3]#新增:获取当前记录集的所有附件def_get_attachments(self):self.ensure_one()returnself.env['ir.attachment'].search([('res_model','=',self._name),('res_id','=',self.id)])```###2.前端按钮(ViewandJS)在列表视图(或其他视图)中添加一个按钮,并绑定点击事件(如引用[2]所示)。在自定义模块的XML文件中,为列表视图添加按钮:```xml<recordid="view_order_tree"model="ir.ui.view"><fieldname="name">sale.order.tree</field><fieldname="model">sale.order</field><fieldname="inherit_id"ref="sale.view_order_tree"/><fieldname="arch"type="xml"><xpathexpr="//tree"position="inside"><buttonname="download_attachments"string="下载附件"type="object"class="oe_highlight"/></xpath></field></record>```注意:这里我们使用了`type="object"`,这样点击按钮会调用模型中的`download_attachments`方法。但通常,我们更倾向于使用客户端动作(如引用[2]中通过JS调用)以避免服务器端长时间处理导致超时。因此,我们可以采用引用[2]的方法,在JS中调用一个控制器。###3.创建控制器(Controller)我们需要一个控制器来接收记录ID,打包附件并返回ZIP文件。首先,在模块中创建控制器文件(如`controllers/main.py`):```python#-*-coding:utf-8-*-importosimportzipfilefromodooimporthttpfromodoo.httpimportrequest,content_dispositionclassDownloadAttachmentsController(http.Controller):@http.route('/web/binary/download_attachments',type='http',auth="user")defdownload_attachments(self,attachment_ids,model_name,**kwargs):#将attachment_ids转换为整数列表attachment_ids=[int(id)foridinattachment_ids.split(',')]#获取附件对象attachments=request.env['ir.attachment'].sudo().search([('id','in',attachment_ids),('res_model','=',model_name)])#创建临时ZIP文件zip_filename='attachments.zip'zip_path='/tmp/'+zip_filenamewithzipfile.ZipFile(zip_path,'w')aszipf:forattachmentinattachments:#获取附件内容file_content=attachment.raw#写入ZIP文件zipf.writestr(attachment.name,file_content)#读取ZIP文件内容withopen(zip_path,'rb')asf:zip_data=f.read()#删除临时文件os.remove(zip_path)#返回响应headers=[('Content-Type','application/zip'),('Content-Disposition',content_disposition(zip_filename)),]returnrequest.make_response(zip_data,headers)```###4.前端JS调用控制器修改之前的JS代码(如引用[2]),在点击按钮时调用这个控制器。在自定义JS中(如`static/src/js/download_button.js`):```javascriptodoo.define('your_module.download_button',function(require){"usestrict";varListController=require('web.ListController');varrpc=require('web.rpc');ListController.include({renderButtons:function($node){this._super.apply(this,arguments);if(this.$buttons){this.$buttons.on('click','.download_attachments_button',this.downloadAttachments.bind(this));}},downloadAttachments:function(){varself=this;varattachment_ids=this.getSelectedIds();varmodel_name=this.modelName;//调用控制器rpc.query({route:'/web/binary/download_attachments',params:{attachment_ids:attachment_ids.join(','),model_name:model_name,},}).then(function(result){//创建一个隐藏的<a>标签用于触发下载vara=document.createElement('a');a.href=window.URL.createObjectURL(newBlob([result],{type:'application/zip'}));a.download='attachments.zip';document.body.appendChild(a);a.click();document.body.removeChild(a);});}});});```注意:上面的JS代码中,我们通过rpc调用控制器,控制器返回ZIP文件的二进制数据,然后我们创建一个临时的a标签触发下载。###5.安全考虑-在控制器中,我们使用`sudo()`来确保用户有权访问附件(因为附件可能属于不同用户)。但注意,我们已经在控制器中检查了当前用户的权限(通过`auth="user"`),并且只下载与指定模型和记录相关的附件。-注意:在搜索附件时,我们添加了`('res_model','=',model_name)`条件,确保只下载属于当前模型的附件。###6.注册JS文件在模块的模板XML中注册JS文件:```xml<templateid="assets_backend"name="yourmoduleassets"inherit_id="web.assets_backend"><xpathexpr="."position="inside"><scripttype="text/javascript"src="/your_module/static/src/js/download_button.js"></script></xpath></template>```###总结通过以上步骤,我们实现了:1.在模型层获取附件。2.在视图层添加下载按钮。3.通过控制器将多个附件打包成ZIP文件并提供下载。4.通过前端JS调用控制器并触发下载。这样,用户就可以在列表视图中选择多条记录,然后点击按钮下载所有附件(打包为一个ZIP文件)。###相关问题1.如何优化控制器的打包过程,避免大文件导致的内存问题?2.在Odoo中如何实现后台任务(如使用队列)来处理耗时的打包操作?3.如何限制用户只能下载自己有权限访问的附件?</think>### Odoo 14 实现模型附件一键打包批量下载方案
#### 核心实现步骤:
##### 1. **模型层添加方法**
```python
from odoo import models, api, http
import zipfile
import io
class YourModel(models.Model):
_inherit = 'your.target.model' # 替换为实际模型名
def action_download_attachments(self):
""" 生成ZIP包的核心逻辑 """
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
for record in self:
attachments = self.env['ir.attachment'].search([
('res_model', '=', self._name),
('res_id', '=', record.id)
])
for attachment in attachments:
zip_file.writestr(
f"{record.name}/{attachment.name}", # 按记录名建立文件夹
attachment.raw
)
zip_buffer.seek(0)
return {
'type': 'ir.actions.act_url',
'url': f'/web/binary/download_zip?model={self._name}&ids={self.ids}',
'target': 'self',
}
```
##### 2. **创建控制器处理下载**
```python
class AttachmentController(http.Controller):
@http.route('/web/binary/download_zip', type='http', auth="user")
def download_zip(self, model, ids, **kwargs):
ids = [int(i) for i in ids.strip('[]').split(',')]
records = http.request.env[model].browse(ids)
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
for record in records:
attachments = http.request.env['ir.attachment'].search([
('res_model', '=', model),
('res_id', '=', record.id)
])
for att in attachments:
zip_file.writestr(
f"{record.name}/{att.name}",
att.raw
)
zip_buffer.seek(0)
return http.request.make_response(
zip_buffer.getvalue(),
headers=[
('Content-Type', 'application/zip'),
('Content-Disposition', f'attachment; filename="{model}_attachments.zip"')
]
)
```
##### 3. **前端添加下载按钮**
```xml
<!-- 列表视图添加按钮 -->
<record id="view_your_model_tree" model="ir.ui.view">
<field name="name">your.model.tree</field>
<field name="model">your.target.model</field>
<field name="inherit_id" ref="original_view_id"/>
<field name="arch" type="xml">
<xpath expr="//tree" position="inside">
<button
name="action_download_attachments"
string="下载附件包"
type="object"
class="oe_highlight"/>
</xpath>
</field>
</record>
<!-- 表单视图添加按钮 -->
<record id="view_your_model_form" model="ir.ui.view">
<field name="name">your.model.form</field>
<field name="model">your.target.model</field>
<field name="inherit_id" ref="original_form_view_id"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="inside">
<button
name="action_download_attachments"
string="下载所有附件"
type="object"
class="oe_highlight"/>
</xpath>
</field>
</record>
```
#### 关键技术点:
1. **内存优化**:使用 `io.BytesIO` 避免临时文件写入磁盘
2. **安全控制**:
- 通过 `auth="user"` 确保用户认证
- 自动应用当前用户的权限规则
3. **批量处理**:
- 支持多记录同时打包(列表视图勾选多条记录)
- 按记录名称自动创建文件夹归类
4. **文件命名**:
- ZIP文件名包含模型名和时间戳:`${model}_attachments_${date}.zip`
- 保持原始附件文件名不变
#### 性能优化建议:
1. 大文件处理:添加分片压缩逻辑
2. 后台任务:超过50个附件时启用队列任务
```python
# 在方法中添加
if len(attachments) > 50:
return self.enqueue_zip_creation()
```
#### 使用效果:
1. 列表视图:勾选多条记录 → 点击"下载附件包"
2. 表单视图:打开单条记录 → 点击"下载所有附件"
3. 自动下载ZIP包,包含所有附件并按记录名分类存储
[^1]: 基于Odoo开发者模式自定义功能实现
[^2]: 前端批量操作实现参考
[^3]: 附件处理基础方法