Django-Migrationsstrategie zum Umbenennen eines Modells und von Beziehungsfeldern

152

Ich plane, mehrere Modelle in einem vorhandenen Django-Projekt umzubenennen, in dem es viele andere Modelle gibt, die Fremdschlüsselbeziehungen zu den Modellen haben, die ich umbenennen möchte. Ich bin mir ziemlich sicher, dass dies mehrere Migrationen erfordert, bin mir aber nicht sicher, wie genau das vorgehen soll.

Angenommen, ich beginne mit den folgenden Modellen in einer Django-App namens 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()

Ich möchte das FooModell umbenennen , da der Name nicht wirklich sinnvoll ist und Verwirrung im Code verursacht und Barzu einem viel klareren Namen führen würde.

Nach dem, was ich in der Django-Entwicklungsdokumentation gelesen habe, gehe ich von der folgenden Migrationsstrategie aus:

Schritt 1

Ändern 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()

Beachten Sie, dass sich der AnotherModelFeldname für foonicht ändert, die Beziehung zum BarModell jedoch aktualisiert wird . Meine Argumentation ist, dass ich nicht zu viel auf einmal ändern sollte und dass barich das Risiko eingehen würde, die Daten in dieser Spalte zu verlieren , wenn ich diesen Feldnamen in ändere .

Schritt 2

Erstellen Sie eine leere Migration:

python manage.py makemigrations --empty myapp

Schritt 3

Bearbeiten Sie die MigrationKlasse in der in Schritt 2 erstellten Migrationsdatei, um die RenameModelOperation zur Operationsliste hinzuzufügen :

class Migration(migrations.Migration):

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

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

Schritt 4

Wenden Sie die Migration an:

python manage.py migrate

Schritt 5

Bearbeiten Sie die zugehörigen Feldnamen in 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()

Schritt 6

Erstellen Sie eine weitere leere Migration:

python manage.py makemigrations --empty myapp

Schritt 7

Bearbeiten Sie die MigrationKlasse in der in Schritt 6 erstellten Migrationsdatei, um die RenameFieldOperation (en) für alle zugehörigen Feldnamen zur Operationsliste hinzuzufügen :

class Migration(migrations.Migration):

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

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

Schritt 8

Wenden Sie die 2. Migration an:

python manage.py migrate

Abgesehen davon, dass der Rest des Codes (Ansichten, Formulare usw.) aktualisiert wird, um die neuen Variablennamen wiederzugeben, würde die neue Migrationsfunktionalität im Grunde so funktionieren?

Auch dies scheint eine Menge Schritte zu sein. Können die Migrationsvorgänge auf irgendeine Weise komprimiert werden?

Vielen Dank!

Fünfer
quelle

Antworten:

125

Als ich das versuchte, schien es, als könnten Sie Schritt 3 - 7 verdichten:

class Migration(migrations.Migration):

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

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

Es kann zu Fehlern kommen, wenn Sie die importierten Namen nicht aktualisieren, z. B. admin.py und sogar ältere Migrationsdateien (!).

Update : Wie Ceasaro erwähnt, können neuere Versionen von Django normalerweise erkennen und fragen, ob ein Modell umbenannt wurde. Versuchen Sie es also manage.py makemigrationszuerst und überprüfen Sie dann die Migrationsdatei.

wasabigeek
quelle
Danke für die Antwort. Ich bin seitdem mit den von mir beschriebenen Schritten migriert, aber ich bin gespannt, ob Sie dies mit vorhandenen Daten oder nur mit einer leeren Datenbank versucht haben.
Fiver
2
Versuchte es mit vorhandenen Daten, wenn auch nur ein paar Zeilen auf SQLite in meiner lokalen Umgebung (wenn ich zur Produktion
wechsle,
4
Sie müssen den Modellnamen in Migrationsdateien nicht ändern, wenn Sie apps.get_modelsie verwenden. Ich habe viel Zeit gebraucht, um das herauszufinden.
Ahmed
9
Wenn Sie in Django 2.0 Ihren Modellnamen ändern, werden Sie vom ./manage.py makemigrations myappBefehl gefragt, ob Sie Ihr Modell umbenannt haben. Beispiel: Haben Sie das myapp.Foo-Modell in Bar umbenannt? [j / N] Wenn Sie mit "y" antworten , enthält Ihre Migration die migration.RenameModel('Foo', 'Bar')gleichen Zählungen für die umbenannten Felder :-)
ceasaro
1
manage.py makemigrations myappMöglicherweise schlägt dies immer noch fehl: "Möglicherweise müssen Sie dies manuell hinzufügen, wenn Sie den Namen des Modells und einige seiner Felder gleichzeitig ändern. Für den Autodetektor sieht es so aus, als hätten Sie ein Modell mit dem alten Namen gelöscht und ein neues mit hinzugefügt Ein anderer Name und die von ihm erstellte Migration verlieren alle Daten in der alten Tabelle. " Django 2.1 Docs Für mich war es ausreichend, eine leere Migration zu erstellen, die Modellumbenennungen hinzuzufügen und dann makemigrationswie gewohnt auszuführen .
Hlongmore
36

