Django:将ManyToManyField转换为ForeignKey

6

假设我创建了两个模型:

class Car(models.Model):
    name =  models.CharField(max_length=50)
    size =  models.IntegerField()


class Manufacturer(models.Model):
    name =  models.CharField(max_length=50)
    country =  models.CharField(max_length=50)
    car = models.ManyToManyField(Car)

我向两个模型添加了项目,然后意识到每辆汽车只与一个制造商相关联。因此,我应该将ManyToManyField转换为ForeignKey:

class Car(models.Model):
    name =  models.CharField(max_length=50)
    size =  models.IntegerField()
    manufacturer = models.ForeignKey(Manufacturer)

class Manufacturer(models.Model):
    name =  models.CharField(max_length=50)
    country =  models.CharField(max_length=50)

我怎样才能转换格式而不丢失我的条目?我查看了South文档但是没有找到这种转换方式...

2个回答

8

根据Thomas Orozco提供的出色答案,我想为Django>=1.7(基本上是点2 将ManyToMany转换为ForeignKey在新版本的Django中有所变化)提供解决方案。所以这里是第二个迁移的代码:

class Migration(migrations.Migration):

    def migrate_m2m_to_fk(apps, schema_editor):
        Manufacturer = apps.get_model("app", "Manufacturer")
        for manufacturer in Manufacturer.objects.all():
             for car in manufacturer.car.all():
                  car.manufacturer = manufacturer
                  car.save()

    def migrate_fk_to_m2m(apps, schema_editor):
        Car = apps.get_model("app", "Car")
        for c in Car.objects.all():
            if c.manufacturer:
                c.manufacturer.car.add(c)
                c.manufacturer.save()

    operations = [
        migrations.RunPython(migrate_m2m_to_fk, migrate_fk_to_m2m)
    ]

"app" 是 Django 应用程序所在的地方。下面展示了正向和反向迁移代码(正如 Thomas 所提到的,如果之前存在多个关系,则运行此迁移可能会导致数据丢失,请注意)。


8
我认为这不是一件简单的事情,你需要三次迁移才能完成:
  1. 添加ForeignKey
  2. ManyToMany转换为ForeignKey(使用forwards方法)。
  3. 删除ManyToMany
你可能可以将1和2或2和3合并在一起,但我不建议这样做。另外,你还应该为2实现一个backwards方法。
2中forwards的示例:
class Migration(SchemaMigration):
    def forwards(self, orm):
        for manufacturer in orm.Manufacturer.objects.all():
             for car in manufacturer.car.all():
                  car.manufacturer = manufacturer
                  car.save()

请注意以下几点:
  • 目前尚无“backwards”方法。
  • 此操作需要进行广泛测试:迁移是一项需要特别小心的任务。
  • 如果一辆汽车有两个制造商,则只保留最后一个。
  • 这种方式非常低效,我们会为每辆汽车的每个制造商进行一次查询!

您还需要更新在步骤2/3中使用这些关系的代码。

你能提供一下Car和Manufacturer模型的forwards方法的例子吗? - gueux
嗯,我在第一步遇到了问题 :) 由于制造商尚未定义,我该如何将ForeignKey添加到Car中? 我收到了一个“NameError:name'Manufacturer'未定义”的错误。 - gueux
1
@gueux 使用 引用语法,ORM 将为您解决此问题:ForeignKey('Manufacturer') - Thomas Orozco
好的,谢谢,这个可行 :-). 我现在遇到了以下问题:当我尝试添加ForeignKey时,South要求我提供一个默认值... 有没有办法将步骤1和步骤2合并? - gueux
@gueux 你可以在第一步中将该字段设置为非必填项,只在第二或第三步中将其设置为必填项。或者,只需设置一个虚拟值即可。 - Thomas Orozco
哦,没问题。我以为ForeignKey不允许使用"blank=True, null=True"。非常感谢!只是为了帮助那些可能遇到同样问题的人:我必须修改ForeignKey和ManyToManyField的"related_name"(可以是除了"manufacturer"和"car"之外的任何内容)。 - gueux

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接