Handling File and Image Uploads
To handle file and image uploads, use the FileField and ImageField classes within your forms to validate incoming data and return UploadedFile objects.
Processing Form Uploads
When a form containing a FileField or ImageField is submitted, the uploaded file data is accessible via request.FILES. You must ensure your HTML form uses enctype="multipart/form-data".
from django import forms
from django.forms.fields import FileField, ImageField
class DocumentForm(forms.Form):
title = forms.CharField(max_length=100)
upload = FileField()
preview = ImageField()
def handle_upload(request):
if request.method == "POST":
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
# These are UploadedFile instances
doc = form.cleaned_data['upload']
img = form.cleaned_data['preview']
print(f"Filename: {doc.name}")
print(f"Size: {doc.size} bytes")
print(f"Content Type: {doc.content_type}")
# Accessing the Pillow Image object from ImageField
print(f"Image Format: {img.image.format}")
The UploadedFile object returned by the form fields is an abstract wrapper. Depending on the file size and your settings, it will be one of two concrete subclasses:
InMemoryUploadedFile: Used for small files that are streamed into memory.TemporaryUploadedFile: Used for larger files streamed to a temporary location on disk. You can access the path usingfile.temporary_file_path().
Efficiently Saving Uploaded Files
To handle large files without exhausting server memory, iterate over the file content using the chunks() method provided by the File base class.
def save_file(uploaded_file):
# chunks() defaults to 64KB (File.DEFAULT_CHUNK_SIZE)
with open(f"uploads/{uploaded_file.name}", "wb+") as destination:
for chunk in uploaded_file.chunks():
destination.write(chunk)
For InMemoryUploadedFile, multiple_chunks() returns False and the entire content is yielded in a single chunk. For TemporaryUploadedFile, it yields data from the disk.
Validating and Inspecting Images
The ImageField uses Pillow to verify that the uploaded file is a valid image. It annotates the file object with an .image attribute (the Pillow Image object) and sets the .content_type based on the image format.
To get image dimensions (width and height) without manually opening the file, wrap the uploaded file in an ImageFile object:
from django.core.files.images import ImageFile
def process_image(uploaded_image):
# ImageFile provides lazy access to dimensions
img = ImageFile(uploaded_image)
print(f"Dimensions: {img.width}x{img.height}")
Programmatically Creating Files for Tests
When writing tests or generating files in code, use SimpleUploadedFile or ContentFile.
Using SimpleUploadedFile
SimpleUploadedFile is specifically designed for testing and allows you to define the name, content, and content type in one call.
from django.core.files.uploadedfile import SimpleUploadedFile
# Create a text file
text_file = SimpleUploadedFile("test.txt", b"Hello World", content_type="text/plain")
# Create a mock image for testing ImageField
image_data = b"GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\xff\xff\xff!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;"
img_file = SimpleUploadedFile("pixel.gif", image_data, content_type="image/gif")
Using ContentFile
ContentFile is a lower-level wrapper that turns raw strings or bytes into a file-like object compatible with Django's storage system.
from django.core.files.base import ContentFile
# ContentFile clears computed size when written to
file_content = ContentFile("Dynamic content", name="dynamic.txt")
Handling Multiple File Uploads
To support multiple file uploads in a single field, extend FileField and use a widget with allow_multiple_selected set to True.
from django.forms import FileField, FileInput
class MultipleFileInput(FileInput):
allow_multiple_selected = True
class MultipleFileField(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)):
return [single_file_clean(d, initial) for d in data]
return single_file_clean(data, initial)
Security and Performance Gotchas
Filename Sanitization
The UploadedFile class automatically sanitizes filenames in its name property setter. It uses os.path.basename to strip directory paths and limits the filename to 255 characters to prevent filesystem issues.
Image Validation DoS Prevention
ImageField.to_python calls Pillow's image.verify() rather than image.load(). This is a security measure to prevent Denial of Service (DoS) attacks where a specially crafted image could force the server to allocate massive amounts of memory by attempting to decode the entire image at once.
Temporary File Cleanup
TemporaryUploadedFile uses tempfile.NamedTemporaryFile. When the file is closed via the close() method, the temporary file on disk is automatically deleted. If you move the file manually, the close() method handles the FileNotFoundError gracefully.
Empty File Handling
By default, FileField raises a ValidationError if the submitted file is empty. If you need to allow empty files, initialize the field with allow_empty_file=True:
field = FileField(allow_empty_file=True)