{"id":8945,"library":"django-concurrency","title":"Django Concurrency","description":"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.","status":"active","version":"2.8.1","language":"en","source_language":"en","source_url":"https://github.com/saxix/django-concurrency","tags":["django","optimistic-lock","concurrency","orm"],"install":[{"cmd":"pip install django-concurrency","lang":"bash","label":"Install latest version"}],"dependencies":[{"reason":"Core framework dependency, requires Django>=3.2.","package":"Django","optional":false},{"reason":"Compatibility layer, though mostly a no-op for modern Python versions required by this library.","package":"six","optional":false}],"imports":[{"note":"The primary version field class was moved and renamed in django-concurrency 2.0. Use `IntegerVersionField` from `concurrency.fields`.","wrong":"from concurrency.models import ConcurrencyField","symbol":"IntegerVersionField","correct":"from concurrency.fields import IntegerVersionField"},{"note":"The mixin for generic views was renamed in django-concurrency 2.0 from `ConcurrencyActionMixin` to `ConcurrentUpdateMixin`.","wrong":"from concurrency.mixins import ConcurrencyActionMixin","symbol":"ConcurrentUpdateMixin","correct":"from concurrency.mixins import ConcurrentUpdateMixin"},{"note":"Similar to the view mixin, the admin mixin for concurrency was renamed to `ConcurrentModelAdmin` in version 2.0.","wrong":"from concurrency.admin import ConcurrencyActionMixin","symbol":"ConcurrentModelAdmin","correct":"from concurrency.admin import ConcurrentModelAdmin"},{"symbol":"RecordModifiedError","correct":"from concurrency.exceptions import RecordModifiedError"}],"quickstart":{"code":"import os\nimport django\nfrom django.conf import settings\nfrom django.db import models\nfrom concurrency.fields import IntegerVersionField\nfrom concurrency.exceptions import RecordModifiedError\n\n# Minimal Django settings for a runnable example\nsettings.configure(\n    INSTALLED_APPS=[\n        'django.contrib.auth',\n        'django.contrib.contenttypes',\n        'concurrency', # Required for django-concurrency\n        __name__ # For models in this file\n    ],\n    DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}},\n    USE_TZ=True # Required by Django 3.2+\n)\ndjango.setup()\n\nclass Product(models.Model):\n    name = models.CharField(max_length=100)\n    description = models.TextField(blank=True)\n    version = IntegerVersionField()\n\n    class Meta:\n        app_label = __name__ # Link model to this module as an app\n\n    def __str__(self):\n        return self.name\n\n# Create tables (usually done with manage.py migrate)\nfrom django.core.management import call_command\nfrom io import StringIO\nout = StringIO()\nerr = StringIO()\ncall_command('makemigrations', __name__, interactive=False, stdout=out, stderr=err)\ncall_command('migrate', interactive=False, stdout=out, stderr=err)\n\n# --- Usage Example ---\n# 1. Create a new product\np = Product.objects.create(name=\"Original Product\")\nprint(f\"Initial product: {p.name}, version: {p.version}\")\n\n# 2. Simulate User A loading and modifying the product\np_user_a = Product.objects.get(pk=p.pk)\np_user_a.name = \"Product updated by User A\"\np_user_a.save()\nprint(f\"User A saved: {p_user_a.name}, version: {p_user_a.version}\")\n\n# 3. Simulate User B loading the product (before User A saved)\n#    and attempting to save their changes after User A.\np_user_b = Product.objects.get(pk=p.pk) # Still has version 1 from load\np_user_b.name = \"Product updated by User B\"\ntry:\n    p_user_b.save()\nexcept RecordModifiedError:\n    print(f\"\\nERROR: User B's save failed due to concurrency conflict!\")\n    print(f\"Original version: {p_user_b.version}, current in DB: {Product.objects.get(pk=p.pk).version}\")\n    print(\"User B would need to reload and re-apply changes.\")\n\n# Verify the final state in the database\nfinal_product = Product.objects.get(pk=p.pk)\nprint(f\"\\nFinal product in DB: {final_product.name}, version: {final_product.version}\")\n","lang":"python","description":"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`."},"warnings":[{"fix":"Update import paths and class names as per official documentation or the `imports` section above. For `ConcurrencyField`, use `IntegerVersionField`.","message":"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`.","severity":"breaking","affected_versions":"<2.0"},{"fix":"Wrap `save()` operations on concurrent models in `try...except RecordModifiedError` blocks and implement appropriate user feedback or retry logic.","message":"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.","severity":"gotcha","affected_versions":"All"},{"fix":"Carefully design your transaction boundaries. For multi-step updates, consider using transactions and custom error handling to manage the `RecordModifiedError` across all related objects, or re-evaluate if optimistic locking is the best fit for that specific workflow.","message":"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.","severity":"gotcha","affected_versions":"All"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Update your model definition to use `from concurrency.fields import IntegerVersionField`.","cause":"Attempting to import the old `ConcurrencyField` from `concurrency.models` after upgrading to django-concurrency 2.0 or later.","error":"AttributeError: module 'concurrency.models' has no attribute 'ConcurrencyField'"},{"fix":"Catch `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.","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.","error":"RecordModifiedError: Record modified by another user."},{"fix":"Add `'concurrency'` to your `INSTALLED_APPS` list in your Django `settings.py` file.","cause":"The `concurrency` Django app has not been added to your project's `INSTALLED_APPS` setting.","error":"ImproperlyConfigured: 'concurrency' must be in INSTALLED_APPS"}]}