Image

Disable Django Model Signals

Dani Hodovic April 5, 2023 2 min read
Responsive image

Every large Django codebase I've encountered uses signals in some form. Sometimes for stats aggregation (counting sign-ups of users), other times to kick off background tasks (resizing images). The most common use case I've encountered is to sync data to external systems such as invaliding a cache or updating an external database (often ElasticSearch).

Sometimes the side-effects of signals are undesirable. For example when creating test data we don't want to trigger a compute-intensive process or send an email. If we're importing data into a Django database we also want to disable signals for old entries which are stale.

Model.objects.bulk_create()

The Django-native way of disabling signals is to perform a bulk create or bulk update operation. This bypasses the signals system altogether:

class Film(models.Model):
    title = models.CharField(max_length=100)

Film.objects.bulk_create([Film(title="Underground")])

The bulk_create method is especially useful when inserting a set of rows at a time as it writes data in a single operation.

Model.save(signals_to_disable=["post_save"])

The alternative is to use the excellent jazzband/django-model-utils library. It contains a mixin which allows us to disable signals on demand.

from model_utils.models import SaveSignalHandlingModel

class Film(SaveSignalHandlingModel):
    title = models.CharField(max_length=100)

film = Film(title="Cidade de Deus")
film.save(signals_to_disable=["post_save"])

The mixin allows to silence signals using the signals_to_disable argument. In the above example we've silenced the post_save signal.

The mixin overrides the save_base method of the Django parent class to temporarily disable signals. This poses a maintainance risk as the code has to be kept up to date with future Django releases, but I trust the developers at Jazzband more than most in the Django ecosystem.

Disabling signals in FactoryBoy

If you're using FactoryBoy there is a handy helper for disabling signals.

@factory.django.mute_signals(signals.pre_save, signals.post_save)
class FilmFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Film

This disables the signal when calling the factory, but enables them after that.