Django 实现下载文件功能的示例
基于Django建立的网站,如果提供文件下载功能,最简单的方式莫过于将静态文件交给Nginx等处理,但有些时候,由于网站本身逻辑,需要通过Django提供下载功能,如页面数据导出功能(下载动态生成的文件)、先检查用户权限再下载文件等。因此,有必要研究一下文件下载功能在Django中的实现。
最简单的文件下载功能的实现
将文件流放入HttpResponse对象即可,如:
deffile_download(request):
#dosomething...
withopen('file_name.txt')asf:
c=f.read()
returnHttpResponse(c)
这种方式简单粗暴,适合小文件的下载,但如果这个文件非常大,这种方式会占用大量的内存,甚至导致服务器崩溃
更合理的文件下载功能
Django的HttpResponse对象允许将迭代器作为传入参数,将上面代码中的传入参数c换成一个迭代器,便可以将上述下载功能优化为对大小文件均适合;而Django更进一步,推荐使用StreamingHttpResponse对象取代HttpResponse对象,StreamingHttpResponse对象用于将文件流发送给浏览器,与HttpResponse对象非常相似,对于文件下载功能,使用StreamingHttpResponse对象更合理。
因此,更加合理的文件下载功能,应该先写一个迭代器,用于处理文件,然后将这个迭代器作为参数传递给StreaminghttpResponse对象,如:
fromdjango.httpimportStreamingHttpResponse defbig_file_download(request): #dosomething... deffile_iterator(file_name,chunk_size=512): withopen(file_name)asf: whileTrue: c=f.read(chunk_size) ifc: yieldc else: break the_file_name="file_name.txt" response=StreamingHttpResponse(file_iterator(the_file_name)) returnresponse
文件下载功能再次优化
上述的代码,已经完成了将服务器上的文件,通过文件流传输到浏览器,但文件流通常会以乱码形式显示到浏览器中,而非下载到硬盘上,因此,还要在做点优化,让文件流写入硬盘。优化很简单,给StreamingHttpResponse对象的Content-Type和Content-Disposition字段赋下面的值即可,如:
response['Content-Type']='application/octet-stream' response['Content-Disposition']='attachment;filename="test.pdf"'
完整代码如下:
fromdjango.httpimportStreamingHttpResponse
defbig_file_download(request):
#dosomething...
deffile_iterator(file_name,chunk_size=512):
withopen(file_name)asf:
whileTrue:
c=f.read(chunk_size)
ifc:
yieldc
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)
returnresponse
具体导出文件格式
导出Excel表格
1.首先是直接导出Excel表格
首先获取要导出的数据、以列表方式保存。
然后将数据写入到Excel,以流的方式返回到页面下载。
importxlwt
importio
importjson
fromdjango.httpimportHttpResponse
defset_style(name,height,bold=False):
style=xlwt.XFStyle()#初始化样式
font=xlwt.Font()#为样式创建字体
font.name=name#'TimesNewRoman'
font.bold=bold
font.color_index=000
font.height=height
style.font=font
#设置单元格边框
#borders=xlwt.Borders()
#borders.left=6
#borders.right=6
#borders.top=6
#borders.bottom=6
#style.borders=borders
#设置单元格背景颜色
#pattern=xlwt.Pattern()
#设置其模式为实型
#pattern.pattern=pattern.SOLID_PATTERN
#设置单元格背景颜色
#pattern.pattern_fore_colour=0x00
#style.pattern=pattern
returnstyle
defwrite_excel(data,name,header):
#打开一个Excel工作簿
file=xlwt.Workbook()
#新建一个sheet,如果对一个单元格重复操作,会引发异常,所以加上参数cell_overwrite_ok=True
table=file.add_sheet(name,cell_overwrite_ok=True)
ifdataisNone:
returnfile
#写标题栏
row0=[u'业务',u'状态',u'北京',u'上海',u'广州',u'深圳',u'状态小计']
foriinrange(0,len(row0)):
table.write_merge(0,0,i,i,row0[i],set_style('TimesNewRoman',220,True))
table.write_merge(0,2,7,9,"单元格合并",set_style('TimesNewRoman',220,True))
"""
table.write_merge(x,x+m,y,w+n,string,sytle)
x表示行,y表示列,m表示跨行个数,n表示跨列个数,string表示要写入的单元格内容,style表示单元格样式。其中,x,y,w,h,都是以0开始计算的。
"""
l=0
n=len(header)
#写入数据
forlineindata:
foriinrange(n):
table.write(l,i,line[header[i]])
l+=1
#直接保存文件
#file.save("D:/excel_name.xls")
#写入IO
res=get_excel_stream(file)
#设置HttpResponse的类型
response=HttpResponse(content_type='application/vnd.ms-excel')
fromurllibimportparse
response['Content-Disposition']='attachment;filename='+parse.quote("excel_name")+'.xls'
#将文件流写入到response返回
response.write(res)
returnresponse
defget_excel_stream(file):
#StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。
excel_stream=io.BytesIO()
#这点很重要,传给save函数的不是保存文件名,而是一个BytesIO流(在内存中读写)
file.save(excel_stream)
#getvalue方法用于获得写入后的byte将结果返回给re
res=excel_stream.getvalue()
excel_stream.close()
returnres
2.导出json文件
导出json文件不像Excel那么麻烦,只需要拼接json格式数据即可,直接导出到本地还是很简单,但是导出到网页,怎么像导出excel一样不保存到本地,直接将流返回?
defwrite_json(data):
try:
json_stream=get_json_stream(data)
response=HttpResponse(content_type='application/json')
fromurllibimportparse
response['Content-Disposition']='attachment;filename='+parse.quote("test")+'.json'
response.write(json_stream)
returnresponse
exceptExceptionase:
print(e)
defget_json_stream(data):
#开始这里我用ByteIO流总是出错,但是后来参考廖雪峰网站用StringIO就没问题
file=io.StringIO()
data=json.dumps(data)
file.write(data)
res=file.getvalue()
file.close()
returnres
3.导出压缩包
由于导出两个文件无法同时都返回,所以考虑将这两个文件放入包中,然后将包以流的方式返回。
思考?此时导出的是zip包中,我怎么将这两个文件流写入zip中,好像有点不太合理。后来在老大指导下先将要打包的文件保存到本地,打包到zip后,将本地的文件删除,随后将该zip文件流读取,写入到response,返回zip文件流。
defwrite_zip(e_data,j_data,export_name): try: #保存到本地文件 #返回文件名,注意此时保存的方法和前面导出保存的json、excel文件区别 j_name=write_json(j_data,export_name[1]) e_name=write_excel(e_data,export_name[1]) #本地文件写入zip,重命名,然后删除本地临时文件 z_name='export.zip' z_file=zipfile.ZipFile(z_name,'w') z_file.write(j_name) z_file.write(e_name) os.remove(j_name) os.remove(e_name) z_file.close() #再次读取zip文件,将文件流返回,但是此时打开方式要以二进制方式打开 z_file=open(z_name,'rb') data=z_file.read() z_file.close() os.remove(z_file.name) response=HttpResponse(data,content_type='application/zip') fromurllibimportparse response['Content-Disposition']='attachment;filename='+parse.quote(z_name) returnresponse exceptExceptionase: logging.error(e) print(e)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。