當(dāng)Django在處理文件上傳的時(shí)候,文件數(shù)據(jù)被保存在request. FILES (更多關(guān)于 request 對(duì)象的信息 請(qǐng)查看 請(qǐng)求和響應(yīng)對(duì)象)。這篇文檔闡述了文件如何上傳到內(nèi)存和硬盤(pán),以及如何自定義默認(rèn)的行為。
警告
允許任意用戶上傳文件是存在安全隱患的。更多細(xì)節(jié)請(qǐng)?jiān)?a rel="nofollow" >用戶上傳的內(nèi)容中查看有關(guān)安全指導(dǎo)的話題。
考慮一個(gè)簡(jiǎn)單的表單,它含有一個(gè)FileField:
# In forms.py...
from django import forms
class UploadFileForm(forms.Form):
title = forms.CharField(max_length=50)
file = forms.FileField()
處理這個(gè)表單的視圖會(huì)在request中接受到上傳文件的數(shù)據(jù)。FILES是個(gè)字典,它包含每個(gè)FileField的鍵 (或者 ImageField,FileField的子類)。這樣的話就可以用request.FILES['file']來(lái)存放表單中的這些數(shù)據(jù)了。
注意request.FILES會(huì)僅僅包含數(shù)據(jù),如果請(qǐng)求方法為POST,并且發(fā)送請(qǐng)求的<form>擁有enctype="multipart/form-data"屬性。否則request.FILES為空。
大多數(shù)情況下,你會(huì)簡(jiǎn)單地從request向表單中傳遞數(shù)據(jù),就像綁定上傳文件到表單描述的那樣。這樣類似于:
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from .forms import UploadFileForm
# Imaginary function to handle an uploaded file.
from somewhere import handle_uploaded_file
def upload_file(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
handle_uploaded_file(request.FILES['file'])
return HttpResponseRedirect('/success/url/')
else:
form = UploadFileForm()
return render_to_response('upload.html', {'form': form})
注意我們必須向表單的構(gòu)造器中傳遞request.FILES。這是文件數(shù)據(jù)綁定到表單的方法。
這里是一個(gè)普遍的方法,可能你會(huì)采用它來(lái)處理上傳文件:
def handle_uploaded_file(f):
with open('some/file/name.txt', 'wb+') as destination:
for chunk in f.chunks():
destination.write(chunk)
遍歷UploadedFile.chunks(),而不是使用read(),能確保大文件并不會(huì)占用系統(tǒng)過(guò)多的內(nèi)存。
UploadedFile對(duì)象也擁有一些其他的方法和屬性;完整參考請(qǐng)見(jiàn)UploadedFile。
如果你在Model上使用FileField保存文件,使用ModelForm可以讓這個(gè)操作更加容易。調(diào)用form.save()的時(shí)候,文件對(duì)象會(huì)保存在相應(yīng)的FileField的upload_to參數(shù)指定的地方。
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import ModelFormWithFileField
def upload_file(request):
if request.method == 'POST':
form = ModelFormWithFileField(request.POST, request.FILES)
if form.is_valid():
# file is saved
form.save()
return HttpResponseRedirect('/success/url/')
else:
form = ModelFormWithFileField()
return render(request, 'upload.html', {'form': form})
如果你手動(dòng)構(gòu)造一個(gè)對(duì)象,你可以簡(jiǎn)單地把文件對(duì)象從request.FILE賦值給模型:
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import UploadFileForm
from .models import ModelWithFileField
def upload_file(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
instance = ModelWithFileField(file_field=request.FILES['file'])
instance.save()
return HttpResponseRedirect('/success/url/')
else:
form = UploadFileForm()
return render(request, 'upload.html', {'form': form})
當(dāng)用戶上傳一個(gè)文件的時(shí)候,Django會(huì)把文件數(shù)據(jù)傳遞給上傳處理器 – 一個(gè)小型的類,會(huì)在文件數(shù)據(jù)上傳時(shí)處理它。上傳處理器在FILE_UPLOAD_HANDLERS中定義,默認(rèn)為:
("django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler",)
MemoryFileUploadHandler 和TemporaryFileUploadHandler一起提供了Django的默認(rèn)文件上傳行為,將小文件讀取到內(nèi)存中,大文件放置在磁盤(pán)中。
你可以編寫(xiě)自定義的處理器,來(lái)定制Django如何處理文件。例如,你可以使用自定義處理器來(lái)限制用戶級(jí)別的配額,在運(yùn)行中壓縮數(shù)據(jù),渲染進(jìn)度條,甚至是向另一個(gè)儲(chǔ)存位置直接發(fā)送數(shù)據(jù),而不把它存到本地。關(guān)于如何自定義或者完全替換處理器的行為,詳見(jiàn)編寫(xiě)自定義的上傳處理器。
在你保存上傳文件之前,數(shù)據(jù)需要儲(chǔ)存在某個(gè)地方。
通常,如果上傳文件小于2.5MB,Django會(huì)把整個(gè)內(nèi)容存到內(nèi)存。這意味著,文件的保存僅僅涉及到從內(nèi)存讀取和寫(xiě)到磁盤(pán),所以非常快。
但是,如果上傳的文件很大,Django會(huì)把它寫(xiě)入一個(gè)臨時(shí)文件,儲(chǔ)存在你系統(tǒng)的臨時(shí)目錄中。在類Unix的平臺(tái)下,你可以認(rèn)為Django生成了一個(gè)文件,名稱類似于/tmp/tmpzfp6I6.upload。如果上傳的文件足夠大,你可以觀察到文件大小的增長(zhǎng),由于Django向磁盤(pán)寫(xiě)入數(shù)據(jù)。
這些特定值 – 2.5 MB,/tmp,以及其它 -- 都僅僅是"合理的默認(rèn)值",它們可以自定義,這會(huì)在下一節(jié)中描述。
Django的文件上傳處理器的行為由一些設(shè)置控制。詳見(jiàn)文件上傳設(shè)置。
有時(shí)候一些特定的視圖需要不同的上傳處理器。在這種情況下,你可以通過(guò)修改request.upload_handlers,為每個(gè)請(qǐng)求覆蓋上傳處理器。通常,這個(gè)列表會(huì)包含FILE_UPLOAD_HANDLERS提供的上傳處理器,但是你可以把它修改為其它列表。
例如,假設(shè)你編寫(xiě)了ProgressBarUploadHandler,它會(huì)在上傳過(guò)程中向某類AJAX控件提供反饋。你可以像這樣將它添加到你的上傳處理器中:
request.upload_handlers.insert(0, ProgressBarUploadHandler())
在這中情況下你可能想要使用list.insert()(而不是append()),因?yàn)檫M(jìn)度條處理器需要在任何其他處理器 之前執(zhí)行。要記住,多個(gè)上傳處理器是按順序執(zhí)行的。
如果你想要完全替換上傳處理器,你可以賦值一個(gè)新的列表:
request.upload_handlers = [ProgressBarUploadHandler()]
注意
你只可以在訪問(wèn)
request.POST或者request.FILES之前修改上傳處理器-- 在上傳處理工作執(zhí)行之后再修改上傳處理就毫無(wú)意義了。如果你在讀取request.FILES之后嘗試修改request.upload_handlers,Django會(huì)拋出異常。所以,你應(yīng)該在你的視圖中盡早修改上傳處理器。
CsrfViewMiddleware也會(huì)訪問(wèn)request.POST,它是默認(rèn)開(kāi)啟的。意思是你需要在你的視圖中使用csrf_exempt(),來(lái)允許你修改上傳處理器。接下來(lái)在真正處理請(qǐng)求的函數(shù)中,需要使用csrf_protect()。注意這意味著處理器可能會(huì)在CSRF驗(yàn)證完成之前開(kāi)始接收上傳文件。例如:
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt
def upload_file_view(request):
request.upload_handlers.insert(0, ProgressBarUploadHandler())
return _upload_file_view(request)
@csrf_protect
def _upload_file_view(request):
... # Process request
譯者:Django 文檔協(xié)作翻譯小組,原文:Overview。
本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉(zhuǎn)載請(qǐng)保留作者署名和文章出處。
Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。