Migrations
Parameters#
django-admin command |
Details |
---|---|
makemigrations <my_app> |
Generate migrations for my_app |
makemigrations |
Generate migrations for all apps |
makemigrations --merge |
Resolve migration conflicts for all apps |
makemigrations --merge <my_app> |
Resolve migration conflicts for my_app |
makemigrations --name <migration_name> <my_app> |
Generate a migration for my_app with the name migration_name |
migrate <my_app> |
Apply pending migrations of my_app to the database |
migrate |
Apply all pending migrations to the database |
migrate <my_app> <migration_name> |
Apply or unapply up to migration_name |
migrate <my_app> zero |
Unapply all migrations in my_app |
sqlmigrate <my_app> <migration_name> |
Prints the SQL for the named migration |
showmigrations |
Shows all migrations for all apps |
showmigrations <my_app> |
Shows all migrations in my_app |
Working with migrations
Django uses migrations to propagate changes you make to your models to your database. Most of the time django can generate them for you.
To create a migration, run:
$ django-admin makemigrations <app_name>
This will create a migration file in the migration
submodule of app_name
. The first migration will be named 0001_initial.py
, the other will start with 0002_
, then 0003
, …
If you omit <app_name>
this will create migrations for all your INSTALLED_APPS
.
To propagate migrations to your database, run:
$ django-admin migrate <app_name>
To show all your migrations, run:
$ django-admin showmigrations app_name
app_name
[X] 0001_initial
[X] 0002_auto_20160115_1027
[X] 0003_somemodel
[ ] 0004_auto_20160323_1826
[X]
means that the migration was propagated to your database[ ]
means that the migration was not propagated to your database. Usedjango-admin migrate
to propagate it
You call also revert migrations, this can be done by passing the migration name to the migrate command
. Given the above list of migrations (shown by django-admin showmigrations
):
$ django-admin migrate app_name 0002 # Roll back to migration 0002
$ django-admin showmigrations app_name
app_name
[X] 0001_initial
[X] 0002_auto_20160115_1027
[ ] 0003_somemodel
[ ] 0004_auto_20160323_1826
Manual migrations
Sometimes, migrations generated by Django are not sufficient. This is especially true when you want to make data migrations.
For instance, let’s you have such model:
class Article(models.Model):
title = models.CharField(max_length=70)
This model already have existing data and now you want to add a SlugField
:
class Article(models.Model):
title = models.CharField(max_length=70)
slug = models.SlugField(max_length=70)
You created the migrations to add the field, but now you would like to set the slug for all existing article, according to their title
.
Of course, you could just do something like this in the terminal:
$ django-admin shell
>>> from my_app.models import Article
>>> from django.utils.text import slugify
>>> for article in Article.objects.all():
... article.slug = slugify(article.title)
... article.save()
...
>>>
But you will have to do this in all your environments (ie. your office desktop, your laptop, …), all your coworkers will have to do so as well, and you will have to think about it on staging and when pushing live.
To make it once and for all, we will do it in a migration. First create an empty migration:
$ django-admin makemigrations --empty app_name
This will create an empty migration file. Open it, it contains an base skeleton. Let’s say your previous migration was named 0023_article_slug
and this one is named 0024_auto_20160719_1734
. Here is what we will write in our migration file:
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-19 15:34
from __future__ import unicode_literals
from django.db import migrations
from django.utils.text import slugify
def gen_slug(apps, schema_editor):
# We can't import the Article model directly as it may be a newer
# version than this migration expects. We use the historical version.
Article = apps.get_model('app_name', 'Article')
for row in Article.objects.all():
row.slug = slugify(row.name)
row.save()
class Migration(migrations.Migration):
dependencies = [
('hosting', '0023_article_slug'),
]
operations = [
migrations.RunPython(gen_slug, reverse_code=migrations.RunPython.noop),
# We set `reverse_code` to `noop` because we cannot revert the migration
# to get it back in the previous state.
# If `reverse_code` is not given, the migration will not be reversible,
# which is not the behaviour we expect here.
]
Fake migrations
When a migration is run, Django stores the name of the migration in a django_migrations table.
Create and Fake initial migrations for existing schema
If your app already has models and database tables, and doesn’t have migrations. First create initial migrations for you app.
python manage.py makemigrations your_app_label
Now fake initial migrations as applied
python manage.py migrate --fake-initial
Fake all migrations in all apps
python manage.py migrate --fake
Fake single app migrations
python manage.py migrate --fake core
Fake single migration file
python manage.py migrate myapp migration_name
Custom names for migration files
Use the makemigrations --name <your_migration_name>
option to allow naming the migrations(s) instead of using a generated name.
python manage.py makemigrations --name <your_migration_name> <app_name>
Solving migration conflicts
Introduction
Sometimes migrations conflict, resulting in making the migration unsuccesful. This can happen in a lot of scenerio’s, however it can occur on a regular basis when developing one app with a team.
Common migration conflicts happen while using source control, especially when the feature-per-branch method is used. For this scenario we will use a model called Reporter
with the attributes name
and address
.
Two developers at this point are going to develop a feature, thus they both get this initial copy of the Reporter
model. Developer A adds an age
which results in the file 0002_reporter_age.py
file. Developer B adds a bank_account
field which resulsts in 0002_reporter_bank_account
. Once these developers merge their code together and attempt to migrate the migrations, a migration conflict occurred.
This conflict occurs because these migrations both alter the same model, Reporter
. On top of that, the new files both start with 0002.
Merging migrations
There are several ways of doing it. The following is in the recommended order:
-
The most simple fix for this is by running the makemigrations command with a —merge flag.
python manage.py makemigrations --merge <my_app>
This will create a new migration solving the previous conflict.
-
When this extra file is not welcome in the development environment for personal reasons, an option is to delete the conflicting migrations. Then, a new migration can be made using the regular
makemigrations
command. When custom migrations are written, such asmigrations.RunPython
, need to be accounted for using this method.
Change a CharField to a ForeignKey
First off, let’s assume this is your initial model, inside an application called discography
:
from django.db import models
class Album(models.Model):
name = models.CharField(max_length=255)
artist = models.CharField(max_length=255)
Now, you realize that you want to use a ForeignKey for the artist instead. This is a somewhat complex process, which has to be done in several steps.
Step 1, add a new field for the ForeignKey, making sure to mark it as null (note that the model we are linking to is also now included):
from django.db import models
class Album(models.Model):
name = models.CharField(max_length=255)
artist = models.CharField(max_length=255)
artist_link = models.ForeignKey('Artist', null=True)
class Artist(models.Model):
name = models.CharField(max_length=255)
…and create a migration for this change.
./manage.py makemigrations discography
Step 2, populate your new field. In order to do this, you have to create an empty migration.
./manage.py makemigrations --empty --name transfer_artists discography
Once you have this empty migration, you want to add a single RunPython
operation to it in order to link your records. In this case, it could look something like this:
def link_artists(apps, schema_editor):
Album = apps.get_model('discography', 'Album')
Artist = apps.get_model('discography', 'Artist')
for album in Album.objects.all():
artist, created = Artist.objects.get_or_create(name=album.artist)
album.artist_link = artist
album.save()
Now that your data is transferred to the new field, you could actually be done and leave everything as is, using the new artist_link
field for everything. Or, if you want to do a bit of cleanup, you want to create two more migrations.
For your first migration, you will want to delete your original field, artist
. For your second migration, rename the new field artist_link
to artist
.
This is done in multiple steps to ensure that Django recognizes the operations properly.