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 the post_save signal sent by the User model.
  • 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_save to run code after a model is saved. Use created=True check for new records only.
  • Use pre_save to modify data before it reaches the database.
  • Use post_delete to clean up related data after a record is deleted.
  • Always use the @receiver decorator to connect a function to a signal.
  • Always register your signals in apps.py inside the ready() method.
  • Always include **kwargs in every receiver function — Django passes extra data through it.

Leave a Comment

Your email address will not be published. Required fields are marked *