Let me start by an easy question. What do you understand by a signal? You're right if your answer is somewhere around, 'A signal is just an indication that a particular thing has taken place'.
Here are some real life examples:
On a road, a red signal (traffic signal) is an indication that the cross traffic or pedestrians need to cross the road. We intercept this signal and just stop our car.
An umpire raising both his hands (signal) is an indication that the ball has cleared the boundary line without touching the ground. The scorer intercepts the signal and increments the runs of the batsman and the team by six.
So, the signals serve as a way to manage actions. They make us aware that something has happened, so do a specific thing accordingly.
Same is the case with Django signals.
Now the question is, 'What are signals in Django used for and how do we use them?'. Let's explain the concept and the need of signals using an example.
Let suppose we have a user registration system and we want to send a welcome email to the user after registering successfully. Let's try to visualise the workflow.
The above image explains the high level process of user registration. So, a request has to undergo two main processes, right. One being creating a model instance and saving it to the database and the other being sending an email to the user. When both the things are complete, either succeed or fail, then only a response is sent to the client. So, can we reduce the time taken by the request to process. We have two processes running on the same thread, right? Let's move them to two different threads. This is where Django Signals come into the play.
The diagram is self-explanatory, right? The request has to undergo only one process to yield a response. The email sending process is a decouple process now and runs on a different thread.
Django has three types of built-in signals. We can also create custom signals in Django. In this article, we'll be discussing about the built-in signals and employing them in our code. We'll discuss about custom signals in the next blog.
So, as already mentioned, Django has three types of built in signals.
pre_save/post_save: This signal works before/after the method save() (i.e) before/after saving a model instance.
pre_delete/post_delete: This signal works before after deleting a model’s instance (method delete()) this signal is thrown (i.e) before/after deleting a model instance.
pre_init/post_init: This signal is thrown before/after instantiating a model (__init__() method).
Let's quickly start our Django Project, add an app and use our signals.
Make sure you create and activate a virtual environment before installing Django. It's a good practice and helps avoid dependency conflicts
pip install django
django-admin startproject djangosignalsdemo
cd djangosignalsdemo
python manage.py startapp home
Our goal is to use Signals to send an email to the user once his account is created or deleted. Since the main topic is Signals, we'll use Django's inbuilt user model because we don't need any additional methods or attributes here. So, in our home app, we'll create a signals.py
file and write the code to send an email to the user before and after creating his account. So, we'll use pre_save/post_save signals to achieve the functionality. Let's jump into the code.
from django.contrib.auth.models import User
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from django.core.mail import send_mail
@receiver(pre_save, sender=User)
def pre_save_user(sender, instance, **kwargs):
# Sending an email before saving the User
send_mail(
'Pre-Save Notification',
f'User {instance.username} will be updated.',
'youremail@email.com',
[instance.email],
fail_silently=False,
)
@receiver(post_save, sender=User)
def post_save_user(sender, instance, created, **kwargs):
# Sending email after saving the User
if created:
send_mail(
'Welcome!',
f'Welcome, {instance.username}! Your account has been created.',
'youremail@example.com',
[instance.email],
fail_silently=False,
)
Let's try to understand the code first.
We've first imported the in-built User model.
Then we have imported pre_save and post_save signals.
Then we have imported receiver. receiver is a method that is used as a function decorator for the receiver functions we have created.
Finally, we have imported the send_mail to send an email to the user.
Then we create our first receiver function. A receiver function is a function that receives the signal. Our first receiver function is pre_save_user. We'll use the imported receiver as a decorator to this function. This decorator takes two arguments. pre_save and sender. pre_save is the signal. That means right before saving the instance to the user model, pre_save_user function will be sent an signal to get executed. The second argument is sender. It is the model that will send the signal. Here we need the User model to send the signal. pre_save_user takes three arguments sender, instance, **kwargs. sender is the sender of the signal, instance is the model instance and **kwargs collects any additional arguments sent by the signal. We can access these additional arguments via the kwargs dictionary.
Inside our pre_save_user function we have just used the send_mail function to send a Pre-Save Notification to the user.
The same logic we have used in the post_save_user function. The key difference points are:
post_save signal has been used. That means when the instance is saved, the receiver function will be called.
created: It signifies whether the model instance has been created. We can use the same signal to send an email when the user instance is updated. Try that out and let me know.
Now, let's create signals to send emails pre and post account deletion.
@receiver(pre_delete, sender=User)
def pre_delete_user(sender, instance, **kwargs):
# Sending email before deleting the User
send_mail(
'Pre-Deletion Notification',
f'User {instance.username} will be deleted.',
'youremail@example.com',
[instance.email],
fail_silently=False,
)
@receiver(post_delete, sender=User)
def post_delete_user(sender, instance, **kwargs):
# Sending email after deleting the User
send_mail(
'Deletion Notification',
f'User {instance.username} has been deleted.',
'youremail@example.com',
[instance.email],
fail_silently=False,
)
The code is quite simple to understand. We have used the pre_delete and post_delete signals to notify the user right before and after his account deletion.
In this exploration of Django signals, we've delved into the powerful orchestration they provide for managing model interactions. From leveraging pre-built signals like pre_save
, post_save
, pre_delete
, and post_delete
to seamlessly integrate with Django's default behaviors, we've witnessed their impact on handling model operations effectively.
In the next blog, we'll take a deeper dive into the creation and utilization of custom signals within Django. By understanding how to define and dispatch signals tailored to specific application needs, we'll unlock a new level of flexibility and customization, allowing us to create a more cohesive and responsive Django ecosystem.
Go Django!
Keep learning. Keep Djangoing!
Peace, Dot!