← Back to Django Course | Chapter 12: Signals & Middleware | Lesson 1 of 8

Introduction to Django Signals

Signals are like a bell that rings automatically so other parts of your app know something happened, without you having to call them directly.

What Is a Django Signal?

A signal is Django's built-in publish/subscribe system. One piece of code sends a signal when something happens, and any number of receiver functions elsewhere can listen for it and react, without the sender needing to know who's listening.

Note: Signals are great for cross-app hooks, like sending a welcome email when any app creates a User.

Example: What Is a Django Signal?

A signal is Django's built-in publish/subscribe system. One piece of code sends a signal when something happens, and any number of receiver functions elsewhere can listen for it and react, without the sender needing to know who's listening.

markup
import django.dispatch

order_placed = django.dispatch.Signal()

def notify_shipping(sender, order_id, **kwargs):
    print(f"Notify shipping: order {order_id} placed")

order_placed.connect(notify_shipping)
order_placed.send(sender=None, order_id=42)
{# Django-only code -- models.py/views.py/urls.py/settings.py snippets, or template markup using Django template tags/variables -- can't run standalone via Judge0 or the browser preview, since it needs a real Django project. Only this course's pure-Python examples (example_lang == 'python', no Django imports) are actually runnable, so those still get the button below. #}

Signals vs Direct Function Calls

Before reaching for a signal, ask whether a plain function call would be simpler. A direct call is easier to trace and debug; a signal is better when the sender genuinely shouldn't know who reacts to it.

Example: Signals vs Direct Function Calls

Before reaching for a signal, ask whether a plain function call would be simpler. A direct call is easier to trace and debug; a signal is better when the sender genuinely shouldn't know who reacts to it.

markup
# Without signals: caller directly imports and calls
from shipping.utils import notify_shipping

def place_order(order_id):
    save_order(order_id)
    notify_shipping(order_id)
{# Django-only code -- models.py/views.py/urls.py/settings.py snippets, or template markup using Django template tags/variables -- can't run standalone via Judge0 or the browser preview, since it needs a real Django project. Only this course's pure-Python examples (example_lang == 'python', no Django imports) are actually runnable, so those still get the button below. #}

Built-in Signals Overview

Django ships several ready-made signals for model lifecycle events, such as post_save, pre_save, and post_delete from django.db.models.signals.

Example: Built-in Signals Overview

Django ships several ready-made signals for model lifecycle events, such as post_save, pre_save, and post_delete from django.db.models.signals.

markup
from django.db.models.signals import post_save, post_delete

def log_save(sender, instance, **kwargs):
    print(f"{sender.__name__} saved: {instance.pk}")

post_save.connect(log_save)
{# Django-only code -- models.py/views.py/urls.py/settings.py snippets, or template markup using Django template tags/variables -- can't run standalone via Judge0 or the browser preview, since it needs a real Django project. Only this course's pure-Python examples (example_lang == 'python', no Django imports) are actually runnable, so those still get the button below. #}

Connecting a Receiver with @receiver

The @receiver decorator is the most common shorthand for connecting a function to a signal, optionally scoped to one model with sender=.

Example: Connecting a Receiver with @receiver

The @receiver decorator is the most common shorthand for connecting a function to a signal, optionally scoped to one model with sender=.

markup
from django.dispatch import receiver
from django.db.models.signals import post_save
from django.contrib.auth.models import User

@receiver(post_save, sender=User)
def welcome_new_user(sender, instance, created, **kwargs):
    if created:
        print(f"Welcome, {instance.username}!")
{# Django-only code -- models.py/views.py/urls.py/settings.py snippets, or template markup using Django template tags/variables -- can't run standalone via Judge0 or the browser preview, since it needs a real Django project. Only this course's pure-Python examples (example_lang == 'python', no Django imports) are actually runnable, so those still get the button below. #}
Common Mistakes
  1. Believing signals run in a guaranteed order across multiple receivers.
  2. Overusing signals for simple logic that a plain function call would handle more clearly.
  3. Forgetting to import the signals module so its receivers actually get connected.
Chapter Summary
  • Signals let one part of Django notify other decoupled parts when something happens.
  • django.dispatch.Signal lets you define and send fully custom signals.
  • Receivers are just functions connected to a signal with @receiver or .connect().
  • Signals are convenient for cross-app decoupling but add an extra layer of indirection to trace.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.