How to upload files and Images with Python Django

Django is a free and open-source, Python-based web framework that follows the model–template–views architectural pattern. Django advertises itself as “the web framework for perfectionists with deadlines” and “Django makes it easier to build better Web apps more quickly and with less code”.  Django is known for the speed at which you can develop apps without compromising on robustness.

Django makes it easy for us to perform file uploads because it provides built-in library and methods that help to upload a file to the server.

In this guide we will learn the concepts behind Django file upload and how to handle file upload using model forms.

Related content:

Setting up Django project

Ensure that you have python 3 installed. Consult your OS installation guide. I can confirm that it is installed in my machine using this command:

➜ python --version
Python 3.9.13

Create virtualenv

A virtual environment is a container (a directory) that holds a specific version of Python and a set of modules (dependencies), and which does not interfere with the operating system’s native Python or other projects on the same computer. Let us create a virtualenv for our project:

python3 -m venv djfiles

Then Activate the virtualenv

source djfiles/bin/activate

Install dependencies

pip install Pillow

Install and setting up django Project

We can now install Django in our virtual env. Use this command to install the latest version of django:

pip install django

Next, we can create a django project.

django-admin startproject djfiles

Next create an uploads application. A Django app is a Python package that contains a models file (models.py by default) and other files such as app-specific urls and views.

cd djfiles
python manage.py startapp uploads

Now add the app to installed apps in djfiles/settings.py:

INSTALLED_APPS = [
    ...
    'uploads',
]

Update the djfiles/urls.py file with the following content to include the posts.urls:

from django.contrib import admin
from django.urls import path, include

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

Basics of Django File upload

When files are submitted to the server, the file data ends up placed in request.FILES.

It is mandatory for the HTML form to have the attribute enctype="multipart/form-data" set correctly. Otherwise the request.FILES will be empty.

The form must be submitted using the POST method.

Django have proper model fields to handle uploaded files: FileField and ImageField.

The files uploaded to FileField or ImageField are not stored in the database but in the filesystem.

FileField and ImageField are created as a string field in the database (usually VARCHAR), containing the reference to the actual file.

If you delete a model instance containing FileField or ImageField, Django will not delete the physical file, but only the reference to the file.

The request.FILES is a dictionary-like object. Each key in request.FILES is the name from the <input type="file" name="" />.

Each value in request.FILES is an UploadedFile instance.

You will need to set MEDIA_URL and MEDIA_ROOT in your project’s settings.py.

MEDIA_URL <strong>=</strong> '/media/'
MEDIA_ROOT <strong>=</strong> os<strong>.</strong>path<strong>.</strong>join(BASE_DIR, 'media')

In the development server you may serve the user uploaded files (media) using django.contrib.staticfiles.views.serve() view.

from django.conf import settings
from django.conf.urls.static import static

urlpatterns <strong>=</strong> [
    <em># Project url patterns...</em>
]

<strong>if</strong> settings<strong>.</strong>DEBUG:
    urlpatterns <strong>+=</strong> static(settings<strong>.</strong>MEDIA_URL, document_root<strong>=</strong>settings<strong>.</strong>MEDIA_ROOT)

To access the MEDIA_URL in template you must add django.template.context_processors.media to your context_processeors inside the TEMPLATES config.

Simple File Upload

The following is a minimal file upload example using FileSystemStorage. Use it just to learn about the flow of the process.

upload.html

{% extends 'base.html' %}

{% load static %}

{% block content %}
  <form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <input type="file" name="myfile">
    <button type="submit">Upload</button>
  </form>

  {% if uploaded_file_url %}
    <p>File uploaded at: <a href="{{ uploaded_file_url }}">{{ uploaded_file_url }}</a></p>
  {% endif %}

  <p><a href="{% url 'home' %}">Return to home</a></p>
{% endblock %}

views.py

from django.shortcuts import render
from django.conf import settings
from django.core.files.storage import FileSystemStorage

<strong>def</strong> <strong>simple_upload</strong>(request):
    <strong>if</strong> request<strong>.</strong>method <strong>==</strong> 'POST' <strong>and</strong> request<strong>.</strong>FILES['myfile']:
        myfile <strong>=</strong> request<strong>.</strong>FILES['myfile']
        fs <strong>=</strong> FileSystemStorage()
        filename <strong>=</strong> fs<strong>.</strong>save(myfile<strong>.</strong>name, myfile)
        uploaded_file_url <strong>=</strong> fs<strong>.</strong>url(filename)
        <strong>return</strong> render(request, 'core/simple_upload.html', {
            'uploaded_file_url': uploaded_file_url
        })
    <strong>return</strong> render(request, 'core/simple_upload.html')

File upload with ModelForm

Model form provides a convenient way of working with uploads. Model forms perform validation, automatically builds the absolute path for the upload, treats filename conflicts and other common tasks.

