Working with Files
When users upload files to your Django application, the incoming file data is placed in the request.FILES dictionary. This dictionary is only populated if the request method is POST and the HTML form includes the enctype="multipart/form-data" attribute.
Once the file data reaches your server, you usually want to attach it to a database model. Django provides the django.db.models.FileField for storing standard files. Everything discussed in this guide applies equally to this field and its specialized counterpart, django.db.models.ImageField. The image field behaves exactly like the standard file field but adds automatic validation to ensure the uploaded file is actually a valid image.
Basic File Uploads
To handle an uploaded file securely, you typically pass the request.FILES data into a form. This binds the file data to the form so you can validate it before doing anything else.
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django import forms
# The form containing FileField
class UploadFileForm(forms.Form):
title = forms.CharField(max_length=50)
file = forms.FileField()
def upload_file(request):
if request.method == "POST":
# Notice that request.Files is passed into the constructor. This is how file data gets bound to the form.
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
handle_uploaded_file(request.FILES["file"])
return HttpResponseRedirect("/success/")
else:
form = UploadFileForm()
return render(request, "upload.html", {"form": form})When you need to save a file manually, you should read it in small pieces rather than loading the entire file into memory at once. The django.core.files.uploadedfile.UploadedFile object (the actual stored file) inside request.FILES provides a chunks() method for this exact purpose. This prevents large files from crashing your server by taking up too much memory.
def handle_uploaded_file(file_obj):
with open("some/file/name.txt", "wb+") as destination:
for chunk in file_obj.chunks():
destination.write(chunk)Using Models and Forms
If you are saving an uploaded file to a database model, using a ModelForm makes the process much simpler. When you call form.save(), Django automatically saves the file to the location defined by the upload_to argument on your model's FileField.
from django.shortcuts import render
from django.http import HttpResponseRedirect
from .forms import ModelFormWithFileField
def upload_model_file(request):
if request.method == "POST":
form = ModelFormWithFileField(request.POST, request.FILES)
if form.is_valid():
form.save()
return HttpResponseRedirect("/success/")
else:
form = ModelFormWithFileField()
return render(request, "upload.html", {"form": form})Uploading Multiple Files
If you want to accept multiple files through a single form field, you must create a custom widget and field. You start by subclassing django.forms.ClearableFileInput and setting its allow_multiple_selected attribute to True. Next, you subclass django.forms.FileField and override its clean method to validate a list of files instead of just one.
from django import forms
class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True
class MultipleFileField(forms.FileField):
widget = MultipleFileInput
def clean(self, data, initial=None):
clean_file = super().clean
# Ensure the data is always treated as a list
if not isinstance(data, (list, tuple)):
data = [data]
return [clean_file(file, initial) for file in data]
class FileFieldForm(forms.Form):
file_field = MultipleFileField()Once your form is set up, your view will receive a list of files. You can then loop over that list inside form.cleaned_data and process each file individually.
WARNING
This will handle multiple files only at the form level. You cannot save multiple files into a single model FileField.
Upload Handlers
Before Django saves an uploaded file permanently, it has to store the incoming data temporarily while the upload is happening.
As a file uploads, Django passes the incoming data to an upload handler. By default, Django uses a memory handler (django.core.files.uploadhandler.MemoryFileUploadHandler) for files smaller than 2.5 megabytes and a temporary file handler (django.core.files.uploadhandler.TemporaryFileUploadHandler) for large files larger than this threshold.
The memory handler holds the entire file in your server's memory. The temporary file handler writes the incoming data to a temporary file in your server's temporary directory, which you can watch grow as the upload progresses. You can customize this 2.5-megabyte threshold by modifying Django's file upload settings.
You can write custom handlers to track upload progress, enforce user quotas, or stream data directly to a cloud service. If you need to change handlers for a specific view, you can modify the request.upload_handlers list.
Because of how Django parses requests, you must assign your custom handlers before accessing request.POST or request.FILES. Doing this also requires you to disable CSRF protection on the view's setup phase using @csrf_exempt and re-enable it on the specific method that processes the data using @csrf_protect.
Here is how you handle this in a standard function-based view:
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
...If you are using a class-based view, you apply @csrf_exempt to the dispatch method to bypass the initial check. You then assign the custom handler inside the setup method before applying @csrf_protect directly to the post method.
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
...Working with File Objects
Django wraps standard Python files in a django.core.files.File class. This wrapper provides a consistent interface across different storage systems and adds helpful features.
When you access a file through a model instance, you get a django.db.models.fields.files.FieldFile object. This object lets you access file properties like size, url, and name. It also gives you methods to read or modify the file directly from the model.
NOTE
It is important to know the difference between FileField and FieldFile. You use FileField (or ImageField) inside your models.py to define the database column. However, when you access that field on an actual model instance, Django gives you a FieldFile (or ImageFieldFile) object to interact with the file itself.
from .models import Document
# Retrieve a document instance from the database
doc = Document.objects.get(id=1)
# Access the FieldFile object associated with the model's FileField (e.g., 'upload')
uploaded_file = doc.upload
# Get the file's properties
file_name = uploaded_file.name # e.g., 'greeting.txt'
file_size = uploaded_file.size # Returns the size in bytes
file_url = uploaded_file.url # Returns the URL to access the file publiclyIf you need to create a file entirely in memory from raw data or a string, you can use the django.core.files.base.ContentFile class. This is extremely useful when you want to save generated content to a model without writing a temporary file to your disk first.
from django.core.files.base import ContentFile
from .models import Document
# Create a new file in memory
file_content = ContentFile(b"Hello, this is a generated text file.")
# Assign and save it directly to the model instance
doc = Document()
doc.upload.save("greeting.txt", file_content)The Storage API
Django uses a dedicated storage system to decide exactly how and where to save files. You interact with this system through the Storage API, which provides a standard way to manage files regardless of where they actually live.
By default, Django uses a local file system storage backend (django.core.files.storage.FileSystemStorage). This saves files directly to the folder specified by your MEDIA_ROOT setting and creates public URLs based on your MEDIA_URL setting. You can access the active storage system directly in your code using django.core.files.storage.default_storage.
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
# Save a file directly using the default storage backend
path = default_storage.save("path/to/file.txt", ContentFile(b"File content"))You can also create custom storage systems to save files to cloud services like Amazon S3 or Google Cloud Storage. To do this, you create a class that inherits from django.core.files.storage.Storage. You then override the _open() method for reading files, the _save() method for writing them, and the url() method to generate access links.
from django.core.files.storage import Storage
class MyCustomStorage(Storage):
def _open(self, name, mode="rb"):
# Logic to retrieve the file from your custom cloud location
pass
def _save(self, name, content):
# Logic to send the file content to your custom cloud location
return name
def url(self, name):
# Return the public URL so users can access the file
return f"https://my-custom-cloud-provider.com/media/{name}"Once your custom storage class is ready, you tell Django to use it by updating the STORAGES dictionary in your project settings.
Generating and Serving CSV Files
Sometimes you need to generate files dynamically and send them directly to the user rather than saving them to your database. You can do this easily with CSV files using Python's built-in csv module.
Because Django's HttpResponse object acts like a regular file, you can pass it directly to the CSV writer. You just need to set the content_type to text/csv and add a Content-Disposition header. This header tells the user's browser to treat the response as a file download and suggests a default filename.
import csv
from django.http import HttpResponse
def export_users_csv(request):
# Set up the response with the correct headers
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = 'attachment; filename="users.csv"'
# Pass the response to the CSV writer
writer = csv.writer(response)
# Write the header row and a data row
writer.writerow(["First Name", "Last Name", "Email"])
writer.writerow(["Jane", "Doe", "[email protected]"])
return responseIf you are generating a very large CSV file, returning it all at once might use too much server memory. For massive datasets, it is better to use Django's StreamingHttpResponse to send the CSV data to the browser in smaller chunks.
Generating and Serving PDF Files
You can generate PDF documents dynamically using a very similar approach. Django does not have built-in PDF generation, so you will need to install a third-party library like ReportLab to create the actual document content.
When working with PDFs, it is safer to construct the file in memory first rather than writing directly to the HTTP response. You can use Python's built-in io.BytesIO to create a temporary memory buffer. Once ReportLab finishes drawing the PDF into this buffer, you extract the final data and attach it to your response using the application/pdf content type.
import io
from django.http import HttpResponse
from reportlab.pdfgen import canvas
def generate_pdf_report(request):
# Create the memory buffer and the PDF object
buffer = io.BytesIO()
pdf_canvas = canvas.Canvas(buffer)
# Draw your PDF content
pdf_canvas.drawString(100, 100, "Hello, this is a generated PDF report.")
# Finalize the page and save the document
pdf_canvas.showPage()
pdf_canvas.save()
# Move back to the beginning of the buffer
buffer.seek(0)
# Create the response and write the PDF data to it
response = HttpResponse(buffer, content_type="application/pdf")
response["Content-Disposition"] = 'attachment; filename="report.pdf"'
return response