Skip to main content

Django Signals Introduction

What Are Django Signals?

Django signals provide a way to allow decoupled applications to get notified when certain events occur elsewhere in the framework. In simple terms, signals are like notifications or announcements that are broadcasted when specific actions happen in your Django application.

Think of signals as a publisher-subscriber pattern:

  • Some code (the "sender") sends a signal when something happens
  • Other code (the "receivers") listens for that signal and executes in response

This creates a powerful event-driven system that allows different parts of your application to communicate without being directly dependent on each other.

Why Use Signals?

Signals are particularly useful when:

  1. You want to execute code in response to built-in Django events (like when a model is saved)
  2. You need decoupled components to react to events without direct dependencies
  3. You want to implement cross-cutting concerns that affect multiple parts of your application
  4. You're building a reusable Django app that needs to hook into events without modifying user code

Built-in Django Signals

Django comes with several pre-defined signals that are sent during various operations:

Model Signals

  • pre_save and post_save: Sent before or after a model's save() method is called
  • pre_delete and post_delete: Sent before or after a model's delete() method is called
  • m2m_changed: Sent when a ManyToMany relation is modified

Request/Response Signals

  • request_started and request_finished: Sent when Django starts or finishes processing an HTTP request
  • got_request_exception: Sent when an exception occurs during request processing

Management Signals

  • pre_migrate and post_migrate: Sent before or after the migrate command is executed

How Signals Work

Django signals operate through a simple process:

  1. A signal is defined (Django has many built-in signals, or you can create custom ones)
  2. A "receiver" function is connected to that signal
  3. When the signal is sent, all connected receiver functions are called

Basic Example: Using pre_save Signal

Let's look at a simple example using the pre_save signal to automatically set a slug field before saving a blog post:

python
# models.py
from django.db import models
from django.utils.text import slugify
from django.db.models.signals import pre_save
from django.dispatch import receiver

class BlogPost(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
slug = models.SlugField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return self.title

@receiver(pre_save, sender=BlogPost)
def create_slug(sender, instance, **kwargs):
if not instance.slug:
instance.slug = slugify(instance.title)

In this example:

  1. We define a BlogPost model with a slug field
  2. We create a receiver function that automatically generates a slug from the title
  3. The function is connected to the pre_save signal for BlogPost models using the @receiver decorator

Now, whenever a BlogPost is about to be saved and doesn't have a slug, one will be automatically created.

Connecting Signals Without Decorators

You can also connect signals without using decorators:

python
# signals.py
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import BlogPost
from django.utils.text import slugify

def create_slug(sender, instance, **kwargs):
if not instance.slug:
instance.slug = slugify(instance.title)

pre_save.connect(create_slug, sender=BlogPost)

Then, to make sure Django loads your signals, you need to import them in your app's apps.py:

python
# apps.py
from django.apps import AppConfig

class BlogConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'blog'

def ready(self):
import blog.signals # Replace 'blog' with your app name

Real-World Application: User Profile Creation

A common use case for signals is automatically creating a user profile when a new user is registered:

python
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
profile_picture = models.ImageField(upload_to='profile_pics', blank=True)

def __str__(self):
return f"{self.user.username}'s Profile"

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()

In this example:

  1. We have a Profile model linked to Django's built-in User model
  2. When a new user is created, the post_save signal triggers the create_user_profile function
  3. This function automatically creates a profile for the new user
  4. Another signal handler ensures that when a user is updated, their profile is also saved

Creating Custom Signals

While Django's built-in signals cover many common scenarios, you can also define your own custom signals:

python
# signals.py
from django.dispatch import Signal

# Define a custom signal
payment_completed = Signal() # You can provide providing_args=['order', 'amount']

# In your view or business logic:
def process_payment(order):
# Process payment logic here
# ...

# If payment is successful, send the signal
payment_completed.send(
sender=order.__class__,
order=order,
amount=order.amount
)

# Create a receiver for your custom signal
@receiver(payment_completed)
def handle_payment_completed(sender, order, amount, **kwargs):
# Send confirmation email
send_payment_confirmation_email(order.user, amount)

# Update inventory
update_inventory(order.items)

# Other tasks that need to happen after payment

This pattern is especially useful for decoupling payment processing from all the side effects that should happen after a payment is completed.

Best Practices for Using Signals

While signals are powerful, they should be used thoughtfully:

  1. Don't overuse signals: For direct relationships where you always want the action to happen, consider using model methods or overriding save() instead

  2. Document signal usage: Because signals create "invisible" connections, make sure to document them well

  3. Be careful with performance: Signals can impact performance if overused, as they add overhead to operations

  4. Handle exceptions: Receivers should handle their exceptions to prevent disrupting the normal flow

  5. Use for truly decoupled functionality: Signals are best when components genuinely need to be separate

Common Signal Pitfalls

Recursive Signal Calls

Be careful not to create infinite loops where a signal handler triggers the same signal:

python
@receiver(post_save, sender=User)
def update_user(sender, instance, **kwargs):
# This will trigger post_save again!
instance.save() # BAD - creates infinite loop

# Better approach
@receiver(post_save, sender=User)
def update_user(sender, instance, created, **kwargs):
if not created and not getattr(instance, '_in_update', False):
instance._in_update = True
# Do your updates
instance.save() # No infinite loop now
instance._in_update = False

Import Cycles

Be careful about circular imports when using signals across different modules.

Summary

Django signals provide a powerful mechanism to implement event-driven programming within your Django applications. They allow you to:

  • Execute code when specific events occur (like model saves or deletions)
  • Keep your components decoupled while still communicating
  • React to built-in Django events or create custom ones

Key things to remember:

  • Use the @receiver decorator or signal.connect() to connect functions to signals
  • Make sure your app's ready() method imports your signals
  • Consider whether signals are the right tool for your specific use case
  • Custom signals can help you implement clean, event-driven architecture

Additional Resources

Exercises

  1. Create a signal that automatically sets a modified_at timestamp whenever a model is saved
  2. Implement a logging system that records when objects are created, updated, or deleted using signals
  3. Create a custom signal that's dispatched when a user completes their profile, and use it to send them a welcome email
  4. Build a signal-based notification system that alerts administrators when certain actions occur in your application

By mastering Django signals, you'll be able to build more modular, maintainable, and reactive applications.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)