Django migration error :you cannot alter to or from M2M fields, or add or remove through= on M2M fields

DjangoForeign KeysMigrationManytomanyfield

Django Problem Overview


I'm trying to modify a M2M field to a ForeignKey field. The command validate shows me no issues and when I run syncdb :

ValueError: Cannot alter field xxx into yyy they are not compatible types (you cannot alter to or from M2M fields, or add or remove through= on M2M fields)

So I can't make the migration.

class InstituteStaff(Person):
    user                 = models.OneToOneField(User, blank=True, null=True)
    investigation_area   = models.ManyToManyField(InvestigationArea, blank=True,)
    investigation_group  = models.ManyToManyField(InvestigationGroup, blank=True)
    council_group        = models.ForeignKey(CouncilGroup, null=True, blank=True)
    #profiles            = models.ManyToManyField(Profiles, null = True, blank = True)
    profiles             = models.ForeignKey(Profiles, null = True, blank = True)

Any suggestions?

Django Solutions


Solution 1 - Django

I stumbled upon this and although I didn't care about my data much, I still didn't want to delete the whole DB. So I opened the migration file and changed the AlterField() command to a RemoveField() and an AddField() command that worked well. I lost my data on the specific field, but nothing else.

I.e.

migrations.AlterField(
    model_name='player',
    name='teams',
    field=models.ManyToManyField(related_name='players', through='players.TeamPlayer', to='players.Team'),
),

to

migrations.RemoveField(
    model_name='player',
    name='teams',
),
migrations.AddField(
    model_name='player',
    name='teams',
    field=models.ManyToManyField(related_name='players', through='players.TeamPlayer', to='players.Team'),
),

Solution 2 - Django

NO DATA LOSS EXAMPLE


I would say: If machine cannot do something for us, then let's help it!

Because the problem that OP put here can have multiple mutations, I will try to explain how to struggle with that kind of problem in a simple way.

Let's assume we have a model (in the app called users) like this:

from django.db import models


class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person)

    def __str__(self):
        return self.name

but after some while we need to add a date of a member join. So we want this:

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership') # <-- through model

    def __str__(self):
        return self.name

# and through Model itself
class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()

