Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best place to increase a counter field in Django REST

Let's say I have two Models in Django:

Book:

class Book(models.Model):
    title = models.CharField(max_length=100, blank=False)
    number_of_readers = models.PositiveIntegerField(default=0)

Reader:

class Reader(models.Model):
    book = models.ForeignKey(Book)
    name_of_reader = models.CharField(max_length=100, blank=False)

Everytime I add a new Reader to the database I want to increase number_of_readers in the Book model by 1. I do not want to dynamically count number of rows Reader rows, related to a particular Book, for performance reasons.

Where would be the best place to increase the number_of_readers field? In the Serializer or in the Model? And what method shall I use? Should I override .save in the Model? Or something else in the Serializer?

Even better if someone could provide a full blown example on how to modify the Book table when doing a post of a new Reader.

Thanks.

like image 518
Henrik Avatar asked Dec 06 '25 07:12

Henrik


1 Answers

I wouldn't do this on the REST API level, I'd do it on the model level, because then the +1 increase will always happen, regardless of where it happened (not only when you hit a particular REST view/serializer)

Django signals

Everytime I add a new Reader to the database I want to increase number_of_readers in the Book model by 1

I'd implement a post_save signal that triggers when a model (Reader) is created

There is a parameter to that signal called created, that is True when the model is created, which makes more convenient than the Model.save() override

Example outline

from django.db.models.signals import post_save

def my_callback(sender, instance, created, **kwargs):
    if created:
        reader = instance
        book = reader.book
        book.number_of_readers += 1 # prone to race condition, more on that below
        book.save(update_fields='number_of_readers') # save the counter field only

post_save.connect(my_callback, sender=your.models.Reader)

https://docs.djangoproject.com/en/1.8/ref/signals/#django.db.models.signals.post_save

Race Conditions

In the above code snippet, if you'd like to avoid a race condition (can happen when many threads updating the same counter), you can also replace the book.number_of_readers += 1 part with an F expression F('number_of_readers') + 1, which makes the read/write on the DB level instead of Python,

book.number_of_readers = F('number_of_readers') + 1
book.save(update_fields='number_of_readers')

more on that here: https://docs.djangoproject.com/en/1.8/ref/models/expressions/#avoiding-race-conditions-using-f

There is a post_delete signal too, to reverse the counter, if you ever think of supporting "unreading" a book :)

Batch or periodic updates

If you wish to have batch imports of readers, or need to periodically update (or "reflow") the reader counts (e.g. once a week), you can in addition of the above, implement a function that recounts the readers and update the Book.number_of_readers

like image 56
bakkal Avatar answered Dec 08 '25 19:12

bakkal



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!