Photo by Clément Hélardot on Unsplash

Django signals: How to automatically trigger additional actions after saving an object

Adrien Van Thong

--

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 Crates 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 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 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 be Crate
  • instance is an instance of the object which was just saved.
  • created is a boolean which will be set to True if the triggering action resulted in the creation of a new record, and False if 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)
>>> 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.

Resources

--

--

No responses yet