Handle File Uploads¶
Problem¶
You want a form that accepts a file upload and saves the file alongside a model instance.
Solution¶
Add a FileField or ImageField to the form, render the form with enctype="multipart/form-data", and call form.save() from the handler.
See Django’s file uploads and managing files for how Django stores the uploaded data.
Walkthrough¶
Define the model.
from django.db import models
class Attachment(models.Model):
title = models.CharField(max_length=120)
file = models.FileField(upload_to="attachments/")
Define the form.
from next.forms import ModelForm
from notes.models import Attachment
class AttachmentForm(ModelForm):
class Meta:
model = Attachment
fields = ("title", "file")
Register the action.
from django.http import HttpResponseRedirect
from django.urls import reverse
from next.forms import action
from notes.forms import AttachmentForm
@action("upload_attachment", form_class=AttachmentForm)
def upload_attachment(form: AttachmentForm) -> HttpResponseRedirect:
form.save()
return HttpResponseRedirect(reverse("next:page_attachments"))
Render the form with the right encoding type.
{% form @action="upload_attachment" enctype="multipart/form-data" %}
{{ form.title }}
{{ form.file }}
<button type="submit">Upload</button>
{% endform %}
Set enctype="multipart/form-data" on the form.
Without it the browser submits only text values and form.file arrives empty.
Configure media storage.
A FileField writes to MEDIA_ROOT under the upload_to subdirectory, and the saved URL is built from MEDIA_URL.
Both settings must be present before any upload is saved.
MEDIA_ROOT = BASE_DIR / "media"
MEDIA_URL = "/media/"
Serve uploaded files in development.
The file router include lives in the root URLconf alongside Django’s static() helper, which exposes MEDIA_ROOT while DEBUG is on.
A production deployment serves the same files through the web server instead.
from django.conf import settings
from django.conf.urls.static import static
from django.urls import include, path
urlpatterns = [
path("", include("next.urls")),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Verification¶
Submit the form with a file and confirm that the model row is created.
Inspect MEDIA_ROOT and verify the file appears under attachments/.
Tests¶
Use SimpleUploadedFile to feed a fake file into NextClient.
from django.core.files.uploadedfile import SimpleUploadedFile
from next.testing.client import NextClient
def test_upload(db) -> None:
fake = SimpleUploadedFile("file.txt", b"hello")
response = NextClient().post_action(
"upload_attachment",
{"title": "First", "file": fake},
)
assert response.status_code == 302
A submission without the file key re-renders the origin page with the missing-file error.
def test_upload_without_file_rerenders(db) -> None:
response = NextClient().post_action(
"upload_attachment",
{"title": "First"},
)
assert response.status_code == 200
assert b"This field is required" in response.content
See Also¶
See also
Actions for handler patterns. Form Templates for the form tag.