将Django模型字段的类型从CharField更改为ForeignKey

Chr*_*isM 16 django foreign-keys database-migration

我需要将我的一个Django模型中的字段类型更改CharFieldForeignKey.这些字段已经填充了数据,所以我想知道这样做的最佳或正确方法是什么.我可以只更新字段类型并进行迁移,还是有任何可能的"陷阱"需要注意?注意:我只使用vanilla Django管理操作(makemigrationsmigrate),而不是南方.

Joe*_*elm 55

这可能是您想要进行多阶段迁移的情况.我对此的建议如下所示.

首先,我们假设这是您的初始模型,在一个名为的应用程序中discography:

from django.db import models

class Album(models.Model):
    name = models.CharField(max_length=255)
    artist = models.CharField(max_length=255)
Run Code Online (Sandbox Code Playgroud)

现在,您意识到您想要为艺术家使用ForeignKey.好吧,如上所述,这不仅仅是一个简单的过程.它必须分几步完成.

步骤1,为ForeignKey添加一个新字段,确保将其标记为null:

from django.db import models

class Album(models.Model):
    name = models.CharField(max_length=255)
    artist = models.CharField(max_length=255)
    artist_link = models.ForeignKey('Artist', null=True)

class Artist(models.Model):
    name = models.CharField(max_length=255)
Run Code Online (Sandbox Code Playgroud)

...并为此更改创建迁移.

./manage.py makemigrations discography
Run Code Online (Sandbox Code Playgroud)

第2步,填充新字段.为此,您必须创建一个空迁移.

./manage.py makemigrations --empty --name transfer_artists discography
Run Code Online (Sandbox Code Playgroud)

完成此空迁移后,您希望向其添加单个RunPython操作以链接记录.在这种情况下,它可能看起来像这样:

def link_artists(apps, schema_editor):
    Album = apps.get_model('discography', 'Album')
    Artist = apps.get_model('discography', 'Artist')
    for album in Album.objects.all():
        artist, created = Artist.objects.get_or_create(name=album.artist)
        album.artist_link = artist
        album.save()
Run Code Online (Sandbox Code Playgroud)

现在您的数据已转移到新字段,您实际上可以完成并保留所有内容,使用新字段进行所有操作.或者,如果要进行一些清理,则需要再创建两次迁移.

对于第一次迁移,您需要删除原始字段artist.对于第二次迁移,请将新字段重命名artist_linkartist.

这是通过多个步骤完成的,以确保Django正确识别操作.你可以手动创建一个迁移来处理这个问题,但我会留给你解决.

  • 感谢您及时而全面的答复。这也激发了我终于坐下来好好学习 Django 迁移! (2认同)

Per*_*ell 6

这是乔伊精彩回答的延续。 如何将新字段重命名为原来的名称?

如果该字段有数据,则可能意味着您正在项目的其他地方使用它,因此此解决方案将为您留下一个命名不同的字段,并且您必须重构项目以使用新字段或删除旧字段,然后重命名新的。

请注意,此过程不会阻止您重构代码。例如,如果您将 CharField 与 CHOICES 结合使用,则可以使用get_filename_display()访问其内容。

如果您尝试删除该字段以进行迁移,然后重命名其他字段并进行另一次迁移,您将看到 Django 抱怨,因为您无法删除项目中正在使用的字段。

只需按照 Joey 的解释创建一个空迁移,并将其放入操作中即可:

operations = [
    migrations.RemoveField(
        model_name='app_name',
        name='old_field_name',
    ),
    migrations.RenameField(
        model_name='app_name',
        old_name='old_field_name_link',
        new_name='old_field_name',
    ),
]
Run Code Online (Sandbox Code Playgroud)

然后运行 ​​migrate,您将在数据库中进行更改,但显然不在您的模型中,现在是时候删除旧字段并将新的foreignkey字段重命名为原始名称。

我不认为这样做是特别hacky,但是,只有当你完全理解你在搞乱什么时才做这种事情。


jer*_*use 6

在 Joey 的回答之上添加了 Django 2.2.11 的详细步骤。

这是我的用例中的模型,由 aCompanyEmployee模型组成。我们必须转换designation为外键字段。应用名称被称为core

class Company(CommonFields):
    name = models.CharField(max_length=255, blank=True, null=True

class Employee(CommonFields):
    company = models.ForeignKey("Company", on_delete=models.CASCADE, blank=True, null=True)
    designation = models.CharField(max_length=100, blank=True, null=True)
Run Code Online (Sandbox Code Playgroud)

第1步

designation_link在Employee中创建外键并标记为null=True

class Designation(CommonFields):
    name = models.CharField(max_length=255)
    company = models.ForeignKey("Company", on_delete=models.CASCADE, blank=True, null=True)

class Employee(CommonFields):
    company = models.ForeignKey("Company", on_delete=models.CASCADE, blank=True, null=True)
    designation = models.CharField(max_length=100, blank=True, null=True)
    designation_link = models.ForeignKey("Designation", on_delete=models.CASCADE, blank=True, null=True)
Run Code Online (Sandbox Code Playgroud)

第2步

创建空迁移。使用命令:

python app_code/manage.py makemigrations --empty --name transfer_designations core

这将在migrations目录中创建以下文件。

# Generated by Django 2.2.11 on 2020-04-02 05:56

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('core', '0006_auto_20200402_1119'),
    ]

    operations = [
    ]
Run Code Online (Sandbox Code Playgroud)

第 3 步

使用循环遍历 all 的函数填充空迁移Employees,创建 aDesignation并将其链接到Employee.

在我的用例中,每个Designation也链接到一个Company. 这意味着Designation可能包含两行“经理”,一行用于 A 公司,另一行用于 B 公司。

最终迁移看起来像这样:

# core/migrations/0007_transfer_designations.py

# Generated by Django 2.2.11 on 2020-04-02 05:56

from django.db import migrations

def link_designation(apps, schema_editor):
    Employee = apps.get_model('core', 'Employee')
    Designation = apps.get_model('core', 'Designation')
    for emp in Employee.objects.all():
        if(emp.designation is not None and emp.company is not None):
            desig, created = Designation.objects.get_or_create(name=emp.designation, company=emp.company)
            emp.designation_link = desig
            emp.save()

class Migration(migrations.Migration):

    dependencies = [
        ('core', '0006_auto_20200402_1119'),
    ]

    operations = [
        migrations.RunPython(link_designation),
    ]
Run Code Online (Sandbox Code Playgroud)

第四步

最后使用以下命令运行此迁移:

python app_code/manage.py migrate core 0007