Миграции 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, но я хочу добавить вычисления значений между ними. Другой вариант, который я рассматривал, это сначала запустить миграцию для добавления полей, затем пользовательскую команду для вычисления полей, а затем вторую миграцию, которая удаляет старые поля, но мне кажется, что в этом случае больше шансов что-то сломать.

Я бы создал пользовательскую миграцию и определил в ней следующую серию операций:

  1. Добавьте поле длины.
  2. Обновить поле длины с вычислениями.
  3. Удалите старое поле.

Таким образом, вы можете создать пользовательскую миграцию с помощью:

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/

Вернуться на верх