Django migration error :you cannot alter to or from M2M fields, or add or remove through= on M2M fields
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)
This is my first Django project so any suggestions are welcome.
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'),
),
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 putthrough
property in theGroup.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 theGroup
model and runpython manage.py makemigrations
, so ourGroup
will look like this:class Group(models.Model): name = models.CharField(max_length=128)
-
add
members
field the theGroup
model, but now withthrough
property and runpython 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.