Django Signals

I’ve been working on a website for some time now, at first I’ve made it with Django + templates and now I use Django Rest Framework and Angular.
The website is supposed to be a platform for uploading papers and offer the ability to peer review them.

What I want to talk about in this short article is how I simplified the development using Django signals for certain use cases.

The official docs describe signals 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. They’re especially useful when many pieces of code may be interested in the same events.

In my website I needed to use signals in the following situations:

  • Create a profile for every user.
  • Update model when something changed.

Create a profile for every user.

I have a profile model already defined, let’s ignore it.

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile(sender, instance, created, **kwargs):
"""
Ensure that every new user gets a profile.
"""
if created:
Profile.objects.create(user=instance)

That was the signal that creates a new profile for every user that is created, pretty easy, right? I will not even bother explaining the code.
At first I was overriding the create function from a custom serialiser, but that only worked when I was creating users via API calls, It didn’t work when the admin created new users, overriding save method of the model was cumbersome and I avoided it.

Update model when something changed.

Here’s the code for the other two signals:

@receiver(pre_save, sender=Paper)
def editor_field_changed(sender, instance, **kwargs):
"""
If there's an editor and paper status is processing. Change the status to under_review.
If there's no editor and the paper status is under_review, change the status to processing.
"""
if instance.editor and instance.status == Paper.STATUS_CHOICES[0][0]:
instance.status = Paper.STATUS_CHOICES[1][0] # under_review
elif instance.editor == None and instance.status == Paper.STATUS_CHOICES[1][0]:
instance.status = Paper.STATUS_CHOICES[0][0] # processing

Basically, what this does is to set the paper status to under_review when an editor is assigned to it and the paper’s current status is processing.
Notice that the signal is transmitted at the pre_save event by Paper model.

@receiver(post_save, sender=Review)
def reviews_changed(sender, instance, **kwargs):
"""
If there's an editor review, set the paper status according to the result of the editor review.
"""
if instance.editor_review:
# If the editor review is positive, change the paper status to published.
if instance.appropriate == Review.APPROPRIATE_CHOICES[0][0] \
and instance.recommendation == Review.RECOMMENDATION_CHOICES[1][0]:
instance.paper.status = Paper.STATUS_CHOICES[3][0] # accepted
else:
instance.paper.status = Paper.STATUS_CHOICES[2][0] # preliminary_reject
instance.paper.save()

This signal is transmitted at post_save by the Review model, it sets the paper status to accepted or preliminary_reject. The review instance has a paper object so the models are tied together.

Thank you, I hope this helped you!
If you have any questions feel free to comment below or ask me on twitter.

Resources

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.