Have you ever needed to undo, fake, or deal with migrations more deeply than the typical
python manage.py migrate? Did you find yourself opening the migration files to find out what they contained? Everybody probably will at some point.
And if you have, you probably know what a pain it can be to go searching through all those files. In this post, we'll talk about the benefits of properly naming your Django migrations and show how doing a little prep work can save you -- and your fellow developers -- a lot of time. But before that, let's cover the basics.
Django migrations are a core part of the Django Object-Relational Mapper, commonly shortened to ORM. If you’re unfamiliar with ORM, it’s one of Django’s powerful features which enables you to interact with your database, like you would with SQL.
The migration framework was brought into Django on version 1.7. Migrations themselves consist of Python code and define the operations to apply to a database schema as it progresses through the Software Development Life Cycle, or SDLC. These migration files for each application live in a migrations directory within the app and, according to Django documentation, are designed to be committed to, and distributed as part of, its codebase.
Django documentation tells us that migrations should be made “once on your development machine and then running the same migrations on your colleagues’ machines, your staging machines, and eventually your production machines.”
It's helpful to think “of migrations as a version control system for your database schema.”
In terms of backend support, migrations are supported on any backends that Django ships with, including third-party backends that have support for schema alteration. But not all databases are equal when it comes to migrations. Django’s documentation states that some are more capable than others and it’s worth understanding the differences.
In terms of schema support, PostgreSQL is deemed the most capable option available. The lone exception is versions before PostgreSQL 11, which added columns with default values, causing a full rewrite of the table, for a time proportional to its size.
Django’s documentation recommends you always create new columns with null=True, as this way they will be added immediately.
Django’s migration documentation includes three suggestions when it comes to MySQL support.
- Roll-Back Risks: MySQL lacks support for transactions around schema alteration operations. If a migration fails, you will have to manually unpick the changes in order to try again. In short, if you find yourself here, it’s impossible to roll back with Django to an earlier point -- for that you’ll need to use raw SQL.
- Slow Execution Times: MySQL fully rewrites tables for almost every schema operation, causing resource usage consideration because it takes time proportional to the number of rows in the table to add or remove columns. On slower hardware, this can really drag out the process to an estimated minute per million rows. - adding a few columns to a table with just a few million rows could lock your site up for over ten minutes.
- Limited Name Lengths: MySQL has relatively small limits on name lengths for columns, tables and indexes, as well as a limit on the combined size of all columns an index covers. This means that indexes that are possible on other backends will fail to be created under MySQL.
SQLite lacks a lot robust built-in schema alteration support, forcing Django to step in an emulate it by:
- Creating a new table with the new schema
- Copying the data across
- Dropping the old table
- Renaming the new table to match the original name
While Django’s documentation is generally optimistic about how the process works in general, it also admits that results can come slowly and the outcome be “occasionally buggy”. It’s not recommended developers run and migrate SQLite in a production environment without first considering the risks and limitations.
Above nearly everything else, Django Migrations are important and helpful because they make developers' lives easier.
Databases are central components of an application and in today’s fast paced world, things change quickly. Agile projects are always changing and sometimes, adjustments are necessary to meet updated requirements. Django migrations help with the process of making, applying, and tracking changes to database schemas.
If you are working with Django then you are more than likely adept at coding in Python because the two go hand-in-hand. In Django, Migrations are written (primarily) in Python -- a language the developers know anyway -- so you’ll be working with a language you’re familiar with anyway.
Without Migrations, developers would have to have to learn SQL commands or work with a GUI like Phpmyadmin every time they wanted to change the model definition and modify the database schema.
Migrations are generated from the models you create so the process doesn’t have to be repeated. In comparison, building a model and then writing SQL to generate database tables is repetitive.
Database schemas don’t live in the code but Django Migrations are housed in the application, right in a folder with an appropriate name. So when you commit changes and make updates to the Migration, the changes are tracked.
One of the really helpful features of Django Migrations is that they can be named. You can attach a descriptive title to each migration in an effort to distinguish it from others. And if your project becomes complex and goes on for an extended period of time, there can be several migrations.
Now, there’s absolutely no issue with deciding to not name migrations. You can certainly get by without doing it.
Still, taking a minute to name Django Migrations is helpful if it only gives you an indication as to what’s inside. Just as it’s worthwhile to be descriptive in naming commits, branches, and nearly anything when it comes to version control in Git, naming Django Migrations can help you when you’re looking back through versions for a specific file.
You can either spend your time going through files one-by-one until you find what you’re looking for or you can see a list of properly named Django Migrations and get a good idea of where something is at first glance.
Clean code makes for easier maintenance. It just makes life a little easier.
Django’s makemigrations command has a flag
--name that can be used to make migrations more readable and easy on the eyes.
Sometimes Django will name your migrations for you but when it doesn't, the resulting title can be unhelpful when read by human beings. When Django names migrations, it comes out looking like this:
What does that file contain? Who knows. Important stuff? Maybe. Non-essential parts? Your guess is as good as mine.
Alternatively, what if you took the time to name a migration like this:
0005_person_email_and_opt_out? Even for someone unfamiliar with the project will probably be able to figure out what that migration contains.
Here’s a simple example I made. The first list only used python manage.py makemigrations:
Now below are the exact same migrations but with the
--name flag included in the
Taking a small bit of time to name your migrations results in a more readable migration history which will make life easier in the future.
Look at the internal Django apps as an example. Every migration is named.
It's that easy.
Django migrations go a long way in making a developer's life easier. But you can level up your game simply and make life easier for everyone when you give the migrations a descriptive, appropriate name. This post originally appeared on our insights blog, where we write about devops consulting services.