Стратегия миграции Django для переименования модели и полей отношений

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

Скажем, я начинаю со следующих моделей в приложении Django под названием myapp:

class Foo(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_ridonkulous = models.BooleanField()

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

Из того, что я прочитал в документации по разработке Django, я предполагаю следующую стратегию миграции:

Шаг 1

Изменить models.py:

class Bar(models.Model):  # <-- changed model name
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    foo = models.ForeignKey(Bar)  # <-- changed relation, but not field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Bar)  # <-- changed relation, but not field name
    is_ridonkulous = models.BooleanField()

Обратите внимание, что имя поля AnotherModel для Foo не изменяется, но отношение обновляется до модели Bar. Мое рассуждение состоит в том, что я не должен слишком сильно меняться, и если я изменил это имя поля на Bar, я бы рискнул потерять данные в этом столбце.

Шаг 2

Создайте пустую миграцию:

python manage.py makemigrations --empty myapp

Шаг 3

Отредактируйте класс Migration в файле миграции, созданном на шаге 2, чтобы добавить операцию RenameModel в список операций:

class Migration(migrations.Migration):

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

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

Шаг 4

Применить миграцию:

python manage.py migrate

Шаг 5

Измените соответствующие имена полей в models.py:

class Bar(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_ridonkulous = models.BooleanField()

Шаг 6

Создайте еще одну пустую миграцию:

python manage.py makemigrations --empty myapp

Шаг 7

Отредактируйте класс Migration в файле миграции, созданном на шаге 6, чтобы добавить операции (t219) для любых связанных имен полей в список операций:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_rename_fields'),  # <-- is this okay?
    ]

    operations = [
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]

Шаг 8

Примените вторую миграцию:

python manage.py migrate

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

Кроме того, это похоже на много шагов. Можно ли каким-либо образом скроить операции миграции?

Спасибо!

Ответ 1

Поэтому, когда я попробовал это, кажется, вы можете сжать Шаг 3 - 7:

class Migration(migrations.Migration):

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

    operations = [
        migrations.RenameModel('Foo', 'Bar'),
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]

Вы можете получить некоторые ошибки, если не обновите имена, в которые они импортированы, например admin.py и даже более старые файлы миграции (!).

Обновление: как упоминает Ceasaro, более новые версии Django обычно могут обнаружить и спросить, переименована ли модель. Поэтому сначала попробуйте manage.py makemigrations а затем проверьте файл миграции.

Ответ 2

Сначала я подумал, что метод Fiver работает для меня, потому что миграция работала хорошо до шага 4. Однако неявные изменения ForeignKeyField (Foo) в ForeignKeyField (Bar) не были связаны ни с какими миграциями. Вот почему миграция не удалась, когда я захотел переименовать поля отношений (шаг 5-8). Это может быть связано с тем, что мои "AnotherModel" и "YetAnotherModel" отправляются в других приложениях в моем случае.

Поэтому мне удалось переименовать мои модели и поля отношений, выполнив следующие шаги:

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

Так что, как Fiver, скажем, у нас в myapp:

class Foo(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)

И в myotherapp:

class AnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_ridonkulous = models.BooleanField()

Шаг 1:

Преобразуйте каждый OneToOneField (Foo) или ForeignKeyField (Foo) в IntegerField(). (Это будет сохранять идентификатор связанного объекта Foo как значение целочисленного поля).

class AnotherModel(models.Model):
    foo = models.IntegerField()
    is_awesome = models.BooleanField()

class YetAnotherModel(models.Model):
    foo = models.IntegerField()
    is_ridonkulous = models.BooleanField()

затем

python manage.py makemigrations

python manage.py migrate

Шаг 2: (Как шаг 2-4 от Fiver)

Изменить название модели

class Bar(models.Model):  # <-- changed model name
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)

Создайте пустую миграцию:

python manage.py makemigrations --empty myapp

Затем отредактируйте это как:

class Migration(migrations.Migration):

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

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

В конце концов

python manage.py migrate

Шаг 3:

Преобразуйте Ваш IntegerField() в их предыдущий ForeignKeyField или OneToOneField, но с новой моделью Bar. (Предыдущее целочисленное поле хранило идентификатор, поэтому django понимает это и восстанавливает соединение, что здорово.)

class AnotherModel(models.Model):
    foo = models.ForeignKey(Bar)
    is_awesome = models.BooleanField()

class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Bar)
    is_ridonkulous = models.BooleanField()

Затем сделайте:

python manage.py makemigrations 

Очень важно, что на этом этапе вы должны изменять каждую новую миграцию и добавлять зависимость от миграций RenameModel Foo-> Bar. Таким образом, если в myotherapp есть и AnotherModel, и YetAnotherModel, созданная миграция в myotherapp должна выглядеть следующим образом:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '00XX_the_migration_of_myapp_with_renamemodel_foo_bar'),
        ('myotherapp', '00xx_the_migration_of_myotherapp_with_integerfield'),
    ]

    operations = [
        migrations.AlterField(
            model_name='anothermodel',
            name='foo',
            field=models.ForeignKey(to='myapp.Bar'),
        ),
        migrations.AlterField(
            model_name='yetanothermodel',
            name='foo',
            field=models.ForeignKey(to='myapp.Bar')
        ),
    ]

затем

python manage.py migrate

Шаг 4:

В конце концов вы можете переименовать свои поля

class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar) <------- Renamed fields
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar) <------- Renamed fields
    is_ridonkulous = models.BooleanField()

а затем сделать автоматическое переименование

python manage.py makemigrations

(Django должен спросить вас, действительно ли вы переименовали имя модели, скажите "да")

python manage.py migrate

И это оно!

Это работает на Django1.8

Ответ 3

