Почему Django случайно ломается, когда я использую удаленную модель в миграции?

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

Процесс:

  • Внесите необходимые изменения в кодовую базу, включая удаление модели и ссылок на нее.
  • Создайте миграцию, которая:
    • копирует данные из модели
    • удаляет модель

Вот пример того, как выглядела бы такая миграция:

from django.db import migrations, models

def doit(apps, schema_editor):
    OldModel = apps.get_model("myapp", "OldModel")
    NewModel = apps.get_model("myapp", "NewModel")
    for obj in OldModel.objects.all():
        ...transform and save data in NewModel...

class Migration(migrations.Migration):
    dependencies = [("myapp", "0001_initial")]
    operations = [
        migrations.RunPython(doit, reverse_code=migrations.RunPython.noop),
        migrations.DeleteModel(name="OldModel"),
    ]

Иногда миграция выполняется, как и ожидалось. В других случаях миграция завершается неудачей:

LookupError: App 'myapp' doesn't have a 'OldModel' model.

Я не могу найти никакой заметной разницы между миграциями, которые работают, и миграциями, которые взрываются таким образом. У меня было множество примеров каждой из них, и я изрядно поломал голову, пытаясь найти разницу.

Насколько я понимаю, объект apps, переданный вашей функции методом RunPython(), обеспечивает симуляцию всех моделей как они существуют в данный момент в цепочке миграции , независимо от состояния вашей кодовой базы, поэтому не должно иметь значения, что модель больше не существует.

И все же, случайным образом, иногда это происходит?

Только что произошел небольшой прорыв. Вот фрагмент моей текущей миграции для контекста:

def migrate__fundmanager(apps, schema_editor):
    Company = apps.get_model("everest", "Company")
    Fund = apps.get_model("everest", "Fund")
    FundManager = apps.get_model("everest", "FundManager")

    for fm in FundManager.objects.all():
        ...

На этом этапе миграции я получил ошибку LookupError для FundManager - хотя его можно было найти буквально двумя строками выше.

Изучение книги слежения дало несколько подсказок:

  • вызывается пакет dirtyfields, что является напоминанием о том, что "симулированная модель" - это только кожа - реальные базовые классы под ней все еще ссылаются
  • поток выполнения, проходящий через dirtyfields, приводит к еще одному вызову apps.get_model() для "FundManager", но на этот раз это реальный django.apps, а не симулированный, используемый в самой миграции
  • .

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

  1. избегать использования крутых базовых классов, таких как пакет dirtyfields (не лучший вариант, так как он очень полезен)
  2. использовать RunSQL для миграции данных вместо RunPython (часто очень сложно по сравнению с решением на Python)

UPDATE: Только что придумал третий вариант - редактирование моей миграции "0001_initial.py" и удаление dirtyfields базового класса из CreateModel заявления FundManager:

bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),

Это сработало. Я по-прежнему совершенно не понимаю, почему это было необходимо для этой модели, но не для другой, почти идентичной, которую я удаляю в той же самой миграции, и которая была удалена без LookupError несмотря на то, что к ней все еще был присоединен базовый класс...dirtyfields

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