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
The models which will be used for this example are below:
from django.db import models
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')
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
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.
The goal is to make sure that whenever a new
Crate object is created, the
Warehouse it is stored in gets its own
newest_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
Crates also update the
newest_crate field in the related
Warehouse 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
Crates, 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
update_newest_crate and attach it to our
from django.dispatch import receiver
from django.db.models.signals import post_save
def update_newest_crate(sender, instance, created, **kwargs):
warehouse = instance.warehouse
warehouse.newest_crate = instance
The params into this method work as follows:
senderis the model class which sent the signal. In our case this will always be
instanceis an instance of the object which was just saved.
createdis a boolean which will be set to
Trueif the triggering action resulted in the creation of a new record, and
Falseif the action was updating an existing record.
Now from within the shell, let’s create a new
Crateand validate the
Warehouse correctly updates automatically:
>>> wh = Warehouse.objects.create(name='First warehouse')
>>> crate1 = Crate.objects.create(name='CR-00531', warehouse=wh)
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
update() method on model instance(s) does not trigger either the
post_save signals. These signals are only triggered by the