Django Migrations Add Field with Default as Function of Model
I added a new, non-nullable field to my Django model and am trying to use migrations to deploy that change. How would I set default value to use for existing models to be some function of those models rather than a constant?
As an example let's say I previously had a created_on
field and I just added an updated_on
field whose value I want to set initially to the model's created_on
. How would I do this in a migration?
This is what I am trying to start with:
migrations.AddField(
model_name='series',
name='updated_as',
field=models.DateTimeField(default=????, auto_now=True),
preserve_default=False,
),
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:
- Initially make the field nullable, so the column will be added.
- Call a function to populate the field as needed.
- 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 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
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),