Django signals

Introduction

If you are one of the lucky ones, you're using the Django framework.

Sometimes in life it's nice to sit back, relax, and let the world come to you. Why rush around to find out what's happening? Wouldn't it be nice to be notified when the things you're interested in are happening?

When programming, similar thoughts periodically rise up in a programmer's mind. Let's say you're tasked with making sure that every instance of a particular action calls a certain function; somewhere in the back of your mind you'll muse over the idea of being able to somehow write the code-equivalent of:

"Hey! You know every time x happens? Can you make sure you run this code? I can't be bothered to work it out, so just make sure you do it. Thanks."

If you are one of the lucky ones, you're programming web sites in Python using the Django framework. And in some circumstances, you'll be able to let Django work out when code needs to run without you needing to call it explicitly.

In Django parlance, this is known as signals, and it's just one of the many reasons why Django makes web development so much more, well, fun.

The aim of this article is to introduce Django signals and cover their uses in detail. Over the next few paragraphs we will:

  • introduce the concept of signalling;
  • state what Django signals are and why we need them;
  • provide examples of listening for Django's built-in signals; and
  • show you how to send your own signals.

This article assumes you are comfortable with Python programming and the Django web framework.

What are signals?

Signals are not a concept introduced by either Django or Python: many high-level languages have capabilities comparable to signals. Java, for example, has listeners and events. Python doesn't support signalling natively, but there is a package called PyDispatcher that offers the needed functionality.

In December 2005, PyDispatcher was added to what would eventually become Django version 0.95, then known as the magic-removal branch. It was included as the django.dispatch package, and added a simple but very useful signal-registration and routing infrastructure. What does that mean? At its most simple, it is the act of sending and receiving.

  • Signal-registration: listening for a particular signal.
  • Routing infrastructure: sending a particular signal.

At any point during execution the code can make a request to ‘listen' for a particular signal. The request includes details on what to do when the signal is received. The request is asynchronous, so after it's made the code carries on without waiting.

At certain points in Django's execution - for example, when an object is saved - a signal is broadcast. If there was any request made for this signal then any tasks the request wanted to be performed on such a signal are completed. Signal requests can be quite specific (to the point of specifying the object type, action performed, and where the signal was sent from), or quite general (up to receiving every signal).

In short, Django signals are an asynchronous request to run specific code upon the sending of a particular signal.

Update: further research shows that signals are synchronous, not asynchronous. More details at imalm.com.

Here's a quick example in plain English: we need a request and a signal. The request might be to send an email notifying the site manager that something has been deleted from the web site, and the signal might be sent each time an object is deleted.

Why do we need signals?

In computing terms, this is the difference between polling and signalling.

Let's start with a real-world example. Your DVD player has broken down, so you ring up the customer support help line. For the next thirty minutes you focus on not falling asleep while you're kept on hold.

And a far better option? If you ring the help line and are put on hold, you could request that an operator ring you back when they are free. You stay in the queue but you're free to go about your business until they phone.

In computing terms, this is the difference between polling and signalling. While you wait on the phone you are essentially polling regularly to see if there is a change of state. The alternative is to signal you want to be notified of a change of state - analogous to asking for a call-back.

To a computing example: an application wants to know when a DVD has been inserted into a DVD drive. It has two choices:

  • stop other work and check once a second to see if a DVD has been inserted; 
or
  • send a request to be notified once a DVD has been inserted and carry on doing other things.

It is obvious that the latter is a much better way of handling events, and it is how modern operating systems operate.

How signals benefit Django

You've finished your first public Django project. We live in a Web 2.0 world, so your project is a blogging application complete with gradients, pastel shades, and rounded corners. You release it to the world and the world loves it. Soon feature requests start flowing in - number one being a plug-in architecture.

How do you provide a plug-in architecture? You could let plug-ins check the database periodically for changes to the data they are interested in, but that sounds quite horrible. And what about plug-ins that want to run when certain views or methods are called?

The answer, as you know by now, is signals.

Provide signals for all the actions in your application. Allow plug-ins to be notified when blog posts are created, changed, deleted, and viewed; allow plug-ins to find out when comments are added, approved, and rejected. Provide a signal for every useful action in your application.

Using Django's built-in signals

Django provides eight signals. Seven are concerned with app models, the other, post_syncdb, with initialising databases.

  • class_prepared: sent once a model's dynamic methods have been created. This is sent before any of the other seven model signals (it's called in Model.__new__()).
  • pre_init: sent as a model is about to be initialised (i.e. the first thing Model.__init__() does).
  • post_init: sent once a model is initialised (i.e. the last thing Model.__init__() does).
  • pre_save: sent as a model object is about to be saved.
  • post_save: sent once a model object has been saved.
  • pre_delete: sent as a model object is about to be deleted.
  • post_delete: sent once a model object has been deleted.
  • post_syncdb: sent once the database tables have been created for all apps in INSTALLED_APPS whose tables had yet to be created.

Every model object used in a Django application will send these signals. And the beauty of it is that any one request can listen (connect in Django-speak) for a signal on one, more than one, or all of the object types in an project.

Let's start with a simple project, a blog, as an example. We want our blog to send an email each time an entry is added to the blog.

