侧边栏壁纸
  • 累计撰写 207 篇文章
  • 累计创建 58 个标签
  • 累计收到 5 条评论

Django在迁移中添加唯一非空字段

barwe
2024-05-17 / 0 评论 / 0 点赞 / 171 阅读 / 2,982 字
温馨提示:
本文最后更新于 2024-05-17,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

首先在模型上添加一个唯一非空的 uuid 字段:

import uuid
from django.db import models

class MyModel(models.Model):
    #...
    uuid = models.UUIDField(default=uuid.uuid4, unique=True)

执行迁移:

python manage.py makemigrations myapp

因为是向已有模型添加 唯一非空字段,这会导致数据表中已有的数据该字段的值不能确定,默认迁移并不能在每一个已有数据上分别执行 uuid.uuid4() 函数来设置不同值。

因此生成迁移文件时会出现下面情况:

Callable default on unique field chat.uuid will not generate unique values upon migrating.
Please choose how to proceed:
 1) Continue making this migration as the first step in writing a manual migration to generate unique values described here: https://docs.djangoproject.com/en/4.1/howto/writing-migrations/#migrations-that-add-unique-fields.
 2) Quit and edit field options in models.py.

这里我们直接输入 1 即可,先强制生成迁移文件。

检查迁移文件列表,我们会发现多了一个包含 AddField 操作的迁移文件,形如 0004_mymodel_uuid.py,内容如下:

from django.db import migrations, models
import uuid

class Migration(migrations.Migration):
    dependencies = [
        ("app_workflow", "0047_remove_pipelinereport_creator_and_more"),
    ]

    operations = [
        migrations.AddField(
            model_name="chat",
            name="uuid",
            field=models.UUIDField(default=uuid.uuid4, unique=True),
        ),
    ]

此时直接执行迁移是行不通的,需要我们在迁移中手动给数据表中已经存在的数据设置字段值,以确保唯一性约束和非空约束。

首先我们修改 0004_mymodel_uuid.py 文件,将其中的 "unique=True" 改成 "null=True",这样会先创建一个值可以重复但不能为空的字段。

然后我们新建一个空的迁移用来为已经存在的数据手动设置字段值:

python manage.py makemigrations myapp --empty

这将生成形如 0005_auto_20240517_1100.py 的迁移文件,内容如下:

from django.db import migrations

class Migration(migrations.Migration):
    dependencies = [
        ("app_workflow", "0004_mymodel_uuid"),
    ]

    operations = []

首先将其重名成 0005_populate_mymodel_uuid_values.py(注意将 mymodel 替换成实际的模型名称)。

然后重写内容,使用 migrations.RunPython 操作来更新字段值:

from django.db import migrations
import uuid

def gen_uuid(apps, schema_editor):
    MyModel = apps.get_model("myapp", "MyModel")
    for row in MyModel.objects.all():
        row.uuid = uuid.uuid4()
        row.save(update_fields=["uuid"])

class Migration(migrations.Migration):
    dependencies = [
        ("myapp", "0004_mymodel_uuid"),
    ]

    operations = [
        # omit reverse_code=... if you don't want the migration to be reversible.
        migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
    ]

migrations.RunPython 的第一个参数是正向操作,reverse_code 是逆向操作。

migrations.RunPython.noop() 函数表示一个空操作。

在正向操作 gen_uuid() 函数中,我们取出模型已有的数据,然后分别为其设置 uuid 值,在客观上实现唯一非空。

最后我们还需要一个恢复字段唯一性约束和非空约束的操作,我们再次新建一个空迁移文件:

python manage.py makemigrations myapp --empty

将其重命名为 0006_remove_mymodel_uuid_null.py(注意将 mymodel 替换成实际的模型名称)。

修改内容,使用 AlterField 操作来恢复唯一性约束:

from django.db import migrations, models
import uuid

class Migration(migrations.Migration):
    dependencies = [
        ("myapp", "0005_populate_mymodel_uuid_values"),
    ]

    operations = [
        migrations.AlterField(
            model_name="chat",
            name="uuid",
            field=models.UUIDField(default=uuid.uuid4, unique=True),
        ),
    ]

最后我们执行迁移:

python manage.py migrate myapp
Operations to perform:
  Apply all migrations: myapp
Running migrations:
  Applying myapp.0004_mymodel_uuid... OK
  Applying myapp.0005_populate_mymodel_uuid_values... OK
  Applying myapp.0006_remove_mymodel_uuid_null... OK

参考资料:

0

评论区