'Django get_or_create then update race condition

I have the following model:

class UserProductBinding:
    product = models.ForeignKey(Product)
    user = models.ForeignKey(User)
    purchased = models.BooleanField(default=False)
    liked = models.BooleanField(default=False)

class Meta(object):
    unique_together = ['product', 'user']

Now I need a function that makes a purchase of a product by a user, which should

  1. Get UserProductBinding with corresponsing user and product, or create one if it doesn't exist
  2. Make sure "purchased" field is False
  3. Set "purchased" to True and deduct money from user's balance

I'm doing it with the following code:

binding, created = UserProductBinding.objects.get_or_create(product=product, user=user)
if binding.purchased:
   raise Error()
with transaction.atomic():
   binding.purchased = True
   user.money -= price
   binding.save()
   user.save()

The problem is that I have a race condition in this code. If this function is called twice in separate threads within a short time period, there is a chance that "if binding.purchased:" check will return False for both threads, hence the function will do its job twice and user's money will be deducted twice as well.

select_for_update doesn't work for me, because I need to create the binding if it doesn't exist. update_or_create doesn't work either because it doesn't make sure that "purchased" field is false before making an update.

Looks like using get_or_create followed by select_for_update would do the trick, but it requires additional database hit.

I could create my own create_or_select_for_update function, which doesn't seem very complicated, but the fact that such function is missing in Django makes me think that I'm maybe going the wrong direction and possibly missing something obvious.

Thanks in advance!



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source