- 這里的文件下載要區分是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