Zuerst dachte ich, dass Fivers Methode für mich funktioniert, da die Migration bis Schritt 4 gut funktioniert hat. Die impliziten Änderungen von 'ForeignKeyField (Foo)' in 'ForeignKeyField (Bar)' waren jedoch in keiner Migration verwandt. Aus diesem Grund ist die Migration fehlgeschlagen, als ich Beziehungsfelder umbenennen wollte (Schritt 5-8). Dies könnte daran liegen, dass mein "AnotherModel" und "YetAnotherModel" in meinem Fall in anderen Apps versendet werden.

Daher konnte ich meine Modelle und Beziehungsfelder wie folgt umbenennen:

Ich habe die Methode daraus und insbesondere den Trick des Otranzers angepasst.

Nehmen wir also wie Fiver an, wir haben in myapp :

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

Und in 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()

Schritt 1:

Transformiere jedes OneToOneField (Foo) oder ForeignKeyField (Foo) in IntegerField (). (Dadurch bleibt die ID des zugehörigen Foo-Objekts als Wert des Ganzzahlfelds erhalten.)

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

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

Dann

python manage.py makemigrations

python manage.py migrate

Schritt 2: (Wie Schritt 2-4 von Fiver)

Ändern Sie den Modellnamen

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

Erstellen Sie eine leere Migration:

python manage.py makemigrations --empty myapp

Dann bearbeiten Sie es wie folgt:

class Migration(migrations.Migration):

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

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

Schließlich

python manage.py migrate

Schritt 3:

Verwandeln Sie Ihr IntegerField () in das vorherige ForeignKeyField oder OneToOneField, jedoch mit dem neuen Balkenmodell. (Das vorherige Integer-Feld hat die ID gespeichert, also versteht Django das und stellt die Verbindung wieder her, was cool ist.)

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

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

Dann mach:

python manage.py makemigrations 

Sehr wichtig ist, dass Sie in diesem Schritt alle neuen Migrationen ändern und die Abhängigkeit von den Migrationen von RenameModel Foo-> Bar hinzufügen müssen. Wenn sich also AnotherModel und YetAnotherModel in myotherapp befinden, muss die in myotherapp erstellte Migration folgendermaßen aussehen:

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

Dann

python manage.py migrate

Schritt 4:

Schließlich können Sie Ihre Felder umbenennen

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

und dann automatisch umbenennen

python manage.py makemigrations

(django sollte dich fragen, ob du den modelnamen tatsächlich umbenannt hast, sag ja)

python manage.py migrate

Und das ist es!

Dies funktioniert auf Django1.8

v.thorey
quelle
3
Danke dir! Das war sehr hilfreich. Aber ein Hinweis - ich musste auch PostgreSQL-Feldindizes von Hand umbenennen und / oder entfernen, da ich nach dem Umbenennen von Foo in Bar ein neues Modell namens Bar erstellt habe.
Anatoly Scherbakov
Danke dafür! Ich denke, dass der Schlüsselteil darin besteht, alle Fremdschlüssel in oder aus dem umzubenennenden Modell in zu konvertieren IntegerField. Dies hat bei mir perfekt funktioniert und hat den zusätzlichen Vorteil, dass sie mit dem richtigen Namen neu erstellt werden. Natürlich würde ich empfehlen, alle Migrationen zu überprüfen, bevor sie tatsächlich ausgeführt werden!
Zelanix
Danke dir! Ich habe viele verschiedene Strategien ausprobiert, um ein Modell umzubenennen, für das andere Modelle Fremdschlüssel haben (Schritte 1-3), und dies war die einzige, die funktioniert hat.
MSH
Das Ändern von ForeignKeys zu IntegerFields hat mir heute den Tag gerettet!
Mehmet
8

Ich musste das Gleiche tun und folgen. Ich habe das Modell auf einmal geändert (Schritte 1 und 5 zusammen aus Fivers Antwort). Anschließend wurde eine Schemamigration erstellt, diese jedoch wie folgt bearbeitet:

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

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

