Django下载xls文件打开损坏、FileResponse传参报错问题求助
Django xls文件下载功能异常修复方案
问题概述
开发环境基于Python+Django,xls文件下载逻辑写在views.py中,业务要求必须使用xls格式(需配套后续导入功能,不可替换为xlsx),两次代码实现均出现异常:
- 第一版代码可触发下载,但下载后的xls文件打开提示损坏
- 第二版改用
FileResponse实现后,访问路由直接报500错误,提示HTTP status code must be an integer
根因分析
第一版代码问题
第一版实现代码如下:
def downloadfile(request, filename=''): if filename != '': BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) filename = 'SampleExcel.xls' filepath = BASE_DIR + "\SampleExcel\SampleExcel.xls" path = open(filepath, encoding='cp437') mime_type, _ = mimetypes.guess_type(filepath) response = HttpResponse(path, content_type=mime_type) response['Content-Disposition'] = "attachment; filename=%s" % filename return response else: return HttpResponseRedirect("adduser", {})
核心问题:xls是二进制格式文件,必须以二进制模式读取。上述代码打开文件时未指定'rb'模式,默认以文本模式打开,还指定了cp437编码,读取过程中会对二进制字节做编码转义处理,直接篡改了原文件的二进制内容,最终下载的文件结构损坏无法打开。
另外手动拼接路径写死\分隔符,在Linux部署环境下会出现路径找不到的问题。
第二版代码问题
第二版调整后的代码如下:
if filename != '': BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) filename = 'SampleExcel.xls' filepath = BASE_DIR + "\SampleExcel\SampleExcel.xls" return FileResponse(open(filepath, 'rb') , True, filename) else: return HttpResponseRedirect("adduser", {})
核心问题:FileResponse参数传递顺序错误。FileResponse第二个位置参数是HTTP状态码status,要求传入整数类型,代码中第二个位置传了布尔值True,第三个位置直接传文件名,不符合参数定义,导致响应初始化失败,最终触发响应写入时的字节类型断言错误,对应错误日志:
Traceback (most recent call last): File "C:\Users\PCSI\AppData\Local\Programs\Python\Python310\lib\wsgiref\handlers.py", line 138, in run self.finish_response() File "C:\Users\PCSI\AppData\Local\Programs\Python\Python310\lib\wsgiref\handlers.py", line 184, in finish_response self.write(data) File "C:\Users\PCSI\AppData\Local\Programs\Python\Python310\lib\wsgiref\handlers.py", line 279, in write assert type(data) is bytes, \ AssertionError: write() argument must be a bytes instance [27/Jun/2022 15:07:10] "GET /downloadfile/SampleExcel.xls HTTP/1.1" 500 59
正确实现代码
修复所有问题后的可运行代码如下:
import os from django.http import FileResponse, HttpResponseRedirect from django.urls import reverse import mimetypes def downloadfile(request, filename=''): if filename: BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 用os.path.join拼接路径,自动适配不同操作系统的路径分隔符 filepath = os.path.join(BASE_DIR, "SampleExcel", "SampleExcel.xls") # 二进制只读模式打开文件,禁止指定文本编码参数,避免文件内容被转义 file = open(filepath, 'rb') mime_type, _ = mimetypes.guess_type(filepath) # 以关键字参数形式传参,避免位置参数顺序错误 response = FileResponse( file, as_attachment=True, filename=filename, content_type=mime_type ) return response else: # 用reverse解析路由别名,避免硬编码路由路径 return HttpResponseRedirect(reverse("adduser"))
注意事项
- 所有二进制格式文件(xls、图片、压缩包等)下载时,都必须以
'rb'二进制只读模式打开,不能用文本模式,也不要给open()传encoding参数,否则会导致文件损坏 - 调用Django内置响应类时,不确定参数顺序尽量用关键字参数传递,避免位置传参错位引发的异常
- 服务器上部署时要保证Django进程对目标xls文件有读权限,否则会触发权限报错
- 路径拼接统一用
os.path.join,不要手动写/或\分隔符,避免跨系统部署时路径不兼容
内容的提问来源于stack exchange,提问作者user19350831
相关产品推荐
相关产品推荐

