Django Sequences
django-sequences is a Python library for Django that provides a reliable way to generate gapless sequences of integer values. Unlike Django's default auto-incrementing primary keys, which can have gaps due to rolled-back transactions, this library ensures sequential integrity. It is currently at version 3.0, actively maintained, and compatible with modern Django versions (3.2 and up).
Common errors
-
IntegrityError: duplicate key value violates unique constraint
cause `get_next_value` was called, but the model saving the value was not wrapped in an atomic transaction or the transaction failed to commit, leading to a duplicate value being attempted later.fixEnsure all calls to `get_next_value()` and the subsequent `model.save()` are within a `with transaction.atomic():` block. -
Application is slow when multiple users/processes try to generate sequence numbers simultaneously.
cause Access to `get_next_value()` for a specific sequence is serialized at the database level to ensure gapless numbers. Long-running transactions or very high contention can lead to performance bottlenecks.fixOptimize the code within the `transaction.atomic()` block to be as fast as possible. If extreme concurrency is needed and occasional non-sequential IDs are acceptable, consider alternative ID generation strategies or sharding sequences across different names. -
Gaps appear in generated sequence numbers in my application's records.
cause This typically happens if the `get_next_value()` call and the record creation/update are not within the same atomic transaction, or if a transaction committed a value, but later records were inserted using a different mechanism or without using the sequence correctly.fixVerify that `django.db.transaction.atomic()` strictly wraps both the `get_next_value()` call and the database `create()` or `save()` for the record that uses the generated number.
Warnings
- gotcha For django-sequences to guarantee gapless values, `get_next_value()` and the corresponding model save operation MUST be executed within the same database transaction. If the transaction is committed, the value is consumed; otherwise, if the transaction rolls back, the value is not consumed by the user's model but the sequence counter is still incremented internally.
- gotcha If a transaction involving `get_next_value()` is rolled back (e.g., due to an error in your code after retrieving the value but before committing), the internal sequence counter for `django-sequences` itself is NOT rolled back for performance reasons. This means there will be a 'gap' in the sequence numbers generated by `django-sequences` if you inspect its internal table, even though your application's committed records remain gapless.
- gotcha Database transactions that call `get_next_value()` for a given sequence are serialized. This means concurrent calls for the *same* sequence will block, potentially impacting performance. Keep transactions involving `get_next_value()` as short as possible.
- breaking The `read uncommitted` database isolation level is NOT supported and will lead to gaps and incorrect behavior. The `repeatable read` level is supported but requires application-level handling of serialization failures and retries.
Install
-
pip install django-sequences
Imports
- get_next_value
from django_sequences import get_next_value
from sequences import get_next_value
- Sequence
from sequences import Sequence
- SequencesConfig
INSTALLED_APPS = [..., 'sequences.apps.SequencesConfig', ...]
Quickstart
import os
from django.db import transaction
from sequences import get_next_value
# Assume 'Invoice' is a Django model with an 'number' field
# from invoices.models import Invoice
# For demonstration, we'll mock a model and save operation
class MockInvoice:
_instances = []
def __init__(self, number):
self.number = number
print(f"Created MockInvoice with number: {self.number}")
MockInvoice._instances.append(self)
def create_invoice_with_sequence(sequence_name='invoice_numbers'):
try:
with transaction.atomic():
next_invoice_number = get_next_value(sequence_name)
# Replace with your actual model creation:
# Invoice.objects.create(number=next_invoice_number)
MockInvoice(number=next_invoice_number)
print(f"Successfully committed invoice with number: {next_invoice_number}")
except Exception as e:
print(f"Transaction failed: {e}")
# Example usage:
create_invoice_with_sequence('orders')
create_invoice_with_sequence('orders')
create_invoice_with_sequence('shipments', initial_value=1000)
create_invoice_with_sequence('shipments')