Django 迁移错误:您无法更改 M2M 字段或从 M2M 字段更改,或通过 = 在 M2M 字段上添加或删除

Posted

技术标签:

【中文标题】Django 迁移错误:您无法更改 M2M 字段或从 M2M 字段更改,或通过 = 在 M2M 字段上添加或删除【英文标题】:Django migration error :you cannot alter to or from M2M fields, or add or remove through= on M2M fields 【发布时间】:2015-01-11 17:01:43 【问题描述】:

我正在尝试将 M2M 字段修改为 ForeignKey 字段。命令 validate 显示没有问题,当我运行 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)

所以我无法进行迁移。

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)

这是我的第一个 Django 项目,欢迎提出任何建议。

【问题讨论】:

我讨厌这个问题。 【参考方案1】:

我偶然发现了这一点,虽然我不太关心我的数据,但我仍然不想删除整个数据库。所以我打开了迁移文件并将AlterField() 命令更改为RemoveField()AddField() 命令,效果很好。我丢失了我在特定领域的数据,但没有别的。

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

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'),
),

【讨论】:

很好的答案,只是不要忘记先用 python manage.py dumpdata app_name.ModelName 转储现有数据,然后用 import codecs import json 将数据重新加载到新模型【参考方案2】:

无数据丢失示例


我想说:如果机器不能为我们做点什么,那就让我们来帮助它吧!

因为 OP 放在这里的问题可以有多个突变,我将尝试以简单的方式解释如何解决这种问题。

假设我们有一个这样的模型(在名为 users 的应用中):

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

但过了一段时间我们需要添加成员加入的日期。所以我们想要这个:

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()

现在,通常您会遇到与 OP 所写相同的问题。要解决它,请按照下列步骤操作:

从这一点开始:

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

通过模型创建并运行python manage.py makemigrations但不要将through属性放在Group.members字段中):

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()

使用python manage.py makemigrations users --empty 命令创建一个空迁移并在python 中创建转换脚本(更多关于python 迁移here),它为旧字段(Group.members)创建新关系(Membership)。它可能看起来像这样:

# 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),
    ]

删除Group 模型中的members 字段并运行python manage.py makemigrations,所以我们的Group 将如下所示:

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

Group 模型中添加members 字段,但现在使用through 属性并运行python manage.py makemigrations

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

就是这样!

现在您需要在代码中以新的方式更改成员的创建方式 - 通过模型。更多关于here。

您还可以选择通过squashing 这些迁移来整理它。

【讨论】:

伟大而彻底的建议,就像一个魅力!我想补充一点,在docs 中不鼓励直接导入模型,而支持AppConfig.get_model(model_name)。如果您已经在表单中使用 m2m 字段,还需要做一些额外的工作。谢谢! 哇,答案太棒了。 如果这个例子引起了任何关于如何做的建议,例如当我通过 Django /admin/ 面板查看 Group 时,members 从管理 UI 中消失?以前我可以直接通过管理 UI 中组的详细视图来编辑组的成员,而现在我只能通过创建或删除成员资格来编辑它。有什么方法可以让 UI 重新出现在群组的管理界面上? @bit 我能够有效地解决这个问题,顺便说一句!您必须在新的 M2M 中间模型上启用 Django Admin View,但是一旦完成,您就可以直接成为模型的成员。 最后我解决了将 Membership 添加为 TabularInline 并使用 autocomplete_fields【参考方案3】:

可能的解决方法:

使用名为 profiles1 的 ForeignKey 关系创建一个新字段,并且不要修改 profiles。制作并运行迁移。您可能需要一个 related_name 参数来防止冲突。执行删除原始字段的后续迁移。然后进行另一次迁移,将profiles1 重命名回profiles。显然,新的 ForeignKey 字段中不会有数据。

编写自定义迁移:https://docs.djangoproject.com/en/1.7/ref/migration-operations/

您可能想要使用makemigrationmigration 而不是syncdb

您的InstituteStaff 是否有您想要保留的数据?

【讨论】:

很好......我一直在努力解决这个问题。我有一个很好的记录,感觉几个月没有迁移错误,最后这打击了我。我正要销毁我的数据库并重新开始,直到我发现这个。 +1 的出色工作 我会这样做,然后添加数据迁移以将数据从旧字段复制到新字段,删除旧字段,然后将它们全部压缩到同一个迁移中。要保留现有字段名称,请先重命名原始字段。 只需重命名字段,更改对我有帮助(不要忘记删除迁移文件) 这就是我喜欢的。简单、耐心的回答。【参考方案4】:

