multipart/form-data is an HTTP media type that lets one form request carry ordinary fields and uploaded files. In a Django form that includes a file field, the HTML form must declare enctype="multipart/form-data"; otherwise the upload does not work properly. section 7.4.3 section 7.6
Think of the setup as a two-sided handshake: the template marks the submitted form as capable of carrying files, and the view reads uploaded data separately from ordinary posted values. Django places normal form values in request.POST and uploaded files in request.FILES. section 7.4.3 section 7.6
For a ModelForm-backed upload, wire together these pieces:
- Include a FileField or ImageField on the model; Django stores a reference to the uploaded file. section 7.6
- Render the ModelForm, which supplies an upload control for the file field. section 7.6
- Set enctype="multipart/form-data" on the form element, along with method="post". section 7.4.3
- Construct or bind the form with request.POST and request.FILES so submitted fields and files reach the form together. section 7.4.3 section 7.6
Uploading a venue picture
A venue editor can use a ModelForm containing name, description, and picture. Its template adds enctype="multipart/form-data" to the form tag. On POST, the view passes request.POST and request.FILES to VenueForm, then can save the venue with its picture and display that picture from the stored field URL. section 7.4.3 section 7.6
Listing 7.19 Template for adding or editing a Venue object
<!-- RiffMates/templates/edit_venue.html -->
{% extends "base.html" %}
{% block content %}
<h1>Venue Details</h1>
<form enctype="multipart/form-data" method="post"> #1
{% csrf_token %}
{{form.as_p}}
<button class="btn btn-dark" type="submit">Save</button>
</form>
{% endblock content %}
The form tag shows the small but essential enctype change: it is easy to omit and prevents file uploads from working when absent. section 7.4.3
Listing 7.18 The self-service view for adding and editing venues
# RiffMates/bands/views.py
...
from django.shortcuts import render, get_object_or_404, redirect
from bands.models import Musician, Band, Venue, Room
from bands.forms import VenueForm
...
@login_required
def edit_venue(request, venue_id=0):
if venue_id != 0: #1
venue = get_object_or_404(Venue,
id=venue_id) #2
if not request.user.userprofile.venues_controlled.filter(
id=venue_id).exists(): #3
raise Http404("Can only edit controlled venues")
if request.method == 'GET': #4
if venue_id == 0:
form = VenueForm()
else:
form = VenueForm(instance=venue)
else: # POST
if venue_id == 0: #5
venue = Venue.objects.create()
form = VenueForm(request.POST, request.FILES, #6
instance=venue)
if form.is_valid():
venue = form.save()
# Add the venue to the user's profile #7
request.user.userprofile.venues_controlled.add(venue)
return redirect("venues")
# Was a GET, or Form was not valid
data = {
"form": form,
}
return render(request, "edit_venue.html", data)
The view shows the matching server-side step: bind a ModelForm with request.POST and request.FILES when handling a POST request. section 7.4.3


