We are intenting to rework one of our models from old start-end date values to use starting date and length. However, this does pose a challenge in that we want to give default values to our new fields.
In this case, is it possible to run migration where we create new field, give it a value based on models old start-end fields
from datetime import date as Date
from django.db import models
class Period(models.Model):
#These are our new fields to replace old fields
period_length=models.IntegerField(default=12)
starting_date=models.DateField(default=Date.today)
#Old fields. We want to calculate the period length based on these before we remove them
start_day = models.IntegerField(default=1)
start_month = models.IntegerField(default=1)
end_day = models.IntegerField(default=31)
end_month = models.IntegerField(default=12)
Starting months and ending months can be anywhere between 1 and 12, so we need to run bunch of calculations to get the correct length. Is there a way for us to run a function in migrations that, after adding the new fields, calculates their new values before calling for removal of the old fields?
I do know I can create basic add/remove fields with makemigrations, but I want to add the value calculations in between. Other option I have considered is to first run a migration to add the fields, then a custom command to calculate fields and then a second migration that deletes the old fields, but this feels like it has greater chance of breaking something.
What I would do is create a custom migration and define the following series of operations there:
So you can create a custom migration with:
python manage.py makemigrations --name migration_name app_name --empty
And then define there the series of operations you need:
operations = [
migrations.AddField (... your length field...),
migrations.RunPython (... the name of your function to compute and store length field ...),
migrations.RemoveField (... your end_date field ...),
]
Edit:
Your migration should be something as below (update_length_field would be your function, with the same parameters):
class Migration(migrations.Migration):
dependencies = [
('app_name', 'your_previous_migration'),
]
def update_length_field(apps, schema_editor):
for period in Period.objects.all():
period.length = ... whatever calculations you need ...
period.save()
operations = [
migrations.AddField (... your length field...),
migrations.RunPython(update_length_field),
migrations.RemoveField (... your end_date field ...),
]
At a basic level, it would be like this.
Now, if you want the migration to be able to be rolled back, you will have to define a second function that does exactly the opposite of what update_length_field does. And put it as the second parameter of migrations.RunPython.
Also, if you want the migration to be compatible with future changes to the model (this is not necessary if the migration is to be deployed only once), you must take the model from the historical version of the code, something like:
def update_length_field(apps, schema_editor):
Period = apps.get_model("app_name", "Period")
for period in Period.objects.all():
period.length = ...
period.save()
More information here: https://docs.djangoproject.com/en/4.0/ref/migration-operations/