Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 29

File Uploads with Forms and Models

Recommended resources

Hello! Welcome back to your course on the Django framework.

In the previous lesson, we explored how to customize the behavior of CreateView and UpdateView by overriding the form_valid() method. This allowed us to inject custom logic, like associating a new object with the currently logged-in user, right after successful form validation.

Today, we'll build directly on that knowledge to tackle another essential feature in web development. Our learning outcome is to handle file uploads using forms and models. You'll learn the complete, end-to-end process: configuring your project to accept files, defining models to store them, creating forms to upload them, and displaying them in your templates. This is a fundamental skill for building applications that require profile pictures, document storage, or any kind of user-uploaded content.


Step 1: Configuring Models and Project Settings for Media Files

Just like any other piece of data, a file needs a place in our model. Django provides two special fields for this:

  • FileField: A general-purpose field for any type of file.
  • ImageField: A specialized version of FileField that validates the uploaded file is an image. It also requires the Pillow Python library to be installed (pip install Pillow).

When using these fields, we must also tell Django two things:

  1. Where on the server's filesystem to store uploaded files. This is defined by the MEDIA_ROOT setting.
  2. What URL to use to access those files from the browser. This is defined by the MEDIA_URL setting.

The following tutorial provides a clear walkthrough of setting up the model and these crucial settings.

Django File/Image Uploads Tutorial

The 'Django File/Image Uploads Tutorial' from LearnDjango is an excellent guide that we will follow for the core setup. It demonstrates how to use ImageField and configure the necessary project settings.

Please read the sections titled 'Models' and 'MEDIA_ROOT'. Focus on how the ImageField is defined with the upload_to parameter and how MEDIA_URL and MEDIA_ROOT are added to settings.py.

Let's recap the key code snippets from that resource.

In your models.py:
You would define a field like this. The upload_to='images/' parameter tells Django to save uploaded images into an images subdirectory inside your MEDIA_ROOT.

# posts/models.py
from django.db import models

class Post(models.Model):
    title = models.TextField()
    cover = models.ImageField(upload_to='images/') # This is the key field

    def __str__(self):
        return self.title

In your settings.py:
You need to add MEDIA_URL and MEDIA_ROOT at the main level of the file.

# your_project/settings.py
import os # Make sure this is imported

# ... other settings

# At the bottom of the file
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")

With this configuration, an image uploaded for a Post object would be saved to a path like /path/to/your/project/media/images/my_image.jpg.

The video below also provides a great walkthrough of this initial setup.

Django Media Files - Handling User Uploads in Django Forms & Models

This video from BugBytes, 'Django Media Files', provides a practical demonstration of configuring settings and models for file uploads.

Watch from the beginning until 04:43. The presenter explains the purpose of MEDIA_ROOT and ImageField, and shows how to configure them in a project.



Step 2: Building the Form and Template

Now that our backend is ready to receive files, we need to create the user-facing interface. This involves two parts: a ModelForm and an HTML template.

The ModelForm is straightforward and looks just like the ones we've used before. The magic happens in the HTML <form> tag. For a browser to correctly handle file uploads, you must add the enctype="multipart/form-data" attribute to your <form> element.

  • The default encoding, application/x-www-form-urlencoded, is fine for text data but cannot handle the binary data of files.
  • multipart/form-data tells the browser to split the form submission into multiple parts, allowing file data to be sent along with the regular text fields.

This image clearly illustrates the necessary template code.

Django File Upload Template Example
This image shows the structure of an HTML template for file uploads. Note the essential `enctype="multipart/form-data"` attribute within the `<form>` tag, which is required for the browser to properly send file data.

Forgetting this enctype is a very common mistake that leads to request.FILES being empty in your view, so it's a critical detail to remember.



Step 3: Handling the Upload in a View

When a form with enctype="multipart/form-data" is submitted, Django separates the uploaded file data from the normal form data.

  • Text-based inputs (like CharField, IntegerField) are found in request.POST.
  • File inputs (FileField, ImageField) are found in request.FILES.

To see this in action, let's first look at the official documentation for a function-based view. It makes the separation of request.POST and request.FILES very explicit.

File Uploads | Django documentation

The official Django documentation on 'File Uploads' explains the fundamentals. Pay close attention to how the form is instantiated in the view.

Read the sections 'Basic file uploads' and 'Handling uploaded files with a model'. Notice the line form = UploadFileForm(request.POST, request.FILES). This is where the file data is bound to the form.

