Django Reset DB Migrations

DB Migrations are Django’s way of propagating changes to data models into DB schema. While the migration code is optimized to deal with a large number of migration files without much slowdown, I find leaner migration history cleaner and easier to manage.

When iterating early, the migration folder on one of my side projects grew to tens of migration files. Since adding/removing fields, updating types, help text, verbose names on fields, etc generates a new migration file, it’s not uncommon to have migration folder look like this after the first few weeks of development:

0001_initial.py
0002_auto_20200916_2004.py
0003_auto_20200916_2101.py
...
0017_auto_20200927_1310.py
0018_auto_20200927_1313.py
...
0082_auto_20210210_0952.py
0083_auto_20210210_1028.py
__init__.py

There are two ways to clean-up migration history while preserving existing database data:

Squashing Migrations

Django admin provides a squashmigrations command to reduce an existing set of migrations down to a few or one migration(s) which still represent the same changes. Django migration optimizer analyzes all Operations, coalescing complementary operations (CreateModel/DeleteModel), flattening operations (AddField/CreateModel), etc as necessary.

1. Squash migrations:

$ python3 manage.py squashmigrations myapp 0083
Will squash the following migrations:
 - 0001_initial
 - 0002_auto_20200916_2004.py
 ...
 - 0083_auto_20210210_1028.py
Do you wish to proceed? [yN] y
...
Created new squashed migration
  0001_squashed_0083_auto_20210210_1028.py
  You should commit this migration but leave the
  old ones in place; the new migration will be
  used for new installs. Once you are sure all
  instances of the codebase have applied the
  migrations you squashed, you can delete them.

2. Delete old migration files:

find . -path "*/migrations/*.py" -not -name
    "__init__.py" -and -not -name
    "0001_squashed_0083_auto_20210210_1028.py"
    -delete
find . -path "*/migrations/*.pyc" -delete

There is a possibility of CircularDependencyError and mis-optimized migrations that do not run, which can be resolved manually with –no-optimize option.

Reset Migrations

Alternatively, it’s possible to re-generate migration file from an existing database schema and compress/reset Django’s migration history while preserving the existing database.

1. Apply any pending migrations:

$ python3 manage.py makemigrations
No changes detected.

2. Clear migration history:

$ python3 manage.py showmigrations
...
myapp
 [X] 0001_initial
 [X] ...
 [X] 0082_auto_20210210_0952.py
 [X] 0083_auto_20210210_1028.py
...
$ python3 manage.py migrate --fake myapp zero
Operations to perform:
  Unapply all migrations: myapp
Running migrations:
  Rendering model states... DONE
  Unapplying myapp.0083_auto_2021... FAKED
  Unapplying myapp.0082_auto_2021... FAKED
  ...
  Unapplying myapp.0001_initial... FAKED
$ python3 manage.py showmigrations
...
myapp
 [ ] 0001_initial
 [ ] ...
 [ ] 0082_auto_20210210_0952.py
 [ ] 0083_auto_20210210_1028.py
...

3. Delete old migration files:

find . -path "*/migrations/*.py"
    -not -name "__init__.py" -delete
find . -path "*/migrations/*.pyc"  -delete
$ python3 manage.py showmigrations
...
myapp
 (no migrations)
...

4. Create new migration files:

$ python3 manage.py makemigrations
Migrations for 'myapp':
  0001_initial.py:
    - ...

5. Fake initial migration:

$ python3 manage.py migrate --fake-initial
Operations to perform:
  Apply all migrations: admin, myapp, contenttypes, auth, sessions
Running migrations:
  Rendering model states... DONE
  Applying myapp.0001_initial... FAKED
$ python3 manage.py showmigrations
...
myapp
 [X] 0001_initial
...