Signals in Django

One of the benefits of the app structure of Django is that disparate pieces of your project don’t necessarily know about each other. But sometimes something happening in one app needs to trigger an action in another. Signals to the rescue.

Some signals, such as post_save, come built in. They are called every time an instance of their sender is saved. For example:

@receiver(post_save, sender='app.Model', dispatch_uid='example_method')
def example_method(sender, instance=None, created=None, update_fields=None, **kwargs):
    ...

That’s a receiver that is called every time a signal is passed. To break it down a little:

  • This is in a file called signals.py, within the app that holds the pieces relevant to what the function will change. Not usually the same app as the sender model
  • It uses the [receiver](https://docs.djangoproject.com/en/1.10/topics/signals/#django.dispatch.receiver) decorator, to declare that the function that follows is the receiver of a signal.
  • The post_save and sender arguments to the decorator that it will be called every time an instance of Model is saved.

The function takes:

  • An instance (the instance of the Model that was saved)
  • Whether the instance was just created or not — you may want different behavior depending on whether it was created or updated
  • update_fields, which is an optional list of the fields that were changed on the instance, in case you want the behavior to differ based on what changed. You’d have to call it like so:
self.user.save(update_fields=['first_name', 'last_name'])

If you don’t want to pass update_fields, you can just call .save() on your object like you would any other time, and the signal will be sent.

Sometimes the built in signals just don’t meet your needs. Here’s how to define a custom one (within the signals.py file of the app where the receiver will be defined):

from django.dispatch import Signal
user_attributes_changed = Signal(providing_args=["user", "attributes"])

You then need to call it manually whenever you want the signal sent:

from app.signals import user_attributes_changed

def some_method:
    ...
    user_attributes_changed.send(sender=self.user.__class__, user=self.user, attributes=['first_name', 'last_name'])

In this case, the user and attributes map pretty well to the instance and update_field attributes used in the built-in post_save signal, but these can be called whatever you want, and you can send anything you want along with the signal though. However, the .send() method does only take two arguments, so these need to be named as part of **kwargs.

Your receiver would then look like this:

from django.dispatch import receiver
@receiver(user_attributes_changed)
def method_to_do_stuff(sender, user, attributes, **kwargs):
    ....

If these are your project’s first signals (or if you’re adding them to an app within your project for the first time), you’ll need to make sure your signals are loaded before any code runs. Within the apps.py file of your app, make sure to import the signals inside your ready function:

class AppNameConfig(AppConfig):
    name = 'name'
    label = 'name'
    def ready(self):
        import name.signals

Questions? Thoughts on ways to do this better or other implementations? I’ve noticed a lack of people writing about using Django, but I’m still new to the framework, and am always open to suggestions!