- 这里的文件下载要区分是txt还是excel或者pdf,其中解决这个问题最恶心的地方就是再向页面传输的时候需要先读取然后以流的方式存到response对象中,那这期间涉及到一个问题就是读取的方式,以最后面的例子为准:
- with open(file_name, 'rb')as f这种方式读取excel就可以在浏览器上点保存后查看excel等正常打开文件
- 但是,如果是withopen(file_name)as f这种方式就不可以,原因是这样读取的文件经过流传送到页面上之后可能由于http协议的问题导致文件打不开,弹出文件已损坏之类的错误提示
基于Django建立的网站,如果提供文件下载功能,最简单的方式莫过于将静态文件交给Nginx等处理,但有些时候,由于网站本身逻辑,需要通过Django提供下载功能,如页面数据导出功能(下载动态生成的文件)、先检查用户权限再下载文件等。因此,有必要研究一下文件下载功能在Django中的实现。
声明,以下文字部分是摘选自Gevin的博客《Django 实现下载文件功能》
最简单的文件下载功能的实现
将文件流放入HttpResponse对象即可,如:
deffile_download(request):# do something...withopen('file_name.txt') as f:
c = f.read()returnHttpResponse(c)
这种方式简单粗暴,适合小文件的下载,但如果这个文件非常大,这种方式会占用大量的内存,甚至导致服务器崩溃
更合理的文件下载功能
Django的HttpResponse对象允许将迭代器作为传入参数,将上面代码中的传入参数c换成一个迭代器,便可以将上述下载功能优化为对大小文件均适合;而Django更进一步,推荐使用StreamingHttpResponse对象取代HttpResponse对象,StreamingHttpResponse对象用于将文件流发送给浏览器,与HttpResponse对象非常相似,对于文件下载功能,使用StreamingHttpResponse对象更合理。
因此,更加合理的文件下载功能,应该先写一个迭代器,用于处理文件,然后将这个迭代器作为参数传递给StreaminghttpResponse对象,如:
from django.http import StreamingHttpResponse
defbig_file_download(request):# do something...
deffile_iterator(file_name, chunk_size=512):
withopen(file_name)asf:whileTrue:
c = f.read(chunk_size)
if c:
yield c
else:
break
the_file_name ="file_name.txt"
response = StreamingHttpResponse(file_iterator(the_file_name))
return response
文件下载功能再次优化
上述的代码,已经完成了将服务器上的文件,通过文件流传输到浏览器,但文件流通常会以乱码形式显示到浏览器中,而非下载到硬盘上,因此,还要在做点优化,让文件流写入硬盘。优化很简单,给StreamingHttpResponse对象的Content-Type和Content-Disposition字段赋下面的值即可,如:
response['Content-Type'] ='application/octet-stream'response['Content-Disposition'] ='attachment;filename="test.pdf"'
完整代码如下:
from django.http import StreamingHttpResponse
def big_file_download(request):
# do something...
deffile_iterator(file_name, chunk_size=512):
with open(file_name) as f:
whileTrue:
c = f.read(chunk_size)
if c:
yield c
else:
break
the_file_name ="big_file.pdf"
response = StreamingHttpResponse(file_iterator(the_file_name))
response['Content-Type'] ='application/octet-stream'response['Content-Disposition'] ='attachment;filename="{0}"'.format(the_file_name)
return response
我亲自试验的代码:
def file_iterator(file_name):
with open(file_name, 'rb') as f: # 切记open打开文件的方式
while True:
c = f.read()
if c:
yield c
else:
break
def download_view(request):
title = [u'快递单号', u'快递类型', u'快递分类', u'快递费(元)' , u'快递员编号', u'发货人姓名',u'发货人联系方式', u'发货人地址', u'收货人姓名', u'收货人联系方式', u'收货人地址', u'货架编号',u'快递状态', u'创建时间']
sql = "select * from express_info order by id desc"
with myapi.connection() as con:
cur = con.cursor()
#todo这里是从你的数据库查询出要导出的数据
filename = "express_{}.xlsx".format(datetime.datetime.now().strftime("%Y%m%d%H%M%S")) #指定excel文件名
PROJECT_HOME = os.path.dirname(os.path.abspath(__file__))
DOWNLOAD_DIR = os.path.join(PROJECT_HOME, '..','static', 'download') #指定存放文件的目录
file_path = "{}/{}".format(DOWNLOAD_DIR,filename)
workbook = xlsxwriter.Workbook(file_path) # 这里我用的xlsxwriter导出文件
table = workbook.add_worksheet()
table.write_row('A1', title)
tmp_line = 2
for l in ls:
tmp = [] #todo 指定每一行的数据
table.write_row("A{}".format(行数), tmp) #自行去查一下write_row方法
tmp_line += 1
workbook.close()
# 可以直接把文件返回页面,如果文件太大,就用下面的方法也可以
# response = HttpResponse()
# response['Content-Disposition'] = "attachment;filename='{0}'".format(filename)
# content = open(file_path, 'rb').read()
# response.write(content)
# return response
from django.http import StreamingHttpResponse
response = StreamingHttpResponse(file_iterator(file_path))
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = "attachment;filename='{0}'".format(filename)
return response