Django Concurrency

2.8.1 · active · verified Thu Apr 16

django-concurrency provides an optimistic locking mechanism for Django models, preventing concurrent editing of database records. It allows multiple users to view and attempt to edit the same record simultaneously, but only the first successful save is committed, while subsequent saves raise an error, notifying the user of the conflict. The current version is 2.8.1, actively maintained with releases tied to Django's lifecycle and Python version support.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to add an `IntegerVersionField` to a Django model to enable optimistic locking. It shows a basic scenario where two users attempt to update the same record, and the second save correctly raises a `RecordModifiedError`, preventing data loss. Remember to add 'concurrency' to your `INSTALLED_APPS`.

import os
import django
from django.conf import settings
from django.db import models
from concurrency.fields import IntegerVersionField
from concurrency.exceptions import RecordModifiedError

# Minimal Django settings for a runnable example
settings.configure(
    INSTALLED_APPS=[
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'concurrency', # Required for django-concurrency
        __name__ # For models in this file
    ],
    DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}},
    USE_TZ=True # Required by Django 3.2+
)
django.setup()

class Product(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    version = IntegerVersionField()

    class Meta:
        app_label = __name__ # Link model to this module as an app

    def __str__(self):
        return self.name

# Create tables (usually done with manage.py migrate)
from django.core.management import call_command
from io import StringIO
out = StringIO()
err = StringIO()
call_command('makemigrations', __name__, interactive=False, stdout=out, stderr=err)
call_command('migrate', interactive=False, stdout=out, stderr=err)

# --- Usage Example ---
# 1. Create a new product
p = Product.objects.create(name="Original Product")
print(f"Initial product: {p.name}, version: {p.version}")

# 2. Simulate User A loading and modifying the product
p_user_a = Product.objects.get(pk=p.pk)
p_user_a.name = "Product updated by User A"
p_user_a.save()
print(f"User A saved: {p_user_a.name}, version: {p_user_a.version}")

# 3. Simulate User B loading the product (before User A saved)
#    and attempting to save their changes after User A.
p_user_b = Product.objects.get(pk=p.pk) # Still has version 1 from load
p_user_b.name = "Product updated by User B"
try:
    p_user_b.save()
except RecordModifiedError:
    print(f"\nERROR: User B's save failed due to concurrency conflict!")
    print(f"Original version: {p_user_b.version}, current in DB: {Product.objects.get(pk=p.pk).version}")
    print("User B would need to reload and re-apply changes.")

# Verify the final state in the database
final_product = Product.objects.get(pk=p.pk)
print(f"\nFinal product in DB: {final_product.name}, version: {final_product.version}")

view raw JSON →