See this Q&A question if you don't know what race conditions are.
The following code may be subject to race conditions :
article = Article.objects.get(pk=69)
article.views_count += 1
article.save()
If views_count
is equal to 1337
, this will result in such query:
UPDATE app_article SET views_count = 1338 WHERE id=69
If two clients access this article at the same time, what may happen is that the second HTTP request executes Article.objects.get(pk=69)
before the first executes article.save()
. Thus, both requests will have views_count = 1337
, increment it, and save views_count = 1338
to the database, while it should actually be 1339
.
To fix this, use an F()
expression:
article = Article.objects.get(pk=69)
article.views_count = F('views_count') + 1
article.save()
This, on the other hand, will result in such query:
UPDATE app_article SET views_count = views_count + 1 WHERE id=69
Let's assume that we want to remove 2 upvotes from all the articles of the author with id 51
.
Doing this only with Python would execute N
queries (N
being the number of articles in the queryset):
for article in Article.objects.filter(author_id=51):
article.upvotes -= 2
article.save()
# Note that there is a race condition here but this is not the focus
# of this example.
What if instead of pulling all the articles into Python, looping over them, decreasing the upvotes, and saving each updated one back to the database, there was another way?
Using an F()
expression, can do it in one query:
Article.objects.filter(author_id=51).update(upvotes=F('upvotes') - 2)
Which can be translated in the following SQL query:
UPDATE app_article SET upvotes = upvotes - 2 WHERE author_id = 51
Why is this better?
F()
expressions can be used to execute arithmetic operations (+
, -
, *
etc.) among model fields, in order to define an algebraic lookup/connection between them.
Let model be:
class MyModel(models.Model):
int_1 = models.IntegerField()
int_2 = models.IntegerField()
Now lets assume that we want to retrieve all the objects of MyModel
table who's int_1
and int_2
fields satisfy this equation: int_1 + int_2 >= 5
. Utilizing annotate()
and filter()
we get:
result = MyModel.objects.annotate(
diff=F(int_1) + F(int_2)
).filter(diff__gte=5)
result
now contains all of the aforementioned objects.
Although the example utilizes Integer
fields, this method will work on every field on which an arithmetic operation can be applied.