Creating a Model

Add this to uploads/models.py

from django.db import models


class Upload(models.Model):
    caption = models.CharField(max_length = 100)
    upload = models.FileField(upload_to='file_uploads')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.caption

    class Meta:
        db_table = "uploads"

Note the upload_to parameter in the models.FileField. The files will be automatically uploaded to MEDIA_ROOT/file_uploads/.

It is also possible to do something like:

document <strong>=</strong> models<strong>.</strong>FileField(upload_to<strong>=</strong>'file_uploads/%Y/%m/%d/')

A file uploaded today would be uploaded to MEDIA_ROOT/file_uploads/2022/07/28/.

The upload_to can also be a callable that returns a string. This callable accepts two parameters, instance and filename.

<strong>def</strong> <strong>user_directory_path</strong>(instance, filename):
    <em># file will be uploaded to MEDIA_ROOT/user_<id>/<filename></em>
    <strong>return</strong> 'user_{0}/{1}'<strong>.</strong>format(instance<strong>.</strong>user<strong>.</strong>id, filename)

<strong>class</strong> <strong>MyModel</strong>(models<strong>.</strong>Model):
    upload <strong>=</strong> models<strong>.</strong>FileField(upload_to<strong>=</strong>user_directory_path)

Creating the form

Add this to uploads/forms.py

from django import forms

from .models import Upload

class PostForm(forms.ModelForm):
    class Meta:
        model = Upload

        fields = [
            "caption",
            "upload",
        ]

Create views

Add this content to uploads/views.py

from django.shortcuts import render, redirect

from .forms import UploadsForm
from .models import Upload

from logging import getLogger
log = getLogger(__name__)


def index(request):
    files = Upload.objects.all()

    if request.method == "POST":
        form = UploadsForm(request.POST, request.FILES)

        if form.is_valid():
            form.save()
            return redirect('uploads:index')
    else:
        form = UploadsForm()

    return render(request, 'index.html', {'files': files, 'form': form})

Create the URLs

Add this to uploads/urls.py

from django.urls import path

from django.urls import re_path as url
from . import views

app_name = 'uploads'


urlpatterns = [
    path('', views.index, name='index'),
]

Add Templates

Add this to uploads/templates/index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
    <meta name="generator" content="Hugo 0.101.0">
    <title>Citizix - Django file upload example</title>

    <link rel="canonical" href="https://citizix.com/">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">

    <meta name="theme-color" content="#712cf9">

    <style>
      .main-content {
        padding: 70px;
        min-height: 400px;
      }
    </style>
  </head>
  <body>

    <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
      <div class="container">
        <a class="navbar-brand" href="/">Dj Files</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
        </button>
      </div>
    </nav>

    <main class="container main-content">
        <h1>Dj Files</h1>
        <form class="form-horizontal" action="{% url 'uploads:index' %}" method="post" enctype="multipart/form-data">
          {% csrf_token %}
          <div class="form-group row">
            <label class="col-sm-2 col-form-label" for="caption">Caption</label>
            <div class="col-sm-8">
              <input type="text" id="caption" name="caption" class="form-control" placeholder="Caption"/>
              <span class="help-block text-danger"></span>
            </div>
          </div>
          <div class="form-group row">
            <label class="col-sm-2 col-form-label" for="upload">Upload</label>
            <div class="col-sm-8">
              <input type="file" id="upload" name="upload" class="form-control" placeholder="Upload" />
              <span class="help-block text-danger"></span>
            </div>
          </div>
          <div class="form-group row">
            <div class="col-sm-8">
              <button type="submit" class="btn btn-primary">Upload</button>
            </div>
          </div>
        </form>

        <hr>

        <div class="files">
          {% if files %}
            {% for file in files %}
              <p><a href="{{file.upload.url}}" target="_blank">{{file.caption}}</a> - {{file.upload}}</p>
            {% endfor %}
          {% else %}
            <p>No uploads</p>
          {% endif %}
        </div>
    </main>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>

  </body>
</html>

Running The Django Project

Migrations are Django’s way of propagating changes you make to your models (adding a field, deleting a model, etc.) into your database schema. They’re designed to be mostly automatic, but you’ll need to know when to make migrations, when to run them, and the common problems you might run into.

First we will need to make migrations for our app:

➜ python manage.py makemigrations
Migrations for 'uploads':
  uploads/migrations/0001_initial.py
    - Create model Upload

Then we can apply the migrations to the database:

➜ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, uploads
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
  Applying uploads.0001_initial... OK

Finally we can run server to check the content we just created:

➜ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
July 28, 2022 - 02:35:02
Django version 4.0.6, using settings 'djfiles.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

From here you can visit http://127.0.0.1:8000/ then navigate the pages to test the file upload functionality that we just added.

Conclusion

In this guide we learnt how to work with django to create a CRUD system.

Checkout this related content:

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy