Django migration error :you cannot alter to or fro

2020-05-15 08:33发布

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.

4条回答
倾城 Initia
2楼-- · 2020-05-15 08:51

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?

查看更多
在下西门庆
3楼-- · 2020-05-15 08:54

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.

查看更多
乱世女痞
4楼-- · 2020-05-15 08:56

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'),
),
查看更多
手持菜刀,她持情操
5楼-- · 2020-05-15 08:57

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.

查看更多
登录 后发表回答