Миграции

Django использует миграции для переноса изменений в моделях (добавление поля, удаление модели и т.д.) на структуру базы данных. Миграции создавались в основном для автоматической работы, но вам необходимо знать когда их создавать, запускать и как решать различные проблемы.

Команды

Django предоставляет две команды для работы с миграциями и структурой базы данных:

  • migrate, which is responsible for applying and unapplying migrations.
  • makemigrations, которая отвечает за создание новых миграций на основе изменений в моделях.
  • sqlmigrate, которая выводит SQL запросы для миграции.
  • showmigrations, which lists a project’s migrations and their status.

Следует рассматривать миграции, как систему контроля версий для базы данных. makemigrations отвечает за сохранение состояния моделей в файле миграции - аналог коммита - а migrate отвечает за их применение к базе данных.

Файлы с миграциями находятся в каталоге «migrations» приложения. Они являются частью приложения и должны распространятся вместе с остальным кодом приложения. Они должны создаваться при разработке и потом применятся на машинах коллег, тестовом и «боевом» серверах.

Примечание

Вы можете изменить пакет, который хранит миграции, указав его в настройке MIGRATION_MODULES.

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

Django создаст миграции при любых изменениях модели или полей - даже тех параметров, которые не влияют на базу данных - т.к. единственный способ восстановить состояние моделей - это хранить все изменения в истории. Вам могут понадобится эти параметры в миграциях данных в будущем (например, если вы добавите собственную проверку данных).

Поддержка бэкендами

Миграции поддерживаются всеми бэкендами, которые предоставляет Django, как и сторонними, если они реализуют API внесения изменений в структуру базы данных (через класс SchemaEditor).

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

PostgreSQL

PostgreSQL is the most capable of all the databases here in terms of schema support.

The only caveat is that prior to PostgreSQL 11, adding columns with default values causes a full rewrite of the table, for a time proportional to its size. For this reason, it’s recommended you always create new columns with null=True, as this way they will be added immediately.

MySQL

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

Кроме того, MySQL будет полностью переписывать таблицы почти для каждой операции изменения структуры, это занимает время, пропорциональное количеству строк в таблице. На медленном оборудовании это может занимать больше, чем минута на миллион строк. Добавление нескольких столбцов в таблицу с всего несколькими миллионами строк может заблокировать ваш сайт на более чем десять минут.

Finally, MySQL has relatively small limits on name lengths for columns, tables and indexes, as well as a limit on the combined size of all columns an index covers. This means that indexes that are possible on other backends will fail to be created under MySQL.

SQLite

SQLite очень плохо поддерживает изменения в структуре базы данных, но Django пытается эмулировать их следующим образом:

  • Создание новой таблицы для новой структуры
  • Копирование данных в новую таблицу
  • Удаление старой таблицы
  • Переименование новой таблицы

Этот процесс как правило хорошо работает, но может быть медленным и иногда глючит. Не рекомендуется использовать и мигрировать SQLite на «боевом» сервере, если вы не очень осведомлены о рисках и его ограничениях. Django поддерживает SQLite, чтобы позволить разработчикам использовать SQLite для разработки простых проектов.

Работа с миграциями

Django can create migrations for you. Make changes to your models - say, add a field and remove a model - and then run makemigrations:

$ python manage.py makemigrations
Migrations for 'books':
  books/migrations/0003_auto.py:
    - Alter field author on book

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

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

$ python manage.py migrate
Operations to perform:
  Apply all migrations: books
Running migrations:
  Rendering model states... DONE
  Applying books.0003_auto... OK

После того, как миграция отработала, добавьте миграции и изменения к моделям в одном коммите - таким образом, когда другие разработчики (или на «боевом» сервере) обновят код, они получат как изменения ваших моделей, так и миграции для них.

If you want to give the migration(s) a meaningful name instead of a generated one, you can use the makemigrations --name option:

$ python manage.py makemigrations --name changed_my_model your_app_label

Контроль версий

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

