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.