The Django Signals is a strategy to allow decoupled applications to get notified when certain events occur. Let’s say you want to invalidate a cached page everytime a given model instance is updated, but there are several places in your code base that this model can be updated. You can do that using signals, hooking some pieces of code to be executed everytime this specific model’s save method is trigged.
Another common use case is when you have extended the Custom Django User by using the Profile strategy through a one-to-one relationship. What we usually do is use a “signal dispatcher” to listen for the User’s post_save event to also update the Profile instance as well. I’ve covered this case in another post, which you can read here: How to Extend Django User Model.
In this tutorial I will present you the built-in signals and give you some general advices about the best practices.
When Should I Use It?
Before we move forward, know when you should use it:
- When many pieces of code may be interested in the same events;
- When you need to interact with a decoupled application, e.g.:
- A Django core model;
- A model defined by a third-party app.
How it works?
If you are familiar with the Observer Design Pattern, this is somewhat how Django implements it. Or at least serves for the same purpose.
There are two key elements in the signals machinary: the senders and the receivers. As the name suggests, the sender is the one responsible to dispatch a signal, and the receiver is the one who will receive this signal and then do something.
A receiver must be a function or an instance method which is to receive signals.
A sender must either be a Python object, or None to receive events from any sender.
The connection between the senders and the receivers is done through “signal dispatchers”, which are instances of Signal, via the connect method.
The Django core also defines a ModelSignal, which is a subclass of Signal that allows the sender to be lazily
specified as a string of the app_label.ModelName
form. But, generally speaking, you will always want to use the
Signal class to create custom signals.
So to receive a signal, you need to register a receiver function that gets called when the signal is sent by using the Signal.connect() method.
Usage
Let’s have a look on the post_save
built-in signal. Its code lives in the django.db.models.signals
module. This
particular signal fires right after a model finish executing its save method.
In the example above, save_profile
is our receiver function, User
is the sender and post_save
is the
signal. You can read it as: Everytime when a User
instance finalize the execution of its save
method, the
save_profile
function will be executed.
If you supress the sender argument like this: post_save.connect(save_profile)
, the save_profile
function will
be executed after any Django model executes the save method.
Another way to register a signal, is by using the @receiver
decorator:
The signal parameter can be either a Signal instance or a list/tuple of Signal instances.
See an example below:
If you want to register the receiver function to several signals you may do it like this:
Where Should the Code Live?
Depending on where you register your application’s signals, there might happen some side-effects because of importing code. So, it is a good idea to avoid putting it inside the models module or in application root module.
The Django documentation advices to put the signals inside your app config file. See below what I usually do, considering a Django app named profiles:
profiles/signals.py:
profiles/app.py:
profiles/__init__.py:
In the example above, just importing the signals module inside the ready() method will work because I’m using
the @receiver()
decorator. If you are connecting the receiver function using the connect() method, refer to
the example below:
profiles/signals.py:
profiles/app.py:
profiles/__init__.py:
Note: The profiles/__init__.py bits are not required if you are already referring to your AppConfig in the
INSTALLED_APPS
settings.
Django Built-in Signals
Here you can find a list of some useful built-in signals. It is not complete, but those are the most common.
Model Signals
django.db.models.signals.pre_init:
django.db.models.signals.post_init:
django.db.models.signals.pre_save:
django.db.models.signals.post_save:
django.db.models.signals.pre_delete:
django.db.models.signals.post_delete:
django.db.models.signals.m2m_changed:
Request/Response Signals
django.core.signals.request_started:
django.core.signals.request_finished:
django.core.signals.got_request_exception:
If you want to see the whole list, click here to access the official docs.