Django file uploads with class-based views

As a word of caution: I'm a data scientist, not a web developer or a developer at all. But sometimes one finds itself in a position, where it's necessary to write a web application. This happened to me recently when I had to write a small Django web app that manages and documents A/B tests.

One of the features of this app is an automated test evaluation. And what do you need for an evaluation? Data! And how do you get data? By uploading a file.

The proven way

How do you upload a file in Django? Well, if you look in the Django docs or various Stackoverflow answers, you'll find something like this:

from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import ModelFormWithFileField

def upload_file(request):
    if request.method == 'POST':
        form = ModelFormWithFileField(request.POST, request.FILES)
        if form.is_valid():
            # file is saved
            form.save()
            return HttpResponseRedirect('/success/url/')
    else:
        form = ModelFormWithFileField()
    return render(request, 'upload.html', {'form': form})

There is nothing wrong with this code. In fact it's copied right from the official docs. But I have a hard time calling this code nice. Here's why:

  • I prefer Django's Class-Based-Views (CBV) to Function-Based-Views (FBV).
  • Using request.method to check the request type just doesn't feel right for me.

After reading the excellent Two Scoops of Django by Daniel Greenfeld and Audrey Roy, I realized that I only want to use FBVs as a last resort; the elegance and simplicity of CBVs are just to good to ignore. So how to implement a file upload with a CBV?

Let's move to CBV

First of all, which generic view is the right one? The most popular CBVs, like CreateView, TemplateView, are too specific. But if we look at the inheritance order and look for something more general, we find the plain and simpleView. This CBV doesn't really do anything, but to implement a dispatch() method that tries to call the method appropriate for the request type, e.g. GET or POST or raises an exception, if the method is not implemented.

So our job is exactly that: subclass View and implement the necessary methods for our request types.

from django.views.generic import View
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import render, redirect

from .forms import MyForm

class FileUploadView(View):
    form_class = MyForm
    success_url = reverse_lazy('home')
    template_name = 'file_upload.html'

    def get(self, request, *args, **kwargs):
        form = self.form_class()
        return render(request, self.template_name, {'form': form})

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            return redirect(self.success_url)
        else:
            return render(request, self.template_name, {'form': form})

Arguably, this code isn't shorter than its FBV counterpart. But in my opinion it's clearer due to the encapsulation in separate methods for GET and POST. With a single look I'm finding the code that's being executed for each request. And the class variables are an easy way to "configure" the view. To change my form class, I just have to change a single line of code. The same approach can be used wherever mostly FBVs are used, e.g. login views.

This isn't exactly rocket science, but since I wasn't able to find a CBV file upload example I thought it might be worth the time to write it down myself.

Write a comment

Comments: 8
  • #1

    Peter (Thursday, 19 November 2015 00:57)

    Thanks Andy, this was exactly what I was looking for. It was definitely worth writing it down, thanks.

  • #2

    Ken (Thursday, 26 April 2018 09:48)

    u da man

  • #3

    Anonymous (Saturday, 02 June 2018 11:23)

    Very nice. thanks. much clearer than using any other generic view

  • #4

    Kim (Friday, 05 April 2019 13:53)

    Thanks. This explains clearly the difference between FBV and CBV.

  • #5

    Jimbosephus (Friday, 10 January 2020 18:28)

    add a space in 'simpleView'

  • #6

    Ess (Wednesday, 14 October 2020 13:07)

    Add enctype="multipart/form-data" to your form element on the template to accept file inputs

  • #7

    shailesh (Friday, 10 September 2021 06:26)

    Still image field gets cleared afrer submitting , geting this message
    {"IngredientPIC": [{"message": "This field is required.", "code": "required"}]}

  • #8

    Antonnifo (Thursday, 27 January 2022 16:05)

    Thanks champ