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

118

Я планирую переименовать несколько моделей в существующем проекте 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

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

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

Спасибо!

Теги:
django-migrations

11 ответов

95
Лучший ответ

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

  • 0
    Спасибо за ответ. С тех пор я мигрировал, используя шаги, которые я обрисовал, но мне любопытно, пробовали ли вы это с существующими данными или просто с пустой базой данных?
  • 1
    Пробовал это с существующими данными, хотя всего несколько строк на sqlite в моем локальном env (когда я перехожу на производство, я намерен стереть все, включая файлы миграции)
Показать ещё 5 комментариев
26

Сначала я подумал, что метод 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
    Спасибо! Это было очень полезно. Но обратите внимание - мне также пришлось переименовывать и / или удалять индексы полей PostgreSQL вручную, потому что после переименования Foo в Bar я создал новую модель с именем Bar.
  • 0
    Спасибо тебе за это! Я думаю, что ключевая часть преобразует все внешние ключи, в или из модели, которая будет переименована, в IntegerField . Это отлично сработало для меня, и имеет дополнительное преимущество в том, что они воссоздаются с правильным именем. Естественно, я бы посоветовал проверить все миграции, прежде чем запускать их!
7

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

  • 0
    Отлично, спасибо, что поделились. Обязательно +1 wasibigeek, если этот ответ помог.
  • 1
    Хороший вопрос - только что сделал.
6

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

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

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

  • 0
    Большое спасибо! Идеально подходит для Django 1.11
4

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

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

  • 0
    Спасибо, чувак, я был смущен, потому что ничего не произошло, когда после нажатия «нет»
4

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

2

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

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

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

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

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

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

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

1

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

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

0

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

-7

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

sudo pip install -U Django

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню