需求:
在项目中,将当前列表中被选中记录(一条记录可能有多个附件)的附件在前端批量下载下来,形成一个压缩包,并且通过记录表单字段值进行分类,同一个记录的附件放在以当前表单字段命名的文件夹中
应为需要多个列表使用,写成通用模式
效果:
模块主要代码:
逻辑:
先进行构思:先选中记录,点击某个按钮或动作(带防抖功能)执行 post 请求并附带选中记录的当前模型名称和ID的内容,
后端在接收到请求后,根据post附带的信息获取对应附件打包压缩发送前端,前端接收后处理数据并下载。
js请求接收代码:
编写发送和接收数据的通用js代码
odoo.define('download_document.download_function', function (require) {
"use strict";
function debounce(func, delay) {
let timerId;
return function (...args) {
clearTimeout(timerId);
timerId = setTimeout(function () {
func.apply(this, args);
}, delay);
};
}
function downloadAttachments(attachment_ids, model_name) {
console.log('attachment_ids:', attachment_ids);
console.log('model_name:', model_name);
if (attachment_ids.length > 0) {
$.ajax({
url: '/custom_module/download_attachments',
method: 'POST',
data: {
attachment: JSON.stringify(attachment_ids),
model_name: JSON.stringify(model_name),
},
xhrFields: {
responseType: 'blob'
},
success: function (response) {
var blob = new Blob([response], {type: 'application/zip'});
var link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'attachments.zip';
link.click();
}
});
}
}
var debouncedDownloadAttachments = debounce(downloadAttachments, 500);
return {
downloadAttachments: debouncedDownloadAttachments
};
});
配置文件:
引入 js 文件:
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<template id="assets_eas_sync" name="eas_sync" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/download_document/static/js/download.js"/>
</xpath>
</template>
</odoo>
在 📎__manifest__.py 文件中加入 这个xml文件
后端逻辑:
class CustomController(http.Controller):
def delete_subfiles_and_subfolders(self, folder_path):
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path, filename)
if os.path.isfile(file_path):
os.remove(file_path)
elif os.path.isdir(file_path):
shutil.rmtree(file_path)
@http.route('/custom_module/download_attachments', methods=["POST"], auth='public', type='http', csrf=False,
website=True, cors='*')
def download_attachments(self, **post):
ids = list(json.loads(post.get('attachment')))
_model_name = json.loads(post.get('model_name'))
attachments = request.env['ir.attachment'].search([('res_model', '=', _model_name), ('res_id', 'in', ids)])
# 创建各个文件夹路径
absolute_path = os.path.join(os.getcwd(), 'download', _model_name,'files')
zip_path = os.path.join(os.getcwd(), 'download', _model_name,'compressed')
zip_filename = 'attachments.zip'
# 判断需要的文件夹路径是否存在
if not os.path.exists(absolute_path):
os.makedirs(absolute_path)
if not os.path.exists(zip_path):
os.makedirs(zip_path)
# 删除上一次的写入文件和对应的压缩文件
self.delete_subfiles_and_subfolders(absolute_path)
self.delete_subfiles_and_subfolders(zip_path)
# 循环创建并写入文件数据
for attachment in attachments:
filename = attachment.res_name
file_content = base64.b64decode(attachment.datas)
save_path = os.path.join(absolute_path, filename)
file_path = os.path.join(save_path, attachment.name)
if not os.path.exists(save_path):
os.makedirs(save_path)
with open(file_path, 'wb') as f:
f.write(file_content)
attachments_file_path = os.path.join(zip_path, zip_filename)
base_folder = os.path.basename(absolute_path)
# 创建ZipFile对象并打开要创建的压缩包文件
with zipfile.ZipFile(attachments_file_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(absolute_path):
for file in files:
file_path = os.path.join(root, file)
relative_path = os.path.relpath(file_path, absolute_path)
if relative_path.startswith(base_folder):
relative_path = relative_path[len(base_folder) + 1:]
zipf.write(file_path, relative_path)
# 读取文件内容
with open(attachments_file_path, 'rb') as file:
file_data = file.read()
# 创建响应对象
response = request.make_response(file_data)
response.mimetype = 'application/zip'
# 设置下载文件的名称
response.headers.set('Content-Disposition', 'attachment', filename='attachments.zip')
return response
模块配置:
1.在需要的模块中安装该模块的依赖
2.在自己的js代码的逻辑中适当调用模块的js代码函数,列如:
odoo.define('order_coordination.download_button', function (require) {
"use strict";
var ListController = require('web.ListController');
# 引入模块的js函数
var downloadFunction = require('download_document.download_function');
ListController.include({
renderButtons: function ($node) {
this._super.apply(this, arguments);
if (this.$buttons) {
this.$buttons.on('click', '.download_button', this.debouncedDownloadAttachments.bind(this));
}
},
debouncedDownloadAttachments: function () {
var self = this;
# 获取当前选中记录的ID数组
var attachment_ids = this.getSelectedIds();
# 获取当前选中记录的模型名称
var model_name = self.modelName;
# 执行模块的js函数
downloadFunction.downloadAttachments(attachment_ids,model_name)
}
});
});