Django signals enable decoupled communication between different parts of an application by allowing senders to notify receivers when certain actions occur.
Built-in Signal Types
Django provides several categories of built-in signals:
-
Model signals
pre_init: emitted before model instance initializationpost_init: emitted after model instance initializationpre_save: emitted before a model instance is savedpost_save: emitted after a model instance is savedpre_delete: emitted before a model instance is deletedpost_delete: emitted after a model instance is deletedm2m_changed: emitted when a many-to-many relation changes viaadd,remove, orclearclass_prepared: emitted when a model class has been prepared and registered
-
Management signals
pre_migrate: emitted before applying migrationspost_migrate: emitted after applying migrations
-
Request/response signals
request_started: emitted at the beginning of a requestrequest_finished: emitted at the end of a requestgot_request_exception: emitted when an exception occurs during request handling
-
Test signals
setting_changed: emitted when a test modifies settingstemplate_rendered: emitted when a template is rendered during tests
-
Database wrapper signals
connection_created: emitted when a new database conneciton is created
Connecting to Built-in Signals
To react to a signal, register a callable using either Signal.connect() or the @receiver decorator.
from django.core.signals import request_finished, request_started, got_request_exception
from django.db.models.signals import (
class_prepared, pre_init, post_init,
pre_save, post_save, pre_delete,
post_delete, m2m_changed,
pre_migrate, post_migrate
)
from django.test.signals import setting_changed, template_rendered
from django.db.backends.signals import connection_created
from django.dispatch import receiver
@receiver(request_finished)
def log_request_completion(sender, **kwargs):
print("Request has completed")
The built-in senders trigger these signals automatically; no manual invocation is required.
Defining Custom Signals
Custom signals allow application-specific events to be dispatched and handled.
- Declare the signal
import django.dispatch
order_processed = django.dispatch.Signal(providing_args=["item_count", "total_price"])
- Connect a handler
def handle_order(sender, **kwargs):
print(f"Order processed: {kwargs['item_count']} items, total ${kwargs['total_price']}")
order_processed.connect(handle_order)
- Emit the signal
order_processed.send(sender='checkout_view', item_count=3, total_price=75.50)
Unlike built-in signals, custom ones must be explicitly sent where appropriate in the codebase.
Example: Logging on Model Creation
To record a log each time a model instance is added, connect handlers to pre_save and post_save.
from django.db.models import signals
def log_pre_save(instance, **ctx):
print(f"Pre-save triggered for {instance.__class__.__name__}", ctx)
def another_pre_save(instance, **ctx):
print(f"Another action before saving {instance.__class__.__name__}", ctx)
def log_post_save(instance, created, **ctx):
if created:
print(f"New instance created: {instance.__class__.__name__}", ctx)
# Register handlers
signals.pre_save.connect(log_pre_save)
signals.pre_save.connect(another_pre_save)
signals.post_save.connect(log_post_save)
These handlers will run automatically whenever a model's save() method is invoked, enabling side effects such as logging without modifying the model's core logic.