Django migration error :you cannot alter to or from M2M fields, or add or remove through= on M2M fields
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 modelsclass Person(models.Model): name = models.CharField(max_length=128) def __str__(self): return self.nameclass 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 itselfclass 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 modelsclass Person(models.Model): name = models.CharField(max_length=128) def __str__(self): return self.nameclass 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 modelsclass Person(models.Model): name = models.CharField(max_length=128) def __str__(self): return self.nameclass Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person) # <-- no through property yet! def __str__(self): return self.nameclass 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:MMimport datetimefrom django.db import migrationsdef 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.
Potential workarounds:
Create a new field with the ForeignKey relationship called
profiles1
and DO NOT modifyprofiles
. Make and run the migration. You might need arelated_name
parameter to prevent conflicts. Do a subsequent migration that drops the original field. Then do another migration that renamesprofiles1
back toprofiles
. 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?