如果您仍在开发应用程序,并且不需要保留现有数据,则可以通过执行以下操作来解决此问题:

    删除并重新创建数据库。

    转到您的项目/应用程序/迁移文件夹

    删除该文件夹中的所有内容,init.py 文件除外。确保您还删除了 pycache 目录。

    运行 syncdb、makemigrations 和 migrate。

【讨论】:

已经习惯于手动更改数据库,我不知道/忘记了运行 makemigrations 时添加的迁移目录和文件。谢谢!【参考方案5】:

另一种对我有用的方法:

删除现有的 M2M 字段并运行迁移。 添加 FK 字段并再次运行迁移。

在这种情况下添加的 FK 字段与之前使用的 M2M 字段无关,因此不会产生任何问题。

【讨论】:

【参考方案6】:

    首先删除您应用中的迁移(“迁移”下的文件夹/文件 文件夹) Showing the 'migrations' folder

    然后删除'db.sqlite3'文件 Showing the 'db.sqlite3' file

    然后运行python manage.py makemigrations name_of_app

    终于运行python manage.py migrate

【讨论】:

【参考方案7】:

This link helps you resolve all problems related to this 对我有用的是 python3 backend/manage.py migrate --fake "app_name"

【讨论】:

你的答案应该是独立的,如果你链接到源代码很好,但你至少应该引用对你有用的解决方案。【参考方案8】:

几天来我确实遇到了同样的错误,我已经尝试了我在这里看到的所有东西,但仍然没有用。 这对我有用:

我删除了迁移文件夹中的所有文件,除了 init.py 我还删除了我的数据库,在我的情况下,它是预安装的 db.sqlite3 在此之后,我从头开始编写模型,虽然我没有更改任何内容,但我确实重新编写了它。 然后在模型上应用迁移,这次它可以正常工作并且没有错误。

【讨论】:

【参考方案9】:

我遇到了同样的问题,发现这篇How to Migrate a ‘through’ to a many to many relation in Django 文章真的帮助我解决了这个问题。请看一看。我将在这里总结他的答案,

有三个模型,一个(CollectionProduct)将连接多对多关系。

这是最终的输出,

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"

这是解决方案,

解决办法

获取您的应用标签(包名称,例如“产品”)和您的 M2M 字段名称,并将它们与下划线组合在一起:

APPLABEL + _ + M2M TABLE NAME + _ + M2M FIELD NAME

例如在我们的例子中,是这样的:

product_collection_products

这是您的 M2M 直通数据库表名。现在您需要将 M2M 的直通模型编辑为:


还在In Django you cannot add or remove through= on M2M fields 文章中找到了另一个解决方案,该解决方案将编辑迁移文件。我没有尝试过,但是如果您没有其他解决方案,请查看。

【讨论】:

迁移到数据库时出现“关系已存在”错误。【参考方案10】:

这也适用于我

    删除上次迁移 运行命令python manage.py migrate --fake &lt;application name&gt; 运行命令'python manage.py makemigrations' 运行命令'python manage.py migrate'

希望这将解决您删除数据库/迁移的问题

【讨论】:

【参考方案11】:

将“通过”属性添加到现有 M2M 字段时会发生这种情况: 因为 M2M 字段默认由模型处理,它们在中定义(如果设置了 through)。 尽管将 through 设置为新模型时,M2M 字段由该新模型处理,因此更改中的错误 解决方案:- 您可以重置数据库或 删除这些 m2m 字段并按照上述说明运行迁移,然后再次创建它们

【讨论】:

以上是关于Django 迁移错误:您无法更改 M2M 字段或从 M2M 字段更改,或通过 = 在 M2M 字段上添加或删除的主要内容,如果未能解决你的问题,请参考以下文章

将 m2m 更改为 char 时,Django South 迁移失败

Django 如何摆脱迁移错误

如何避免 django“与相关 m2m 字段冲突”错误?

Django - 管理员中的 UserProfile m2m 字段 - 错误

Django 在本地迁移工作,但不在 Elastic Beanstalk 生产上

Django 维护模型对象的版本