Showing posts with label models. Show all posts
Showing posts with label models. Show all posts

Sunday, 17 May 2009

Signals: Letting People Know When You've Got Something To Say

This week marks a break away from the topic of templates and instead will be focussing on signals.


Signals are Django's way of allowing you to listen out for certain events that either occur by default in the Django code or that have been specified by an application developer. The signals provided by default are explained in a fair amount of detail in the Django Built-in Signal Reference (djangoproject.com) so I won't go into too much depth on what they do, but they are as follows (skip to the first example):



pre_init
Occurs at the start of the process of initialising a model;

post_init
Occurs at the end of the process of initialising a model;

pre_save
Occurs at the start of the process of saving a model;

post_save
Occurs at the end of the process of saving a model;

pre_delete
Occurs at the start of the process of deleting a model;

post_delete
Occurs at the end of the process of deleting a model;

class_prepared
Internally used, occurring when a model class has been registered with Django;

post_syncdb
Occurs after syncdb installs an application;

request_started
Occurs just before Django starts to process a request;

request_finished
Occurs just after Django processes a request;

got_request_exception
Occurs when Django encounters an exception whilst processing a request;

template_rendered
Occurs only whilst running tests when Django renders a template;

comment_will_be_posted
Occurs if you have Django's comment app installed and a comment is just about to be saved;

comment_was_posted
Occurs if you have Django's comment app installed and a comment has just been saved;

comment_was_flagged
Occurs if you have Django's comment app installed and a comment has had a flag set (i.e. a moderator just approved it).


For my first example this week I'm going to be looking at the post_save signal and how it can be used to ensure that a User object always has a profile instance associated with it.


# File: test_signal/models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save

class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
allow_our_emails = models.BooleanField(default=False)
allow_other_emails = models.BooleanField(default=False)

def ensure_profile_exists(sender, **kwargs):
if kwargs.get('created', False):
UserProfile.objects.create(user=kwargs.get('instance'))

post_save.connect(ensure_profile_exists, sender=User)

# File: (somewhere in) settings.py
AUTH_PROFILE_MODULE = "test_signal"

As you can see this defines a fairly simple profile model (supposedly for storing a user's e-mail preferences), the key part of this comes from the function ensure_profile_exists. With all signals it can only be guaranteed that the sender keyword argument will be present therefore we also need to catch everything else using **kwargs. Inside the function we use the fact that post_save is supposed to provide the arguments created and instance to check if the save was for a new object and if so to assign the created instance to the user property of a freshly created UserProfile object.


Finally we connect our function to the post_save signal with instructions to only receive signals when the sending class is User


If the test_signal app is now added to the list of installed apps in settings.py then all new users created will now have an associated profile created also.


The second example for this week demonstrates how you can create your very own signals for code to listen to.


# File: test_signal/signals.py
from django.dispatch import Signal

form_post_received = Signal(providing_args=["path","arguments",]

Here we tell Django that we want form_post_received to be a signal and that it should provide the arguments path and arguments. Now if we add the following:


# File: test_signal/views.py
from signals import form_post_received
from django.conf import settings

def view1(request):
if request.method == "POST":
form_post_received.send(sender=request, path=request.path, arguments=request.POST)
# Continue to do stuff with the form.

def view2(request):
if request.method == "POST":
form_post_received.send(sender=request, path=request.path, arguments=request.POST)
# Continue to do stuff with the form.

def log_form_received(sender, **kwargs):
print "Form Received at %(path)s with arguments %(arguments)s" % kwargs

if settings.DEBUG:
form_post_received.connect(log_form_received)

When pointing views at view1 and view2 whenever the server is in debug mode extra information should be output to the terminal informing us of whenever a form has been posted to either view.


This is done by calling send on our signal along with an argument for sender, in this case we use the request that triggered the view, along with values for each of the other arguments we specified. Django then passes them on to each function that has connected to our signal, which in this case is our logging function (but only when the server is in debug mode, otherwise connect will never be called).


That concludes this weeks tutorial on signals. Next week we shall be looking at the useful functions Django has built in for performing pagination.

UPDATE:There is now a second part to this tutorial talking about more advanced uses of signals. Check it out at Signals - Part 2: Return to sender