Django includes a “signal emitter” to help decoupled applications get notified when certain actions occur elsewhere in the framework. In a word, the signaling mechanism allows a particular sender to notify multiple receivers that certain actions have taken place. Signaling is especially useful when many discrete pieces of code are interested in the same event.

Django provides a set of built-in signals to notify user code when certain actions have occurred. It contains a number of useful signals:

  • django.db.models.signals.pre_save & django.db.models.signals.post_save

The signal sent before or after a model’s save function is called.

  • django.db.models.signals.pre_delete & django.db.models.signals.post_delete

The signal sent before or after a model’s DELETE function or a query set’s DELETE function is called.

  • django.db.models.signals.m2m_changedz

The signal sent when a ManyToMany field is modified.

  • django.core.signals.request_started & django.core.signals.request_finished

A signal sent when Django starts or ends an HTTP request.

3.22.1 Monitor signal

To receive a Signal, a receiver function needs to be registered with signal.connect (). When a Signal is sent, this receiver function is executed. All receiver functions are executed one at a time, in the order in which they were registered.

Signal.connect(receiver,sender=None,weak=True,dispatch_uid=None)

Parameter Description:

  • Receiver – A callback function bound to a signal.
  • Sender – Specifies a specific sender to send a signal.
  • Weak-django stores signal handlers as weak references by default, so if your receiver callback is a local function, it may be garbage collected.
  • Dispatch_uid – a unique identifier for a signal in case more than one signal is sent.

Let’s get an idea of how the signaling mechanism works by registering a receiver function that is called at the end of each HTTP request, which will be bound to the Request_FINISHED signal.

Receiver function

First, we need to define a receiver function. A receiver function can be any Python function or method:

def my_callback(sender, **kwargs): print("Request finished!" )Copy the code

Note that this function requires a sender argument and a keyword argument (**kwargs); All signal processing functions must contain these parameters.

We’re going to look at the sender argument later, but now we’re going to look at the **kwargs argument. All signals send keyword arguments, and it is possible to change those keyword arguments at some point. In the request_FINISHED document, it is stated that the signal was sent with no parameters, which means we could be induced to write a signal handler like my_callback(sender).

However, this is an error, and in fact, Django will throw an error if you do. This is because arguments can be added, and your receiver function must be able to handle these new arguments.

Bind receiver function

There are two ways to bind a receiver function to a signal. The first is:

from django.core.signals import request_finished
request_finished.connect(my_callback)
Copy the code

The other option is to use the receiver() decorator.

receiver(signal)

Parameter sginal- signals bound to a function (single or multiple, organized ina list)

Here is an example of how to use this decorator:

from django.core.signals import request_finished from django.dispatch import receiver @receiver(request_finished) def my_callback(sender,**kwargs): print("Request finished!" )Copy the code

Our my_callback function will now execute at the end of each HTTP request.

Where does this code go?

Strictly speaking, the signal-handling functions and registration code can be written anywhere you like. But we recommend that you avoid writing this code in the root directory of your application and in the Models module to minimize the side effects of new code.

In practice, signal handlers are usually defined in the signals submodule of the associated application and bound to signals in the reday function of the application configuration class. If you are using the Receiver () decorator, import the signals submodule in the Ready function.

Bind to a signal sent by a particular sender

A signal can be sent many times by many different senders, but you may only be interested in some of them. As an example, the django. Db. Models. Signals. Pre_save this signal will be saved in a model before sending. Most of the time, you don’t need to know when all the models were saved, just when a particular model was saved.

In this case, you can register a receiver function that accepts only signals sent by a particular sender. For django. Db. Models. Signals. Pre_save, the sender is a model class, so you need to specify which class you need to send the signal.

from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
@receiver(pre_save,sender=MyModel)
def my_handler(sender,**kwargs):
    ...
Copy the code

Now, the my_handler function will only be executed when MyModel is saved.

Different signals use different objects as their sender, and you need to query the relevant documentation for the details of each particular signal.

Avoid copying signals

In some cases, the code that binds the receiver function to the signal may be run more than once. This will cause your receiver function to be registered multiple times, so when a signal is sent once, the receiver function will be executed multiple times. For example, the ready() function may be executed multiple times during testing (as mentioned above, it is common to put code that binds receiver functions and signals into the Ready function). More commonly, this happens when your project imports a module that defines signals, because the signal registration code runs every time it is imported.

If this behavior causes problems (such as sending a message when a model is saved), you can use a unique identifier -dispatch_uid parameter to identify your receiver function. This identifier is usually a string. The end result is that for each identifier, your receiver function will be bound to the signal only once, as shown in the following example:

from django.core.signals import request_finished

request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
Copy the code

3.22.2 Define and send signals

Your application can take advantage of the signal mechanism while also providing custom signals.

When to use custom signals?

A signal is an implicit function call that makes debugging more difficult. If both sender and receiver are in your project, you are better off using an explicit function call.

Define the signal

class Signal(providing_args = list)

All signals are instances of Django.dispatch.signal. The parameter providing_args is a list of parameter names supplied by the signal to the listener. However, there is no mechanism to detect whether the signal provides these parameters to the listener, so this parameter simply makes the document prettier.

Here’s an example:

import django.dispatch

pizza_done = django.dispatch.Signal(providing_args=["toppings","size"])

A pizza_done signal is declared, and its receiver is supplied with the toppings and size parameters.

Keep in mind that you can change this parameter list at any time, so it is not necessary to make the API correct on the first try.

Send a signal

In Django, there are two ways to send signals.

Signal. Send (sender, **kwargs)- All built-in signals are sent using this method

Signal.send_robust(sender, **kwargs)

To send a signal, you need to call one of these two methods. You must provide the sender argument (usually a class), and you can provide other keyword arguments as you like.

As an example, here’s the code for sending the pizza_done signal we defined above:

class PizzaStore:

    ...

    def send_pizza(self,toppings,size):

        pizza_done.send(sender=self.__class__,toppings=toppings,size=size)
Copy the code

Both send() and send_robust() return a list of tuples [(receiver,response),…] Represents all receiver functions and their return values.

Send () differs from send_robust() in how one is handled when the receiver function throws an exception. Send () does not catch any exceptions and allows errors to multiply. So not all receivers will be notified of the signal when there is an error.

Send_robust () catches all errors (in this case, errors derived from Python’s Exception class) and ensures that all receiver functions are notified of the signal. If an error occurs, the error instance is contained in a tuple pair and returned to the receiver who threw the error.

3.22.3 Unbinding from signals

Signal.disconnect(receiver=None,sender=None,dispatch_uid=None)

This article is only based on the translation of official documents, as a lecture note is a bit of the book, after practical experience will be improved, so for now, let you reluctant to read it.