Avoiding DB Race Conditions in Django
This is evil. If you have a view that is called in fast succession or running longer than your average request you might walk into problems correctly managing database flags for mail sending e.g.
You can avoid this by using Django's select_for_update-queryset-method, which locks a row until a transaction is finished, letting all subsequent requests wait for the first view. So parallel requests don't trigger critical stuff twice or more.
Normally Django autocommits every request, using transaction.autocommit. To get rid of that, you'll need to use a different transaction decorator, to exclude your view from this behaviour. The most convenient in this case should be commit_on_success, which auto-commits on return from the view and auto-rollbacks on an exception. In both cases releasing the lock.
from django.db import transaction
@transaction.atomic
def get_stuff(request, id):
try:
# use select_for_update() to lock the rows and nowwait=False to keep other requests waiting until the lock is released
stuff = Stuff.objects.select_for_update(nowait=False).get(id=id)
except Stuff.DoesNotExist:
return HttpResponseNotFound('stuff does not exist')
if not stuff.has_flag:
stuff.has_flag = True
stuff.save()
critial_method()
For older Django versions you may need to use
@transaction.commit_on_success