Django calls signal receivers in the order they were connected. This means the first receiver connected is called first, then the next, and so on.
from django.db.models.signals import post_save from django.dispatch import receiver from myapp.models import MyModel # Receiver function @receiver(post_save, sender=MyModel) def my_handler(sender, instance, created, **kwargs): pass
Using the @receiver decorator with both the signal and sender specified is the correct and clean way to connect a receiver.
from django.db.models.signals import post_save from django.dispatch import receiver from myapp.models import MyModel @receiver(post_save, sender=object) def my_handler(sender, instance, created, **kwargs): print('Signal received') # Somewhere else in the code obj = MyModel() obj.save()
Without specifying sender=MyModel, the receiver listens to all models. But if the signal is sent with a sender filter, the receiver may not be called. Usually, specifying sender is needed to ensure the receiver is called for that model.
from django.db.models.signals import post_save from django.dispatch import receiver from myapp.models import MyModel @receiver(post_save, sender=MyModel) def receiver_one(sender, instance, **kwargs): instance.name = 'First' instance.save() @receiver(post_save, sender=MyModel) def receiver_two(sender, instance, **kwargs): instance.name = 'Second' instance.save() obj = MyModel(name='Original') obj.save() print(MyModel.objects.get(pk=obj.pk).name)
Calling save() inside a post_save receiver triggers the signal again, causing infinite recursion and eventually a RuntimeError.
Django copies the list of connected receivers before calling them to avoid issues if receivers are added or removed during dispatch, ensuring thread safety.