When I first learned about Django signals, it gave me the same warm fuzzy feeling I got when I started using RSS. Define what I'm interested in, sit back, relax, and let the information come to me.

You can do the same in Django, but instead of getting news type notifications, you define what stuff should happen when other stuff happens, and best of all, the so-called stuff is global throughout your project, not just within applications (supporting decoupled apps).

For example:

Before a blog comment is published, check it for spam.

After a user logs in successfully, update his twitter status.

What you can do with signals are plentiful, and up to your imagination, so lets get into it.

What are Django signals?

In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place. They're especially useful when many pieces of code may be interested in the same events.

Django provides a set of built-in signals that let user defined code get notified by Django itself of certain actions, for example:

# Sent before or after a model's save() method is called.
django.db.models.signals.pre_save | post_save
# Sent before or after a model's delete() method is called.
django.db.models.signals.pre_delete | post_delete
# Sent when a ManyToManyField on a model is changed.
django.db.models.signals.m2m_changed
# Sent when Django starts or finishes an HTTP request.
django.core.signals.request_started | request_finished

Built-in signals are really useful, but what I really like is the ability to define custom signals, and due to the way the "signal dispatcher" works, it allows decoupled applications to be notified when actions occur elsewhere in the framework.

In other words, one of your apps can send a signal when something happens, and a different app can listen for the signal and do something when it's received.

Using Django signals

Defining and sending a signal

All signals are django.dispatch.Signal instances. The providing_args is a list of the names of arguments the signal will provide to listeners.

Now, when a user logs in successfully and the signal is sent, it will be intercepted and the handler can then do what ever is required. Note that multiple receivers can be registered for a single signal.

Where should the code live?

You can put signal handling and registration code anywhere you like.

However, you'll need to make sure that the module it's in gets imported early on so that the signal handling gets registered before any signals need to be sent. This makes your apps models.py a good place to put registration of signal handlers.

Integrating with django.contrib.auth

Seeing as I opened the door to this, let me digress a little.

The login and logout methods of django.contrib.auth do not send signals, and is discussed in this ticket. The main reason is that signals are synchronous (discussed in the next section) and would slow down the login/logout process.

If you do need authentication related signals, there are a few ways of accomplishing it, each with their own pros and cons, such as:

Patch django.contrib.auth

Create a custom auth backend

Create a wrapping view

Creating a wrapping view provides the most flexibility, while writing less code and leveraging great code that already exists.

The above will wrap the auth.login method, and send a signal when a user successfully logs in. We are also passing a custom login template, but of course you don't need to.

Signals are synchronous

As I mentioned above, signals are synchronous, or blocking (just like everything else in Django). This means that the request will not be returned until all signal handlers are done.

There have been multiple requests to add asynchronous support to Django, namely via the Python threading module, but I doubt it will happen anytime soon, if at all. In my opinion it comes down to separation of concerns, and there is a whole other world called message queuing, namely AMQP which is designed for these sort of things.

In an upcoming post I will discuss implementing AMQP (Advanced Message Queuing Protocol) with RabbitMQ and Celery.

I cannot seem to get this going. I don;t need a custom login template - could you explain how to this without? When I try to use:
url(r'^login/$', login, name='auth_login'),
in my urls.py I get an error.
Thanks!

Have tried with some other options. With urlpatterns += patterns('', (r'^login/$', 'myproject.mypapp.views.login', {'template_name': 'myapp/templates/admin/login.html'}),) I can get the login to work (adding the name='' gives a syntax error), but the user_login_handle function is never called (I added a print statement to the code there as a double check).
What else can I try here?

Since version 1.0 (not sure what version you are using), Django has supported Naming URL patterns. It's not required for signaling, but it's really useful when using the same view function in multiple URL patterns, and specifying urls in templates.

Regarding your problem, could you post some code snippets? I'm not sure how productive it would be to make wild guesses. But, just for fun, you mentioned user_login_handle, my example was user_login_handler, could it be a typo?