Django by Example: Signal Receiver Functions
Signals allow decoupled applications to get notified when certain actions occur elsewhere in the framework. This code example shows a common use case: creating a UserProfile automatically when a new User is registered.
Code
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import Profile
# 1. Define Receiver Function
# The @receiver decorator connects the function to the signal
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
Creates a Profile object whenever a new User is created.
"""
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
"""
Saves the Profile object whenever the User is saved.
"""
# instance.profile refers to the related Profile object
instance.profile.save()
# 2. Pre-delete example
@receiver(pre_delete, sender=User)
def backup_user_data(sender, instance, **kwargs):
print(f"User {instance.username} is about to be deleted!")
# Perform backup logic here...
# Note: You must import this signals.py file in your
# apps.py ready() method for it to work!Explanation
Signals are Django's implementation of the Observer pattern, allowing decoupled applications to get notified when actions occur. While useful, they should be used judiciously. Overusing signals can make the control flow hard to trace, as logic is executed implicitly.
A common alternative is overriding the model's save() method. Use save() when the logic is strictly related to the model itself (e.g., updating a timestamp). Use signals when the logic involves other models or external side effects (e.g., sending a welcome email), or when you can't modify the model code directly (e.g., third-party apps).
Remember to connect your signals in the ready() method of your AppConfig to ensure they are registered when Django starts. Also, be aware that bulk operations like bulk_create do not send signals.
Code Breakdown
@receiver(post_save, sender=User). This registers the function to listen for the post_save signal, but ONLY when sent by the User model. Without sender=User, it would run for every model save in the system.created boolean. This argument is specific to post_save. It is True if a new record was inserted, and False if an existing record was updated.Profile.objects.create(user=instance). instance is the actual User object that was just saved. We use it to create the relationship.ready() method of your apps.py configuration.
