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

huangapple go评论106阅读模式
英文:

Convert many to many relationship to one to many in 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)
    # 其他字段...

尽管Django会自动为许多对多关系创建中间表,但如果我们不明确定义它,我已经明确定义了第三个中间表以存储有关每个关系的额外元数据。

因此,根据新的业务需求,我们需要从表中删除许多对多关系,并将其替换为一对多关系,即B的每条记录可以引用多个A对象,但A的每条记录始终只能引用一个B对象。

另外,由于这些表已经在生产中使用,因此我们需要迁移现有数据。

我迄今为止尝试过的事情

  1. 从模型A中删除了许多对多关系。
class A(models.Model):
    name = models.CharField(max_length=255) 
    data = models.ForeignKey(B)
  1. 更新模型C的现有行。

例如:

# 这可能返回多个对象,因为有多对多关系
test = C.objects.filter(a__id=<a对象的id>)

for t in test:
    t.b_id = <指向B记录的某个id>

基本上,在执行上述步骤后,它将会将a_id更新为某个b对象的id。

我面临的问题

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

英文:

What I'm trying to achieve

Till now we've models which have many to many relationships like given below.

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

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....

Although Django creates an intermediate table automatically for many to many relationships if we don't define it explicitly as you can notice I've defined the third intermediate table explicitly to store extra metadata about each relation.

So, as per new business requirements, we need to remove many to many relationships from the table and replace it with one to many relationships i.e each record of B can have more than one references to obj A but record of A will always have one reference to A obj.

also, since these tables are already being used in production therefore we need to migrate the existing data as well.

Things which I've tried so far

Removed many to many from model A.

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

Updated the existing row of model C.

for eg:

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

for t in test:
   t.b_id = &lt;some id pointing to record of B&gt;

basically, after doing the above-mentioned steps, it will update the records with a_id to id of some obj of b.

Problem I'm Facing

when I tried to run makemigrations it asked for the default value of data, I ain't sure what to put there. In model A, I can point to the data field to any record of B, but then I'll lose the use of 3rd table C, also table C is being used widely across the system.
So, Do I need to drop table C?
also, wanted to know is there any better way to change many-to-many to one to many relationships.
Any help would be useful.

答案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
# 命令
python manage.py makemigrations <<<APP_NAME>>> --empty
# 在空的迁移文件中
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. A模型上将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中。

英文:

In such case when your data is inside an intermediary table,
I would recommend you to do the following.

  1. Add and temporary column to your A table
class A(models.Model):
    name = models.CharField(max_length=255)
    data = models.ManyToManyField(B, related_name=&#39;data&#39;, through=&#39;C&#39;)
    # the on_delete part is not related, I added it just to avoid errors.
    tmp_data = models.ForeignKey(B, on_delete=models.DO_NOTHING)

and then do python manage.py makemigrations

  1. Create an empty migration file and make it run this code to copy all A-B relation data from table C to table A
# Command
python manage.py makemigrations &lt;&lt;&lt;APP_NAME&gt;&gt;&gt; --empty
# Inside Empty migrations file
def migrate_c_to_a(apps, schema_editor):
    C = apps.get_model(&#39;&lt;&lt;&lt;APP NAME&gt;&gt;&gt;&#39;, &#39;C&#39;)
    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. Remove data column from you A model and makemigtations
  2. Rename tmp_data column to data on your A model, then makemigrations
    > Careful, make sure that makemigrations won't remove the tmp_data and data and then adds new field data, this is going to cause loss of data, I will show you how to do it in the snippet below.

Inside migrations file, if this happened

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

Change it to this

...
operations = [
        migrations.RemoveField(
            model_name=&#39;a&#39;,
            name=&#39;data&#39;,
        ),
        migrations.RenameField(
            model_name=&#39;a&#39;,
            old_name=&#39;tmp_data&#39;,
            new_name=&#39;data&#39;
        ),
    ]
...
  1. python manage.py migrate

This should do it for good.
after that, if you want to remove table C, it is up to you, since the data inside of it is safely migrated to table A

huangapple
  • 本文由 发表于 2020年1月6日 17:34:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/59609667.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定