Django ListView批量选择行导出ZIP包无法触发下载问题求助
Django批量导出ZIP压缩包无法触发下载问题修复方案
核心问题原因
- 响应链路断裂:
batch_csv_zip方法生成的FileResponse没有被上层方法接收返回,post方法最终固定返回重定向响应,下载响应直接被丢弃 FileResponse使用错误:不应该传入文件读取后的字节串,直接传入文件对象即可,避免大文件场景下内存溢出- 存在高危安全漏洞:直接使用
eval解析用户提交的inputString,攻击者可传入恶意代码执行服务器端任意操作 - 不必要的全局状态修改:
os.chdir修改进程工作目录,并发请求场景下会导致路径混乱 - 响应头不规范:
Content-Type头名小写,filename未加引号,部分浏览器可能无法识别下载规则 - 文件操作冗余:使用
with上下文管理文件时不需要手动调用close,且原有代码未加括号f.close()属于无效调用
修复步骤
- 调整请求处理链路,导出ZIP时不返回重定向,直接返回下载响应
- 替换
eval为json.loads,前端提交时把数据转为JSON字符串而非直接传Python表达式 - 修正
FileResponse用法和响应头配置 - 移除不必要的
os.chdir调用,修正路径拼接逻辑,避免相对路径定位错误 - 优化目录创建逻辑,避免目录已存在时抛出异常
注意:如果你的批量操作是通过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
相关产品推荐
相关产品推荐

