Django 迁移添加默认字段作为模型函数

我在 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,
),
34777 次浏览

You need to do it in two migrations. First of all, add your field, but make nullable. Create a migration file as usual. After that set your field to not-nullable and run makemigrations again, but don't lauch migrate yet. Open the second migration and define a function at the top:

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)

and it should do it

I just learned how to do this with a single migration!

When running makemigrations django should ask you to set a one-off default. Define whatever you can here to keep it happy, and you'll end up with the migration AddField you mentioned.

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

Change this one operation into 3 operations:

  1. Initially make the field nullable, so the column will be added.
  2. Call a function to populate the field as needed.
  3. Alter the field (with AlterField) to make it not nullable (like the above, with no default).

So you end up with something like:

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),
),

with your functions defined as something like:

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

Except, you know, not terrible.

Note: Consider using F expressions and/or database functions to increase migration performance for large databases.

You should also define a reverse for your function set_my_defaults(), in case you what to revert the migration in the future.

def reverse_set_default(apps, schema_editor):
pass

The reverse function in this case need to do nothing, since you are removing the field.

And add it to RunPython:

migrations.RunPython(set_my_defaults, reverse_set_default),

run makemigrations and set a default value based on field type.

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),
),