Getting started with Django and Celery
August 5, 2021
When we are working with data-intensive applications, long-running tasks slow down the application and the website load time. We can improve the application load time by offloading some work from the application server to a message broker server in such an application.
In this tutorial, we will learn how to use Celery in a Django application to perform long-running background tasks.
Table of contents
- Table of Contents
- Prerequisites
- Workers
- Celery Message Queue
- Celery
- Project Setup
- View
- Setting up base templates directory
- Containerization
- Testing
- Conclusion
Prerequisites
In order to follow along with this tutorial - you will need the following:
- Python installed in your computer.
- A good understanding of Python and Django.
- Docker installed on your computer.
Workers
The background-based task servers are called workers. In an application with web servers, we can have several workers perform the heavy computations in the background and send back the response to the application through webhooks or callbacks.
Celery message queue
A queue is a data structure that works based on the first-in, first-out principle. We assign work to the workers through a message queue. The worker processes the tasks in the order in which the message broker queued them.
The queue ensures that each worker processes a single task at a time, and only a single worker processes a particular task.
Celery
Celery makes it easier to implement the task queues for many workers in a Django application.
Functions of Celery:
- Define tasks as python functions.
- Listen to a message broker for new tasks.
- Assign the tasks to workers.
- Monitor the workers and tasks.
Project setup
- Create a working directory by executing the command below.
mkdir project
- Change the working directory to the
projectdirectory created above by executing the command below.
cd project
- Create a Django application with the command below.
django-admin startproject celerytask
- Create a virtual environment where the packages will be installed with the command below.
virtualenv venv
- Activate the virtual environment by executing the command below.
source venv/bin/activate
- Install Django into the virtual environment we have created above by executing the command below.
pip install django
- Migrate the database models by executing the command below.
cd celerytask
python manage.py migrate
- Now we can add celery to our application.
pip install celery
- Start the Django web server by executing the command below.
python manage.py runserver
- Navigate to http://localhost:8000/ to confirm that the application is up and running.
Adding Celery configuration to Django application
-
In the project folder where the
settings.pyfile exists, create a new Python file namedcelery.py. -
Add the code snippet below to the file created above.
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
# setting the Django settings module.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celery_task.settings')
app = Celery('celery_task')
app.config_from_object('django.conf:settings', namespace='CELERY')
# Looks up for task modules in Django applications and loads them
app.autodiscover_tasks()
The above configuration creates a Celery application using the Django settings. app.autodiscover_tasks() tries to discover a file named task.py in all of our Django applications.
In the __init__.py file within the package where settings.py file is located, add the code snippet below.
from .celery import app as celery_app
__all__ = ['celery_app']
The above code snippet imports Celery every time our application starts.
Creating a Celery task
Let’s create a Django app from where we will set up the Celery task.
- To create a new Django app, execute the command below. In the command,
taskwill be the name of our app.
python manage.py startapp task
-
Create a Python file named
task.pyin thetaskdirectory that we have just created. -
Add the Code snippet below into the
task.pycreated above.
import string
from django.contrib.auth.models import User
from django.utils.crypto import get_random_string
from celery import shared_task
@shared_task
def create_random_user_accounts(total):
for i in range(total):
username = 'user_{}'.format(get_random_string(10, string.ascii_letters))
email = '{}@example.com'.format(username)
password = get_random_string(50)
User.objects.create_user(username=username, email=email, password=password)
return '{} random users created with success!'.format (total)
The above function creates random user accounts.
The method signature of a Celery task is as shown below.
from celery import shared_task
@shared_task
def name_of_your_function(optional_param):
pass # do some long running task
Creating the HTML files
Create a folder named templates in the project’s root directory. In the templates directory created above, create another directory within it named task as it will hold the HTML files for our Django task.
-
In the
task, directory created above, create a file namedbase.htmland add the code snippets below into it. The below code snippet is the base template that other template files will extend from.<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Celery tasks</title> <style type="text/css"> body { width: 800px; margin: 20px auto; } </style> </head> <body> <h1>Django Celery Task</h1> <a href="{% url 'users_list' %}">Users List</a> / <a href="{% url 'generate' %}">Generate Random Users</a> <hr> {% if messages %} {% for message in messages %} <p style="color: green">{{ message }}</p> {% endfor %} <hr> {% endif %} {% block content %} {% endblock %} </body> </html> -
In the
taskdirectory, create a file nameduser_list.htmland add the code snippets below. The code snippet below will display the list of generated random users.
{% extends 'task/base.html' %}
{% block content %}
<h2>Users List</h2>
<ul>
{% for user in object_list %}
<li>{{ user.username }} - {{ user.email }} - {{ user.date_joined }}</li>
{% empty %}
<li>No users. <a href="{% url 'generate' %}">Generate some random users.</a></li>
{% endfor %}
</ul>
{% endblock %}
- In the
taskdirectory create a file namedgenerate_random_user.htmland add the code snippets below. The below code snippet contains an input field where we will specify the number of random users to generate.
{% extends 'task/base.html' %}
{% block content %}
<h2>Generate Random Users</h2>
<form method="post">
{% csrf_token %}
<table>
{{ form }}
</table>
<button type="submit">Submit</button>
</form>
{% endblock %}
Form
In the task directory (not the one in the templates directory), create a Python file named form.py and add the code snippet below:
from django import forms
from django.core.validators import MinValueValidator, MaxValueValidator
class GenerateRandomUserForm(forms.Form):
total = forms.IntegerField(
validators=[
MinValueValidator(50),
MaxValueValidator(500)
]
)
View
Add the code snippet below into the views.py file in the task:
from django.contrib.auth.models import User
from django.contrib import messages
from django.views.generic import ListView
from django.views.generic.edit import FormView
from django.shortcuts import redirect
from .form import GenerateRandomUserForm
from .task import create_random_user_accounts
# returns a list of generated user accounts
class UsersListView(ListView):
template_name = 'task/user_list.html'
model = User
# A page with the form where we can input the number of accounts to generate
class GenerateRandomUserView(FormView):
template_name = 'task/generate_random_user.html'
form_class = GenerateRandomUserForm
def form_valid(self, form):
total = form.cleaned_data.get('total')
create_random_user_accounts.delay(total)
messages.success(self.request, 'We are generating your random users! Wait a moment and refresh this page.')
return redirect('users_list')
In the code snippet above, notice that we did not call the create_random_user_accounts method instead we called create_random_user_accounts.delay(total). This instructs Celery to perform the task as a background process.
URLs
Update the urls.py with the code snippet below.
from django.conf.urls import url
from django.contrib import admin
from django.urls import path
from task.views import GenerateRandomUserView, UsersListView
urlpatterns = [
path('admin/', admin.site.urls),
url('users/', UsersListView.as_view(), name='users_list'),
url('generate/', GenerateRandomUserView.as_view(), name='generate')
]
Setting up base templates directory
In the settings.py file, update the TEMPLATES section with the code snippet below.
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # We are adding in the directory where our templates will be stored.
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
- Execute the command below to create a
requirements.txtfile that we will use when building the Docker image.
pip freeze > requirements.txt
requirements.txtfile contains all the packages required by our application.
Containerization
Creating the Dockerfile
In the root project directory, project/celerytask, create a file named Dockerfile and add the code snippet below.
# pull the official base image
FROM python:3.9.5-alpine
# set work directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt /usr/src/app/requirements.txt
RUN pip install -r requirements.txt
# copy project
COPY . /usr/src/app/
RUN python manage.py migrate
EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
The code snippet above contains directives that will be used to create the Docker image. For more information on creating Django Docker images, visit creating Django Docker images.
Create a Docker compose deployment file
In the working directory project, create a file named docker-compose.yml and add the code snippet below.
version: '3.3'
services:
web:
build: ./celerytask #path to the root project folder
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./celerytask:/usr/src/app/
ports:
- 8000:8000 # sets the port that maps to internal port in docker container
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
- CELERY_BROKER=redis://redis:6379/0
- CELERY_BACKEND=redis://redis:6379/0
depends_on:
- redis
celery:
build: ./celerytask
command: celery worker --app=core --loglevel=info --logfile=logs/celery.log # Command used to start the Celery worker in the Docker container
volumes:
- ./celerytask:/usr/src/app
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
- CELERY_BROKER=redis://redis:6379/0
- CELERY_BACKEND=redis://redis:6379/0
# depends on show that celery worker service requires the web service and the redis service to run
depends_on:
- web
- redis
redis:
image: redis:6-alpine
In the above docker-compose.yml file, we have 3 services:
web- is the service that runs our application code.celery- is the service that runs the Celery worker.redis- is the service that runs the Redis server. Redis is a key-pair datastore that will be used to store the queued events.
For more information and a getting started guide on Docker compose, visit Docker compose guide.
Building the image
In the root project directory, execute the command below to create an image.
docker build -t celerytask .
-tadds a tagcelerytaskto the image that will be created..the end of the command indicates that Dockerfile is in the current directory from where the command is being executed.
To verify that the image has been created successfully, execute the command below:
karen@karen:~$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
celerytask latest a9803f267258 About a minute ago 172MB
Deploying with Docker compose
Execute the command below to create and start the services we declared in the docker-compose.yml file.
docker-compose up -d --build
Testing
- Open your browser and navigate to http://localhost:8000/generate and input the number of users to generate.

- On clicking generate users, Celery schedules a background task that generates random user accounts in the background as shown below.

- When refreshing the
userspage after few seconds, we see a list of randomly generated users as shown below.

Make sure to run migrations before starting the celery worker.
Conclusion
Now that you have learned how to integrate Celery into a Django application and perform periodic tasks, create a Django application that runs a backup script to backup itself every 1 hour.
You can download the full source code here.
Happy coding!
Peer Review Contributions by: Mohan Raj