Стратегия миграции 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, чтобы добавить 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

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

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

Спасибо!

14 ответов

Решение

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

Сначала я подумал, что метод 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:

Преобразуйте каждый OntToOneField(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

В текущей версии Django вы можете переименовать модель и запустить python manage.py makemigrations, тогда django спросит, хотите ли вы переименовать модель, и если вы выберете «Да», то весь процесс переименования будет выполнен автоматически.

Мне нужно было сделать то же самое. Я изменил модель все сразу (т.е. шаг 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/

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

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

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

Я также столкнулся с проблемой, как описал 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

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

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

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

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

Я использую 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

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

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

Решение для этого случая:

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

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

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

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

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

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

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

Обновление 2023 года:

  1. Переименуйте модель Foo->Bar
  2. Бегать
  3. Переименуйте поля в связанных моделях foo->bar
  4. Бегатьmakemigrations
  5. Мигрируйте и наслаждайтесь

Если вы одновременно переименуете модель и связанные с ней поля, вместо переименования старая модель будет удалена и создана новая.

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

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

sudo pip install -U Django

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

Другие вопросы по тегам