SoFunction
Updated on 2024-11-17

Deeper Understanding of Django-Signals Signals

Defining Signals

Django itself provides some common signals, and users can define their own signals as well

Defining a signal is simple, just instantiate a Signal instance

When instantiating a Signal, you can pass in the keyword argument providing_args, providing_args is a list that defines the arguments that can be passed in when the current signal calls the send method.

# 

from  import Signal

request_started = Signal(providing_args=["environ"])
request_finished = Signal()
got_request_exception = Signal(providing_args=["request"])
setting_changed = Signal(providing_args=["setting", "value", "enter"])

where the initialization of Signal is also relatively simple, by defining a thread lock for the instantiated signal

class Signal(object):
  def __init__(self, providing_args=None, use_caching=False):
     = []
    if providing_args is None:
      providing_args = []
    self.providing_args = set(providing_args)
     = ()
    self.use_caching = use_caching
    # For convenience we create empty caches even if they are not used.
    # A note about caching: if use_caching is defined, then for each
    # distinct sender we cache the receivers that sender has in
    # 'sender_receivers_cache'. The cache is cleaned when .connect() or
    # .disconnect() is called and populated on send().
    self.sender_receivers_cache = () if use_caching else {}
    self._dead_receivers = False

Defining Signal Handler Functions

A Signal handler is a function or a method of an instance and must satisfy the following conditions:

  1. hashable
  2. Can receive keyword parameters

There are two keyword arguments that must be included in one of the handler functions:

  • signal, the Signal instance to be received
  • sender, the trigger of the Signal to be received
# .__init__.py

from  import signals
from  import ConnectionHandler

connections = ConnectionHandler()


def reset_queries(**kwargs):
  for conn in ():
    conn.queries_log.clear()
signals.request_started.connect(reset_queries)


def close_old_connections(**kwargs):
  for conn in ():
    conn.close_if_unusable_or_obsolete()
signals.request_started.connect(close_old_connections)
signals.request_finished.connect(close_old_connections)

Handler Binding Signal

django provides two ways to bind Signal handlers to Signal instances:

  • Calling the connect method manually
  • Using the decorator receiver

Actually, the decorator receiver ends up calling the connect method to bind the handler function to the Signal instance

The connect method of the Signal class is defined as follows:

class Signal(object):

  ...
  
  def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
    from  import settings

    # If DEBUG is on, check that we got a good receiver
    if  and :
      assert callable(receiver), "Signal receivers must be callable."

      # Check for **kwargs
      if not func_accepts_kwargs(receiver):
        raise ValueError("Signal receivers must accept keyword arguments (**kwargs).")

    if dispatch_uid:
      lookup_key = (dispatch_uid, _make_id(sender))
    else:
      lookup_key = (_make_id(receiver), _make_id(sender))

    if weak:
      ref = 
      receiver_object = receiver
      # Check for bound methods
      if hasattr(receiver, '__self__') and hasattr(receiver, '__func__'):
        ref = WeakMethod
        receiver_object = receiver.__self__
      if six.PY3:
        receiver = ref(receiver)
        (receiver_object, self._remove_receiver)
      else:
        receiver = ref(receiver, self._remove_receiver)

    with :
      self._clear_dead_receivers()
      for r_key, _ in :
        if r_key == lookup_key:
          break
      else:
        ((lookup_key, receiver))
      self.sender_receivers_cache.clear()

Each signal can get a lookup_key that uniquely identifies a Signal and its handler, when calling the connect method of a Signal instance, it will determine if the bound handler is already in its own receivers, and if it is, it won't re-register.

Send Singal

With the Signal instance defined earlier, and the Signal instance handler defined, a Signal instance bound by a handler function can be used to send signals where necessary, and then be handled by the bound handler function.

# 

from threading import Lock

from  import signals
from  import base

 

class WSGIHandler():

  ...

  def __call__(self, environ, start_response):

    ...
    
    signals.request_started.send(sender=self.__class__, environ=environ)
    
    ...

Signal most Django a core knowledge in the project rarely used, so many people do not understand or have not heard of (including me). Simply put, before and after some operations we can issue a signal to get a specific operation, these operations include

.pre_save&.post_save
Sent before or after the model save() method is called.

.pre_delete&.post_delete
Sent before or after the model delete() method or the delete() method call for a query set.

.m2m_changed
Sent on ManyToManyField modifications on the model.

.request_started&.request_finished
Sent when Django starts or completes an HTTP request.

For other detailed points, you can click on the link to see them explained directly through an example:

In the custom user model class, in the background to add user data because of the use of custom model class create so the password will be saved in clear text, the next use of semaphores in the saved immediately after the password to change the solution. (An example of an online project)

users/

from  import post_save
from  import receiver
from  import get_user_model
User = get_user_model()


# post_save: one of the seven methods above: operations after the model has been saved
# sender: the mod that sends the signal
@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
  """
  sender:model class。
  instance:Practical examples of preservation。
  created:If a new record is createdTrue。
  update_fields:()The set of fields to be updated,If not passed then theNone
  """
  if created:
    password = 
    # instance is equivalent to user
    instance.set_password(password)
    ()

users/

from  import AppConfig


class UsersConfig(AppConfig):
  name = 'users'
  verbose_name = 'User Management'

  def ready(self):
  """Use ready to load, otherwise it won't work."""
    import 

This is the whole content of this article.