Previous

Django Signals Explained

Next

Django signals are a powerful feature that allows certain senders to notify a set of receivers when certain actions occur. They're essentially a way to allow decoupled applications to get notified when certain events happen elsewhere in the application.

Key Concepts

  1. Signal: A signal is an object that represents a particular event (like a model being saved or deleted).

  2. Sender: The component that dispatches (sends) the signal when an event occurs.

  3. Receiver: A Python function (or method) that gets called when the signal it's connected to is sent.

Common Built-in Signals

Django comes with several built-in signals, primarily related to model lifecycle events:

  • pre_save & post_save: Sent before/after a model's save() method is called

  • pre_delete & post_delete: Sent before/after a model's delete() method is called

  • m2m_changed: Sent when a ManyToManyField is changed

  • request_started & request_finished: Sent when HTTP requests start/complete

How to Use Signals

1. Connecting Receivers

First, you need to define a receiver function and connect it to a signal:

from django.db.models.signals import post_save
from django.dispatch import receiver
from myapp.models import MyModel

@receiver(post_save, sender=MyModel)
def my_handler(sender, instance, created, **kwargs):
    """
    This function will be called after a MyModel instance is saved.
    - 'created' is True if this is a new record
    - 'instance' is the actual instance being saved
    """
    if created:
        print(f"A new {instance} was created!")
    else:
        print(f"{instance} was updated!")

2. Where to Put Signal Code

It's recommended to put signal handlers in a signals.py file in your app, and then import them in your app's apps.py ready() method:

# myapp/apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'myapp'

    def ready(self):
        import myapp.signals  # this imports and registers the signals

 

3. Creating Custom Signals

You can also define your own signals:

# myapp/signals.py
from django.dispatch import Signal

# Define the signal
order_completed = Signal(providing_args=["order", "user"])

# Receiver function
@receiver(order_completed)
def handle_order_completion(sender, order, user, **kwargs):
    print(f"Order {order} completed by {user}")

 

Then dispatch it somewhere in your code:

order_completed.send(sender=self.__class__, order=order, user=request.user)

 

Best Practices

  1. Keep signal handlers lightweight: They execute synchronously and can slow down your application.

  2. Be careful with infinite loops: Saving an object in a post_save handler will trigger another post_save signal.

  3. Document your signals: Especially custom ones, so other developers know when they fire.

  4. Consider alternatives: Sometimes signals are overkill - simple method calls or overriding model methods might be cleaner.

When to Use Signals

Signals are particularly useful when:

  • You want to perform actions when models change, but can't modify the model code

  • You need multiple unrelated apps to react to the same event

  • You're developing a reusable app that needs to hook into Django's core events