Django signals: How to automatically trigger additional actions after saving an object
Django provides a robust signaling framework which can be extremely useful for triggering additional logic after specific events happen, i.e. saving an object.
This article will demonstrate how to set up Django signals to trigger a method every time a specific model object is saved.
For the example use case, let’s imagine we have a warehouse tracking each crate that are coming and going via a Warehouse
model and Crate
model accordingly. For the purpose of this example, the Warehouse
model has a ForeignKey
field pointing at the most recent Crate
object that came in which needs to be reliably updated whenever a new Crate
enters that Warehouse
Models
The models which will be used for this example are below:
from django.db import models
class Warehouse(models.Model):
name = models.CharField(max_length=128)
address = models.TextField(null=True, blank=True)
newest_crate = models.OneToOneField('Crate', on_delete=models.SET_NULL, null=True, blank=True, related_name='newest')
class Crate(models.Model):
name = models.CharField(max_length=64)
description = models.TextField(null=True, blank=True)
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
Crate
has a ForeignKey relation to Warehouse
which signifies which Warehouse
the Crate
is stored in — this is a Many-To-One relationship as a single crate can only be inside a single warehouse at a time. Warehouse
also has a ForeignKey relationship back to Crate
which represents a shortcut to the most recent crate which was added to this warehouse — this is a One-To-One relationship.
Signals
The goal is to make sure that whenever a new Crate
object is created, the Warehouse
it is stored in gets its ownnewest_crate
field updated to point to this new crate.
One potential solution is to go through any forms, APIs, views, etc which are responsible for creating new Crate
s also update the newest_crate
field in the relatedWarehouse
model. The big downside to this approach is that it is error-prone and repetitive (not DRY). We could abstract away the logic into a single method which handles this logic, but if we forget to call that method in any of the many code paths that may create Crate
s, we’ve introduced a bug.
How can we ensure that any action which results in a new Crate
being created automatically updates the Warehouse
model, regardless of the code path that was taken to get there?
Enter: Django Signals. These allow us to attach methods which will be invoked whenever a particular action happens on a model — for example, whenever a model gets saved.
Signals in action!
The beauty of Django signals is how easy they are to use: write a method to execute the custom logic that will trigger after an event, then decorate that method using the @receiver
decorator to specify which action(s) , and which Model class(es) will trigger this custom logic.
Let’s try this on our models. Let’s create a new receiver
method, update_newest_crate
and attach it to our Crate
model.
from django.dispatch import receiver
from django.db.models.signals import post_save
@receiver(post_save, sender=Crate)
def update_newest_crate(sender, instance, created, **kwargs):
if created:
warehouse = instance.warehouse
warehouse.newest_crate = instance
warehouse.save()
The params into this method work as follows:
sender
is the model class which sent the signal. In our case this will always beCrate
instance
is an instance of the object which was just saved.created
is a boolean which will be set toTrue
if the triggering action resulted in the creation of a new record, andFalse
if the action was updating an existing record.
Now from within the shell, let’s create a new Crate
and validate the Warehouse
correctly updates automatically:
>>> wh = Warehouse.objects.create(name='First warehouse')
>>> crate1 = Crate.objects.create(name='CR-00531', warehouse=wh)
>>> wh.newest_crate.name
'CR-00531'
And that’s it! Happy signaling!
Check the Django signals reference docs, linked below, to see the full list of signals (pre_save, post_save, pre_delete, post_migrate, etc) which Django supports, as well as how to dispatch your own signals and other advanced topics on the subject.
Word of caution
The update()
method on model instance(s) does not trigger either the pre_save
or post_save
signals. These signals are only triggered by the save()
method.