Das hat perfekt funktioniert. Alle meine vorhandenen Daten wurden angezeigt, alle anderen Tabellen, auf die verwiesen wurde, waren in Ordnung.

von hier: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/

John Q.
quelle
Großartig, danke fürs Teilen. Stellen Sie sicher, dass Sie +1 wasibigeek erhalten, wenn diese Antwort hilfreich ist.
Fiver
7

Für Django 1.10 gelang es mir, zwei Modellklassennamen (einschließlich eines ForeignKey und mit Daten) zu ändern, indem ich einfach Makemigrations ausführte und dann für die App migrierte. Für den Schritt Makemigrations musste ich bestätigen, dass ich die Tabellennamen ändern wollte. Migrate hat die Namen der Tabellen problemlos geändert.

Dann habe ich den Namen des ForeignKey-Felds entsprechend geändert und wurde erneut von Makemigrations gebeten, zu bestätigen, dass ich den Namen ändern wollte. Migrieren als die Änderung vorgenommen.

Also habe ich dies in zwei Schritten ohne spezielle Dateibearbeitung gemacht. Ich habe zuerst Fehler erhalten, weil ich vergessen habe, die Datei admin.py zu ändern, wie von @wasibigeek erwähnt.

Excyberlabber
quelle
Vielen Dank! Perfekt für Django 1.11 auch
Francisco
5

Ich habe mich auch dem Problem gestellt, wie v.thorey beschrieben hat, und festgestellt, dass sein Ansatz sehr nützlich ist, aber in weniger Schritte zusammengefasst werden kann, die tatsächlich die Schritte 5 bis 8 sind, wie Fiver ohne Schritt 1 bis 4 beschrieben hat, außer dass Schritt 7 als mein geändert werden muss unter Schritt 3. Die allgemeinen Schritte sind wie folgt:

Schritt 1: Bearbeiten Sie die zugehörigen Feldnamen in 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()

Schritt 2: Erstellen Sie eine leere Migration

python manage.py makemigrations --empty myapp

Schritt 3: Bearbeiten Sie die Migrationsklasse in der in Schritt 2 erstellten Migrationsdatei

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

Schritt 4: Wenden Sie die Migration an

python manage.py migrate

Getan

PS Ich habe diesen Ansatz auf Django 1.9 ausprobiert

Curtis Lo
quelle
5

Ich benutze Django Version 1.9.4

Ich habe die folgenden Schritte befolgt: -

Ich habe gerade das Modell oldName in NewName Run umbenannt python manage.py makemigrations. Sie werden aufgefordert Did you rename the appname.oldName model to NewName? [y/N], Y auszuwählen

Laufen Sie python manage.py migrateund es wird Sie fragen

Die folgenden Inhaltstypen sind veraltet und müssen gelöscht werden:

appname | oldName
appname | NewName

Alle Objekte, die durch einen Fremdschlüssel mit diesen Inhaltstypen verknüpft sind, werden ebenfalls gelöscht. Möchten Sie diese Inhaltstypen wirklich löschen? Wenn Sie sich nicht sicher sind, antworten Sie mit "Nein".

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

Es benennt alle vorhandenen Daten um und migriert sie für mich in eine neue benannte Tabelle.

Piyush S. Wanare
quelle
Danke, Alter, ich war verwirrt, weil nichts passiert ist, als ich "Nein"
gedrückt habe
3

Leider habe ich Probleme (bei jedem Django 1.x) mit der Umbenennungsmigration festgestellt, bei denen alte Tabellennamen in der Datenbank verbleiben.

Django probiert nicht einmal etwas auf dem alten Tisch aus, sondern benennt einfach sein eigenes Modell um. Das gleiche Problem mit Fremdschlüsseln und Indizes im Allgemeinen - Änderungen dort werden von Django nicht richtig verfolgt.

Die einfachste Lösung (Problemumgehung):

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

Die eigentliche Lösung (eine einfache Möglichkeit, alle Indizes, Einschränkungen, Trigger, Namen usw. in 2 Commits zu wechseln, jedoch für kleinere Tabellen):

Commit A:

  1. Erstellen Sie das gleiche Modell wie das alte
# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
    ...

class Bar(model.Model):
    ...
  1. Code wechseln, um nur mit neuem Modell zu arbeiten Bar. (einschließlich aller Beziehungen im Schema)

Bei der Migration vorbereiten RunPython, welche Daten von Foo nach Bar kopieren (einschließlich idvon Foo)

  1. optionale Optimierung (falls für größere Tabellen erforderlich)

