When I was working on my first Django project, I encountered a problem where modifications done inside pre_save
signal were not stored in the database. Back then it took me a couple of hours to figure out what is wrong and how to fix it. I’m sharing what I’ve learned to save that time someone else.
Imagine the application to track your gym workouts. At the core of application is exercise session, which knows how many repetitions you want to do and how many you have done so far. Once you do enough repetitions, session is considered complete. It could be implemented like that:
class ExerciseSession(models.Model):
target_repetitions = models.IntegerField(null=False, blank=False)
repetitions = models.IntegerField(null=False, blank=False, default=0)
completed = models.BooleanField(null=False, blank=False, default=False)
Since “session is complete once you do enough repetitions” is a hard rule, it needs to be enforced. pre_save
signal sounds like reasonable place to implement it, as it allows us to have many functions that modify repetitions
. Signal handler might look like that:
@receiver(pre_save, sender=models.ExerciseSession)
def exercise_session_pre_save(sender, instance, raw, using, update_fields, **kwargs):
if instance.repetitions >= instance.target_repetitions:
instance.completed = True
With the above setup we can modify the model instance and rest assured that exercise session is marked as completed when needed:
exercise_session.repetitions = 10
exercise_session.save(update_fields=["repetitions"])
Except that… this doesn’t work. Once you retrieve this exercise session from database, completed
will be set to False.
The reason for this is at intersection of pre_save
signal and update_fields
argument. pre_save
is called early in save()
call and may be used to modify the instance before data is saved in database. However, SQL query constructed later in save()
call will contain only the fields listed in update_fields
argument.
So when calling a save()
, you need to predict that pre_save
signal might modify the instance, and include possibly modified fields in update_fields
list:
exercise_session.save(update_fields=["repetitions", "completed"])
The complete code used in this blog post is available in GitHub repository. See the README
file for instructions how to set up and run the project.
Comments