I have 2 models: Product and Order.
Product has an integer field for stock, whereas Order has a status and a foreign key to Product:
class Product(models.Model):
    name = models.CharField(max_length=30)
    stock = models.PositiveSmallIntegerField(default=1)
class Order(models.Model):
    product = models.ForeignKey('Product')
    DRAFT = 'DR'; INPROGRESS = 'PR'; ABORTED = 'AB'
    STATUS = ((INPROGRESS, 'In progress'),(ABORTED, 'Aborted'),)
    status = models.CharField(max_length = 2, choices = STATUS, default = DRAFT)
My goal is to have the product's stock decrease by one for each new order, and increase by one for each order cancellation. In that purpose, I've overloaded the save method of the Order model as such (inspired by Django: When saving, how can you check if a field has changed?):
from django.db.models import F
class Order(models.Model):
    product = models.ForeignKey('Product')
    status = models.CharField(max_length = 2, choices = STATUS, default = DRAFT)
    EXISTING_STATUS = set([INPROGRESS])
    __original_status = None
    def __init__(self, *args, **kwargs):
        super(Order, self).__init__(*args, **kwargs)
        self.__original_status = self.status
    def save(self, *args, **kwargs):
        old_status = self.__original_status
        new_status = self.status
        has_changed_status = old_status != new_status
        if has_changed_status:
            product = self.product
            if not old_status in Order.EXISTING_STATUS and new_status in Order.EXISTING_STATUS:
                product.stock = F('stock') - 1
                product.save(update_fields=['stock'])
            elif old_status in Order.EXISTING_STATUS and not new_status in Order.EXISTING_STATUS:
                product.stock = F('stock') + 1
                product.save(update_fields=['stock'])
        super(Order, self).save(*args, **kwargs)
        self.__original_status = self.status
Using the RestFramework, I've created 2 views, one for creating new orders, one for cancelling existing orders. Both use a straightforward serializer:
class OrderSimpleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Order
        fields = (
            'id',
            'product',
            'status',
        )
        read_only_fields = (
            'status',
        )
class OrderList(generics.ListCreateAPIView):
    model = Order
    serializer_class = OrderSimpleSerializer
    def pre_save(self, obj):
        super(OrderList,self).pre_save(obj)
        product = obj.product
        if not product.stock > 0:
            raise ConflictWithAnotherRequest("Product is not available anymore.")
        obj.status = Order.INPROGRESS
class OrderAbort(generics.RetrieveUpdateAPIView):
    model = Order
    serializer_class = OrderSimpleSerializer
    def pre_save(self, obj):
        obj.status = Order.ABORTED
Here is how to access these two views:
from myapp.views import *
urlpatterns = patterns('',
    url(r'^order/$', OrderList.as_view(), name='order-list'),
    url(r'^order/(?P<pk>[0-9]+)/abort/$', OrderAbort.as_view(), name='order-abort'),
)
I am using Django 1.6b4, Python 3.3, Rest Framework 2.7.3 and PostgreSQL 9.2.
Here is the script I use to demonstrate that:
import sys
import urllib.request
import urllib.parse
import json
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor)
def create_order():
    url = 'http://127.0.0.1:8000/order/'
    values = {'product':1}
    data  = urllib.parse.urlencode(values).encode('utf-8')
    request = urllib.request.Request(url, data)
    response = opener.open(request)
    return response
def cancel_order(order_id):
    abort_url = 'http://127.0.0.1:8000/order/{}/abort/'.format(order_id)
    values = {'product':1,'_method':'PUT'}
    data  = urllib.parse.urlencode(values).encode('utf-8')
    request = urllib.request.Request(abort_url, data)
    try:
        response = opener.open(request)
    except Exception as e:
        if (e.code != 403):
            print(e)
    else:
        print(response.getcode())
def main():
    response = create_order()
    print(response.getcode())
    data = response.read().decode('utf-8')
    order_id = json.loads(data)['id']
    time.sleep(1)
    for i in range(2):
        p = Process(target=cancel_order, args=[order_id])
        p.start()
if __name__ == '__main__':
    main()
This script gives the following output, for a product with a stock of 1:
201 # means it creates an order for Product, thus decreasing stock from 1 to 0
200 # means it cancels the order for Product, thus increasing stock from 0 to 1
200 # means it cancels the order for Product, thus increasing stock from 1 to 2 (shouldn't happen)
I've added a sample project to reproduce the bug: https://github.com/ThinkerR/django-concurrency-demo
Take a look at django-concurrency. It handles concurrent editing using optimistic concurrency control pattern.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With