Back
Aug 25, 2017

How to Upload Files With Django

Pasha Volkov

There are several approaches to uploading files in Django. Each developer faces the most obvious one regularly, when a regular file upload field is used, and it has some known disadvantages. Namely, in case of validation errors the file upload field is reset and the user is forced to select the file again, which is quite tedious. Moreover, in the modern interfaces it is often needed to see the upload result in the form immediately, especially when it comes to the image or when the form is sent with Ajax.

In this article, we will look at two main alternative approaches to the file uploading process:

  • Using a separate upload handler and sending a file with ajax
  • Using a separate file model and fk storing

We will omit the implementation details of the files uploading process on the client side, since it depends heavily on the frontend framework used, and focus on the backend. In any case, the file should be sent using a POST request and included into the request.FILES dictionary on the django side. So:

Handle file upload with separate view

Generally, with this approach, when a file is sent and saved, a disk path is returned, and this path is used as the string value in the corresponding field of the form. No significant changes of the model are required, and the whole front-end task is reduced to sending file to the server after selecting it, processing the response, and substituting the result in the required field. At the same time, a classic example from the django documentation suggests doing the following:

# forms.py
from django import forms

class UploadFileForm(forms.Form):
    title = forms.CharField(max_length=50)
    file = forms.FileField()

#views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import UploadFileForm

# Imaginary function to handle an uploaded file.
def handle_uploaded_file(f):
    with open('some/file/name.txt', 'wb+') as destination:
        for chunk in f.chunks():
            destination.write(chunk)

def upload_file(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            handle_uploaded_file(request.FILES['file'])
            return HttpResponseRedirect('/success/url/')
    else:
        form = UploadFileForm()
    return render(request, 'upload.html', {'form': form})

This is a too low-level approach and it has obvious drawbacks:

  • You have to manually write the path to save the file
  • You work with a file object directly
  • The original file name is not used while saving

It is much easier and more convenient to use the built-in django tools to work with the storage - default_storage. The above example can be transformed as follows:

#views.py
Import os

from django.conf import settings


def file_upload(request):
    save_path = os.path.join(settings.MEDIA_ROOT, 'uploads', request.FILES['file'])
    path = default_storage.save(save_path, request.FILES['file'])
    return default_storage.path(path)

With this approach, the following advantages are obvious:

  • The file is saved in the location specified in the settings.
  • The integrated mechanism manages the file saving

As a result, we can get all the necessary data and pass it to the frontend

So, if we need to upload a picture, we can immediately get a link to it and send json to the client side to display the image immediately after uploading, or use it for the further processing (crop, filters, etc.)

Handle file upload with file model

In some cases, you need to store additional information about the uploads: who, when uploaded it and so on. For this purpose it makes sense to create a separate model for storing such data and make a link to it in the main model with the foreign key:

#models.py
from django.db import models


class Document(models.Model):
    upload_by = models.ForeignKey('auth.User', related_name='uploaded_documents')
    datestamp = models.DateTimeField(auto_now_add=True)
    document = models.Field(upload_to='uploads/')
    # ...


class MainModel(models.Model):
    title = models.CharField(max_length=42)
    document = models.ForeignKey(Document)
    #...

Then, our view that deals with processing of file uploads can be modified as follows:

#views.py
Import os

from django.conf import settings
from django.http import JsonResponse
from django.views.decorators.http import require_POST

@require_POST
def file_upload(request):
    save_path = os.path.join(settings.MEDIA_ROOT, 'uploads', request.FILES['file'])
    path = default_storage.save(save_path, request.FILES['file'])
    document = Document.objects.create(document=path, upload_by=request.user)
    return JsonResponse({'document': document.id})

And on the frontend we will fill not the file field, but the object id field for the ForeignKey.

Conclusion

Both of these methods allow you to handle files upload more flexibly and bypass the unpleasant limitation with resetting the file upload field.

More thoughts

Sep 23, 2010Technology
Dynamic class generation, QuerySetManager and use_for_related_fields

It appears that not everyone knows that in python you can create classes dynamically without metaclasses. I'll show an example of how to do it.So we've learned how to use custom QuerySet to chain requests:Article.objects.old().public()Now we need to make it work for related objects:user.articles.old().public()This is done using use_for_related_fields, but it needs a little trick.

Vladimir Sidorenko
Oct 11, 2010Technology
Char search in Emacs as in Vim

In VIM there is a command for char search: f. After first use it can be repeated with ;. I like to navigate in line with it. You see that you need to go to bracket in a middle of a line - you press f( and one-two ; and you are there. There's no such command in Emacs, so I had to write my own. I've managed even to implement repetition with ;.

Vladimir Sidorenko
Jun 27, 2018Technology
How to Work With Legacy Code: Code Refactoring Techniques

In this article we'll review general approach to working with the best kind of projects - the ones with old untested and undocumented spaghetti code and a tight schedule. We'll review anger management techniques, coping mechanisms and some refactoring tips that might come in handy.

Vladimir Sidorenko
May 26, 2017Technology
Tutorial: Django User Registration and Authentication

In this beginners friends article I'll explain how to make authentication with Google account on your Django site and how to make authentication for you REST API.

Denis Untevskiy
Sep 1, 2021TechnologyBusiness
Top 10 Web Development Frameworks in 2021 - 2022

We have reviewed the top web frameworks for server and client-side development and compared their pros and cons. Find out which one can be a great fit for your next project.

Marina Sharapova
Mar 6, 2010ManagementTechnology
Supplementing settings in settings_local

For local project settings, I use old trick with settings_local file:try:from settings_local import \*except ImportError:passSo in settings_local.py we can override variables from settings.py. I didn't know how to supplement them. For example how to add line to INSTALLED_APPS without copying whole list.Yesterday I finally understood that I can import settings from settings_local:# settings_local.pyfrom settings import \*INSTALLED_APPS += (# ...)

Vladimir Sidorenko