A blog entry could be created in many places in the project logic: in the admin, from the Python shell, or from a view somewhere in the app code. How do we make sure we always catch a blog entry being created? We listen for the ‘blog entry created' signal.

from django.contrib.auth.models import User
from django.db import models
from django.db.models import signals
from django.dispatch import dispatcher
from myproject.blog.signals import send_entry_created_email


class Entry(models.Model):
      """An individual entry (post) in a blog."""
      headline = models.CharField(maxlength=255)
      slug = models.SlugField(prepopulate_from=
           ['headline'], unique_for_year='published')
      body = models.TextField()
      published = models.DateTimeField()
      author = models.ForeignKey(User)

      class Meta:
           get_latest_by = 'published'
           ordering = ['-published']
           verbose_name_plural = 'entries'

      class Admin:
           list_display = ['headline', 'author',
               'published']

      def __str__(self):
           return self.headline

      def get_absolute_url(self):
           return '/blog/%04d/%s/' %
               (self.published.year, self.slug)

dispatcher.connect(send_entry_created_email,
      signal=signals.post_save, sender=Entry)

 

Much of this code is standard Django fare; it's the last two lines that are important. The dispatcher.connect method is the code that tells Django to listen for a given signal. It takes four arguments:

* receiver: a callable Python object (in our example, the function send_entry_created_email). This object is called when the signal is sent (i.e. the function send_entry_created_email will be executed).
* signal: the signal the receiver should respond to.
* sender: the sender the receiver should respond to.
* weak: indicates whether the signal is ‘weak-referencable' or not. You probably don't need to worry about this and so, as in the example above, you can ignore it.

Note this doesn't cover everything you can do with the parameters (you don't have to specify exact senders and signals, for example) but it's enough for our purposes. For detailed documentation, see the dispatch.dispatcher module documentation.

Now all we have to do is write the send_entry_created_email function. There's no standard place for this function to go in the code, so we'll put it in a file named signals.py in the blog app package (e.g. myproject/blog/signals.py).

from django.core.mail import send_mass_mail
from django.template import Context, loader
from myproject.blog.models import Post


def send_entry_created_email(sender, instance, signal,
     *args, **kwargs):
     """
     Sends an email out to a number of people informing
     them of a new blog entry.
     """
     def get_recipient_list():
         # Get a list of the email addresses the message
         # should be sent to.
         recipient_list = [] # Left for you to implement.
         return recipient_list

     try:
         Post.objects.get(id=instance._get_pk_val())
     except (Post.DoesNotExist, AssertionError):
         # The attempt to get a post with the same id
         # as our instance failed - a good indication
         # that this is a new post.
         t = loader.get_template('blog/new_entry_email.txt')
         c = Context({
         'entry': instance,
         })
         message = t.render(c)

         subject = 'A new blog entry is available'
         from_email = instance.author.email
         recipient_list = get_recipient_list()

         email = (subject, message, from_email, recipient_list)
         send_mass_mail(email)

 

Although this function could do anything (signals don't limit what a receiver can do), given it's name it seems unfair to do anything other then send an email notifying the recipients of a new blog entry.

It takes three named arguments, sender,instance, and signal. The most interesting is instance: as its name implies, this is an instance of the object that was in use at the time of the signal - in our case, the new blog entry. This allows us to use the entry's properties - the headline, published date, entry content, and so on.

The email message is created from a Django template with the blog entry passed in as a variable, allowing the message to contain anything a normal web page output by Django can.

Further examples of signals

The example above doesn't need to use signals; the code in send_entry_created_email could either be part of the Post.save() method or could be called from there. So here's were we delegate the provision of more complex examples of using signals to others.

Creating your own signals

If you've got this far, we admire you perseverance. We've talked an awful lot about Django signals. And there's one last topic before we finish: sending custom signals. It won't take long, because, as with most things in Django, it's very easy. In fact, it's a mere two extra lines of code.

The first line creates your new signal. It doesn't matter where in the code you add this line as long it's imported before you need it. We usually put it in the signals.py file.

my_new_signal = object()

The second line of code sends the signal. Place it anywhere (and any number of times) in the code you want the signal to be sent. All arguments are optional, but it makes sense to at least include signal:

dispatcher.send(signal=my_new_signal, sender=sender,
instance=self)

Summary

In this article we have introduced the concept of signalling, discussed Django signals and the need for them, provided examples of listening for Django's built-in signals, and shown how to send custom signals.

Signalling is a much better alternative to polling. When using polling, you are restricted to halting execution and waiting for a change of state. With signalling you can make a request to be notified of a change of state, and continue execution until that notification.

Since December 2005, when PyDispatcher was added, Django has supported signalling. The examples of using signals were simple ones, but we hope that they show what Django signals have to offer, and induce you to writing your own.

Technorati tags
Django; Python; PyDispatcher; and signals.

References

Mercurytide is a forward thinking, dynamic, and innovative Internet applications development company. We are well-established with a proven track-record within the industry of attracting blue chip clients from around the world. We produce regular white papers on a variety of technology-orientated topics. For more details see the Mercurytide web site.


www.mercurytide.co.uk would like to store information (cookies) on your computer. By continuing to use this site, you consent to this.
More info