Django Signals
Event-driven communication between components
📡 What are Django Signals?
Signals allow decoupled applications to get notified when actions occur elsewhere in the framework. They're like event listeners that trigger automatic actions when something happens in your Django app.
# Basic signal example
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=User)
def user_created(sender, instance, created, **kwargs):
if created:
print(f"New user created: {instance.username}")
Built-in Signal Types
pre_save
Triggered before saving a model
@receiver(pre_save, sender=Article)
def before_save(sender, instance, **kwargs):
instance.slug = slugify(instance.title)
post_save
Triggered after saving a model
@receiver(post_save, sender=Order)
def after_save(sender, instance, created, **kwargs):
if created:
send_confirmation_email(instance)
pre_delete
Triggered before deleting a model
@receiver(pre_delete, sender=Profile)
def before_delete(sender, instance, **kwargs):
instance.avatar.delete()
m2m_changed
Triggered on many-to-many changes
@receiver(m2m_changed, sender=User.groups.through)
def groups_changed(sender, instance, **kwargs):
print(f"Groups updated for {instance}")
🔹 Creating Signal Receivers
Signal receivers are functions that automatically execute when specific events occur. They help you add functionality without modifying existing code, keeping your application modular and maintainable.
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import Profile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""Create a profile when a new user is created"""
if created:
Profile.objects.create(user=instance)
print(f"Profile created for {instance.username}")
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
"""Save profile when user is saved"""
instance.profile.save()
print(f"Profile saved for {instance.username}")
When User is Created:
Profile created for john_doe
Profile saved for john_doe
🔹 Registering Signals
Signals must be imported when Django starts. The best practice is to import them in your app's apps.py ready() method to ensure they're loaded properly.
# 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 # Import signals here
# __init__.py
default_app_config = 'myapp.apps.MyAppConfig'
🔹 Custom Signals
You can create your own custom signals for application-specific events. This is useful for creating loosely coupled components that react to custom business logic events.
# signals.py
from django.dispatch import Signal, receiver
# Define custom signal
order_placed = Signal()
# Send signal from view
def place_order(request):
order = Order.objects.create(user=request.user)
order_placed.send(sender=Order, order=order, user=request.user)
return HttpResponse("Order placed!")
# Receive custom signal
@receiver(order_placed)
def send_order_notification(sender, order, user, **kwargs):
print(f"Order #{order.id} placed by {user.username}")
# Send email, update inventory, etc.
Output:
Order #1234 placed by john_doe
🔹 Practical Example: Email Notifications
This example shows how to automatically send welcome emails when users register and notification emails when they make purchases.
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.mail import send_mail
from django.contrib.auth.models import User
from .models import Order
@receiver(post_save, sender=User)
def send_welcome_email(sender, instance, created, **kwargs):
if created:
send_mail(
subject='Welcome to Our Site!',
message=f'Hi {instance.username}, thanks for joining!',
from_email='[email protected]',
recipient_list=[instance.email],
)
print(f"Welcome email sent to {instance.email}")
@receiver(post_save, sender=Order)
def send_order_confirmation(sender, instance, created, **kwargs):
if created:
send_mail(
subject='Order Confirmation',
message=f'Your order #{instance.id} has been received!',
from_email='[email protected]',
recipient_list=[instance.user.email],
)
print(f"Order confirmation sent for Order #{instance.id}")
Console Output:
Welcome email sent to [email protected]
Order confirmation sent for Order #5678
🔹 Signal Best Practices
Follow these guidelines to use signals effectively:
- Keep it simple: Signal handlers should be fast and lightweight
- Avoid circular imports: Import signals in apps.py ready() method
- Use for decoupling: Signals are great for separating concerns
- Don't overuse: Sometimes direct method calls are clearer
- Handle exceptions: Wrap signal code in try-except blocks