在前篇教程裏,我們講解了如何利用Django上傳文件,今天我們就來講解下如何利用Django處理文件下載,並談下文件私有化的大概思路。本文是在前篇基礎上寫的,所以項目相關配置方面參閱前篇內容。

  • Django基礎(17): 如何上傳處理文件及Ajax文件上傳示範(附GitHub源碼)

為什麼需要編寫下載視圖方法?

你或許知道,我們上傳的文件默認放在media文件夾中的,且Django會為每個上傳的靜態文件分配一個靜態url。在模板中,你可以使用{{ mymodel.file.url }}獲取每個文件的鏈接(url),瀏覽器也是可以直接打開這個url的,如下所示。

<td><a href="/media/files/b1957d79f3.JPG/">/media/files/b1957d79f3.JPG</a></td>

然而當你碰到如下2種情況時,你需要編寫自己的視圖下載方法。

  • 你希望用戶以附件形式獲得文件,而不是瀏覽器直接打開。
  • 你希望允許用戶下載一些保密文件,而不希望在html模板中暴露它們。

具體思路

我們先新建一個file_download的app,添加如下urls。該URL了包含了一個文件的相對路徑file_path作為參數, 其對應視圖是file_download方法。我們現在就開始嘗試用不同方法來處理文件下載。

from django.urls import path, re_path
from . import views

# namespace
app_name = file_download

urlpatterns = [

re_path(r^download/(?P<file_path>.*)/$, views.file_download, name=file_download),

]

模板templates/file_upload/file_list.html改為如下所示, 而不再採用option 1。

{% for file in files %}
<tr>
<!-- Option 1 <td><a href="{{ file.file.url }}/">{{ file.file.url }}</a></td> -->
<td><a href="/file/download{{ file.file.url }}/">{{ file.file.url }}</a></td>
<td>{{ file.file.size | filesizeformat }}</td>
<td>{{ file.upload_method }}</td>
</tr>
{% endfor %}

方法一: 使用HttpResonse

下面方法從url獲取file_path, 打開文件,讀取文件,然後通過HttpResponse方法輸出。

import os
from django.http import HttpResponse

def file_download(request, file_path):
# do something...
with open(file_path) as f:
c = f.read()
return HttpResponse(c)

然而該方法有個問題,如果文件是個二進位文件,HttpResponse輸出的將會是亂碼。對於一些二進位文件(圖片,pdf),我們更希望其直接作為附件下載。當文件下載到本機後,用戶就可以用自己喜歡的程序(如Adobe)打開閱讀文件了。這時我們可以對上述方法做出如下改進, 給response設置content_type和Content_Disposition。

import os
from django.http import HttpResponse, Http404

def media_file_download(request, file_path):
with open(file_path, rb) as f:
try:
response = HttpResponse(f)
response[content_type] = "application/octet-stream"
response[Content-Disposition] = attachment; filename= + os.path.basename(file_path)
return response
except Exception:
raise Http404

HttpResponse有個很大的弊端,其工作原理是先讀取文件,載入內存,然後再輸出。如果下載文件很大,該方法會佔用很多內存。對於下載大文件,Django更推薦StreamingHttpResponse和FileResponse方法,這兩個方法將下載文件分批(Chunks)寫入用戶本地磁碟,先不將它們載入伺服器內存。

方法二: 使用SteamingHttpResonse

import os
from django.http import HttpResponse, Http404, StreamingHttpResponse

def stream_http_download(request, file_path):
try:
response = StreamingHttpResponse(open(file_path, rb))
response[content_type] = "application/octet-stream"
response[Content-Disposition] = attachment; filename= + os.path.basename(file_path)
return response
except Exception:
raise Http404

方法三: 使用FileResonse

FileResponse方法是SteamingHttpResponse的子類,是小編我推薦的文件下載方法。如果我們給file_response_download加上@login_required裝飾器,那麼我們就可以實現用戶需要先登錄才能下載某些文件的功能了。

import os
from django.http import HttpResponse, Http404, FileResponse

def file_response_download1(request, file_path):
try:
response = FileResponse(open(file_path, rb))
response[content_type] = "application/octet-stream"
response[Content-Disposition] = attachment; filename= + os.path.basename(file_path)
return response
except Exception:
raise Http404

然而即使加上了@login_required的裝飾器,用戶只要獲取了文件的鏈接地址, 他們依然可以通過瀏覽器直接訪問那些文件。我們等會再談保護文件的鏈接地址和文件私有化,因為此時我們還有個更大的問題需要擔憂。我們定義的下載方法可以下載所有文件,不僅包括.py文件,還包括不在media文件夾裏的文件(比如非用戶上傳的文件)。比如當我們直接訪問127.0.0.1:8000/file/download/file_project/settings.py/時,你會發現我們連file_project目錄下的settings.py都下載了。如果哪個程序員這麼蠢,你可以將他直接fire了。所以我們在編寫下載方法時,我們一定要限定那些文件可以下,哪些不能下或者限定用戶只能下載media文件夾裏的東西。

def file_response_download(request, file_path):
ext = os.path.basename(file_path).split(.)[-1].lower()
# cannot be used to download py, db and sqlite3 files.
if ext not in [py, db, sqlite3]:
response = FileResponse(open(file_path, rb))
response[content_type] = "application/octet-stream"
response[Content-Disposition] = attachment; filename= + os.path.basename(file_path)
return response
else:
raise Http404

文件私有化的兩種方法

如果你想實現只有登錄過的用戶才能查看和下載某些文件,大概有兩種方法,這裡僅提供思路。

  • 上傳文件放在media文件夾,文件名使用很長的隨機字元串命名(uuid), 讓用戶無法根據文件名猜出這是什麼文件。視圖和模板裏驗證用戶是否已登錄,登錄或通過許可權驗證後才顯示具體的url。- 簡單易實現,安全性不高,但對於一般項目已足夠。
  • 上傳文件放在非media文件夾,用戶即使知道了具體文件地址也無法訪問,因為Django只會給media文件夾裏每個文件創建獨立url資源。視圖和模板裏驗證用戶是否已登錄,登錄或通過許可權驗證後通過自己編寫的下載方法下載文件。- 安全性高,但實現相對複雜。

GitHub源碼

github.com/shiyunbo/dja

小結

我們總結了何時需要自定義文件下載方法,並講解了HttpResponse, StreamingHttpResponse和FileResponse的基本用法。我們還總結了使用自定義下載方法需要注意的事項(比如限定文件和文件夾),最後談了下如何實現文件私有化。

如果喜歡我們的文章,就加入微信收藏或點贊吧。

Best regards,

大江狗

2018.10.24


推薦閱讀:
相關文章