Drop-in versioning for Django models — releases, data status workflow, and CI integration
Project description
django-versioned-models
django-versioned-models
Drop-in versioning for Django models. Every model that inherits from VersionedModel gets full release management, data status workflow, and CI integration — automatically.
🚀 Features
- Release management - every row in every table is tagged to a release. Branch, lock, patch, and deprecate with simple commands.
- Data status workflow - DRAFT → FUTURE → APPROVED. CI only sees approved rows. Live edits by architects never break tests.
- Lock enforcement - locked releases are immutable at the model level. No edits, no deletes, from anywhere — Admin, API, or shell.
- Auto-discovery - inherit VersionedModel and your model is versioned. No registration needed, no matter how many models.
- Topological FK sort - when copying a release, models are duplicated in the correct dependency order automatically.
- Soft deprecation - old releases are hidden by default but data is always preserved and fully reversible.
- Standalone releases - create a release with no source for bootstrapping a new project or parallel versioning.
- CI-ready commands - create_release, approve_release, lock_release and more, ready to plug into any pipeline.
Installation
pip install django-versioned-models
Quick Start
1. Add to INSTALLED_APPS
INSTALLED_APPS = [
...
'django_versioned_models',
]
2. Run migrations
python manage.py migrate
3. Create your models
from django_versioned_models.mixins import VersionedModel
class MyModel(VersionedModel):
name = models.CharField(max_length=255)
class Meta:
unique_together = [('release', 'name')] # unique per release, not globally
4. Run migrations for your models
python manage.py makemigrations
python manage.py migrate
5. Create the first release
python manage.py create_release --release-version v1.0.0
6. Add data, then lock
# Add data via Admin or API...
python manage.py lock_release --release-version v1.0.0
Ongoing Flow
# Create a new release branched from the previous one
python manage.py create_release --release-version v1.1.0 --based-on v1.0.0
# Architects edit data (status=DRAFT by default)
# CI approves stable rows
python manage.py approve_release --release-version v1.1.0
# Run tests against approved data only
pytest --release-version v1.1.0
# Lock and ship
python manage.py lock_release --release-version v1.1.0
# Bug found after deployment? Create a patch — never modify a locked release
python manage.py create_release --release-version v1.1.1 --based-on v1.1.0
How It Works
Every model that inherits from VersionedModel gets two fields added automatically:
release— which version this row belongs tostatus— data readiness (draft/future/approved)
Data Status Workflow
DRAFT <-> FUTURE -> APPROVED (one-way, CI only)
| Status | Who | Meaning |
|---|---|---|
DRAFT |
Architects | Being worked on |
FUTURE |
Architects | Planned for a future release |
APPROVED |
CI only | Stable — what tests run against |
CI always queries approved rows. DRAFT and FUTURE are invisible to CI — live edits never break tests.
MyModel.objects.approved(release) # CI — stable rows only
MyModel.objects.for_release(release) # everyone — all statuses
Lock Enforcement
Locked releases are immutable. Any attempt to save or delete a row in a locked release raises a ValidationError — from the Admin, the API, the shell, anywhere.
# This will raise ValidationError if release is locked
my_instance.save()
my_instance.delete()
Auto-Discovery
All models that inherit from VersionedModel are discovered automatically on every create_release. No manual registration needed. FK dependencies are resolved via topological sort — no ordering required.
Management Commands
| Command | Description |
|---|---|
create_release --release-version v1.0.0 |
Create a standalone release (unlocked) |
create_release --release-version v1.1.0 --based-on v1.0.0 |
Branch from a locked release |
approve_release --release-version v1.1.0 |
Approve all DRAFT rows (CI only — FUTURE untouched) |
lock_release --release-version v1.1.0 |
Lock a release (immutable) |
unlock_release --release-version v1.1.0 |
Unlock (only before deployment) |
deprecate_release --release-version v1.0.0 |
Soft-delete (data preserved, hidden by default) |
deprecate_release --release-version v1.0.0 --undo |
Restore a deprecated release |
Querying
from django_versioned_models.models import Release
release = Release.objects.get(version='v1.1.0')
# All rows for a release
MyModel.objects.for_release(release)
# Approved rows only (CI)
MyModel.objects.approved(release)
# Filter by status
MyModel.objects.filter(release=release, status='future')
Imports Reference
from django_versioned_models.mixins import VersionedModel, DataStatus
from django_versioned_models.models import Release
from django_versioned_models.services import (
get_versioned_models,
get_versioned_models_ordered,
create_release,
lock_release,
)
🤝 Contributing
Contributions are welcome! Please fork the repo, create a branch, and submit a pull request.
📄 License
MIT License — see LICENSE for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_versioned_models-1.0.0.tar.gz.
File metadata
- Download URL: django_versioned_models-1.0.0.tar.gz
- Upload date:
- Size: 10.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c135b32934aebfaf949abb6d24c2570ff6c82f4faa1c17827a81c8c71d45c7c1
|
|
| MD5 |
4decc03edca4b6c662490e7aa58c9055
|
|
| BLAKE2b-256 |
7bf93fbda9aa339a560b81be2a6cc831212a756b7bb4c0dc722f0104c0760597
|
File details
Details for the file django_versioned_models-1.0.0-py3-none-any.whl.
File metadata
- Download URL: django_versioned_models-1.0.0-py3-none-any.whl
- Upload date:
- Size: 14.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1bd9c2f14f5e30d2aed3657b44953d60ceccf04c95cdf2efbfa89e92faf2506c
|
|
| MD5 |
5e7a00bb117bd781c6861deb24535f7a
|
|
| BLAKE2b-256 |
21e01b658fb00cd2063da1bfd5e2452490d25de683cfba2650c332112a31e653
|