Commit B: (keine Eile, mach es, wenn ein ganzes Team migriert wird)

  1. sicherer Fall des alten Modells Foo

weitere Bereinigung:

  • Squash auf Migrationen

Fehler in Django:

Sławomir Lenart
quelle
3

Ich wollte nur den Kommentar von ceasaro bestätigen und hinzufügen. Django 2.0 scheint dies jetzt automatisch zu tun.

Ich bin auf Django 2.2.1, alles was ich tun musste, um das Modell umzubenennen und auszuführen makemigrations .

Hier wird gefragt, ob ich die bestimmte Klasse von Ain umbenannt habe. Ich habe BJa gewählt und migrate ausgeführt, und alles scheint zu funktionieren.

Hinweis Ich habe den alten Modellnamen in keiner Datei im Projekt- / Migrationsordner umbenannt.

Peheje
quelle
1

Ich musste ein paar Tabellen umbenennen. Django bemerkte jedoch nur eine Umbenennung des Modells. Dies geschah, weil Django über hinzugefügte und dann entfernte Modelle iteriert. Für jedes Paar wird geprüft, ob sie zur selben App gehören und identische Felder haben . Nur eine Tabelle hatte keine Fremdschlüssel für umbenennende Tabellen (Fremdschlüssel enthalten, wie Sie sich erinnern, den Namen der Modellklasse). Mit anderen Worten, nur eine Tabelle hatte keine Feldänderungen. Deshalb wurde es bemerkt.

Die Lösung besteht also darin, jeweils eine Tabelle umzubenennen models.py, möglicherweise den Namen der Modellklasse zu ändern views.pyund eine Migration durchzuführen. Überprüfen Sie anschließend Ihren Code auf andere Referenzen (Modellklassennamen, verwandte (Abfrage-) Namen, Variablennamen). Führen Sie bei Bedarf eine Migration durch. Kombinieren Sie dann optional alle diese Migrationen zu einer (kopieren Sie auch die Importe).

x-yuri
quelle
1

Ich würde @ceasaro Worte machen, meine zu seinem Kommentar zu dieser Antwort .

Neuere Versionen von Django können Änderungen erkennen und nachfragen, was getan wurde. Ich würde auch hinzufügen, dass Django die Reihenfolge der Ausführung einiger Migrationsbefehle mischen könnte.

Es wäre klug, kleine Änderungen zu übernehmen und ausführen makemigrationsund migratefalls der Fehler der Datei bearbeitet werden kann Migration auftritt.

Die Ausführungsreihenfolge einiger Zeilen kann geändert werden, um Fehler zu vermeiden.

diogosimao
quelle
Gut zu beachten, dass dies nicht funktioniert, wenn Sie Modellnamen ändern und Fremdschlüssel definiert sind, etc ...
Dean Kayton
Erweitern des vorherigen Kommentars: Wenn ich nur Modellnamen ändere und Makemigrationen ausführe, wird in Fremdschlüsseln usw. 'NameError: Name' <oldmodel> 'nicht definiert' angezeigt. Wenn ich das ändere und Makemigrationen ausführe, erhalte ich Importfehler in admin.py ... Wenn ich das behebe und makemigrations erneut ausführe, erhalte ich die Aufforderung 'Haben Sie das Modell <app.oldmodel> in <newmodel> umbenannt?'. Beim Anwenden von Migrationen erhalte ich jedoch 'ValueError: Das Feld <app .newmodel.field1> wurde mit einem faulen Verweis auf '<app.oldmodel>' deklariert, aber App '<app>' bietet kein Modell '<oldmodel>' usw.
Dean Kayton
Dieser Fehler sieht so aus, als müssten Sie die Referenzen in Ihren historischen Migrationen umbenennen.
mhatch
@ DeanKayton würde sagen, dass migrations.SeparateDatabaseAndStatedas helfen kann?
Diogosimao
1

Wenn Sie eine gute IDE wie PyCharm verwenden, können Sie mit der rechten Maustaste auf den Modellnamen klicken und einen Refactor ausführen -> umbenennen. Dies erspart Ihnen die Mühe, den gesamten Code durchzugehen, der auf das Modell verweist. Führen Sie dann Makemigrationen durch und migrieren Sie. Django 2+ bestätigt einfach die Namensänderung.

Josh
quelle
-10

Ich habe Django von Version 10 auf Version 11 aktualisiert:

sudo pip install -U Django

( -Ufür "Upgrade") und es löste das Problem.

Muhammad Hafid
quelle