Django cookbook · Intermediate
Validate uploads and store them in object storage
Users upload files that are too large, the wrong type, or end up on the web server's disk.
Approach
Validate size and extension in the form or serializer, store files through the STORAGES setting (S3 via
django-storages), generate random names, and serve private files with short-lived signed URLs.
Solution
# settings.py
STORAGES = {
"default": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {"bucket_name": "app-uploads", "querystring_auth": True, "querystring_expire": 300},
},
"staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"},
}
DATA_UPLOAD_MAX_MEMORY_SIZE = 5 * 1024 * 1024
# documents/models.py
import uuid
from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator
from django.db import models
def max_size_10mb(file):
if file.size > 10 * 1024 * 1024:
raise ValidationError("Files must be 10 MB or smaller.")
def upload_to(instance, filename):
return f"documents/{instance.owner_id}/{uuid.uuid4().hex}.pdf"
class Document(models.Model):
owner = models.ForeignKey("accounts.User", on_delete=models.CASCADE)
file = models.FileField(
upload_to=upload_to, validators=[FileExtensionValidator(["pdf"]), max_size_10mb]
)
uploaded_at = models.DateTimeField(auto_now_add=True)
Pitfalls
- Trusting the client's file name or content type; generate names and check content when it matters.
- Serving user uploads from the same domain as the app without
Content-Disposition, enabling stored XSS. - Leaving orphaned files when records are deleted; delete them in a task after commit.
Variations
- For large files, let browsers upload directly to S3 with presigned POST URLs, then confirm in Django.
- Scan uploads for malware in a background task before marking them available.
Further reading: Managing files
Reference: https://docs.djangoproject.com/en/6.0/topics/files/
More Django recipes
- Add a required column without downtime
- Add full-text search with PostgreSQL
- Add health checks and structured logging
- Avoid N+1 queries with select_related and prefetch_related
- Cache expensive queries and invalidate on change
- Call external APIs concurrently in async views
- Configure settings from environment variables
- Enforce rules with database constraints