How to Disallow Auto-named Django Migrations2020-02-24
When you run Django’s
manage.py makemigrations, it will try to generate a name for the migration based upon its contents.
For example, if you are adding a single field, it will call the migration
However when your migration contains more than one step, it instead uses a simple ‘auto’ name with the current date + time, e.g.
You can provide the
--name argument to
makemigrations, but developers often forget this.
Naming things is a known hard problem in programming. Having migrations with these automatic names makes managing them harder: You can’t tell which is which without opening them, and you could still mix them up if they have similar names due to being generated on the same day.
This becomes painful when:
- rebasing branches
- digging through history
- deploying to production
In the worst case, running the wrong migration could lead to data loss!
It’s also all too easy to forget to fix the name and commit since Django doesn’t prompt you for a better name. We can guard against this with some automation!
Let’s look at three techniques to do so.
makemigrations to require
This uses the same technique of overriding a built-in management command as I used in my post “Make Django Tests Always Rebuild the Database if It Exists”.
Add a new
makemigrations command to the “core” app in your project (e.g.
myapp/management/commands/makemigrations.py) with this content:
(Replace “Myproject” with the name of your project.)
makemigrations will output a message like this:
Because this only applies to
makemigrations, it automatically only affects new migrations, and not those in third party apps.
2. A Custom System Check
This is a custom system check that I’ve used on a few client projects.
To add it your project, you’ll first want to add it to a module inside one of your apps.
I normally write add
checks.py in the project’s “core” app (whatever it’s called):
Some notes on the code:
- We need to use an inner import for
MigrationLoader, since it depends on all the Django having loaded all the apps, and we will import our check before then.
- We tell the migration loader to load the names of all migrations from disk and iterate over them.
- We use the standard library
fnmatchfunction to perform simple string matching on the filename. This is easier to read and write than using regular expressions.
- We have
IGNORED_BADLY_NAMED_MIGRATIONSat the bottom, a set of two-tuples like (app name, migration name). I’ve left a commented example of the expected data structure.
To run the check we need to register it in our app’s
…and ensure we use our
Running checks will highlight any problematic migration files:
Django also runs checks at the start of most
manage.py commands, and in the test runner.
If you’re adding this to a project with pre-existing auto-named migrations, you will see each as an error.
You should add them to
IGNORED_BADLY_NAMED_MIGRATIONS, rather than renaming them.
Django only knows migrations by name, so if you rename them, it will detect them as not applied and try apply them again - woops.
3. With a pre-commit Hook
If you’re using pre-commit (and you should, it’s really good!), you can also use a hook to ban auto-generated files with much less code:
This uses the
fail pseudo-language to automatically fail any files matching that regex.
The only downside of this approach is that you have to use a long regex in
exclude to skip pre-existing badly named migrations.
I hope this helps you keep your projects’ code just a bit cleaner,
Interested in Django or Python training? I'm taking bookings for workshops.
One summary email a week, no spam, I pinky promise.
- How to Add Database Modifications Beyond Migrations to Your Django Project
- Moving to Django 3.0's Field.choices Enumeration Types
- Common Issues Using Celery (And Other Task Queues)
© 2020 All rights reserved.