Не волнуйтесь - номер просто для разработчиков, Django главное, чтобы миграции имели уникальные названия. Миграции определяют зависимости к другим миграции - включая предыдущую миграцию из этого приложения. Таким образом можно определить наличие двух миграций с неправильным порядком.

Когда это произойдет, Django предложит выбрать один из вариантов действий. Если Django определит, что достаточно безопасно может сам решить проблему, он предложит автоматически поменять порядок конфликтующим миграциям. Если нет, то вам придется самостоятельно это сделать - не волнуйтесь, это не трудно, и описано в Файлы с миграциями ниже.

Зависимости

While migrations are per-app, the tables and relationships implied by your models are too complex to be created for one app at a time. When you make a migration that requires something else to run - for example, you add a ForeignKey in your books app to your authors app - the resulting migration will contain a dependency on a migration in authors.

Это означает, что при запуске миграций, сначала применяются миграции приложения authors, создаётся таблица, на которую ссылается ForeignKey, и затем миграция, которая создаёт ForeignKey. Иначе миграция попыталась бы создать ForeignKey на таблицу, которая может ещё не существовать, и мы получили бы ошибку.

Такое поведение зависимостей влияет на большинство миграционных операций, которые вы ограничили рамками одного приложения. Подобное ограничение (в makemigrations или migrate) было отличным обещанием, но не гарантией. Любое другое приложение, которое потребуется для удовлетворения зависимости, будет использовано автоматически.

Apps without migrations must not have relations (ForeignKey, ManyToManyField, etc.) to apps with migrations. Sometimes it may work, but it’s not supported.

Файлы с миграциями

Migrations are stored as an on-disk format, referred to here as «migration files». These files are actually normal Python files with an agreed-upon object layout, written in a declarative style.

Базовый файл миграции выглядит следующим образом:

from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [('migrations', '0001_initial')]

    operations = [
        migrations.DeleteModel('Tribble'),
        migrations.AddField('Author', 'rating', models.IntegerField(default=0)),
    ]

При загрузке файла миграции (импортируя как Python модуль) Django ищет дочерний класс django.db.migrations.Migration, который должен называться Migration. Затем он анализирует четыре атрибута класса, из которых обычно используются только два:

  • dependencies - список зависимых миграций.
  • operations - список подклассов Operation, которые определяют необходимые для миграции операции.

Список операций - самое главное в миграции. Он декларативно описывает необходимые операции для изменения структуры базы данных. Django анализирует все операции и в памяти строит схему всех необходимых изменений, чтобы сгенерировать необходимые SQL-запросы.

Схема в памяти также используется чтобы определить отличия между вашими текущими моделями и состоянием моделей, описанном в миграциях. Django проходит по всем изменениям и создает структуру моделей на момент последнего выполнения makemigrations. Затем эта структура сравнивается со структурой моделей в models.py и определяются изменения, которые вы внесли в ваши модели.

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

Собственные поля

Вы не можете поменять количество позиционных аргументов в уже промигрированном собственном поле, в таком случае будет вызвано исключение TypeError. Это вызвано тем, что старая миграция попытается вызвать измененный метод __init__ со старыми аргументами. Поэтому, если вам необходимо добавить новый аргумент в конструктор поля, используйте именованный аргумент и добавьте в конструктор что-то вроде assert 'argument_name' in kwargs.

Менеджеры модели

Вы можете сериализировать менеджеры модели в миграции, чтобы использовать в их операциях RunPython. Для этого укажите атрибут use_in_migrations в классе менеджера:

class MyManager(models.Manager):
    use_in_migrations = True

class MyModel(models.Model):
    objects = MyManager()

Если вы используете функцию :meth:`~django.db.models.from_queryset`для создания менеджера, вам следует унаследоваться от сгенерированного класса:

class MyManager(MyBaseManager.from_queryset(CustomQuerySet)):
    use_in_migrations = True

class MyModel(models.Model):
    objects = MyManager()

