January 1, 2012
Using Signals in your Django App
What is a signal anyway?
A signal is defined by the Django documentation as:
“Django includes a “signal dispatcher” which helps allow decoupled applications get notified when actions occur elsewhere in the framework. In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place.”
A good example to highlight this is a recent upgrade I made to biblion which is the blog app that I use to drive this site. After my previous article was getting hammered from being on page 1 of HackerNews for an extended period of time, I noticed the page response times were starting to suffer.
I took a more careful look at the detail view serving up the page and
discovered it was calling
post.inc_views() which would be making an update
to a count on the row that was just selected. I wasn't using these counts at
all in my site and even if I did, I'd prefer to use redis or some form of a
But how was I going to have access to do that at the site level?
The answer was to replace the
post.inc_views() call with a signal. So I
upgraded biblion to allow this by adding the following code:
Changes in biblion
import django.dispatch post_viewed = django.dispatch.Signal(providing_args=["post","request"])
In this module, I replaced the
post.inc_views() call with:
post_viewed.send(sender=post, post=post, request=request)
Since I didn't care about incrementing the view count in my site, I could safely just ignore wiring up a receiver for this signal and it would become a no-op call in biblion. All I did was upgrade biblion in my requirements file and redeploy (thanks to Gondor this was super easy to redeploy).
However, for the sake of illustration if I did want to record the view count in a way that was not impeding the performance of heavy traffic, I could do something like the following.
Changes in your site
from django.dispatch import receiver from biblion.signals import post_viewed @receiver(post_viewed) def handle_post_viewed(sender, **kwargs): post = kwargs.get("post") request = kwargs.get("request") if post: # Increment a redis key or kick off a background task or whatever here
Keep in mind that when handling that receiver that you are in the middle of a request/response cycle so you want to be as lightweight as possible. In fact, you should really consider putting any activity here in a background task via celery.