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:
- Getting started with Django – A Simple CRUD application
- How to run Django and Postgres in docker-compose
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: