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

154

Я планирую переименовать несколько моделей в существующем проекте 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, чтобы добавить RenameFieldоперацию (и) для любых связанных имен полей в список операций:

class Migration(migrations.Migration):

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

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

Шаг 8

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

python manage.py migrate

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

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

Спасибо!

пятерка
источник

Ответы:

128

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

wasabigeek
источник
Спасибо за ответ. С тех пор я мигрировал, используя шаги, которые я обрисовал, но мне любопытно, пробовали ли вы это с существующими данными или просто с пустой базой данных?
Fiver
2
Пробовал это с существующими данными, хотя всего несколько строк на sqlite в моем локальном окружении (когда я перехожу в производство, я намерен стереть все, включая файлы миграции)
wasabigeek
4
Вам не нужно менять название модели в файлах миграции, если вы используете apps.get_modelих. Мне понадобилось много времени, чтобы понять это.
Ахмед
9
В django 2.0, если вы измените название вашей модели, ./manage.py makemigrations myappкоманда спросит вас, переименовали ли вы вашу модель. Например: вы переименовали модель myapp.Foo в Bar? [y / N] Если вы ответите 'y', ваша миграция будет содержать migration.RenameModel('Foo', 'Bar')одинаковые значения для переименованных полей :-)
ceasaro
1
manage.py makemigrations myappможет по-прежнему не работать: «Возможно, вам придется добавить это вручную, если вы измените название модели и несколько ее полей одновременно; для автоопределителя это будет выглядеть так, как будто вы удалили модель со старым именем и добавили новую с другое имя, и созданная им миграция потеряет все данные в старой таблице. " Django 2.1 Docs Для меня было достаточно создать пустую миграцию, добавить к ней переименование модели, а затем запустить makemigrationsкак обычно.
Хлонгмор
37

Сначала я подумал, что метод 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, но с новой моделью бара. (Предыдущее целочисленное поле хранило идентификатор, поэтому 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

v.thorey
источник
3
Спасибо! Это было очень полезно. Но обратите внимание - мне также пришлось переименовывать и / или удалять индексы полей PostgreSQL вручную, потому что после переименования Foo в Bar я создал новую модель с именем Bar.
Анатолий Щербаков
Спасибо тебе за это! Я думаю, что ключевая часть преобразует все внешние ключи, в или из модели, которая будет переименована, в IntegerField. Это отлично сработало для меня и имеет дополнительное преимущество, что они воссоздаются с правильным именем. Естественно, я бы посоветовал проверить все миграции, прежде чем запускать их!
Zelanix
Спасибо! Я перепробовал много разных стратегий, чтобы переименовать модель, к которой другие модели имеют внешние ключи (шаги 1-3), и это была единственная работающая модель.
MSH
Изменение ForeignKeys в IntegerFields спасло мой день сегодня!
Мехмет
8

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

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/

Джон Кью
источник
Отлично, спасибо, что поделились. Обязательно +1 wasibigeek, если этот ответ помог.
Fiver
7

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

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

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

excyberlabber
источник
Большое спасибо! Идеально подходит и для Django 1.11
Франциско
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

Готово

PS я пробовал этот подход на Django 1.9

Кертис Ло
источник
5

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

Я сделал следующие шаги: -

Я только что переименовал модель oldName в NewName Run 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

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

Пиюш С. Ванаре
источник
Спасибо, чувак, я был смущен, потому что ничего не произошло, когда после удара "нет"
farhawa
3

К сожалению, я обнаружил проблемы (каждый 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 (включая idFoo)

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

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

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

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

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

ошибка в Django:

Славомир Ленарт
источник
3

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

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

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

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

Peheje
источник
1

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

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

х-юри
источник
1

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

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

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

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

diogosimao
источник
Приятно отметить, что это не сработает, если вы измените названия моделей и определены внешние ключи и т. Д.
Дин Кейтон,
Расширение предыдущего комментария: если все, что я делаю, это изменяю имена моделей и запускаю makemigrations, я получаю 'NameError: name' <oldmodel> 'не определено' в Foreignkeys и т. Д. ... Если я изменяю это и запускаю makemigrations, я получаю ошибки импорта в admin.py ... если я исправлю это и снова выполню makemigrations, я получаю подсказки: «Вы переименовали модель <app.oldmodel> в <newmodel>», но затем, применяя миграции, я получаю «ValueError: Поле <app .newmodel.field1> было объявлено с ленивой ссылкой на «<app.oldmodel>», но приложение «<app>» не предоставляет модель «<oldmodel>» и т. д ... »
Дин Кейтон,
Эта ошибка выглядит так, как будто вам нужно переименовать ссылки в ваших исторических миграциях.
mhatch
@DeanKayton сказал бы, что migrations.SeparateDatabaseAndStateможет помочь?
diogosimao
1

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

мистифицировать
источник
-10

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

sudo pip install -U Django

( -Uдля «апгрейда») и это решило проблему.

Мухаммед Хафид
источник