Django`squashmigrations` оставляет много RemoveField

Я пытаюсь использовать Django manage.py squashmigrations для уменьшения количества файлов миграции и операций миграции, после нескольких лет их накопления.

Django должен, по идее, оптимизировать миграции, и в некоторой степени он это делает. Однако другие случаи, которые должны быть очевидно оптимизированы, пропускаются, например, ниже, где простое поле AddField+RemoveField не оптимизируется. Только AddField вставляется в CreateModel, но RemoveField все равно остается, вместо того чтобы полностью убрать поле из CreateModel.

В идеале после сквоша не должно остаться ни одного RemoveField, если я не ошибаюсь.

Миграция 1: Добавить модель

class Migration(migrations.Migration):
    dependencies = [("data", "0104")]

    operations = [
        migrations.CreateModel(
                    name="MyModel",
                    fields=[... ]
        )
    ]

Миграция 2: Добавление поля в модель

class Migration(migrations.Migration):
    dependencies = [("data", "0107")]

    operations = [
        migrations.AddField(
            model_name="mymodel",
            name="process_name",
            field=models.CharField(default=None, max_length=300, null=True),
        ),
    ]

Миграция 3: Удаление того же поля из модели

class Migration(migrations.Migration):
    dependencies = [("data", "0121")]

    operations = [migrations.RemoveField(model_name="mymodel", name="process_name")]

Результирующая сминаемая миграция:

class Migration(migrations.Migration):
    replaces = [...]

    initial = True

    operations = [
        migrations.CreateModel(
            name="MyModel",
            fields=[
                (
                    "process_name",                  # Should be possible to leave out ???
                    models.CharField(default=None, max_length=300, null=True),
                ),
            ],
        ),
        migrations.RemoveField(model_name="mymodel", name="process_name"),           # ???
    ]

Ожидается ли это или что может вызвать это?

Я не использую никаких расширенных функций миграции, таких как RunSql или RunPython. Нет других таблиц или миграций, которые зависят от поля process_name.

Есть ли способ дать подсказку squashmigrations? Могу ли я запустить сквош еще раз, чтобы оптимизировать дальше?

(Django версии 5.1)

Я предполагаю, что это связано с тем, как их оптимизатор реализован .

Ваша миграция состоит из трех операций

  • CreateModel
  • AddField
  • RemoveField

Когда запускается оптимизатор

    for i, operation in enumerate(operations):
        right = True  # Should we reduce on the right or on the left.
        # Compare it to each operation after it
        for j, other in enumerate(operations[i + 1 :]):
            result = operation.reduce(other, app_label)
            ....

Итерация начинается с i=0, operation=CreateModel и ищет все, что можно .reduce

j=0, other=AddField является сводимой, поэтому она была сведена к одной операции

        if isinstance(operation, AddField):
            return [
                CreateModel(
                    self.name,
                    fields=self.fields + [(operation.name, operation.field)],
                    options=self.options,
                    bases=self.bases,
                    managers=self.managers,
                ),
            ]

Во время итерации i=0, j=1, other=RemoveField не является сводимым, так как поле process_name не существовало в операции i=0, operation=CreateModel.

Во время второй итерации i=1, operation=AddField выглядит оптимизируемым с RemoveField, но я полагаю, что оптимизируемая операция не была выбрана оптимизатором

            if isinstance(result, list):
                in_between = operations[i + 1 : i + j + 1]
                if right:
                    new_operations.extend(in_between)
                    new_operations.extend(result)
                elif all(op.reduce(other, app_label) is True for op in in_between):
                    # Perform a left reduction if all of the in-between
                    # operations can optimize through other.
                    new_operations.extend(result)
                    new_operations.extend(in_between)
                else:
                    # Otherwise keep trying.
                    new_operations.append(operation)
                    break
                new_operations.extend(operations[i + j + 2 :])
                return new_operations

Я лишь бегло просмотрел исходный код, поэтому вполне вероятно, что я интерпретирую все неправильно.

Если ваша история миграции проста и все машины (production/dev/etc) находятся под вашим контролем. Возможно, будет достаточно

  • сначала измените миграцию на ту, которая была до сминания migrate data 0103
  • .
  • удалите миграции, которые вы хотите смять (с 0107 по 0121), и создайте новый набор, снова выполнив makemigrations data.
  • нормально мигрировать

или с небольшим количеством ручной работы

  • squashmigrations data 0107 0121 first
  • перевести скомканную миграцию в нормальную миграцию (doc), то есть
    • удалить атрибут replace
    • обновить атрибут dependencies в других файлах миграции, чтобы не использовать оригинальные несквошированные файлы миграции (0107 - 0121)
    • удалите замененные оригинальные миграции
  • squashmigrations data 0104 0107_squash_0121 снова

Зависит от вашей мотивации, вся эта ручная работа с файлами миграции может не стоить того, как отметили другие комментаторы

При подавлении миграции есть и другие проблемы, которые могут быть неочевидны. Я написал заметку здесь

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