Советуем ознакомиться с заметками в разделе «Исторические» модели.

Начальные миграции

Migration.initial

The «initial migrations» for an app are the migrations that create the first version of that app’s tables. Usually an app will have one initial migration, but in some cases of complex model interdependencies it may have two or more.

Начальные миграции помечаются атрибутом initial = True в классе миграции. Если атрибут initial не указан, миграция будет считаться «начальной», если это первая миграция в приложении (то есть она не зависит от другой миграции текущего приложения).

When the migrate --fake-initial option is used, these initial migrations are treated specially. For an initial migration that creates one or more tables (CreateModel operation), Django checks that all of those tables already exist in the database and fake-applies the migration if so. Similarly, for an initial migration that adds one or more fields (AddField operation), Django checks that all of the respective columns already exist in the database and fake-applies the migration if so. Without --fake-initial, initial migrations are treated no differently from any other migration.

History consistency

As previously discussed, you may need to linearize migrations manually when two development branches are joined. While editing migration dependencies, you can inadvertently create an inconsistent history state where a migration has been applied but some of its dependencies haven’t. This is a strong indication that the dependencies are incorrect, so Django will refuse to run migrations or make new migrations until it’s fixed. When using multiple databases, you can use the allow_migrate() method of database routers to control which databases makemigrations checks for consistent history.

Добавление миграций в приложение

New apps come preconfigured to accept migrations, and so you can add migrations by running makemigrations once you’ve made some changes.

If your app already has models and database tables, and doesn’t have migrations yet (for example, you created it against a previous Django version), you’ll need to convert it to use migrations by running:

$ python manage.py makemigrations your_app_label

This will make a new initial migration for your app. Now, run python manage.py migrate --fake-initial, and Django will detect that you have an initial migration and that the tables it wants to create already exist, and will mark the migration as already applied. (Without the migrate --fake-initial flag, the command would error out because the tables it wants to create already exist.)

Обратите внимание, это работает только при соблюдении следующих условий:

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

Reverting migrations

Any migration can be reverted with migrate by using the number of previous migrations:

$ python manage.py migrate books 0002
Operations to perform:
  Target specific migration: 0002_auto, from books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0003_auto... OK

If you want to revert all migrations applied for an app, use the name zero:

$ python manage.py migrate books zero
Operations to perform:
  Unapply all migrations: books
Running migrations:
  Rendering model states... DONE
  Unapplying books.0002_auto... OK
  Unapplying books.0001_initial... OK

«Исторические» модели

When you run migrations, Django is working from historical versions of your models stored in the migration files. If you write Python code using the RunPython operation, or if you have allow_migrate methods on your database routers, you need to use these historical model versions rather than importing them directly.

Предупреждение

If you import models directly rather than using the historical models, your migrations may work initially but will fail in the future when you try to re-run old migrations (commonly, when you set up a new installation and run through all the migrations to set up the database).

This means that historical model problems may not be immediately obvious. If you run into this kind of failure, it’s OK to edit the migration to use the historical models rather than direct imports and commit those changes.

Т.к. невозможно сериализовать произвольный код Python, эти версии моделей не будут содержать методы, который вы добавили в модели. Однако, модели будут содержать аналогичные поля, связи, менеджеры(только те, которые содержат use_in_migrations = True) и параметры Meta (с учетом версии модели, так что они могут отличаться от моделей, которые находятся на данный момент в приложении).

Предупреждение

Это означает, что вы НЕ сможете использовать переопределённый метод save(). Также НЕ будет использоваться переопределённый конструктор модели. Вы должны учитывать это при создании Python кода миграций!

Ссылки на функции, которые используются в параметрах поля (например, upload_to и limit_choices_to), и менеджеры моделей с use_in_migrations = True будут сериализированы в миграциях. Такие функции нельзя удалять из кода проекта, пока существуют миграции, которые ссылаются на них. Все собственные поля моделей также должы быть доступны т.к. они явно импортируются в миграциях.

