File uploads require proper HTML encoding, view constructor arguments, and storage configuration for binary data to work end-to-end.
Key Concepts
- enctype=multipart/form-data: Required HTML attribute for file uploads.
- request.FILES: MultiValueDict of uploaded files, separate from POST.
- FileField/ImageField: Form fields for file validation.
- upload_to: Controls storage subdirectory.
- MEDIA_ROOT/MEDIA_URL: Storage path and URL settings.
Real World Context
A job portal accepts resumes (PDF, max 5MB) and photos. Passing request.FILES to the form constructor is the most commonly missed step for new Django developers.
Deep Dive
File Upload Basics
Handling file uploads requires proper form encoding, validation, and storage configuration.
Form Configuration
pythonfrom django import forms class UploadForm(forms.Form): title = forms.CharField(max_length=100) document = forms.FileField() image = forms.ImageField(required=False) # Requires Pillow
Template Requirements
html<!-- enctype is required for file uploads! --> <form method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} <button type="submit">Upload</button> </form>
View Handling
pythondef upload_file(request): if request.method == 'POST': form = UploadForm(request.POST, request.FILES) # Include FILES! if form.is_valid(): handle_uploaded_file(request.FILES['document']) return redirect('success') else: form = UploadForm() return render(request, 'upload.html', {'form': form}) def handle_uploaded_file(f): """Save uploaded file to disk.""" with open(f'uploads/{f.name}', 'wb+') as destination: for chunk in f.chunks(): destination.write(chunk)
Using FileField on Models
python# models.py from django.db import models class Document(models.Model): title = models.CharField(max_length=100) file = models.FileField(upload_to='documents/') uploaded_at = models.DateTimeField(auto_now_add=True) class Photo(models.Model): title = models.CharField(max_length=100) image = models.ImageField(upload_to='photos/%Y/%m/') # Organized by date thumbnail = models.ImageField(upload_to='thumbs/', blank=True)
ModelForm with Files
pythonclass DocumentForm(forms.ModelForm): class Meta: model = Document fields = ['title', 'file'] def upload_document(request): if request.method == 'POST': form = DocumentForm(request.POST, request.FILES) if form.is_valid(): document = form.save() return redirect('document_detail', pk=document.pk) else: form = DocumentForm() return render(request, 'upload.html', {'form': form})
File Validation
pythonfrom django.core.validators import FileExtensionValidator from django.core.exceptions import ValidationError def validate_file_size(value): filesize = value.size if filesize > 10 * 1024 * 1024: # 10 MB raise ValidationError('Maximum file size is 10MB') class DocumentForm(forms.Form): file = forms.FileField( validators=[ FileExtensionValidator(allowed_extensions=['pdf', 'doc', 'docx']), validate_file_size, ] )
Custom File Validation
pythonclass ImageUploadForm(forms.Form): image = forms.ImageField() def clean_image(self): image = self.cleaned_data['image'] # Check file size if image.size > 5 * 1024 * 1024: # 5 MB raise ValidationError('Image must be smaller than 5MB') # Check dimensions (requires Pillow) from PIL import Image img = Image.open(image) if img.width > 4000 or img.height > 4000: raise ValidationError('Image dimensions too large') if img.width < 100 or img.height < 100: raise ValidationError('Image too small') # Reset file position image.seek(0) return image
Settings Configuration
python# settings.py import os # Media files (user uploads) MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # Maximum upload size (default is 2.5MB) DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10 MB FILE_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10 MB
python# urls.py (development only) from django.conf import settings from django.conf.urls.static import static urlpatterns = [ # ... your urls ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Common Pitfalls
- Missing enctype -- Browser sends filename not content.
- Not passing request.FILES -- File fields appear empty.
- Serving media in production like dev -- static() only works with DEBUG=True.
Best Practices
- Validate file size and type -- Use FileExtensionValidator plus python-magic.
- Use upload_to functions -- Organize by date to prevent bloat.
- Use file.chunks() -- Avoid loading entire file into memory.
Summary
- Forms need enctype=multipart/form-data for files.
- Pass both request.POST and request.FILES to the constructor.
- FileField validates presence; ImageField validates format.
- Configure MEDIA_ROOT and MEDIA_URL.
- Validate file size and MIME type.
Code Examples
python
from django import forms
class UploadForm(forms.Form):
title = forms.CharField(max_length=100)
document = forms.FileField()
image = forms.ImageField(required=False) # Requires Pillow
# In the view: pass both POST and FILES
def upload_view(request):
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
uploaded = request.FILES['document']
# uploaded.name, uploaded.size, uploaded.content_type