Мне нужно было сделать то же самое. Я изменил модель все сразу (то есть, шаг 1 и шаг 5 вместе). Затем создал схему миграции, но отредактировал ее так:

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_table('Foo','Bar')

    def backwards(self, orm):
        db.rename_table('Bar','Foo')

Это сработало отлично. Все мои существующие данные обнаружились, все остальные таблицы ссылались на Бар в порядке.

отсюда: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/

Ответ 4

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

Затем я изменил имя поля ForeignKey, чтобы он совпал, и снова попросил Makemigrations подтвердить, что я хотел изменить имя. Миграция, чем внесение изменений.

Итак, я сделал это в два этапа без специального редактирования файлов. Сначала я получил ошибки, потому что забыл изменить файл admin.py, как упоминалось в @wasibigeek.

Ответ 5

Я использую Django версии 1.9.4

Я выполняю следующие шаги: -

Я только что переименовал модель oldName в NewName Запустите python manage.py makemigrations. Он попросит вас Did you rename the appname.oldName model to NewName? [y/N] выберите Y

Запустите python manage.py migrate, и он попросит вас

Следующие типы содержимого устаревают и их необходимо удалить:

appname | oldName
appname | NewName

Любые объекты, связанные с этими типами содержимого внешним ключом, также будут быть удалена. Вы действительно хотите удалить эти типы контента? Если вы не уверены, ответьте "нет".

Type 'yes' to continue, or 'no' to cancel: Select No

Переименовать и перенести все существующие данные в новую именованную таблицу для меня.

Ответ 6

Я также столкнулся с проблемой, описанной в v.thorey, и обнаружил, что его подход очень полезен, но может быть сконденсирован на меньшее количество шагов, которые на самом деле являются шагами 5-8, как описано в Fiver без шага 1-4, за исключением того, что этап 7 должен быть изменен как мой ниже шаг 3. Общие шаги следующие:

Шаг 1: отредактируйте соответствующие имена полей в models.py

class Bar(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_ridonkulous = models.BooleanField()

Шаг 2. Создайте пустую миграцию

python manage.py makemigrations --empty myapp

Шаг 3: Отредактируйте класс миграции в файле миграции, созданном на шаге 2

class Migration(migrations.Migration):

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

operations = [
    migrations.AlterField(
        model_name='AnotherModel',
        name='foo',
        field=models.IntegerField(),
    ),
    migrations.AlterField(
        model_name='YetAnotherModel',
        name='foo',
        field=models.IntegerField(),
    ),
    migrations.RenameModel('Foo', 'Bar'),
    migrations.AlterField(
        model_name='AnotherModel',
        name='foo',
        field=models.ForeignKey(to='myapp.Bar'),
    ),
    migrations.AlterField(
        model_name='YetAnotherModel',
        name='foo',
        field=models.ForeignKey(to='myapp.Bar'),
    ),
    migrations.RenameField('AnotherModel', 'foo', 'bar'),
    migrations.RenameField('YetAnotherModel', 'foo', 'bar')
]

Шаг 4: Примените миграцию

python manage.py migrate

Готово

P.S. Я пробовал этот подход на Django 1.9

Ответ 7

К сожалению, я обнаружил проблемы (каждый django 1.x) с миграцией переименования, которая оставляет старые имена таблиц в базе данных.

Джанго даже не пробовал ничего на старом столе, просто переименовал свою модель. Та же проблема с внешними ключами и индексами в целом - изменения не отслеживаются должным образом Django.

Самое простое решение (обходной путь):

class Foo(models.Model):
     name = models.CharField(unique=True, max_length=32)
     ...
Bar = Foo  # and use Bar only

Реальное решение (простой способ переключить все индексы, ограничения, триггеры, имена и т.д. В 2 коммитах):

совершить A:

  1. создать ту же модель, что и старая
# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
    ...

class Bar(model.Model):
    ...
  1. переключить код для работы только с новой моделью Bar. (включая все отношения на схеме)

В миграции подготовьте RunPython, который копирует данные из Foo в Bar (включая id Foo)

  1. дополнительная оптимизация (если необходимо для больших таблиц)

коммит Б: (не торопитесь, делайте это, когда мигрирует вся команда)

  1. безопасное падение старой модели Foo

дальнейшая очистка:

  • сквош на миграцию

ошибка в Django:

Ответ 8

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

Таким образом, решение состоит в том, чтобы переименовывать по одной таблице за раз, изменяя имя класса модели в models.py, возможно views.py и делая миграцию. После этого проверьте ваш код на наличие других ссылок (имен классов моделей, связанных (запрос) имен, имен переменных). Сделайте миграцию, если это необходимо. Затем, при желании, можно объединить все эти миграции в одну (обязательно скопируйте также и импорт).

Ответ 9

Я хотел бы написать слова @ceasaro, мои в своем комментарии к этому ответу.

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

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

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

Ответ 10

Если вы используете хорошую IDE, такую как PyCharm, вы можете щелкнуть правой кнопкой мыши на названии модели и выполнить рефакторинг → переименовать. Это избавит вас от необходимости проходить весь код, который ссылается на модель. Затем запустите makemigrations и мигрируйте. Джанго 2+ просто подтвердит изменение имени.

Ответ 11

Просто хотел подтвердить и добавить комментарий Ceasaro. Django 2.0 теперь делает это автоматически.

Я на Jango 2.2.1, все что мне нужно было сделать, это переименовать модель и запустить makemigrations.

Здесь он спрашивает, переименовал ли я определенный класс из A в B, я выбрал yes и запустил migrate, и все, кажется, работает.

Примечание. Я не переименовал имя старой модели ни в одном файле внутри папки проекта/миграции.

Ответ 12

Я обновил Django с версии 10 до версии 11:

sudo pip install -U Django

(-U для "обновления" ), и он решил проблему.