Django Signals
Sometimes you want something to happen automatically when an event occurs in your Django app — for example, send a welcome email when a new user registers, or create a profile record automatically when a User account is saved. Django's Signals system is built exactly for this purpose.
What are Django Signals?
A Signal is a notification that Django sends out when a specific event happens. Another piece of code can "listen" for that signal and run automatically when it fires. Signals let different parts of your application communicate without being directly connected to each other.
Example: Imagine a school bell system. When the bell rings (signal), every classroom teacher knows to end the lesson (receiver function). The bell does not need to know which teacher is in which room — it just rings. Each teacher independently listens for the bell and responds. Signals work exactly the same way — one event, multiple automatic responses.
Why Use Signals?
Signals are useful when:
- You want to run some code every time a model is saved or deleted.
- You want to keep two parts of your app loosely connected — they don't need to know about each other directly.
- You want to perform automatic tasks like creating related records, sending notifications, or clearing caches.
Django's Built-in Signals
Django provides several built-in signals ready to use:
- pre_save — Fires just before a model is saved to the database.
- post_save — Fires just after a model is saved to the database.
- pre_delete — Fires just before a model record is deleted.
- post_delete — Fires just after a model record is deleted.
- m2m_changed — Fires when a ManyToMany relationship is changed.
- request_started — Fires when Django receives an HTTP request.
- request_finished — Fires when Django finishes sending a response.
Your First Signal — Auto-Create a Profile on User Registration
A very common use case: every time a new User is created, automatically create a Profile record for them.
First, create the Profile model
# school/models.py
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
profile_photo = models.ImageField(upload_to='profiles/', blank=True, null=True)
def __str__(self):
return f'{self.user.username} Profile'
Now create the signal in a new file signals.py
# school/signals.py
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile
@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created: # Only run when a NEW user is created (not on updates)
Profile.objects.create(user=instance)
print(f'Profile created for {instance.username}')
Let us break down the parts:
@receiver(post_save, sender=User)— Listen for thepost_savesignal sent by theUsermodel.sender— The model that sent the signal (User in this case).instance— The actual User object that was just saved.created— True if this is a brand new record, False if an existing record was updated.**kwargs— Extra keyword arguments passed by the signal (always include this).
Example: This signal is like a rule at a school: "Whenever a new student is enrolled (User created), automatically issue them a library card (Profile created)." The enrollment desk (User model) does its job, and the library (signal receiver) automatically does its job without either needing to know about the other.
Registering the Signal — Connecting it to the App
Signals must be imported when the app starts or they will not work. The right place to do this is in the app's apps.py file using the ready() method:
# school/apps.py
from django.apps import AppConfig
class SchoolConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'school'
def ready(self):
import school.signals # <-- This loads your signals when the app starts
Also make sure your settings.py uses this AppConfig. Django usually sets this automatically, but double check:
INSTALLED_APPS = [
...
'school.apps.SchoolConfig', # <-- Use the full app config path
...
]
The pre_save Signal — Running Code Before Saving
Use pre_save when you want to modify data or do a check before it is saved to the database:
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import Student
@receiver(pre_save, sender=Student)
def capitalize_name(sender, instance, **kwargs):
# Automatically capitalize the student name before saving
instance.name = instance.name.strip().title()
Now every time a Student is saved, their name is automatically formatted — "rahul sharma" becomes "Rahul Sharma" without any extra code in the view.
The post_delete Signal — Running Code After Deletion
from django.db.models.signals import post_delete
from django.dispatch import receiver
from .models import Student
import os
@receiver(post_delete, sender=Student)
def delete_student_photo(sender, instance, **kwargs):
# Delete the profile photo file from disk when a Student is deleted
if instance.profile_photo:
if os.path.isfile(instance.profile_photo.path):
os.remove(instance.profile_photo.path)
Example: This signal is like a janitor at a hotel. Whenever a guest checks out (Student deleted), the janitor automatically cleans the room (deletes the photo file). The receptionist (view/form) doesn't need to write "and also tell the janitor" — the janitor is always listening for the checkout signal.
Custom Signals — Creating Your Own
You can also create your own custom signals for events specific to your application:
# school/signals.py
from django.dispatch import Signal
# Define a custom signal
exam_result_published = Signal()
# In a view or service, send the signal
def publish_results(request):
# ... publish logic here ...
exam_result_published.send(sender=None, student_name="Rahul", grade="A")
return redirect('results')
# In the receiver
from django.dispatch import receiver
@receiver(exam_result_published)
def notify_student(sender, student_name, grade, **kwargs):
print(f'Notifying {student_name}: your grade is {grade}')
Signals File Structure in a Django App
school/
├── __init__.py
├── admin.py
├── apps.py <-- Register signals in ready() method here
├── models.py
├── signals.py <-- Write all your signal receivers here
├── views.py
└── urls.py
Quick Recap
- Signals let one part of your app automatically trigger actions in another part without direct coupling.
- Use
post_saveto run code after a model is saved. Usecreated=Truecheck for new records only. - Use
pre_saveto modify data before it reaches the database. - Use
post_deleteto clean up related data after a record is deleted. - Always use the
@receiverdecorator to connect a function to a signal. - Always register your signals in
apps.pyinside theready()method. - Always include
**kwargsin every receiver function — Django passes extra data through it.
