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