在 django 中将多对多关系转换为一对多



我要实现的目标

到目前为止,我们有模型,这些模型具有许多对许多关系,如下所示。

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 的每个记录可以有多个对 obj A 的引用,但 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>

基本上,在完成上述步骤后,它将更新记录,a_id 到 B 的某个 OBJ 的 ID。

我面临的问题

当我尝试运行makemigrations它要求数据的默认值时,我不确定要放什么。在模型 A 中,我可以将数据字段指向 B 的任何记录,但随后我将失去第 3 个表 C 的使用,表 C 在整个系统中也被广泛使用。 那么,我需要删除表 C 吗? 另外,想知道是否有更好的方法可以将多对多关系更改为一对多关系。 任何帮助都会很有用。

在这种情况下,当您的数据位于中间表中时, 我建议您执行以下操作。

  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. 创建一个空的迁移文件,并使其运行此代码以将所有A-B关系数据从表C复制到表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. 模型中删除dataAmakemigtations
  2. 重命名tmp_data列以在A模型上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

最新更新