你或許知道,我們上傳的文件默認放在media文件夾中的筝家,且Django會為每個上傳的靜態(tài)文件分配一個靜態(tài)url陷谱。在模板中迷扇,你可以使用{{ mymodel.file.url }}獲取每個文件的鏈接(url)惧浴,瀏覽器也是可以直接打開這個url的,如下所示奕剃。
<td><a href="/media/files/b1957d79f3.JPG/">/media/files/b1957d79f3.JPG</a></td>
然而當你碰到如下2種情況時衷旅,你需要編寫自己的視圖下載方法。
1纵朋、你希望用戶以附件形式獲得文件柿顶,而不是瀏覽器直接打開。
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如下所示
{% for file in files %}
<tr>
<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/時旨袒,你會發(fā)現我們連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文件夾里每個文件創(chuàng)建獨立url資源蔚龙。視圖和模板里驗證用戶是否已登錄,登錄或通過權限驗證后通過自己編寫的下載方法下載文件映胁。- 安全性高木羹,但實現相對復雜。
————————————————
版權聲明:本文為CSDN博主「大江狗」的原創(chuàng)文章解孙,遵循CC 4.0 BY-SA版權協議坑填,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_42134789/article/details/83346714