将Django中的多对多关系转换为一对多关系

3

我想要实现的目标

到目前为止,我们已经有了像下面这样具有多对多关系的模型。

class A(models.Model):
    name = models.CharField(max_length=255)
    data = models.ManyToManyField(B, related_name='data', through='C')

class B(models.Model):
    name=models.CharField(max_length=255)

class C(models.Model):
    a = ForeignKey(A)
    b = ForeignKey(B)
    c = model.CharField(max_length=255)
    active = models.BooleanField(default=True)
    and so on....

尽管Django会自动为许多对多关系创建一个中间表,但如果我们没有明确定义它,就像您已经注意到的那样,我已经明确定义了第三个中间表来存储有关每个关系的额外元数据。
因此,根据新的业务要求,我们需要将表中的许多对多关系移除,并替换为一对多关系,即B的每个记录可以有多个对A obj的引用,但A的记录始终只有一个对A obj的引用。
另外,由于这些表已经在生产中使用,因此我们还需要迁移现有数据。
到目前为止,我尝试了以下方法:
从模型A中删除了许多对多关系。
class A(models.Model):
    name = models.CharField(max_length=255) 
    data = models.ForeignKey(B)

更新了模型 C 的现有行。

例如:

#this might return more than one obj because of many to many relationship
test = C.objects.filter(a__id=<a obj id>)

for t in test:
   t.b_id = <some id pointing to record of B>

基本上,完成以上步骤后,它将使用b中某个对象的id更新具有a_id记录的条目。

我遇到的问题

当我尝试运行makemigrations时,它要求提供data字段的默认值,但我不确定应该放什么。在模型A中,我可以将data字段指向B的任何记录,但这样我将失去第三个表C的使用,而且表C广泛用于整个系统。 那么,我是否需要删除表C? 此外,想知道是否有更好的方法将多对多关系更改为一对多关系。 任何帮助都将很有用。

1个回答

2
在您的数据位于中间表中的情况下,我建议您执行以下操作:
  1. 向您的 A 表添加一个临时列
class A(models.Model):
    name = models.CharField(max_length=255)
    data = models.ManyToManyField(B, related_name='data', through='C')
    # the on_delete part is not related, I added it just to avoid errors.
    tmp_data = models.ForeignKey(B, on_delete=models.DO_NOTHING)

然后执行python manage.py makemigrations

  1. 创建一个空的迁移文件,并运行以下代码将表C中所有A-B关系数据复制到表A
# Command
python manage.py makemigrations <<<APP_NAME>>> --empty

# Inside Empty migrations file
def migrate_c_to_a(apps, schema_editor):
    C = apps.get_model('<<<APP NAME>>>', 'C')
    for c in C.objects.all().iterator():
        a = c.a
        a.tmp_data = c.b
        a.save()

class Migration(migrations.Migration):
    ...
    ...
    operations = [
        migrations.RunPython(migrate_c_to_a)
    ]
  1. 从你的A模型中删除data列,并运行makemigrations
  2. tmp_data列重命名为data,然后运行makemigrations

    注意,确保makemigrations不会删除tmp_datadata,然后添加新字段data,否则会导致数据丢失。我将在下面的代码片段中向您展示如何操作。

如果出现这种情况,请在迁移文件中进行以下操作

...
operations = [
        migrations.RemoveField(
            model_name='a',
            name='tmp_data',
        ),
        migrations.RemoveField(
            model_name='a',
            name='data',
        ),
        migrations.AddField(
            model_name='a',
            name='data',
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='tsting.B'),
        ),
    ]
...

将其更改为以下内容
...
operations = [
        migrations.RemoveField(
            model_name='a',
            name='data',
        ),
        migrations.RenameField(
            model_name='a',
            old_name='tmp_data',
            new_name='data'
        ),
    ]
...
  1. python manage.py migrate

这样做就可以了。 之后,如果你想删除表格C,那就由你决定,因为它内部的数据已经安全地迁移到了表格A中。


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