odoo14前端批量下载附件(可以多个附件)模块

需求:

在项目中,将当前列表中被选中记录(一条记录可能有多个附件)的附件在前端批量下载下来,形成一个压缩包,并且通过记录表单字段值进行分类,同一个记录的附件放在以当前表单字段命名的文件夹中

应为需要多个列表使用,写成通用模式

模块主要代码:

逻辑:

先进行构思:先选中记录,点击某个按钮或动作(带防抖功能)执行 post 请求并附带选中记录的当前模型名称和ID的内容,

后端在接收到请求后,根据post附带的信息获取对应附件打包压缩发送前端,前端接收后处理数据并下载。

js请求接收代码:

编写发送和接收数据的通用js代码

javascript 复制代码
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 复制代码
<?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文件

后端逻辑:

ini 复制代码
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代码函数,列如:

javascript 复制代码
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)

        }
    });
});
相关推荐
木头左2 小时前
逻辑回归的Python实现与优化
python·算法·逻辑回归
quant_19863 小时前
R语言如何接入实时行情接口
开发语言·经验分享·笔记·python·websocket·金融·r语言
失败又激情的man8 小时前
python之requests库解析
开发语言·爬虫·python
打酱油的;8 小时前
爬虫-request处理get
爬虫·python·django
用什么都重名10 小时前
MinerU:高效智能PDF文档解析工具完全指南
人工智能·python·pdf·mineru·makedown
倔强青铜三10 小时前
苦练Python第4天:Python变量与数据类型入门
前端·后端·python
这我可不懂10 小时前
Python 项目快速部署到 Linux 服务器基础教程
linux·服务器·python
倔强青铜三10 小时前
苦练Python第3天:Hello, World! + input()
前端·后端·python
小白学大数据10 小时前
Python爬取闲鱼价格趋势并可视化分析
开发语言·python
七月初七淮水竹亭~11 小时前
Pycharm 报错 Environment location directory is not empty 如何解决
ide·python·pycharm