Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add through option to existing ManyToManyField with migrations and data in django

Tags:

I can't find reference to particular issue in docs or online.

I have an existing many to many relation.

class Books(models.Model):     name = models.CharField(max_length=100)  class Authors(models.Model):     name = models.CharField(max_length=100)     books = models.ManyToManyField(Books) 

This has migrations and data. Now I need to use through option in order to add one extra field in table holding many to many relation.

class Authorship(models.Model):     book = models.ForeignKey(Books)     author = models.ForeignKey(Authors)     ordering = models.PositiveIntegerField(default=1)  class Authors(models.Model):     name = models.CharField(max_length=100)     books = models.ManyToManyField(Books, through=Authorship) 

When I run migrations, django creates fresh migration for Authorship model. I tried to create migration file manually by adding ordering column in Authorship table and altering books column in Authors table but I get some migration issues.

operations = [     migrations.AddField(         model_name='authorship',         name='ordering',         field=models.PositiveIntegerField(default=1),     ),     migrations.AlterField(         model_name='authors',         name='books',         field=models.ManyToManyField(to='app_name.Books', through='app_name.Authorship'),     ), ] 

When trying to migrate, it gives KeyError: ('app_name', u'authorship') I bet there are other things that are affected and thus errors.

What things am I missing? Is there any other approach to work with this?

like image 737
chhantyal Avatar asked Oct 21 '15 10:10

chhantyal


1 Answers

There is a way to add "through" without data migrations. I managed to do it based on this @MatthewWilkes' answer.

So, to translate it to your data model:

  1. Create the Authorship model only with book and author fields. Specify the table name to use the same name as the auto-generated M2M table you already have. Add the 'through' parameter.

    class Authorship(models.Model):     book = models.ForeignKey(Books)     author = models.ForeignKey(Authors)      class Meta:         db_table = 'app_name_authors_books'  class Authors(models.Model):     name = models.CharField(max_length=100)     books = models.ManyToManyField(Books, through=Authorship) 
  2. Generate a migration, but don't run it yet.

  3. Edit the generated migration and wrap the migration operations into a migrations. SeparateDatabaseAndState operation with all the operations inside state_operations field (with database_operations left empty). You will end up with something like this:

    operations = [     migrations.SeparateDatabaseAndState(state_operations=[         migrations.CreateModel(             name='Authorship',             fields=[                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),                 ('book', models.ForeignKey(to='app_name.Books')),             ],             options={                 'db_table': 'app_name_authors_books',             },         ),         migrations.AlterField(             model_name='authors',             name='books',             field=models.ManyToManyField(through='app_name.Authorship', to='app_name.Books'),         ),         migrations.AddField(             model_name='authorship',             name='author',             field=models.ForeignKey( to='app_name.Author'),         ),     ]) ] 
  4. You can now run the migration and add the extra ordering field to your M2M table.

Edit: Apparently, column names in the DB are generated slightly differently for automatic M2M tables as for models-defined tables. (I am using Django 1.9.3.)

After the described procedure, I also had to manually change the column names of a field with a 2-word name (two_words=models.ForeignKey(...)) from twowords_id to two_words_id.

like image 104
grain Avatar answered Oct 22 '22 02:10

grain