The key insight is that the form must be instantiated with both dictionaries: form = MyForm(request.POST, request.FILES).

Now, how does this apply to the CreateView we've been using? The good news is that Django's generic CBVs handle this for you automatically! As long as you've set up your ModelForm correctly, the CreateView will populate it with both request.POST and request.FILES behind the scenes. When form.save() is called (which happens by default inside the form_valid method), Django saves the model instance and also handles writing the uploaded file to the directory specified by MEDIA_ROOT and upload_to.

The following video shows a concise, practical example of using a CreateView to handle a file upload.

Class-Based Views - Django File Upload Tutorial - Part 3

This short video, 'Class-Based Views - Django File Upload Tutorial', demonstrates exactly how to implement a CreateView for file uploads.

Watch from 07:28 to 12:40. The presenter builds a CreateView for a model with a file field and shows the complete upload process.



Step 4: Displaying Uploaded Files in Development

You've successfully uploaded a file. The record is in the database, and the file is in your media directory. But when you try to display it in a template using {{ post.cover.url }}, you see a broken image. What's wrong?

For security and performance reasons, the Django development server does not serve user-uploaded media files by default. You must explicitly configure your project's urls.py to do this during development.

This is a development-only convenience. In a production environment, you would configure a dedicated web server like Nginx to serve these files directly.

Django File/Image Uploads Tutorial

Let's return to the LearnDjango tutorial, which has a clear explanation for updating your URL configuration.

Read the 'URLs' section. The key part is the if settings.DEBUG: block that is added to your project's urls.py file.

The necessary code for your project-level urls.py looks like this:

# your_project/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings             # New
from django.conf.urls.static import static   # New

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('posts.urls')),
]

# Add this block
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

This tells Django that during development, any request for a URL starting with your MEDIA_URL (/media/) should be served by fetching the corresponding file from your MEDIA_ROOT directory.

Test your understanding!

You've built a feature to upload a profile picture. The User model has an ImageField, and the form submission works without errors. However, the profile picture doesn't appear on the user's profile page. What are the two most likely missing pieces of configuration?

Show answer
  1. The MEDIA_URL and MEDIA_ROOT settings are likely not configured in settings.py. Django doesn't know where to store or how to URL-map the uploaded files.
  2. The project's urls.py file has not been updated with the static() helper function to serve media files during development (if settings.DEBUG:).


Step 5: Good Practice - Cleaning Up Orphan Files

Here's a final, important consideration. When you delete a model instance that has a FileField or ImageField, Django deletes the row from the database, but it does not delete the actual file from your server's filesystem. This "orphaned" file just sits there, taking up disk space.

A clean way to handle this is to override the model's delete() method.

Django Media Files - Handling User Uploads in Django Forms & Models

The BugBytes video we saw earlier has an excellent segment on this exact topic. It shows a simple and effective way to ensure files are deleted along with their model instances.

Watch from 26:44 to 29:19. The presenter explains why files are orphaned and demonstrates how to override the delete() method on the model to clean them up.

The logic shown in the video can be implemented in your model like this:

# your_app/models.py
from django.db import models

class MyModel(models.Model):
    # ... other fields
    my_file = models.FileField(upload_to='documents/')

    def delete(self, *args, **kwargs):
        # First, delete the file from storage
        self.my_file.delete(save=False)
        # Then, call the superclass's delete method to delete the database record
        super().delete(*args, **kwargs)

This ensures that whenever you call .delete() on an instance of MyModel, the associated file is removed from your media directory first.



Conclusion

Congratulations! You have now learned the entire workflow for handling file uploads in Django, a powerful and common requirement for modern web applications.

Key Takeaways:

  • Model: Use ImageField or FileField with an upload_to path.
  • Settings: Configure MEDIA_ROOT (filesystem path) and MEDIA_URL (URL prefix) in settings.py.
  • Template: Your HTML <form> tag must include enctype="multipart/form-data".
  • View: When using CBVs like CreateView with a ModelForm, file handling is automatic. For function-based views, you must pass request.FILES to the form instance.
  • URLs: To view files during development, you must add a special static() configuration to your project's urls.py.
  • Cleanup: Override the model's delete() method to prevent orphaned files on your server.

In our next lesson, we will shift gears to another major topic in the Django framework: user authentication and authorization. You'll learn about Django's built-in User model and how to implement user registration, login, and logout functionality.