You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django ListView批量选择行导出ZIP包无法触发下载问题求助

Django批量导出ZIP压缩包无法触发下载问题修复方案

核心问题原因

  • 响应链路断裂:batch_csv_zip方法生成的FileResponse没有被上层方法接收返回,post方法最终固定返回重定向响应,下载响应直接被丢弃
  • FileResponse使用错误:不应该传入文件读取后的字节串,直接传入文件对象即可,避免大文件场景下内存溢出
  • 存在高危安全漏洞:直接使用eval解析用户提交的inputString,攻击者可传入恶意代码执行服务器端任意操作
  • 不必要的全局状态修改:os.chdir修改进程工作目录,并发请求场景下会导致路径混乱
  • 响应头不规范:Content-Type头名小写,filename未加引号,部分浏览器可能无法识别下载规则
  • 文件操作冗余:使用with上下文管理文件时不需要手动调用close,且原有代码未加括号f.close()属于无效调用

修复步骤

  1. 调整请求处理链路,导出ZIP时不返回重定向,直接返回下载响应
  2. 替换eval为json.loads,前端提交时把数据转为JSON字符串而非直接传Python表达式
  3. 修正FileResponse用法和响应头配置
  4. 移除不必要的os.chdir调用,修正路径拼接逻辑,避免相对路径定位错误
  5. 优化目录创建逻辑,避免目录已存在时抛出异常

注意:如果你的批量操作是通过AJAX发起的POST请求,后端返回的文件流无法直接触发浏览器下载,需要额外在前端处理响应的Blob数据,或者改成普通表单提交的方式发起导出请求。

修复代码示例

1. 修改post方法

import json

def post(self, request, *args, **kwargs):
    input_str = request.POST.get('inputString')
    # 用json.loads替代eval修复安全问题
    input_dict = json.loads(input_str)
    modalID = input_dict["modalID"]
    pkList = input_dict["pks"].split(",")
    # 接收操作返回值,如果是下载响应就直接返回
    action_resp = self.batch_actions(request, modalID, pkList)
    if action_resp:
        return action_resp
    return HttpResponseRedirect(self.get_success_url())

2. 修改batch_actions方法

def batch_actions(self, request, modalID, pkList):
    if modalID == "deleteModalSubmit":
        self.batch_delete(pkList)
        self.success_message = 'SlateDocs删除成功!'
    elif modalID == "csvModalSubmit":
        # 直接返回ZIP下载响应
        resp = self.batch_csv_zip(pkList)
        self.success_message = 'SlateDocs已成功导出为ZIP包!'
        messages.success(self.request, self.success_message)
        return resp
    elif modalID == "vantageModalSubmit":
        self.batch_csv_to_vantage(request, pkList)
        self.success_message = 'SlateDocs已成功发送到Vantage!'
    messages.success(self.request, self.success_message)

3. 修改batch_csv_zip方法

def batch_csv_zip(self, pkList):
    from django.conf import settings
    t = time.time()
    # 避免和datetime模块重名,修改变量名
    datetime_str = time.strftime('%Y%m%d%H%M', time.localtime(t))
    # 用BASE_DIR拼接绝对路径,避免相对路径定位错误
    csv_filepath = os.path.join(settings.BASE_DIR, "ngceng/static/docs/csv")
    zippath = os.path.join(settings.BASE_DIR, "ngceng/static/docs/zip")
    zipfilename = f"{datetime_str}_slatedoc_csv"
    csv_temp_dir = os.path.join(csv_filepath, datetime_str)
    # 增加exist_ok=True避免目录已存在时报错
    os.makedirs(csv_temp_dir, exist_ok=True)

    for pk in pkList:
        item = SlateDoc.objects.get(pk=pk)
        slatedoc_resource = SlateDocResource()
        queryset = SlateDoc.objects.filter(pk=pk)
        dataset = slatedoc_resource.export(queryset)
        filename = item.filename
        filename_list = re.split('(\-+)|(\_+)', filename)
        filepath = os.path.join(csv_temp_dir, f"{filename_list[0]}.csv")

        with open(filepath, 'w', newline='') as f:
            writer = csv.writer(f, delimiter=',')
            writer.writerow(fieldnames_export)
            writer.writerows(dataset)

    zip_f = os.path.join(zippath, f"{zipfilename}.zip")
    # 不需要修改全局工作目录,直接指定root_dir参数
    shutil.make_archive(os.path.join(zippath, zipfilename), 'zip', root_dir=csv_temp_dir)
    
    # FileResponse直接传入文件对象,不要调用read()
    response = FileResponse(open(zip_f, 'rb'), content_type='application/zip')
    # filename加双引号兼容含特殊字符的文件名
    response['Content-Disposition'] = f"attachment; filename=\"{zipfilename}.zip\""
    return response

内容的提问来源于stack exchange,提问作者stevec

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 23:45:10