In addition, the concrete base classes of the model are stored as pointers, so you must always keep base classes around for as long as there is a migration that contains a reference to them. On the plus side, methods and managers from these base classes inherit normally, so if you absolutely need access to these you can opt to move them into a superclass.

To remove old references, you can squash migrations or, if there aren’t many references, copy them into the migration files.

Советы по удалению полей модели

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

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

Добавьте атрибут system_check_deprecated_details к полю модели:

class IPAddressField(Field):
    system_check_deprecated_details = {
        'msg': (
            'IPAddressField has been deprecated. Support for it (except '
            'in historical migrations) will be removed in Django 1.9.'
        ),
        'hint': 'Use GenericIPAddressField instead.',  # optional
        'id': 'fields.W900',  # pick a unique ID for your field.
    }

После определенного периода поддержки устаревшего кода (Django использует три мажорных релиза для своих полей), замените system_check_deprecated_details на system_check_removed_details со следующим значением:

class IPAddressField(Field):
    system_check_removed_details = {
        'msg': (
            'IPAddressField has been removed except for support in '
            'historical migrations.'
        ),
        'hint': 'Use GenericIPAddressField instead.',
        'id': 'fields.E900',  # pick a unique ID for your field.
    }

Вы должны сохранить методы поля поля, которые используются в миграции: __init__(), deconstruct() и get_internal_type(). Храните это поле-заглушку, пока существуют миграции, которые ссылаются на него. Например, после объединения миграций и удаления старых, вы сможете полностью удалить эти поля.

Миграция данных

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

Миграции, которые изменяют данные, обычно называют «миграциями данных». Их лучше выносить в отдельную миграцию.

Django не может автоматически создать миграции данных для вас, как это происходит с миграциями структуры, но их не сложно создать самостоятельно. Файлы миграций в Django содержат Операции. Для миграций данных вы будете использовать в основном RunPython.

Для начала создайте пустую миграцию (Django создаст файл миграции, положит его в правильно место, создаст название и добавит необходимые зависимости):

python manage.py makemigrations --empty yourappname

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

# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('yourappname', '0001_initial'),
    ]

    operations = [
    ]

Теперь необходимо создать новую функцию и указать её в RunPython. RunPython принимает в качестве аргумента функцию (или любой другой вызываемый объект), которая принимает два аргумента. Первый - это регистр приложений, который содержит приложения и исторические версии моделей, соответствующие текущей структуре, описанной в миграциях. Второй - SchemaEditor, который можно использовать для изменения структуры базы данных (но будьте осторожны, это может сбить с толку автоматическое определение изменений структуры моделей!)

Let’s write a migration that populates our new name field with the combined values of first_name and last_name (we’ve come to our senses and realized that not everyone has first and last names). All we need to do is use the historical model and iterate over the rows:

from django.db import migrations

def combine_names(apps, schema_editor):
    # We can't import the Person model directly as it may be a newer
    # version than this migration expects. We use the historical version.
    Person = apps.get_model('yourappname', 'Person')
    for person in Person.objects.all():
        person.name = '%s %s' % (person.first_name, person.last_name)
        person.save()

