Signals in Django

(Comments)

Introduction

This tutorial deals with implementing event driven, decoupled logic building in Django. This kind of logic building is especially helpful where a certain function or block of code has to be run at every occurrence of a particular event. In other words, Signals help us in triggering a function or block of code to be executed when a event (or signal) is raised.



Built-in Signals

Django provides a set of built-in signals that can be piggybacked to implement certain actions. Some of the useful notifications provided are as follows:


  • django.db.models.signals.pre_save & django.db.models.signals.post_save
    Sent before or after a model’s save() method is called.

  • django.db.models.signals.pre_delete & django.db.models.signals.post_delete
    Sent before or after a model’s delete() method or queryset’s delete() method is called.

  • django.core.signals.request_started & django.core.signals.request_finished
    Sent when Django starts or finishes an HTTP request.

For the complete set of built-in signals and a complete explanation of each signal, please visit the Django Documentation.

You can also define and send your own custom signals, which is explained below.


Listening for a built-in Signal

Let us assume, that your project layout is as follows:

// File Structure
- project/
    - project/
        -__init__.py
        - settings.py
        - urls.py       
    - myapp/
        - forms.py
        - models.py
        - signals.py
        - tasks.py
        - urls.py
        - views.py
        - ...
- manage.py

For the sake of example lets assume we want to run some task before saving a model by the name Users which is defined in the same app in file project/myapp/models.py. First create a receiver function. A receiver function can be any Python function or method. In our case lets call the receiver user_register_handler

# Python Code
# project/myapp/tasks.py

from .models import Users # importing the model
from django.db.models.signals import pre_save  # importing the built-in signal
from django.dispatch import receiver #importing the receiver decorator

@receiver(pre_save, sender=Users)# assigning the sender so it responds only before saving any object of model 'Users'.
def user_register_handler(sender, **kwargs): 
    """ do some task here """
    ...
    ...
    ...



Defining and sending custom signals

We can create our own custom signals taking advantage of the signal infrastructure.


Defining signals

For the sake of our example lets implement the same functionality as above using our own custom signal. First we define a signal as mentioned below

# Python Code
# project/myapp/signals.py

from django.dispatch import Signal

user_register = Signal(providing_args=["request", "user"]) # The providing_args is a list of the names of arguments the signal will provide to listeners before saving any object of model 'Users'

The providing_args argument is purely documentational, however, as there is nothing that checks that the signal actually provides these arguments to its listeners.


Sending signals

For sending a signal, call either Signal.send() or Signal.send_robust(). You must provide the sender argument (which is a class most of the time) and may provide as many other keyword arguments as you like.


send() differs from send_robust() in how exceptions raised by receiver functions are handled. send() does not catch any exceptions raised by receivers; it simply allows errors to propagate. Thus not all receivers may be notified of a signal in the face of an error, whereas send_robust() catches all errors derived from Python’s Exception class, and ensures all receivers are notified of the signal. If an error occurs, the error instance is returned in the tuple pair for the receiver that raised the error.


For our case we will be using just the send() method. Also the signal is to be called (or raised) explicitly in the logic (usually from the view). So in our example we sed the signal in the register view where the user is registered just before actually populating the Users model, since we want the pre_save functionality

# Python Code
# project/myapp/views.py

from . import signals

def register(request):
    ...
    signals.user_register.send(sender=None, request=request, user=request.user)
    ...
    """ Save the user model here """
    ...


Connecting the signal

Connecting the signal to a receiver function is the same as above barring the fact that we refer our custom signal instead of the built-in pre_save signal

# Python Code
# project/myapp/tasks.py

from .models import Users # importing the model
from .signals import user_register  # importing the custom signal
from django.dispatch import receiver #importing the receiver decorator

@receiver(user_register, sender=Users)# assigning the sender so it responds only before saving any object of model 'Users'.
def user_register_handler(sender, **kwargs): 
    """ do some task here """
    request = kwargs['request']
    user = kwargs['user']
    ...
    ...



For more detailed explanation and complete documentation, please visit the Django Documentation on Signals.

Comments

Recent Posts

Archive

2022
2021
2020
2019
2018
2017
2016
2015
2014

Tags

Authors

Feeds

RSS / Atom