Миграции Django, вычислять значение новых полей на основе старых полей перед удалением старых полей?
Мы собираемся переделать одну из наших моделей, перейдя от старых значений даты начала-конец к использованию начальной даты и длины. Однако это создает проблему, поскольку мы хотим дать значения по умолчанию нашим новым полям.
В этом случае, возможно ли запустить миграцию, где мы создадим новое поле, присвоим ему значение, основанное на старых полях start-end модели
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)
Начальные и конечные месяцы могут быть любыми от 1 до 12, поэтому нам нужно выполнить кучу вычислений, чтобы получить правильную длину. Есть ли способ запустить в миграции функцию, которая после добавления новых полей вычисляет их новые значения перед тем, как вызвать удаление старых полей?
Я знаю, что могу создать основные поля для добавления/удаления с помощью makemigrations, но я хочу добавить вычисления значений между ними. Другой вариант, который я рассматривал, это сначала запустить миграцию для добавления полей, затем пользовательскую команду для вычисления полей, а затем вторую миграцию, которая удаляет старые поля, но мне кажется, что в этом случае больше шансов что-то сломать.
Я бы создал пользовательскую миграцию и определил в ней следующую серию операций:
- Добавьте поле длины.
- Обновить поле длины с вычислениями.
- Удалите старое поле.
Таким образом, вы можете создать пользовательскую миграцию с помощью:
python manage.py makemigrations --name migration_name app_name --empty
И затем определите там серию операций, которые вам нужны:
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 ...),
]
Редактирование:
Ваша миграция должна быть примерно такой (update_length_field будет вашей функцией, с теми же параметрами):
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 ...),
]
На базовом уровне это будет выглядеть следующим образом.
Теперь, если вы хотите, чтобы миграцию можно было откатить, вам нужно будет определить вторую функцию, которая делает прямо противоположное тому, что делает update_length_field. И поместить ее в качестве второго параметра migrations.RunPython.
Кроме того, если вы хотите, чтобы миграция была совместима с будущими изменениями в модели (это не обязательно, если миграция будет развернута только один раз), вы должны взять модель из исторической версии кода, что-то вроде:
def update_length_field(apps, schema_editor):
Period = apps.get_model("app_name", "Period")
for period in Period.objects.all():
period.length = ...
period.save()
Дополнительная информация здесь: https://docs.djangoproject.com/en/4.0/ref/migration-operations/