Prevents data loss during Django migration rollbacks and git branch switches
Project description
django-migcare
Prevents data loss during Django migration rollbacks and git branch switches.
The problem
Two common scenarios silently destroy production data:
- Rolling back a migration that contained
RemoveFieldorDeleteModel— Django drops the column/table and the data is gone. - Switching git branches — migration files from the old branch are still marked as applied in the database, leaving the DB in an inconsistent state that can cause
manage.py migrateto fail or silently corrupt data.
django-migcare addresses both.
Features
| Feature | Description |
|---|---|
| Risk analysis engine | Classifies every operation in a migration plan as SAFE, WARNING, or DANGER |
| Auto-snapshot | Automatically backs up affected rows to MigrationSnapshot before any destructive migration |
safe_migrate command |
Drop-in replacement for manage.py migrate — shows a risk report, creates snapshots, and (optionally) prompts for confirmation |
migcare_check command |
Inspect pending migrations, risk summary, and existing snapshots at any time |
migcare_backup command |
Manually snapshot any table or column |
migcare_restore command |
Restore data from a saved snapshot |
| Git hook integration | Installs a post-checkout hook that warns about ghost migrations after branch switches |
Installation
pip install django-migcare
Add to INSTALLED_APPS:
INSTALLED_APPS = [
...
"migcare",
]
Run migrations to create the snapshot table:
python manage.py migrate migcare
Quick start
Use safe_migrate instead of migrate
python manage.py safe_migrate
Output example when a destructive migration is detected:
django-migcare risk analysis:
django-migcare — 1 DANGER operation(s):
• [DANGER] myapp.0003_remove_user_ssn: RemoveField will DROP column 'ssn'
from 'user' — all data in that column will be permanently lost.
django-migcare: snapshot #7 created — myapp_user.ssn (45,210 rows)
Operations to perform:
Apply all migrations: myapp
Running migrations:
Applying myapp.0003_remove_user_ssn... OK
Check migration state at any time
python manage.py migcare_check
python manage.py migcare_check --app myapp
python manage.py migcare_check --json # machine-readable output
Manually snapshot before a risky operation
python manage.py migcare_backup myapp 0003_remove_user_ssn \
--table myapp_user \
--column ssn \
--notes "Before quarterly archive run"
Restore a snapshot
# List available snapshots
python manage.py migcare_restore --list
# Restore snapshot #7
python manage.py migcare_restore 7
Protect against branch-switch surprises
python manage.py migcare_install_hooks
After installation, switching branches triggers an automatic check. Example output after git checkout feature/payments:
GHOST MIGRATIONS — 2 migration(s) applied in DB but missing from the current branch:
• myapp.0012_add_payment_table
• myapp.0013_populate_payment_data
To resolve: run `python manage.py migrate` to reconcile, or switch back to
the branch where these migrations were created.
Remove the hook at any time:
python manage.py migcare_install_hooks --remove
Configuration
All settings are optional. Override via settings.MIGCARE:
MIGCARE = {
# Auto-snapshot before every DANGER-level migration (default: True)
"AUTO_BACKUP": True,
# Prompt for confirmation before applying DANGER migrations via safe_migrate
# (default: False — useful in CI to prevent accidental rollbacks)
"REQUIRE_CONFIRMATION": False,
# Max rows captured per table per snapshot (default: 10_000)
"BACKUP_ROW_LIMIT": 10_000,
# Purge snapshots older than N days; 0 = never (default: 30)
"MAX_BACKUP_AGE_DAYS": 30,
# Emit warnings when ghost migrations are detected after git checkout (default: True)
"WARN_ON_BRANCH_SWITCH": True,
}
Risk levels
| Level | Operations |
|---|---|
| DANGER | RemoveField (forward), DeleteModel (forward), AddField (rollback), CreateModel (rollback) |
| WARNING | AlterField, RenameField, RenameModel, RunSQL, RunPython |
| SAFE | Everything else (AddField forward, CreateModel forward, index/constraint changes, …) |
Restoring after accidental data loss
If you ran a destructive migration and need the data back:
- Roll back the migration to re-create the schema:
python manage.py migrate myapp 0002_previous_migration
- Restore the snapshot:
python manage.py migcare_restore --list python manage.py migcare_restore <snapshot_id>
How it works
- Analysis (
migcare/analysis.py): CallsMigrationExecutor.migration_plan()and inspects each operation's type and direction to produce aPlanReport. - Backup (
migcare/backup.py): Uses Django's database introspection layer to serialize table/column data intoMigrationSnapshotJSON records. - Signals (
migcare/signals.py): Hooks into Django'spre_migratesignal to trigger auto-backup whenAUTO_BACKUP = True. - Git hooks (
migcare/git_hooks.py): Installs apost-checkoutshell script that callsmigcare_check --git-post-checkoutto detect ghost migrations.
Running the tests
pip install -e ".[dev]"
pytest
License
MIT
Project details
Release history Release notifications | RSS feed
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_migcare-0.1.0.tar.gz.
File metadata
- Download URL: django_migcare-0.1.0.tar.gz
- Upload date:
- Size: 24.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03be99a5af8177f3c2e37be850e9275f86a1018d9671c7ef854bbb18e2184abe
|
|
| MD5 |
b88767e813319a88a3ecf656d354799e
|
|
| BLAKE2b-256 |
3a6182102536b76433157b78742f613e76f59fe42c4ca352b9b42a0e2a7d223a
|
Provenance
The following attestation bundles were made for django_migcare-0.1.0.tar.gz:
Publisher:
publish.yml on tejasekhande1/django-migcare
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_migcare-0.1.0.tar.gz -
Subject digest:
03be99a5af8177f3c2e37be850e9275f86a1018d9671c7ef854bbb18e2184abe - Sigstore transparency entry: 1340484039
- Sigstore integration time:
-
Permalink:
tejasekhande1/django-migcare@6cb930598df3b0d69abcd4e8067867175f87516c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/tejasekhande1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6cb930598df3b0d69abcd4e8067867175f87516c -
Trigger Event:
release
-
Statement type:
File details
Details for the file django_migcare-0.1.0-py3-none-any.whl.
File metadata
- Download URL: django_migcare-0.1.0-py3-none-any.whl
- Upload date:
- Size: 26.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
409d16087c7b1310e2d4143442c7966c87603d4052a27e8c5287e4dad41db673
|
|
| MD5 |
caa2a8e1ad6264bd55c02923aa7f117a
|
|
| BLAKE2b-256 |
b14d7b515fbbd1549a99817f84755136397d7e5525b7a5481cc4bb7ebc42406a
|
Provenance
The following attestation bundles were made for django_migcare-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on tejasekhande1/django-migcare
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_migcare-0.1.0-py3-none-any.whl -
Subject digest:
409d16087c7b1310e2d4143442c7966c87603d4052a27e8c5287e4dad41db673 - Sigstore transparency entry: 1340484042
- Sigstore integration time:
-
Permalink:
tejasekhande1/django-migcare@6cb930598df3b0d69abcd4e8067867175f87516c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/tejasekhande1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6cb930598df3b0d69abcd4e8067867175f87516c -
Trigger Event:
release
-
Statement type: