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

Posted

技术标签:

【中文标题】将 Django 模型字段的类型从 CharField 更改为 ForeignKey【英文标题】:Change type of Django model field from CharField to ForeignKey 【发布时间】:2016-06-30 04:17:36 【问题描述】:

我需要将我的一个 Django 模型中的字段类型从 CharField 更改为 ForeignKey。这些字段已经填充了数据,所以我想知道最好或正确的方法是什么。我可以只更新字段类型并迁移,还是有任何可能的“陷阱”需要注意? 注意:我只使用普通的 Django 管理操作(makemigrationsmigrate),而不是 South。

【问题讨论】:

如果您对 CharField 没有唯一约束,请注意重复值?这实际上取决于您之前是否在复制数据,或者只是想将其移出到自己的表中。如果是后者,最好的做法实际上取决于您的数据的含义。 Django 无法推断 CharField 和 ForeignKey 之间的某种魔术转换。您必须自己编写一个合理的迁移。 我认为这与您正在寻找的内容相似。 ***.com/questions/5373906/… 【参考方案1】:

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

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

from django.db import models

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

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

第一步,为 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)

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

./manage.py makemigrations discography

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

./manage.py makemigrations --empty --name transfer_artists discography

完成此空迁移后,您希望向其中添加一个 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()

现在您的数据已传输到新字段,您实际上可以完成并保留所有内容,将新字段用于所有内容。或者,如果你想做一些清理工作,你想再创建两个迁移。

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

这是在多个步骤中完成的,以确保 Django 正确识别操作。您可以手动创建迁移来处理此问题,但我会让您自己弄清楚。

【讨论】:

感谢您及时而全面的回复。这也刺激了我终于坐下来好好学习 Django 迁移! 不应该是./manage.py makemigrations --empty ...吗?第 2 步有 makemigration(单数)。 谢谢,我只需要 --empty 在两个应用程序之间进行迁移 我更喜欢在纯 python 脚本中传输数据,但在 django 迁移中更改数据库架构。 Joey 我添加了一个问题,您可能想考虑复制以补充您的问题。随意这样做。【参考方案2】:

这是 Joey 出色回答的延续。 如何将新字段重命名为原名?

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

请注意,此过程不会阻止您重构代码。例如,如果您在 CHOICES 中使用 CharField,则您正在使用 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',
    ),
]

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

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

【讨论】:

【参考方案3】:

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

这是我的用例中的模型,由 CompanyEmployee 模型组成。我们必须将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)

第 1 步

在Employee中创建外键designation_link并将其标记为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)

第 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 = [
    ]

第 3 步

使用循环所有Employees 的函数填充空迁移,创建Designation 并将其链接到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),
    ]

第 4 步

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

python app_code/manage.py migrate core 0007

【讨论】:

解释得很好@jerrymouse

以上是关于将 Django 模型字段的类型从 CharField 更改为 ForeignKey的主要内容,如果未能解决你的问题,请参考以下文章

如何从 django 模型中为模型字段定义 json 类型

如何重命名从 Django 模型自动生成的 GraphQL 类型字段?

将属性添加到 Django 模型的 Meta 类中

模型字段类型从 CharField 更改为 ForeignKey 时 Django 模板损坏

将 url 添加到 Django admin 中 TabularInline 中的字段

在 Django 模型中保存时区