Django Concurrency
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
-
AttributeError: module 'concurrency.models' has no attribute 'ConcurrencyField'
cause Attempting to import the old `ConcurrencyField` from `concurrency.models` after upgrading to django-concurrency 2.0 or later.fixUpdate your model definition to use `from concurrency.fields import IntegerVersionField`. -
RecordModifiedError: Record modified by another user.
cause An attempt was made to save a model instance whose `version` field value no longer matches the current version stored in the database, indicating a concurrent modification.fixCatch `RecordModifiedError` during the `save()` operation. Reload the object from the database (`obj.refresh_from_db()`), re-apply any user changes (if desired), and attempt to save again, or inform the user about the conflict. -
ImproperlyConfigured: 'concurrency' must be in INSTALLED_APPS
cause The `concurrency` Django app has not been added to your project's `INSTALLED_APPS` setting.fixAdd `'concurrency'` to your `INSTALLED_APPS` list in your Django `settings.py` file.
Warnings
- breaking Major symbol renames occurred in django-concurrency 2.0. `ConcurrencyField` was moved to `concurrency.fields.IntegerVersionField`, and admin/view mixins (`ConcurrencyActionMixin`) were renamed to `ConcurrentUpdateMixin` and `ConcurrentModelAdmin`.
- gotcha Optimistic locking with `django-concurrency` requires explicit handling of `RecordModifiedError` in your application logic (e.g., forms, views, APIs) to inform users or re-present data for reconciliation.
- gotcha django-concurrency works best for single-object updates in user-facing forms. For complex workflows involving multiple related objects or highly concurrent background processes, ensure your transaction management and error handling are robust across all affected models.
Install
-
pip install django-concurrency
Imports
- IntegerVersionField
from concurrency.models import ConcurrencyField
from concurrency.fields import IntegerVersionField
- ConcurrentUpdateMixin
from concurrency.mixins import ConcurrencyActionMixin
from concurrency.mixins import ConcurrentUpdateMixin
- ConcurrentModelAdmin
from concurrency.admin import ConcurrencyActionMixin
from concurrency.admin import ConcurrentModelAdmin
- RecordModifiedError
from concurrency.exceptions import RecordModifiedError
Quickstart
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}")