Django:如何从ManyToMany迁移到ForeignKey?

时间:2019-02-15 08:00:44

标签: django postgresql django-models database-migration django-migrations

我正在使用Django和Django Rest Framework构建REST API和服务器。我们正在使用一个postgres数据库。

我需要简化一个设计不良的关系。我们有一个模型(House)与另一个(ManyToMany)有City关系。实际上,如果这是ForeignKey关系,就足够了。

我用Google搜索,但找不到任何博客文章或文档,说明如何朝着这个方向正确迁移。我只能找到其他方式(从FK到M2M)。

我有98%的把握确保服务器上的所有数据都符合FK关系(这意味着我敢肯定,所有房屋都只有一个城市)。我们出于几种原因需要更改关系,但无法保留M2M。

恐怕只是更改模型并运行makemigrationsmigrate。我想知道,您如何正确地从M2M迁移到FK?有什么需要注意的警告事项吗?如果令人惊讶的是有多个城市的房屋,我该如何处理数据?如果重要的话,数据集仍然很小(少于1万个条目)。

非常感谢您。

4 个答案:

答案 0 :(得分:2)

编辑,首先创建数据库备份

首先创建一个新的临时FK关系

_city = models.ForeignKey(...)

并进行迁移

python manage.py makemigration
python manage.py migrate

然后您需要通过创建一个空的迁移文件在相关的应用中创建一个new data migration

python manage.py makemigration --empty myhouseapp

,然后在该文件中手动将新关系从M2M分配给FK。看起来像这样:

from django.db import migrations


def save_city_fk(apps, schema):
    City = apps.get_model('myapp', 'City')
    House = apps.get_model('myapp', 'City')
    for house in House.objects.all():
        house._city = house.cities.all().first()  # Or whatever criterea you want
        house._city.save()


def save_city_m2m(apps, schema):
    # This should do the reverse
    City = apps.get_model('myapp', 'City')
    House = apps.get_model('myapp', 'City')
    for house in House.objects.all():
        if house._city:
            house.cities.add(house._city)


class Migration(migrations.Migration):

    dependencies = [
    ]

    operations = [
        migrations.RunPython(save_city_fk, save_city_m2m)
    ]

删除M2M字段,然后创建另一个迁移。

python manage.py makemigrations

将FK从_city重命名为city并创建最终迁移

python manage.py makemigrations

然后迁移:

python manage.py migrate

答案 1 :(得分:1)

首先,显然,您需要确保(通过进行适当的查询)每个房屋实际上只有一个城市。如果有多个城市的房屋,则需要通过从关系中删除城市,分割房屋等来解决冲突。

在那之后,您可以分步骤进行:

  • House中创建一个新的FK,并使用旧的m2m关系中的一个城市ID进行迁移并填充该城市
  • 重命名m2m字段,进行迁移,然后根据需要将新的FK命名为旧的m2m字段,然后再次迁移
  • 检查您的查询是否有效,并根据需要进行调整
  • 在确信一切正常后,请删除旧的m2m字段-仅在迁移此步骤后,您才失去回滚数据库的能力

答案 2 :(得分:0)

对于M2M关系,最好建立一个新的MOdel来表示该关系。以下是一个雇员具有多个指定并且由多个雇员获得的指定的示例

class Designation(models.Model):
desig_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=60, unique=True)
job_discription = models.CharField(max_length=2000)

def __str__(self):
    return self.title

class Employee(AbstractUser):
middle_name = models.CharField(max_length=20, blank=True,  null=True)
basic_salary = models.FloatField(default=1)
designation = models.ManyToManyField(Designation, default=None, blank=True,
                                     through='EmployeeDesignation')
certification = models.ManyToManyField(Certification, default=None, null=True, blank=True,
                                        through='EmployeeCertification')
emp_img = models.FileField(default=None,upload_to='employees')
leaves_allowed = models.IntegerField(default=25)
leave_balance = models.IntegerField(default=25)
leave_count = models.IntegerField(default=0)
objects = EmployeeManager()

def __str__(self):
    return self.first_name+' '+self.last_name

class EmployeeDesignation(models.Model):

desig = models.ForeignKey(Designation, on_delete=models.CASCADE)
emp = models.ForeignKey(Employee, on_delete=models.CASCADE)



class Meta:
    unique_together = (('emp', 'desig'),)

答案 3 :(得分:0)

基于Timmy's answer,这是我所做的:

  1. 我添加了一个像city = models.ForeignKey(City, related_name='has_houses', blank=True, null=True)这样的字段,以避免related_name的反向关系并使FK空白。然后我运行了makemigrationsmigrate

  2. 接下来,我运行python manage.py makemigrations --empty houses是因为我的应用名为houses

  3. 我添加了迁移代码(请参见下文)。然后我跑了migrate

  4. 我删除了M2M字段以及related_namenullblank约束,最后一次运行了makemigrationsmigrate

  5. p>

迁移代码:

# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2019-02-15 09:09
from __future__ import unicode_literals

from django.db import migrations


def save_city_fk(apps, schema):
    City = apps.get_model('cities', 'City')
    House = apps.get_model('houses', 'House')
    for house in House.objects.all():
        house.city = house.cities.all().first()
        house.save()


class Migration(migrations.Migration):

    dependencies = [
        ('houses', '0003_auto_20190215_0949'),
    ]

    operations = [
        migrations.RunPython(save_city_fk),
    ]