Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django data migration when changing a field to ManyToMany

I have a Django application in which I want to change a field from a ForeignKey to a ManyToManyField. I want to preserve my old data. What is the simplest/best process to follow for this? If it matters, I use sqlite3 as my database back-end.

If my summary of the problem isn't clear, here is an example. Say I have two models:

class Author(models.Model):       author = models.CharField(max_length=100)   class Book(models.Model):       author = models.ForeignKey(Author)       title = models.CharField(max_length=100) 

Say I have a lot of data in my database. Now, I want to change the Book model as follows:

class Book(models.Model):       author = models.ManyToManyField(Author)       title = models.CharField(max_length=100)  

I don't want to "lose" all my prior data.

What is the best/simplest way to accomplish this?

Ken

like image 202
Ken H Avatar asked Feb 08 '10 19:02

Ken H


People also ask

How do you add a new field to a model with new Django migrations?

To answer your question, with the new migration introduced in Django 1.7, in order to add a new field to a model you can simply add that field to your model and initialize migrations with ./manage.py makemigrations and then run ./manage.py migrate and the new field will be added to your DB.

How does Django know which migrations to run?

As a part of the answer to the question "how does Django know what migrations have been run?", they store records of applied migrations in the database! If you want to have a peek at what they store in the database, take a gander at the following using the Django shell.


1 Answers

I realize this question is old and at the time the best option for Data Migrations was using South. Now Django has its own migrate command, and the process is slightly different.

I've added these models to an app called books -- adjust accordingly if that's not your case.

First, add the field to Book and a related_name to at least one, or both of them (or they'll clash):

class Book(models.Model):       author = models.ForeignKey(Author, related_name='book')     authors = models.ManyToManyField(Author, related_name='books')     title = models.CharField(max_length=100)  

Generate the migration:

$ ./manage.py makemigrations Migrations for 'books':   0002_auto_20151222_1457.py:     - Add field authors to book     - Alter field author on book 

Now, create an empty migration to hold the migration of the data itself:

./manage.py makemigrations books --empty     Migrations for 'books': 0003_auto_20151222_1459.py: 

And add the following content to it. To understand exactly how this works, check the documentation on Data Migrations. Be careful not to overwrite the migration dependency.

# -*- coding: utf-8 -*- from __future__ import unicode_literals  from django.db import models, migrations   def make_many_authors(apps, schema_editor):     """         Adds the Author object in Book.author to the         many-to-many relationship in Book.authors     """     Book = apps.get_model('books', 'Book')      for book in Book.objects.all():         book.authors.add(book.author)   class Migration(migrations.Migration):      dependencies = [         ('books', '0002_auto_20151222_1457'),     ]      operations = [         migrations.RunPython(make_many_authors),     ] 

Now remove the author field from the Model -- it should look like this:

class Book(models.Model):     authors = models.ManyToManyField(Author, related_name='books')     title = models.CharField(max_length=100) 

Create a new migration for that, and run them all:

$ ./manage.py makemigrations Migrations for 'books':   0004_remove_book_author.py:     - Remove field author from book  $ ./manage.py migrate Operations to perform:   Synchronize unmigrated apps: messages, staticfiles   Apply all migrations: admin, auth, sessions, books, contenttypes Synchronizing apps without migrations:   Creating tables...     Running deferred SQL...   Installing custom SQL... Running migrations:   Rendering model states... DONE   Applying books.0002_auto_20151222_1457... OK   Applying books.0003_auto_20151222_1459... OK   Applying books.0004_remove_book_author... OK 

And that's it. The authors previously available at book.author now should be in the queryset you get from book.authors.all().

like image 153
Rodrigo Deodoro Avatar answered Oct 26 '22 12:10

Rodrigo Deodoro