Assume I have those Django models:
class Book(models.Model):
title = models.CharField(max_length=100)
class Author(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Book)
I already have a production system with several objects and several Author <-> Book connections.
Now I want to switch to:
class Book(models.Model):
title = models.CharField(max_length=100)
class BookAuthor(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
author = models.ForeignKey("Author", on_delete=models.CASCADE)
impact = models.IntegerField(default=1)
class Meta:
unique_together = ("book", "author")
class Author(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Book, through=BookAuthor)
If I do this Migration:
from django.db import migrations
def migrate_author_books(apps, schema_editor):
Author = apps.get_model('yourappname', 'Author')
BookAuthor = apps.get_model('yourappname', 'BookAuthor')
for author in Author.objects.all():
for book in author.books.all():
# Create a BookAuthor entry with default impact=1
BookAuthor.objects.create(author=author, book=book, impact=1)
class Migration(migrations.Migration):
dependencies = [
('yourappname', 'previous_migration_file'),
]
operations = [
migrations.CreateModel(name="BookAuthor", ...),
migrations.RunPython(migrate_author_books),
migrations.RemoveField(model_name="author", name="books"),
migrations.AddField(model_name="author", name="books", field=models.ManyToManyField(...),
]
then the loop for book in author.books.all() will access the new (and empty) BookAuthor table instead of iterating over the existing default table Django created.
How can I make the data migration?
The only way I see is to have two releases:
- Just add the new
BookAuthormodel and fill it with data, but keep the existing one. So introducing a new field and keeping the old one. Also change every single place where author.books is used toauthor.books_new - Release + migrate on prod
- Remove author.books and rename books_new to books. Another migration, another release.
Isn't there a simpler way?
author.booksin the code already has the custom throguh model (BookAuthor) which is empty. Instead, it should useauthor.booksfrom before the code change (the default one)makemigration. You simply "fold" multiple migrations in one file. But regardless, you don't need to move each migration individually to production.makemigration. This is extremely dangerous. You make the migrations when you develop, and then run these migrations on production. By making migrations on production, these can be different than the ones in development, so the migration table of the dev/prod database are no longer in sync, and you get situations like the one you desribe.