Django-Migrationsstrategie zum Umbenennen eines Modells und von Beziehungsfeldern
-
02-01-2020 - |
Frage
Ich plane, mehrere Modelle in einem bestehenden 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 dafür mehrere Migrationen erforderlich sein werden, aber ich bin mir über die genaue Vorgehensweise nicht sicher.
Nehmen wir an, 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 umbenennen Foo
Modell, weil der Name keinen Sinn ergibt und Verwirrung im Code verursacht, und Bar
würde zu einem viel klareren Namen führen.
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 das AnotherModel
Feldname für foo
ändert sich nicht, aber die Beziehung wird auf aktualisiert Bar
Modell.Meine Argumentation ist, dass ich nicht zu viel auf einmal ändern sollte und dass ich diesen Feldnamen in ändern würde bar
Ich würde riskieren, die Daten in dieser Spalte zu verlieren.
Schritt 2
Erstellen Sie eine leere Migration:
python manage.py makemigrations --empty myapp
Schritt 3
Bearbeiten Sie die Migration
Klasse in der in Schritt 2 erstellten Migrationsdatei, um die hinzuzufügen RenameModel
Operation zur Operationsliste hinzufü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 Migration
class in der in Schritt 6 erstellten Migrationsdatei, um die hinzuzufügen RenameField
Operation(en) für alle zugehörigen Feldnamen zur Operationsliste:
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 von der Aktualisierung des restlichen Codes (Ansichten, Formulare usw.), um die neuen Variablennamen widerzuspiegeln, würde die neue Migrationsfunktion im Grunde so funktionieren?
Außerdem scheinen das viele Schritte zu sein.Können die Migrationsvorgänge irgendwie komprimiert werden?
Danke!
Lösung
Als ich das ausprobiert habe, scheint es, dass Sie die Schritte 3 bis 7 zusammenfassen können:
class Migration(migrations.Migration):
dependencies = [
('myapp', '0001_initial'),
]
operations = [
migrations.RenameModel('Foo', 'Bar'),
migrations.RenameField('AnotherModel', 'foo', 'bar'),
migrations.RenameField('YetAnotherModel', 'foo', 'bar')
]
Möglicherweise erhalten Sie Fehlermeldungen, wenn Sie die Namen dort, wo sie importiert werden, nicht aktualisieren, z. B.admin.py und sogar ältere Migrationsdateien (!).
Aktualisieren:Als Caesar Wie bereits erwähnt, sind neuere Versionen von Django normalerweise in der Lage, die Umbenennung eines Modells zu erkennen und zu fragen.Also versuche manage.py makemigrations
zuerst und überprüfen Sie dann die Migrationsdatei.
Andere Tipps
Zuerst dachte ich, dass die Methode von Fiver für mich funktioniert, weil die Migration bis Schritt 4 gut funktioniert hat.Die impliziten Änderungen von „ForeignKeyField(Foo)“ in „ForeignKeyField(Bar)“ waren jedoch bei keiner Migration im Zusammenhang.Aus diesem Grund schlug die Migration fehl, 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 ist es mir gelungen, meine Modelle und Beziehungsfelder mithilfe der folgenden Schritte umzubenennen:
Ich habe die Methode von angepasst Das und insbesondere der Trick von Otranzer.
Nehmen wir also wie Fiver an, wir sind dabei meine App:
class Foo(models.Model):
name = models.CharField(unique=True, max_length=32)
description = models.TextField(null=True, blank=True)
Und in meineotherapp:
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:
Verwandeln Sie 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')
]
Letztlich
python manage.py migrate
Schritt 3:
Transformieren Sie Ihr IntegerField() wieder in das vorherige ForeignKeyField oder OneToOneField, jedoch mit dem neuen Balkenmodell.(Das vorherige Ganzzahlfeld speicherte die ID, 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
Ganz wichtig ist, dass Sie in diesem Schritt alle neuen Migrationen ändern und die Abhängigkeit von den RenameModel Foo-> Bar-Migrationen hinzufügen müssen.Wenn sich also sowohl AnotherModel als auch YetAnotherModel in myotherapp befinden, muss die erstellte Migration in myotherapp so 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:
Eventuell 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 führen Sie dann eine automatische Umbenennung durch
python manage.py makemigrations
(Django sollte Sie fragen, ob Sie den Modellnamen tatsächlich umbenannt haben, sagen Sie „Ja“)
python manage.py migrate
Und das ist es!
Dies funktioniert auf Django1.8
Ich musste das Gleiche tun.Ich habe das Modell auf einmal geändert (d. h. Schritt 1 und Schritt 5 zusammen).Dann 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, sind in Ordnung.
von hier: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/
Für Django 1.10 ist es mir gelungen, zwei Modellklassennamen (einschließlich eines ForeignKey und mit Daten) zu ändern, indem ich einfach „Makemigrations“ und dann „Migrate“ für die App ausgeführt habe.Für den Makemigrations-Schritt musste ich bestätigen, dass ich die Tabellennamen ändern wollte.Migrate hat die Namen der Tabellen problemlos geändert.
Dann änderte ich den Namen des ForeignKey-Felds entsprechend und wurde erneut von Makemigrations gebeten, zu bestätigen, dass ich den Namen ändern wollte.Migrieren Sie dann die Änderung.
Also habe ich dies in zwei Schritten ohne besondere Dateibearbeitung durchgeführt.Zuerst bekam ich Fehlermeldungen, weil ich vergessen hatte, die Datei admin.py zu ändern, wie von @wasibigeek erwähnt.
Ich war auch mit dem von v.thorey beschriebenen Problem konfrontiert und stellte fest, dass sein Ansatz sehr nützlich ist, aber auf weniger Schritte reduziert werden kann, nämlich die Schritte 5 bis 8, wie Fiver es beschrieben hat, ohne die Schritte 1 bis 4, außer dass Schritt 7 als mein geändert werden muss unten 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
Erledigt
P.S.Ich habe diesen Ansatz auf Django 1.9 ausprobiert
Ich verwende Django Version 1.9.4
Ich habe die folgenden Schritte befolgt:-
Ich habe gerade den Model Oldname in NewName Run umbenannt python manage.py makemigrations
.Es wird Sie danach fragenDid you rename the appname.oldName model to NewName? [y/N]
Wählen Sie Y
Laufen python manage.py migrate
und es wird dich darum bitten
Die folgenden Inhaltstypen sind veraltet und müssen gelöscht werden:
appname | oldName
appname | NewName
Alle Objekte, die sich mit diesen Inhaltstypen von einem Fremdschlüssel beziehen, werden ebenfalls gelöscht.Sind Sie sicher, dass Sie diese Inhaltstypen löschen möchten?Wenn Sie sich nicht sicher sind, antworten Sie mit „Nein“.
Type 'yes' to continue, or 'no' to cancel: Select No
Für mich werden alle vorhandenen Daten umbenannt und in eine neue benannte Tabelle migriert.
Leider habe ich Probleme (jeweils Django 1.x) bei der Umbenennungsmigration festgestellt, die dazu führen, dass 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 ordnungsgemäß verfolgt.
Die einfachste Lösung (Workaround):
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, Auslöser, Namen usw. in 2 Commits zu wechseln, sondern für eher kleiner Tabellen):
Commit A:
- Erstellen Sie die Dasselbe Modell wie das alte
# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
...
class Bar(model.Model):
...
- Ändern Sie den Code, um mit dem neuen Modell zu arbeiten
Bar
nur.(einschließlich aller Beziehungen im Schema)
Bereiten Sie sich auf die Migration vor RunPython
, die Daten von Foo in Bar kopieren (einschließlich id
von Foo)
- optionale Optimierung (bei Bedarf für größere Tabellen)
Commit B: (keine Eile, tun Sie es, wenn ein ganzes Team migriert wird)
- sicherer Abwurf des alten Modells
Foo
weitere Aufräumarbeiten:
- Squash auf Migrationen
Fehler in Django:
Ich musste ein paar Tabellen umbenennen.Aber nur eine Modellumbenennung wurde von Django bemerkt.Das geschah, weil Django erst hinzugefügte und dann entfernte Modelle iterierte.Für jedes Paar wird geprüft, ob es sich um dieselbe App handelt identische Felder.Nur eine Tabelle hatte keine Fremdschlüssel für umzubenennende Tabellen (Fremdschlüssel enthalten, wie Sie sich erinnern, den Namen der Modellklasse).Mit anderen Worten: Nur eine Tabelle hatte keine Feldänderungen.Deshalb ist es aufgefallen.
Die Lösung besteht also darin, jeweils eine Tabelle umzubenennen und den Namen der Modellklasse in zu ändern models.py
, möglicherweise views.py
, und eine Migration durchführen.Überprüfen Sie anschließend Ihren Code auf andere Referenzen (Modellklassennamen, zugehörige (Abfrage-)Namen, Variablennamen).Führen Sie bei Bedarf eine Migration durch.Kombinieren Sie dann optional alle diese Migrationen in einer (achten Sie darauf, auch Importe zu kopieren).
Ich würde die Worte von @ceasaro zu meinem Kommentar zu diesem Thema machen Antwort.
Neuere Versionen von Django können Änderungen erkennen und nachfragen, was getan wurde.Ich möchte auch hinzufügen, dass Django die Ausführungsreihenfolge einiger Migrationsbefehle verwechseln könnte.
Es wäre ratsam, kleine Änderungen vorzunehmen und auszuführen makemigrations
Und migrate
und wenn der Fehler auftritt, kann die Migrationsdatei bearbeitet werden.
Die Ausführungsreihenfolge einiger Zeilen kann geändert werden, um Fehler zu vermeiden.
Ich wollte den Kommentar von Ceasaro nur bestätigen und ergänzen.Django 2.0 scheint dies jetzt automatisch zu tun.
Ich verwende Jango 2.2.1 und musste lediglich das Modell umbenennen und makemigrations ausführen.
Hier wird gefragt, ob ich die spezifische Klasse von A in B umbenannt habe, ich habe „Ja“ gewählt und „migrate“ ausgeführt, und alles scheint zu funktionieren.
Hinweis: Ich habe den alten Modellnamen in keiner der Dateien im Ordner „project/migrations“ umbenannt.
Wenn Sie eine gute IDE wie PyCharm verwenden, können Sie mit der rechten Maustaste auf den Modellnamen klicken und einen Refactor -> Umbenennen durchführen.Dies erspart Ihnen die Mühe, Ihren gesamten Code durchzugehen, der auf das Modell verweist.Führen Sie dann makemigrations aus und migrieren Sie.Django 2+ bestätigt lediglich die Namensänderung.
Ich habe Django von Version 10 auf Version 11 aktualisiert:
sudo pip install -U Django
(-U
für „Upgrade“) und es hat das Problem gelöst.