Now, normally you will hit the same problem as OP wrote. To solve it, follow these steps:

  • start from this point:

     from django.db import models
    
    
     class Person(models.Model):
         name = models.CharField(max_length=128)
    
         def __str__(self):
             return self.name
    
     class Group(models.Model):
         name = models.CharField(max_length=128)
         members = models.ManyToManyField(Person)
    
         def __str__(self):
             return self.name
    
  • create through model and run python manage.py makemigrations (but don't put through property in the Group.members field yet):

     from django.db import models
    
    
     class Person(models.Model):
         name = models.CharField(max_length=128)
    
         def __str__(self):
             return self.name
    
     class Group(models.Model):
         name = models.CharField(max_length=128)
         members = models.ManyToManyField(Person) # <-- no through property yet!
    
         def __str__(self):
             return self.name
    
     class Membership(models.Model): # <--- through model
         person = models.ForeignKey(Person, on_delete=models.CASCADE)
         group = models.ForeignKey(Group, on_delete=models.CASCADE)
         date_joined = models.DateField()
    
  • create an empty migration using python manage.py makemigrations users --empty command and create conversion script in python (more about the python migrations here) which creates new relations (Membership) for an old field (Group.members). It could look like this:

     # Generated by Django A.B on YYYY-MM-DD HH:MM
     import datetime
    
     from django.db import migrations
    
    
     def create_through_relations(apps, schema_editor):
         Group = apps.get_model('users', 'Group')
         Membership = apps.get_model('users', 'Membership')
         for group in Group.objects.all():
             for member in group.members.all():
                 Membership(
                     person=member,
                     group=group,
                     date_joined=datetime.date.today()
                 ).save()
    
     class Migration(migrations.Migration):
    
         dependencies = [
             ('myapp', '0005_create_models'),
         ]
    
         operations = [
             migrations.RunPython(create_through_relations, reverse_code=migrations.RunPython.noop),
         ]
    
  • remove members field in the Group model and run python manage.py makemigrations, so our Group will look like this:

     class Group(models.Model):
         name = models.CharField(max_length=128)
    
  • add members field the the Group model, but now with through property and run python manage.py makemigrations:

     class Group(models.Model):
         name = models.CharField(max_length=128)
         members = models.ManyToManyField(Person, through='Membership')
    

and that's it!

Now you need to change creation of members in a new way in your code - by through model. More about here.

You can also optionally tidy it up, by squashing these migrations.

Solution 3 - Django

Potential workarounds:

  • Create a new field with the ForeignKey relationship called profiles1 and DO NOT modify profiles. Make and run the migration. You might need a related_name parameter to prevent conflicts. Do a subsequent migration that drops the original field. Then do another migration that renames profiles1 back to profiles. Obviously, you won't have data in the new ForeignKey field.

  • Write a custom migration: https://docs.djangoproject.com/en/1.7/ref/migration-operations/

You might want to use makemigration and migration rather than syncdb.

Does your InstituteStaff have data that you want to retain?

Solution 4 - Django

If you're still developing the application, and don't need to preserve your existing data, you can get around this issue by doing the following:

  1. Delete and re-create the db.

  2. go to your project/app/migrations folder

  3. Delete everything in that folder with the exception of the init.py file. Make sure you also delete the pycache dir.

  4. Run syncdb, makemigrations, and migrate.

Solution 5 - Django

Another approach that worked for me:

  • Delete the existing M2M field and run migrations.
  • Add the FK field and run migrations again.

FK field added in this case has no relation to the previously used M2M field and hence should not create any problems.

Solution 6 - Django

This link helps you resolve all problems related to this The one which worked for me is python3 backend/manage.py migrate --fake "app_name"

Solution 7 - Django

I literally had the same error for days and i had tried everything i saw here but still didn'y work. This is what worked for me:

  • I deleted all the files in migrations folder exceps init.py
  • I also deleted my database in my case it was the preinstalled db.sqlite3 After this, i wrote my models from the scratch, although i didn't change anything but i did write it again.
  • Apply migrations then on the models and this time it worked and no errors.

Solution 8 - Django

  1. First delete the migrations in your app (the folders/ files under 'migrations' folder) Showing the 'migrations' folder

  2. Then delete the 'db.sqlite3' file Showing the 'db.sqlite3' file

  3. And run python manage.py makemigrations name_of_app

  4. Finally run python manage.py migrate

Solution 9 - Django

I had the same problem and found this How to Migrate a ‘through’ to a many to many relation in Django article which is really really helped me to solve this problem. Please have a look. I'll summarize his answer here,

There is three model and one(CollectionProduct) is going to connect as many-to-many relationship.

enter image description here

This is the final output,

class Product(models.Model):
    pass


class Collection(models.Model):
    products = models.ManyToManyField(
        Product,
        blank=True,
        related_name="collections",
        through="CollectionProduct",
        through_fields=["collection", "product"],
    )


class CollectionProduct(models.Model):
    collection = models.ForeignKey(Collection, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)

    class Meta:
        db_table = "product_collection_products"

and here is the solution,

> The solution > > Take your app label (the package name, e.g. ‘product’) and your M2M field name, and combine them together with and underscore: > > APPLABEL + _ + M2M TABLE NAME + _ + M2M FIELD NAME > > For example in our case, it’s this: > > product_collection_products > > This is your M2M’s through database table name. Now you need to edit your M2M’s through model to this:


Also found another solution in In Django you cannot add or remove through= on M2M fields article which is going to edit migration files. I didn't try this, but have a look if you don't have any other solution.

Solution 10 - Django

This worked for Me as well

  1. Delete last migrations
  2. run command python manage.py migrate --fake <application name>
  3. run command 'python manage.py makemigrations '
  4. run command 'python manage.py migrate'

Hope this will solve your problem with deleting database/migrations

Solution 11 - Django

this happens when adding 'through' attribute to an existing M2M field: as M2M fields are by default handled by model they are defined in (if through is set). although when through is set to new model the M2M field is handled by that new model, hence the error in alter solutions:- you can reset db or remove those m2m fields and run migration as explained above then create them again

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionloarView Question on Stackoverflow
Solution 1 - DjangowanaryytelView Answer on Stackoverflow
Solution 2 - DjangoturkusView Answer on Stackoverflow
Solution 3 - DjangoPaul WolfView Answer on Stackoverflow
Solution 4 - Djangouser3136977View Answer on Stackoverflow
Solution 5 - DjangoShreyas ShettyView Answer on Stackoverflow
Solution 6 - DjangoAbednegoView Answer on Stackoverflow
Solution 7 - DjangoJyotiprakash JenaView Answer on Stackoverflow
Solution 8 - Djangopymail pymailView Answer on Stackoverflow
Solution 9 - DjangoKushan GunasekeraView Answer on Stackoverflow
Solution 10 - DjangoMARK IView Answer on Stackoverflow
Solution 11 - DjangoVishal GuptaView Answer on Stackoverflow