檔案上傳¶
當 Django 處理檔案上傳時,檔案資料最終會放置在 request.FILES
中(有關 request
物件的更多資訊,請參閱請求和回應物件的文件)。本文將說明檔案如何儲存在磁碟和記憶體中,以及如何自訂預設行為。
警告
如果您接受來自不受信任使用者的上傳內容,則存在安全風險!請參閱安全指南中關於使用者上傳內容的主題,以了解緩解措施的詳細資訊。
基本檔案上傳¶
考慮一個包含 FileField
的表單
forms.py
¶from django import forms
class UploadFileForm(forms.Form):
title = forms.CharField(max_length=50)
file = forms.FileField()
處理此表單的視圖將在 request.FILES
中接收檔案資料,這是一個字典,其中包含表單中每個 FileField
(或 ImageField
,或其他 FileField
子類別) 的鍵。因此,上述表單中的資料可以透過 request.FILES['file']
來存取。
請注意,只有當請求方法是 POST
,至少實際提交了一個檔案欄位,並且發送請求的 <form>
具有屬性 enctype="multipart/form-data"
時,request.FILES
才會包含資料。否則,request.FILES
將為空。
大多數情況下,您會將來自 request
的檔案資料傳遞到表單中,如 將上傳的檔案繫結到表單 中所述。看起來會像這樣
views.py
¶from django.http import HttpResponseRedirect
from django.shortcuts import render
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(request, "upload.html", {"form": form})
請注意,我們必須將 request.FILES
傳遞到表單的建構函式中;這是檔案資料如何繫結到表單的方式。
以下是一種您可能處理上傳檔案的常見方式
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()
,可以確保大型檔案不會使您的系統記憶體不堪負荷。
在 UploadedFile
物件上還有其他一些可用的方法和屬性;有關完整參考,請參閱 UploadedFile
。
使用模型處理上傳的檔案¶
如果您要將檔案儲存到具有 FileField
的 Model
上,使用 ModelForm
會使此過程更容易。當呼叫 form.save()
時,檔案物件將儲存到對應 FileField
的 upload_to
參數指定的位置
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})
如果您要手動建構物件,您可以將來自 request.FILES
的檔案物件指派給模型中的檔案欄位
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})
如果您要在請求之外手動建構物件,您可以將類似 File
的物件指派給 FileField
from django.core.management.base import BaseCommand
from django.core.files.base import ContentFile
class MyCommand(BaseCommand):
def handle(self, *args, **options):
content_file = ContentFile(b"Hello world!", name="hello-world.txt")
instance = ModelWithFileField(file_field=content_file)
instance.save()
上傳多個檔案¶
如果您想使用一個表單欄位上傳多個檔案,請建立該欄位 widget 的子類別,並將其 allow_multiple_selected
類別屬性設定為 True
。
為了讓所有這些檔案都由您的表單驗證(並且欄位的值包含所有這些檔案),您還必須對 FileField
進行子類別化。請參閱下面的範例。
多個檔案欄位
Django 將來很可能會支援適當的多個檔案欄位。
forms.py
¶from django import forms
class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True
class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs)
def clean(self, data, initial=None):
single_file_clean = super().clean
if isinstance(data, (list, tuple)):
result = [single_file_clean(d, initial) for d in data]
else:
result = [single_file_clean(data, initial)]
return result
class FileFieldForm(forms.Form):
file_field = MultipleFileField()
然後覆寫 FormView
子類別的 form_valid()
方法來處理多個檔案上傳
views.py
¶from django.views.generic.edit import FormView
from .forms import FileFieldForm
class FileFieldFormView(FormView):
form_class = FileFieldForm
template_name = "upload.html" # Replace with your template.
success_url = "..." # Replace with your URL or reverse().
def form_valid(self, form):
files = form.cleaned_data["file_field"]
for f in files:
... # Do something with each file.
return super().form_valid(form)
警告
這將允許您僅在表單層級處理多個檔案。請注意,您不能使用它將多個檔案放在單一模型實例(在單一欄位中),例如,即使自訂 widget 用於與模型 FileField
相關的表單欄位也是如此。
上傳處理器¶
當使用者上傳檔案時,Django 會將檔案資料傳遞給上傳處理器 — 一個處理上傳檔案資料的小類別。上傳處理器最初在 FILE_UPLOAD_HANDLERS
設定中定義,預設為
[
"django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler",
]
結合 MemoryFileUploadHandler
和 TemporaryFileUploadHandler
提供了 Django 的預設檔案上傳行為,即將小檔案讀取到記憶體中,將大檔案讀取到磁碟上。
您可以撰寫自訂處理器,以自訂 Django 處理檔案的方式。例如,您可以使用自訂處理器來實施使用者層級的配額、即時壓縮資料、渲染進度列,甚至直接將資料傳送到另一個儲存位置,而無需將其儲存在本機。請參閱 撰寫自訂上傳處理器,以了解如何自訂或完全取代上傳行為的詳細資訊。
上傳的資料儲存在哪裡¶
在儲存上傳的檔案之前,資料需要儲存在某個地方。
預設情況下,如果上傳的檔案小於 2.5 MB,Django 會將上傳的全部內容保存在記憶體中。這表示儲存檔案僅涉及從記憶體讀取和寫入磁碟,因此速度非常快。
但是,如果上傳的檔案太大,Django 會將上傳的檔案寫入儲存在系統臨時目錄中的臨時檔案中。在類似 Unix 的平台上,這表示您可以預期 Django 會產生一個類似 /tmp/tmpzfp6I6.upload
的檔案。如果上傳的檔案夠大,您可以在 Django 將資料串流到磁碟時觀察此檔案的大小增長。
這些細節 — 2.5 MB;/tmp
;等等 — 是「合理的預設值」,可以按照下一節所述進行自訂。
變更上傳處理器的行為¶
有一些設定可以控制 Django 的檔案上傳行為。請參閱 檔案上傳設定 以了解詳細資訊。
動態修改上傳處理器¶
有時,特定的視圖會需要不同的上傳行為。在這些情況下,您可以透過修改 request.upload_handlers
,來針對每個請求覆寫上傳處理器。預設情況下,此列表會包含 FILE_UPLOAD_HANDLERS
所提供的上傳處理器,但您可以像修改其他列表一樣修改此列表。
例如,假設您撰寫了一個 ProgressBarUploadHandler
,它會向某種 AJAX 小工具提供上傳進度的回饋。您會像這樣將此處理器新增到您的上傳處理器中:
request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
在這種情況下,您可能想要使用 list.insert()
(而不是 append()
),因為進度列處理器需要在任何其他處理器之前執行。請記住,上傳處理器會按照順序處理。
如果您想要完全替換上傳處理器,您可以指定一個新的列表:
request.upload_handlers = [ProgressBarUploadHandler(request)]
注意
您只能在存取 request.POST
或 request.FILES
之前修改上傳處理器 – 在上傳處理已經開始後變更上傳處理器是沒有意義的。如果您嘗試在從 request.POST
或 request.FILES
讀取資料後修改 request.upload_handlers
,Django 會拋出錯誤。
因此,您應該盡可能在視圖中盡早修改上傳處理器。
此外,request.POST
會被預設啟用的 CsrfViewMiddleware
存取。這表示您需要對您的視圖使用 csrf_exempt()
,以允許您變更上傳處理器。然後,您需要在實際處理請求的函數上使用 csrf_protect()
。請注意,這表示處理器可能會在 CSRF 檢查完成之前開始接收檔案上傳。範例程式碼:
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt
def upload_file_view(request):
request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
return _upload_file_view(request)
@csrf_protect
def _upload_file_view(request):
# Process request
...
如果您使用基於類的視圖,您需要在其 dispatch()
方法上使用 csrf_exempt()
,並在實際處理請求的方法上使用 csrf_protect()
。範例程式碼:
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@method_decorator(csrf_exempt, name="dispatch")
class UploadFileView(View):
def setup(self, request, *args, **kwargs):
request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
super().setup(request, *args, **kwargs)
@method_decorator(csrf_protect)
def post(self, request, *args, **kwargs):
# Process request
...