Міграційна стратегія Джанго для перейменування моделі та полів відносин


153

Я планую перейменувати кілька моделей у існуючому проекті 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означало б набагато чіткіше ім'я.

З того, що я прочитав у документації щодо розвитку Джанго, я припускаю таку міграційну стратегію:

Крок 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

Застосувати другу міграцію:

python manage.py migrate

Окрім того, не оновлюючи решту коду (перегляди, форми тощо), щоб відобразити нові імена змінних, це в основному, як працює нова функція міграції?

Також це здається безліччю кроків. Чи можна міграційними операціями якось стиснутись?

Дякую!

Відповіді:


127

Отож, коли я спробував це, вам здається, ви можете конденсувати крок 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

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 Документи Для мене достатньо було створити порожню міграцію, додати до неї перейменування моделі, а потім запустити makemigrationsяк завжди.
hlongmore

37

Спочатку я подумав, що метод Фівера працює для мене, оскільки міграція працювала добре до кроку 4. Однак неявні зміни "ForeignKeyField (Foo)" на "ForeignKeyField (Bar)" не пов'язані з жодними міграціями. Ось чому міграція не вдалася, коли я хотів перейменувати поля відносин (крок 5-8). Це може бути пов’язано з тим, що мій 'AnotherModel' та 'YetAbodyModel' надсилаються в інших додатках у моєму випадку.

Тому мені вдалося перейменувати свої моделі та поля стосунків, виконавши наступні кроки:

Я адаптував метод із цього і, особливо, хитрість отранзера.

Так, як 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. Отже, якщо обидва AnotherModel і YetATHERModel знаходяться в myotherapp, створена міграція в 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.
Анатолій Щербаков

Дякую за це! Я думаю, що ключова частина полягає в перетворенні всіх сторонніх ключів у моделі, яка перейменовується або виходить із неї, у IntegerField. Це прекрасно працювало для мене і має додаткову перевагу в тому, що вони відтворюються з правильною назвою. Звичайно, я б радив переглянути всі міграції, перш ніж їх реально запустити!
zelanix

Дякую! Я спробував багато різних стратегій для того, щоб перейменувати модель, для якої інші моделі мають зовнішні ключі (кроки 1-3), і ця функція була єдиною.
МШ

Змінення ForeignKeys на IntegerFields сьогодні врятувало мій день!
mehmet

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')

Це спрацювало чудово. Усі мої наявні дані з'явилися, всі інші таблиці посилалися на Bar штрафу.

звідси: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/


Чудово, дякую за обмін. Обов’язково позначте +1 wasibigeek, якщо ця відповідь допомогла.
Фівер

7

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

Потім я змінив назву поля ForeignKey на відповідність, і Makemigrations знову попросив мене підтвердити, що я хочу змінити ім'я. Міграція, ніж внесені зміни.

Тож я зробив це в два кроки без спеціального редагування файлів. Я спершу отримав помилки, тому що забув змінити файл admin.py, як згадував @wasibigeek.


Дуже дякую! Ідеально підходить для Джанго 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

Я використовую версію 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) з міграцією перейменувань, які залишають старі назви таблиць у базі даних.

Джанго навіть не пробує нічого на старому столі, просто перейменував власну модель. Та сама проблема з іноземними ключами та індексами загалом - зміни там не відстежуються належним чином Джанго.

Найпростіший спосіб вирішення:

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. необов'язкова оптимізація (якщо потрібно для більших таблиць)

виконувати B: (не поспішайте, робіть це, коли вся команда переміщена)

  1. безпечне падіння старої моделі Foo

подальша очистка:

  • сквош на міграціях

помилка в Джанго:


3

Просто хотів підтвердити та додати коментар ceasaro. Django 2.0, здається, робить це автоматично зараз.

Я на Django 2.2.1, все, що мені потрібно було зробити, щоб перейменувати модель і запустити makemigrations.

Тут він запитує, чи я перейменував конкретний клас з Aв B, я вибрав так і побіг мігрувати, і все, здається, працює.

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


1

Мені потрібно було перейменувати пару таблиць. Але Джанго помітив лише одне перейменування моделі. Це сталося тому, що Джанго повторює додані, а потім зняті моделі. Для кожної пари він перевіряє, чи є вони в одному додатку та чи однакові поля . Тільки в одній таблиці не було сторонніх ключів до таблиць, які слід перейменовувати (зовнішні ключі містять назву класу моделі, як ви пам’ятаєте). Іншими словами, лише в одній таблиці не було змін поля. Ось чому це було помічено.

Таким чином, рішення полягає в перейменуванні однієї таблиці за одною, змінюючи ім'я класу моделі models.py, можливо views.py, і здійснюючи міграцію. Після цього перевірте свій код щодо інших посилань (назви класів моделей, пов'язаних (імена запитів), назв змінних). Зробіть міграцію, якщо потрібно. Потім необов’язково об'єднайте всі ці міграції в одну (обов’язково скопіюйте також імпорт).


1

Я хотів би сказати слова @ceasaro, моє за коментар до цієї відповіді .

Новіші версії Django можуть виявити зміни та запитати про те, що було зроблено. Я також додам, що Джанго може змішати порядок виконання деяких міграційних команд.

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

Деякі рядки порядок виконання можна змінити, щоб уникнути помилок.


Приємно зауважити, що це не працює, якщо ви змінюєте назви моделей, а також визначені зовнішні ключі тощо ...
Дін Кейтон

Розширення на попередній коментар: Якщо все, що я роблю, - це змінити назви моделей та запустити макеміграції, я отримаю 'NameError: ім'я' <oldmodel> 'не визначено' в іноземних клавішах тощо. Якщо я зміню це і запускаю макеміграції, я отримую помилки імпорту у admin.py ... якщо я виправлю це та запускаю макеміграції ще раз, я отримую підказки: Ви перейменували модель <app.oldmodel> в <newmodel> ', але тоді, застосовуючи міграції, я отримую' ValueError: Поле <додаток .newmodel.field1> був оголошений ледачим посиланням на "<app.oldmodel>", але додаток "<app>" не передбачає модель "<oldmodel>" і т.д. ... "
Дін Кейтон

Ця помилка виглядає так, що вам потрібно перейменувати посилання на ваші історичні міграції.
матч

@DeanKayton скаже, що migrations.SeparateDatabaseAndStateможе допомогти?
diogosimao

1

Якщо ви використовуєте такий хороший IDE, як PyCharm, ви можете клацнути правою кнопкою миші ім'я моделі та зробити рефактор -> перейменувати. Це позбавить вас від проблеми переглядати весь код, на який посилається модель. Потім запустіть макеміграції та мігруйте. Django 2+ просто підтвердить зміну імені.


-10

Я модернізував Django з версії 10 до версії 11:

sudo pip install -U Django

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.