Race conditions in django
Django 1.4+ supports select_for_update, in earlier versions you may execute raw SQL queries e.g. select ... for update
which depending on underlying DB will lock the row from any updates, you can do whatever you want with that row until the end of transaction. e.g.
from django.db import transaction@transaction.commit_manually()def add_points(request): user = User.objects.select_for_update().get(id=request.user.id) # you can go back at this point if something is not right if user.points > 1000: # too many points return user.points += calculate_points(user) user.save() transaction.commit()
As of Django 1.1 you can use the ORM's F() expressions to solve this specific problem.
from django.db.models import Fuser = request.useruser.points = F('points') + calculate_points(user)user.save()
For more details see the documentation:
https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F
Database locking is the way to go here. There are plans to add "select for update" support to Django (here), but for now the simplest would be to use raw SQL to UPDATE the user object before you start to calculate the score.
Pessimistic locking is now supported by Django 1.4's ORM when the underlying DB (such as Postgres) supports it. See the Django 1.4a1 release notes.