Search code examples
djangodjango-modelsdjango-south

Migrating data from "Many-To-Many" to "Many-To-Many Through" in django


I've got a model

class Category(models.Model):
    title           = models.CharField(...)
    entry           = models.ManyToManyField(Entry,null=True,blank=True,
                                             related_name='category_entries',
                                             )

That I wish to refactor to have additional data with each relationship:

class Category(models.Model):
    title           = models.CharField(...)
    entry           = models.ManyToManyField(Entry,null=True,blank=True,
                                             related_name='category_entries',
                                             through='CategoryEntry',
                                             )

But south deletes the existing table. How can I preserve the existing m-t-m relationships?


Solution

    1. Create your intermediate model without any extra fields, for now. Give it a unique constraint to match the existing one and specify the table name to match the existing one:

      class CategoryEntry(models.Model):
          category = models.ForeignKey(Category)
          entry = models.ForeignKey(Entry)   
      
          class Meta:
              db_table='main_category_entries'   #change main_ to your application
              unique_together = (('category', 'entry'))
      
    2. Run the South schema migration.

    3. Edit the generated schema migration script and comment-out all the forwards and backwards entries, since you'll be re-using the existing intersection table. Add pass to complete the methods.

    4. Run the migration.

    5. Update any existing code. As it says in https://docs.djangoproject.com/en/dev/topics/db/models/#many-to-many-relationships, "Unlike normal many-to-many fields, you can't use add, create, or assignment to create relationships" so you'll need to modify any existing application code, e.g.

      c.entry.add(e)
      

      could become:

      try:
          categoryentry = c.categoryentry_set.get(entry = e)
      except CategoryEntry.DoesNotExist:
          categoryentry = CategoryEntry(category=c, entry=e)
          categoryentry.save()
      

      and:

      e.category_entries.add(c)
      

      could become:

      categoryentry = CategoryEntry(category=c, entry=e)  #set extra fields here
      categoryentry.save()                
      

      and:

      c.entry.remove(e)
      

      could become:

      categoryentry = c.categoryentry_set.get(entry = e)
      categoryentry.delete()
      
    6. Once this initial pseudo migration has been done, you should then be able to add the extra fields to the CategoryEntry and create further migrations as normal.