Reputation: 1995
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.
Upvotes: 2
Views: 2385
Reputation: 55448
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)
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 :)
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
Upvotes: 8
Reputation: 6013
It depends on the design of your app and particularly on where you will reuse this logic.
For example, if you want the same logic for adding Reader
s everywhere in your app, do it in a signal, as bakkal suggests or in save
. If it depends on the API endpoint, you might want to do it in a view.
It will also depend if you are doing bulk inserts of readers: if you do it in save
or a pre_
/post_save
it will not work for bulk updates, so it would be better to do it in QuerySet
's create
and bulk_create
methods etc.
From performance point of view, you might want to use F expressions, no matter where you do it:
book.number_of_readers = F('number_of_readers') + added_readers_count
Upvotes: 1