class Migration(migrations.Migration):

    dependencies = [
        ('yourappname', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(combine_names),
    ]

Once that’s done, we can run python manage.py migrate as normal and the data migration will run in place alongside other migrations.

Вы можете передать вторым аргументом RunPython еще одну функцию, которая будет использоваться при отмене миграции. Если такая функция не указана, то при попытке отменить миграцию будет вызвано исключение.

Доступ к моделям в других приложениях

Если функция для RunPython использует модели другого приложения, атрибут dependencies миграции должен включать последнюю миграцию приложения, модель которого используется. Иначе вы можете получить исключение: LookupError: No installed app with label 'myappname' в функции RunPython при использовании apps.get_model().

В примере у нас есть миграция из приложения app1, которая используем модели из приложения app2. Нас не интересует реализация move_m1, но мы знаем, что этой функции необходим доступ к моделям из обоих приложений. Для этого мы добавили в зависимости последнюю миграцию app2:

class Migration(migrations.Migration):

    dependencies = [
        ('app1', '0001_initial'),
        # added dependency to enable using models from app2 in move_m1
        ('app2', '0004_foobar'),
    ]

    operations = [
        migrations.RunPython(move_m1),
    ]

Миграции для продвинутых

Если вы хотите узнать как писать более сложные миграции, или как создавать миграции с нуля, смотрите раздел об операциях в миграциях и «how-to» о создании миграций.

Объединение миграций

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

Объединение позволяет сократить количество миграций до одной (или нескольких), которая будет выполнять аналогичные изменения структуры.

Django загрузит все ваши миграции, соберёт последовательность объектов Operation, затем попытается оптимизировать и сократить этот список. Например, Django понимает, что CreateModel и DeleteModel отменяют друг друга, и что AddField может быть добавлена в CreateModel.

Once the operation sequence has been reduced as much as possible - the amount possible depends on how closely intertwined your models are and if you have any RunSQL or RunPython operations (which can’t be optimized through unless they are marked as elidable) - Django will then write it back out into a new set of migration files.

These files are marked to say they replace the previously-squashed migrations, so they can coexist with the old migration files, and Django will intelligently switch between them depending where you are in the history. If you’re still part-way through the set of migrations that you squashed, it will keep using them until it hits the end and then switch to the squashed history, while new installs will use the new squashed migration and skip all the old ones.

This enables you to squash and not mess up systems currently in production that aren’t fully up-to-date yet. The recommended process is to squash, keeping the old files, commit and release, wait until all systems are upgraded with the new release (or if you’re a third-party project, ensure your users upgrade releases in order without skipping any), and then remove the old files, commit and do a second release.

The command that backs all this is squashmigrations - pass it the app label and migration name you want to squash up to, and it’ll get to work:

$ ./manage.py squashmigrations myapp 0004
Will squash the following migrations:
 - 0001_initial
 - 0002_some_change
 - 0003_another_change
 - 0004_undo_something
Do you wish to proceed? [yN] y
Optimizing...
  Optimized from 12 operations to 7 operations.
Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001_squashed_0004_undo_somthing.py
  You should commit this migration but leave the old ones in place;
  the new migration will be used for new installs. Once you are sure
  all instances of the codebase have applied the migrations you squashed,
  you can delete them.

Use the squashmigrations --squashed-name option if you want to set the name of the squashed migration rather than use an autogenerated one.

Обратите внимание, зависимости между моделями могут быть очень сложными. В результате объединения может получиться неработающая миграция или неправильно оптимизированная (в этом случае можете попробовать с опцией --no-optimize, и желательно сообщить нам о проблеме), или может вызывать исключение CircularDependencyError (в этом случае вы можете самостоятельно исправить проблему).

To manually resolve a CircularDependencyError, break out one of the ForeignKeys in the circular dependency loop into a separate migration, and move the dependency on the other app with it. If you’re unsure, see how makemigrations deals with the problem when asked to create brand new migrations from your models. In a future release of Django, squashmigrations will be updated to attempt to resolve these errors itself.

После объединения миграций закоммитьте их, не удаляя старые миграции. После этого обновите все установленные версии проекта.

You must then transition the squashed migration to a normal migration by:

  • Deleting all the migration files it replaces.
  • Updating all migrations that depend on the deleted migrations to depend on the squashed migration instead.
  • Removing the replaces attribute in the Migration class of the squashed migration (this is how Django tells that it is a squashed migration).

Примечание

Объединив миграции в новую, вы не можете снова использовать её при новом объединении, пока она не будет преобразована в обычную миграцию.

Сериализация значений

Migrations are Python files containing the old definitions of your models - thus, to write them, Django must take the current state of your models and serialize them out into a file.

Хотя Django может сериализовать большинство вещей, есть некоторые веши, которые нельзя сериализовать в представление Python - нет в Python стандарта, который определяет как преобразовать значение обратно в код (repr() работает только для простых значений и не позволяет указать путь для импорта).

Django позволяет сериализовать следующее:

  • int, float, bool, str, bytes, None, NoneType
  • list, set, tuple, dict, range.
  • Объекты datetime.date, datetime.time и datetime.datetime (включая те, у которых указан часовой пояс)
  • Объекты decimal.Decimal
  • enum.Enum instances
  • uuid.UUID instances
  • functools.partial() and functools.partialmethod instances which have serializable func, args, and keywords values.
  • Объекты LazyObject, которые содержат сериализируемое значение.
  • Enumeration types (e.g. TextChoices or IntegerChoices) instances.
  • Любое поле Django
  • Ссылку на любую функцию или метод (например datetime.datetime.today) (должны быть доступны на уровне модуля)
  • Unbound methods used from within the class body
  • Ссылка на класс (должны быть доступны на уровне модуля)
  • Любой объект с методом deconstruct() (смотрите ниже)
Changed in Django 2.2:

Serialization support for NoneType was added.

Django не может сериализовать:

  • Вложенные классы
  • Экземпляры произвольного класса (например MyClass(4.3, 5.7))
  • Лямбда-функции

Custom serializers

New in Django 2.2.

You can serialize other types by writing a custom serializer. For example, if Django didn’t serialize Decimal by default, you could do this:

from decimal import Decimal

from django.db.migrations.serializer import BaseSerializer
from django.db.migrations.writer import MigrationWriter

class DecimalSerializer(BaseSerializer):
    def serialize(self):
        return repr(self.value), {'from decimal import Decimal'}

MigrationWriter.register_serializer(Decimal, DecimalSerializer)

The first argument of MigrationWriter.register_serializer() is a type or iterable of types that should use the serializer.

The serialize() method of your serializer must return a string of how the value should appear in migrations and a set of any imports that are needed in the migration.

Adding a deconstruct() method

Вы можете научить Django сериализовать объекты вашего класса, добавив ему метод deconstruct(). Он не принимает аргументы и должен вернуть кортеж из трех элементов (path, args, kwargs):

  • path - Python путь для импорта класса, включая название класса (например myapp.custom_things.MyClass). Если класс не доступен для импорта напрямую из модуля, его нельзя сериализировать.
  • args - список позиционных аргументов, которые необходимо передать в метод __init__. Аргументы также должны быть сериализируемыми.
  • kwargs - список именованных аргументов, которые необходимо передать в метод __init__. Аргументы также должны быть сериализируемыми.

Примечание

Метод deconstruct() собственных полей немного отличается, он должен возвращать кортеж из четырех элементов.

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

Чтобы Django не создавал новую миграцию при каждом выполнении makemigrations, добавьте метод __eq__() к декорируемому классу. Эта функция будет вызываться Django, чтобы определить не поменялось ли состояние моделей.

Если все аргументы вашего класса сериализируемые, то для автоматического создания метода вы можете использовать декоратор класса @deconstructible из django.utils.deconstruct, чтобы добавить метод deconstruct():

from django.utils.deconstruct import deconstructible

@deconstructible
class MyCustomClass:

    def __init__(self, foo=1):
        self.foo = foo
        ...

    def __eq__(self, other):
        return self.foo == other.foo

Декоратор сохраняет все аргументы, передаваемые в конструктор, и возвращает их при вызове deconstruct().

Поддержка нескольких версий Django

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

Система миграций поддерживает обратную совместимость как и остальная часть Django, и файлы миграций для Django X.Y должны работать для Django X.Y+1. Однако, система миграций не гарантирует обратную совместимость. Могут добавляться новые возможности и новые миграции не будут работать на старой версии Django.

См.также

The Migrations Operations Reference
Описывает API операций для изменений структуры базы данных, специальные операции, и создания своих операций.
The Writing Migrations «how-to»
Описывает как самостоятельно создать миграции для решения различных ситуаций.