Django迁移:添加字段并将其默认值设置为模型函数

95

我向我的 Django 模型添加了一个新的非空字段,并尝试使用迁移来部署该更改。如何将现有模型的默认值设置为这些模型的某些函数而不是常量?

举个例子,假设我之前有一个 created_on 字段,我刚刚添加了一个 updated_on 字段,其值我想最初设置为模型的 created_on。我应该如何在迁移中实现这一点?

以下是我要开始的内容:

migrations.AddField(
    model_name='series',
    name='updated_as',
    field=models.DateTimeField(default=????, auto_now=True),
    preserve_default=False,
),
4个回答

149

我刚学会了如何使用单个迁移来完成这个操作!

当运行makemigrations时,django会要求你设置一个一次性的默认值。在此定义任何东西以使其正常,然后你将得到你提到的迁移AddField

migrations.AddField(
    model_name='series',
    name='updated_as',
    field=models.DateTimeField(default=????, auto_now=True),
    preserve_default=False,
),

将这个操作拆分为三个操作:

  1. 最开始将该字段设置为可空的,这样就可以添加该列。
  2. 调用函数按需填充字段。
  3. 使用 AlterField 修改该字段,使其变为不可空(与上述相同,没有默认值)。

这样你最终会得到类似以下的结果:

migrations.AddField(
    model_name='series',
    name='updated_as',
    field=models.DateTimeField(null=True, auto_now=True),
),
migrations.RunPython(set_my_defaults, reverse_func),
migrations.AlterField(
    model_name='series',
    name='updated_as',
    field=models.DateTimeField(auto_now=True),
),

假设您的函数定义如下:

def set_my_defaults(apps, schema_editor):
    Series = apps.get_model('myapp', 'Series')
    for series in Series.objects.all().iterator():
        series.updated_as = datetime.now() + timedelta(days=series.some_other_field)
        series.save()

def reverse_func(apps, schema_editor):
    pass  # code for reverting migration, if any

除了,你知道的,不算糟糕。

注意:考虑使用F表达式和/或数据库函数来提高大型数据库的迁移性能。


8
您可以使用以下代码: Series.objects.all().update(updated_as=datetime.now() + timedelta(days=series.some_other_field))

update函数作为单个请求运行。

- Искрен Станиславов
10
应该使用migrations.RunPython.noop代替reverse_func。请查看文档以获取进一步的细节 - https://docs.djangoproject.com/en/2.1/ref/migration-operations/#django.db.migrations.operations.RunPython.noop - Fusion
能否从一个函数中获取类似的动态值,并在提示中要求默认值?提示确实提到:
请现在输入有效的Python默认值
- user3245268
@Fusion 或者如果您不希望迁移是可逆的,您可以省略第二个参数。请参阅https://docs.djangoproject.com/en/2.2/howto/writing-migrations/#id6 - hashlash

25

你需要分两次迁移来完成。首先,添加你的字段,但要将其设为可空。像往常一样创建一个迁移文件。然后将你的字段设置为不可空,并再次运行makemigrations,但不要立即进行迁移。打开第二个迁移并在顶部定义一个函数:

def set_field_values(apps, schema_editor):
    # use apps.get_model("app_name", "model_name") and set the defualt values

然后,在您的迁移文件中有一组操作。在修改字段操作之前添加以下内容:

then, in your migration file there is a list of operations. Before the alter field operation add


RunPython(set_field_values)

而且它应该做到这一点


10

为了在未来撤销迁移,您还应该为您的函数set_my_defaults()定义一个反向操作。

def reverse_set_default(apps, schema_editor):
    pass

在这种情况下,reverse函数不需要执行任何操作,因为您正在删除该字段。
并将其添加到RunPython中:
migrations.RunPython(set_my_defaults, reverse_set_default),

1
你应该将这个作为评论发布到@FraggaMuffin的回答中。 - Samuel Dion-Girardeau
2
如果你只是使用带有 pass 的函数,那么你应该使用 migrations.RunPython.noop。因此,migrations.RunPython(set_my_defaults, migrations.RunPython.noop) - Richard de Wit

1

运行makemigrations并根据字段类型设置默认值。

migrations.AddField(
    model_name='series',
    name='updated_as',
    field=models.DateTimeField(null=True, auto_now=True),
),
migrations.RunSQL(
  (
   "UPDATE myapp_series x"
   "SET x.updated_as = x.created_on"
   ),
   migrations.RunSQL.noop
),
migrations.AlterField(
    model_name='series',
    name='updated_as',
    field=models.DateTimeField(auto_now=True),
),

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接