Anozie Baron Chibuikem
5 min readFeb 20, 2021

--

Hi welcome, If you are here, it means you either have a feature to implement in django that involves scheduling a periodic task to run automatically for a specific period of time or you just want to learn how to implement such feature incase you need it in the future. If that is the case, then you are in the right place.

Important things to note
1) I’m going to be running my code on ubuntu os
2) I’m assuming you have a basic understanding of django and django_rest

What are we building
We will be building a very simple API endpoint for a subscription app where users can subscribe to a newsletter by entering their email and we will implement a functionality that sends notification email to them every minutes(which you can configure to send based on your use case).

Let’s get started.
We will setup our virtual environment and create our project

$ mkdir djangoproject
$ cd djangoproject
$ pip -m venv myenv
$ source myenv/bin/activate
$ pip install django djangorestframework celery django-celery-beat
$ django-admin startproject scheduledjangotask
$ cd scheduledjangotask

We created a folder called djangoproject and inside the project, we created our virtual enviroment named “myenv” and proceeded to activating it. Next we installed couple of libraries we will be needing and created our django project name “scheduledjangotask” . With that all done, we can now go ahead and start configuring our project.
Please note that i have redis installed on my system which i will start when it’s time. You can use rabbitMQ too if you wish.

You should see the following folder structure when you navigate to your project

├── manage.py
└── scheduledjangotask
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py

Next create an application called app_schedule using

$ python manage.py startapp app_schedule

once this is done, you should have the following folder structure

├── manage.py
└── app_schedule
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── tests.py
└── views.py
├── scheduledjangotask
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py

Inside your app_schedule, add the following to your models.py

from django.db import modelsclass SubscribeToNewsletter(models.Model):
email = models.EmailField(unique=True)
def __str__(self):
return self.email

create a serializers.py inside the app_schedule folder and add the following

from rest_framework import serializers
from app_schedule import SubscribeToNewsletter
class SubscribeSerializer(serializers.ModelSerializer):

class Meta:
models = SubscribeToNewsletter
fields = ('email',)

Inside your views.py located at the app_schedule folder add the following

from rest_framework import status, generics, permissions
from rest_framework.response import Response
from app_schedule.models import SubscribeToNewsletter
from app_schedule.serializers import SubscribeSerializer
class SubscribeView(generics.ListCreateAPIView):
queryset = SubscribeToNewsletter.objects.all()
serializer_class = SubscribeSerializer

create a urls.py file inside the app_schedule and add the following

from django.urls import path
from app_schedule.views import SubscribeView
urlpatterns = [
path("", SubscribeView.as_view(), name="subscribe-newsletter")
]

Our app_schedule folder should be looking like this by now

├── manage.py
└── app_schedule
├── __init__.py
├── admin.py
├── models.py
├── serializers.py #new
├── tests.py
├── urls.py #new
└── views.py
├── scheduledjangotask
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py

Now go to your scheduledjangotask urls.py file and add the following

from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include('app_schedule.urls')),
path('admin/', admin.site.urls),
]

Time to register our app and make migration. Go to the settings.py inside your scheduledjangotask folder and add the following.

INSTALLED_APPS = (
...,
'rest_framework', #new addition
'app_schedule', #new addition
'django_celery_beat', #new addition
'django_celery_results', #new addition
)

with that done, go ahead and run the following commands.

$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py runserver

if you go to 127.0.0.1:8000 you should see your site loaded with django-rest interface. With our application now setup, let’s go ahead and add celery.

We installed celery and django-celery-beat at the begining of the article, if you want to learn about celery, you can visit their official doc

Add the following in your settings.py

# celery configuration
BROKER_URL = 'redis://localhost:6379'
CELERY_RESULT_BACKEND = 'redis://localhost:6379'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Africa/Lagos' # change to your preferred choice
# configuration for sending email using gmail
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = "enter your own gmail account"
EMAIL_HOST_PASSWORD = 'enter your own gmail account password'
EMAIL_PORT = 587

create a celery.py file in your scheduledjangotask and your folder structure should be looking like this

├── scheduledjangotask
│ ├── __init__.py
│ ├── celery.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py

Inside the __init__.py add the following

from .celery import app as celery_app__all__ = ('celery_app',)

open the celery.py file and add the following

import os
from celery import Celery
from celery.schedules import crontab
# set the default Django settings module for the 'celery' program.os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'scheduledjangotask.settings')app = Celery('scheduledjangotask')# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')# Load task modules from all registered Django app configs.app.autodiscover_tasks()app.conf.beat_schedule = {
# Executes every Monday morning at 7:30 a.m.
'send-reminder': {
# run this task every minute
'task': 'app_schedule.tasks.send_reminder',
'schedule': crontab()
},
}

Alot of the code snippets above are default configurations from celery, we also imported crontab because we want more control over when the task is to be executed, for the task set in the “send-reminder”, we are scheduling it to run every minute. Visit this link to learn more about scheduling periodic tasks in celery.
if your check the “task” object, you will notice that the value is “app_schedule.tasks.send_reminder” that means our schedule task is located in our app_schedule folder, inside a file called tasks which will have a method called “send_reminder”. So go ahead and create the tasks file inside our app_schedule and add the following code snippets

from django.core.mail import EmailMessage
from celery.utils.log import get_task_logger
from scheduledjangotask.celery import app
from app_schedule.models import SubscribeToNewsletter
logger = get_task_logger(__name__)@app.task
def send_reminder():
# this will be used to send mail to all subscribed users
all_users = SubscribeToNewsletter.objects.all()
for user in all_users:
logger.info("Reminder message sent")
email = EmailMessage(
subject='Reset your password',
body= 'Hi, thank you for subscribing to our
newsletter, we will send you reminder
periodically to keep you updated',
to=[user.email]
)
email.send(fail_silently=True)
return {'status': 'sent successfully'}

NOTE: you may need to go to your gmail and change setting to allow less secured app, but please note, it is generally recommended to Not enable this option as it may make it easier for someone to gain access to your account. https://www.google.com/settings/security/lesssecureapps.

With that, less run our application. Open four terminals and run the following command respectively

$ python manage.py runserver $ celery -A scheduledjangotask worker -l debug $ celery -A scheduledjangotask beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler$ sudo systemctl start redis

Remember to run each of the command in different terminal. With that done, we should have our server running, celery running, our redis running, and celery beat running.
Go ahead and visit http://127.0.0.1:8000 in your browser and subscribe to the newsletter.
After a minute, check your email, you should be receiving an email every minute.

If you found this helpful, don’t forget to leave a clap. Thanks for reading.

--

--

Anozie Baron Chibuikem

A backend engineer constantly building a shit ton of things with python and javascript and occasionally writes on technical topics.