Dies ist der zweite Artikel in unserer Reihe zu Django-Migrationen:
- Teil 1:Django-Migrationen:Eine Einführung
- Teil 2:Tiefer in Django-Migrationen eintauchen (aktueller Artikel)
- Teil 3:Datenmigrationen
- Video:Django 1.7-Migrationen – eine Einführung
Im vorherigen Artikel dieser Reihe haben Sie den Zweck von Django-Migrationen kennengelernt. Sie haben grundlegende Nutzungsmuster wie das Erstellen und Anwenden von Migrationen kennengelernt. Jetzt ist es an der Zeit, tiefer in das Migrationssystem einzutauchen und einen Blick auf einige der zugrunde liegenden Mechanismen zu werfen.
Am Ende dieses Artikels wissen Sie:
- Wie Django Migrationen verfolgt
- Wie Migrationen wissen, welche Datenbankoperationen auszuführen sind
- Wie Abhängigkeiten zwischen Migrationen definiert werden
Sobald Sie sich mit diesem Teil des Django-Migrationssystems vertraut gemacht haben, sind Sie gut darauf vorbereitet, Ihre eigenen benutzerdefinierten Migrationen zu erstellen. Lass uns direkt da weitermachen, wo wir aufgehört haben!
Dieser Artikel verwendet den bitcoin_tracker
Django-Projekt, das in Django Migrations:A Primer erstellt wurde. Sie können dieses Projekt entweder neu erstellen, indem Sie diesen Artikel durcharbeiten, oder Sie können den Quellcode herunterladen:
Quellcode herunterladen: Klicken Sie hier, um den Code für das Django-Migrationsprojekt herunterzuladen, das Sie in diesem Artikel verwenden werden.
Woher Django weiß, welche Migrationen anzuwenden sind
Lassen Sie uns den allerletzten Schritt des vorherigen Artikels in der Serie zusammenfassen. Sie haben eine Migration erstellt und dann alle verfügbaren Migrationen mit python manage.py migrate
angewendet .Wenn dieser Befehl erfolgreich ausgeführt wurde, stimmen Ihre Datenbanktabellen jetzt mit den Definitionen Ihres Modells überein.
Was passiert, wenn Sie diesen Befehl erneut ausführen? Probieren wir es aus:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
No migrations to apply.
Nichts ist passiert! Sobald eine Migration auf eine Datenbank angewendet wurde, wendet Django diese Migration nicht erneut auf diese bestimmte Datenbank an. Um sicherzustellen, dass eine Migration nur einmal angewendet wird, müssen die angewendeten Migrationen nachverfolgt werden.
Django verwendet eine Datenbanktabelle namens django_migrations
. Django erstellt diese Tabelle automatisch in Ihrer Datenbank, wenn Sie zum ersten Mal Migrationen anwenden. Für jede angewendete oder gefälschte Migration wird eine neue Zeile in die Tabelle eingefügt.
So sieht diese Tabelle beispielsweise in unserem bitcoin_tracker
aus Projekt:
ID | App | Name | Angewandt |
---|---|---|---|
1 | contenttypes | 0001_initial | 2019-02-05 20:23:21.461496 |
2 | auth | 0001_initial | 2019-02-05 20:23:21.489948 |
3 | admin | 0001_initial | 2019-02-05 20:23:21.508742 |
4 | admin | 0002_logentry_remove... | 2019-02-05 20:23:21.531390 |
5 | admin | 0003_logentry_add_ac... | 2019-02-05 20:23:21.564834 |
6 | contenttypes | 0002_remove_content_... | 2019-02-05 20:23:21.597186 |
7 | auth | 0002_alter_permissio... | 2019-02-05 20:23:21.608705 |
8 | auth | 0003_alter_user_emai... | 2019-02-05 20:23:21.628441 |
9 | auth | 0004_alter_user_user... | 2019-02-05 20:23:21.646824 |
10 | auth | 0005_alter_user_last... | 2019-02-05 20:23:21.661182 |
11 | auth | 0006_require_content... | 2019-02-05 20:23:21.663664 |
12 | auth | 0007_alter_validator... | 2019-02-05 20:23:21.679482 |
13 | auth | 0008_alter_user_user... | 2019-02-05 20:23:21.699201 |
14 | auth | 0009_alter_user_last... | 2019-02-05 20:23:21.718652 |
15 | historical_data | 0001_initial | 2019-02-05 20:23:21.726000 |
16 | sessions | 0001_initial | 2019-02-05 20:23:21.734611 |
19 | historical_data | 0002_switch_to_decimals | 2019-02-05 20:30:11.337894 |
Wie Sie sehen können, gibt es einen Eintrag für jede angewendete Migration. Die Tabelle enthält nicht nur die Migrationen aus unseren historical_data
App, sondern auch die Migrationen von allen anderen installierten Apps.
Beim nächsten Ausführen von Migrationen überspringt Django die in der Datenbanktabelle aufgeführten Migrationen. Das bedeutet, dass Django diese Änderungen ignoriert, selbst wenn Sie die Datei einer bereits angewendeten Migration manuell ändern, solange es bereits einen Eintrag dafür in der Datenbank gibt.
Sie könnten Django dazu verleiten, eine Migration erneut auszuführen, indem Sie die entsprechende Zeile aus der Tabelle löschen, aber das ist selten eine gute Idee und kann zu einem kaputten Migrationssystem führen.
Die Migrationsdatei
Was passiert, wenn Sie python manage.py makemigrations <appname>
ausführen ? Django sucht nach Änderungen, die an den Modellen in Ihrer App <appname>
vorgenommen wurden . Wenn es welche findet, wie z. B. ein hinzugefügtes Modell, erstellt es eine Migrationsdatei in migrations
Unterverzeichnis. Diese Migrationsdatei enthält eine Liste von Vorgängen, um Ihr Datenbankschema mit Ihrer Modelldefinition zu synchronisieren.
Hinweis: Ihre App muss in INSTALLED_APPS
aufgeführt sein -Einstellung und muss einen migrations
enthalten Verzeichnis mit einem __init__.py
Datei. Andernfalls erstellt Django keine Migrationen dafür.
Die migrations
Verzeichnis wird automatisch erstellt, wenn Sie eine neue App mit dem startapp
erstellen Verwaltungsbefehl, aber es ist leicht zu vergessen, wenn Sie eine App manuell erstellen.
Die Migrationsdateien sind nur Python, also werfen wir einen Blick auf die erste Migrationsdatei in den historical_prices
App. Sie finden es unter historical_prices/migrations/0001_initial.py
. Es sollte etwa so aussehen:
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.CreateModel(
name='PriceHistory',
fields=[
('id', models.AutoField(
verbose_name='ID',
serialize=False,
primary_key=True,
auto_created=True)),
('date', models.DateTimeField(auto_now_add=True)),
('price', models.DecimalField(decimal_places=2, max_digits=5)),
('volume', models.PositiveIntegerField()),
('total_btc', models.PositiveIntegerField()),
],
options={
},
bases=(models.Model,),
),
]
Wie Sie sehen können, enthält es eine einzelne Klasse namens Migration
das von django.db.migrations.Migration
erbt . Dies ist die Klasse, nach der das Migrations-Framework sucht und es ausführt, wenn Sie es auffordern, Migrationen anzuwenden.
Die Migration
Klasse enthält zwei Hauptlisten:
dependencies
operations
Migrationsvorgänge
Schauen wir uns die operations
an zuerst auflisten. Diese Tabelle enthält die Operationen, die im Rahmen der Migration durchgeführt werden sollen. Operationen sind Unterklassen der Klasse django.db.migrations.operations.base.Operation
. Hier sind die allgemeinen Operationen, die in Django integriert sind:
Operationsklasse | Beschreibung |
---|---|
CreateModel | Erstellt ein neues Modell und die entsprechende Datenbanktabelle |
DeleteModel | Löscht ein Modell und legt seine Datenbanktabelle ab |
RenameModel | Benennt ein Modell um und benennt seine Datenbanktabelle um |
AlterModelTable | Benennt die Datenbanktabelle für ein Modell um |
AlterUniqueTogether | Ändert die eindeutigen Einschränkungen eines Modells |
AlterIndexTogether | Ändert die Indizes eines Modells |
AlterOrderWithRespectTo | Erzeugt oder löscht den _order Spalte für ein Modell |
AlterModelOptions | Ändert verschiedene Modelloptionen ohne Auswirkungen auf die Datenbank |
AlterModelManagers | Ändert die während der Migration verfügbaren Manager |
AddField | Fügt ein Feld zu einem Modell und der entsprechenden Spalte in der Datenbank hinzu |
RemoveField | Entfernt ein Feld aus einem Modell und löscht die entsprechende Spalte aus der Datenbank |
AlterField | Ändert die Definition eines Felds und ändert bei Bedarf seine Datenbankspalte |
RenameField | Benennt ein Feld und ggf. auch seine Datenbankspalte um |
AddIndex | Erzeugt einen Index in der Datenbanktabelle für das Modell |
RemoveIndex | Entfernt einen Index aus der Datenbanktabelle für das Modell |
Beachten Sie, wie die Operationen nach Änderungen an Modelldefinitionen benannt werden, nicht die Aktionen, die in der Datenbank ausgeführt werden. Wenn Sie eine Migration anwenden, ist jede Operation für das Generieren der erforderlichen SQL-Anweisungen für Ihre spezifische Datenbank verantwortlich. Beispiel:CreateModel
würde eine CREATE TABLE
erzeugen SQL-Anweisung.
Standardmäßig unterstützen Migrationen alle Standarddatenbanken, die Django unterstützt. Wenn Sie sich also an die hier aufgeführten Operationen halten, können Sie mehr oder weniger alle gewünschten Änderungen an Ihren Modellen vornehmen, ohne sich um das zugrunde liegende SQL kümmern zu müssen. Das ist alles für Sie erledigt.
Hinweis: In einigen Fällen erkennt Django Ihre Änderungen möglicherweise nicht richtig. Wenn Sie ein Modell umbenennen und mehrere seiner Felder ändern, könnte Django dies mit einem neuen Modell verwechseln.
Anstelle eines RenameModel
und mehrere AlterField
Operationen, wird ein DeleteModel
erstellt und ein CreateModel
Betrieb. Anstatt die Datenbanktabelle für das Modell umzubenennen, wird sie gelöscht und eine neue Tabelle mit dem neuen Namen erstellt, wodurch effektiv alle Ihre Daten gelöscht werden!
Machen Sie es sich zur Gewohnheit, die generierten Migrationen zu überprüfen und auf einer Kopie Ihrer Datenbank zu testen, bevor Sie sie auf Produktionsdaten ausführen.
Django bietet drei weitere Operationsklassen für fortgeschrittene Anwendungsfälle:
RunSQL
ermöglicht es Ihnen, benutzerdefiniertes SQL in der Datenbank auszuführen.RunPython
ermöglicht es Ihnen, beliebigen Python-Code auszuführen.SeparateDatabaseAndState
ist ein spezialisierter Betrieb für fortgeschrittene Anwendungen.
Mit diesen Operationen können Sie grundsätzlich alle gewünschten Änderungen an Ihrer Datenbank vornehmen. Diese Operationen finden Sie jedoch nicht in einer Migration, die automatisch mit makemigrations
erstellt wurde Verwaltungsbefehl.
Seit Django 2.0 sind auch einige PostgreSQL-spezifische Operationen in django.contrib.postgres.operations
verfügbar die Sie verwenden können, um verschiedene PostgreSQL-Erweiterungen zu installieren:
BtreeGinExtension
BtreeGistExtension
CITextExtension
CryptoExtension
HStoreExtension
TrigramExtension
UnaccentExtension
Beachten Sie, dass eine Migration, die einen dieser Vorgänge enthält, einen Datenbankbenutzer mit Superuser-Berechtigungen erfordert.
Zu guter Letzt können Sie auch eigene Operationsklassen erstellen. Wenn Sie sich damit befassen möchten, werfen Sie einen Blick auf die Django-Dokumentation zum Erstellen benutzerdefinierter Migrationsvorgänge.
Migrationsabhängigkeiten
Die dependencies
Liste in einer Migrationsklasse enthält alle Migrationen, die angewendet werden müssen, bevor diese Migration angewendet werden kann.
In der 0001_initial.py
Migration, die Sie oben gesehen haben, muss nichts vorher angewendet werden, sodass es keine Abhängigkeiten gibt. Schauen wir uns die zweite Migration in den historical_prices
an App. In der Datei 0002_switch_to_decimals.py
, die dependencies
Attribut von Migration
hat einen Eintrag:
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('historical_data', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='pricehistory',
name='volume',
field=models.DecimalField(decimal_places=3, max_digits=7),
),
]
Die obige Abhängigkeit besagt, dass die Migration 0001_initial
der App historical_data
muss zuerst ausgeführt werden. Das macht Sinn, weil die Migration 0001_initial
erstellt die Tabelle, die das Feld enthält, das die Migration 0002_switch_to_decimals
enthält will sich ändern.
Eine Migration kann auch wie folgt von einer Migration aus einer anderen App abhängig sein:
class Migration(migrations.Migration):
...
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]
Dies ist normalerweise erforderlich, wenn ein Modell einen Fremdschlüssel hat, der auf ein Modell in einer anderen App zeigt.
Alternativ können Sie auch erzwingen, dass vorher eine Migration durchgeführt wird eine weitere Migration mit dem Attribut run_before
:
class Migration(migrations.Migration):
...
run_before = [
('third_party_app', '0001_initial'),
]
Abhängigkeiten können auch kombiniert werden, sodass Sie mehrere Abhängigkeiten haben können. Diese Funktionalität bietet viel Flexibilität, da Sie Fremdschlüssel aufnehmen können, die von Modellen aus verschiedenen Apps abhängen.
Die Möglichkeit, Abhängigkeiten zwischen Migrationen explizit zu definieren, bedeutet auch, dass die Nummerierung der Migrationen (normalerweise 0001
, 0002
, 0003
, …) stellt nicht genau die Reihenfolge dar, in der Migrationen angewendet werden. Sie können jede gewünschte Abhängigkeit hinzufügen und so die Reihenfolge steuern, ohne alle Migrationen neu nummerieren zu müssen.
Migration anzeigen
Sie müssen sich im Allgemeinen nicht um das SQL kümmern, das bei Migrationen generiert wird. Aber wenn Sie überprüfen möchten, ob das generierte SQL sinnvoll ist, oder einfach nur neugierig sind, wie es aussieht, dann ist Django mit sqlmigrate
genau das Richtige für Sie Verwaltungsbefehl:
$ python manage.py sqlmigrate historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
CREATE TABLE "historical_data_pricehistory" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"date" datetime NOT NULL,
"price" decimal NOT NULL,
"volume" integer unsigned NOT NULL
);
COMMIT;
Dadurch werden die zugrunde liegenden SQL-Abfragen aufgelistet, die von der angegebenen Migration basierend auf der Datenbank in Ihrer settings.py
generiert werden Datei. Wenn Sie den Parameter --backwards
übergeben , generiert Django die SQL, um die Migration rückgängig zu machen:
$ python manage.py sqlmigrate --backwards historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
DROP TABLE "historical_data_pricehistory";
COMMIT;
Sobald Sie die Ausgabe von sqlmigrate
sehen Für eine etwas komplexere Migration werden Sie es vielleicht zu schätzen wissen, dass Sie all dieses SQL nicht von Hand erstellen müssen!
Wie Django Änderungen an Ihren Modellen erkennt
Sie haben gesehen, wie eine Migrationsdatei aussieht und wie ihre Liste von Operation
ist Klassen definiert die Änderungen, die an der Datenbank vorgenommen werden. Aber woher weiß Django genau, welche Operationen in eine Migrationsdatei aufgenommen werden sollen? Sie erwarten vielleicht, dass Django Ihre Modelle mit Ihrem Datenbankschema vergleicht, aber das ist nicht der Fall.
Beim Ausführen von makemigrations
, Django nicht überprüfen Sie Ihre Datenbank. Es vergleicht auch Ihre Modelldatei nicht mit einer früheren Version. Stattdessen geht Django alle durchgeführten Migrationen durch und erstellt einen Projektstatus, wie die Modelle aussehen sollen. Dieser Projektstatus wird dann mit Ihren aktuellen Modelldefinitionen verglichen, und es wird eine Liste von Vorgängen erstellt, die bei Anwendung den Projektstatus mit den Modelldefinitionen auf den neuesten Stand bringen würden.
Schach spielen mit Django
Sie können sich Ihre Modelle wie ein Schachbrett vorstellen, und Django ist ein Schachgroßmeister, der Ihnen dabei zusieht, wie Sie gegen sich selbst spielen. Aber der Großmeister beobachtet nicht jede deiner Bewegungen. Der Großmeister schaut nur auf das Brett, wenn Sie makemigrations
rufen .
Da es nur eine begrenzte Anzahl möglicher Züge gibt (und der Großmeister ein Großmeister ist), kann sie sich die Züge einfallen lassen, die passiert sind, seit sie das letzte Mal auf das Brett geschaut hat. Sie macht sich ein paar Notizen und lässt dich spielen, bis du makemigrations
rufst nochmal.
Beim nächsten Blick auf das Brett erinnert sich die Großmeisterin nicht mehr daran, wie das Schachbrett beim letzten Mal aussah, aber sie kann ihre Notizen zu den vorherigen Zügen durchgehen und sich ein mentales Modell davon erstellen, wie das Schachbrett aussah.
Wenn Sie jetzt migrate
rufen , spielt die Großmeisterin alle aufgezeichneten Züge auf einem anderen Schachbrett ab und notiert in einer Tabelle, welche ihrer Rekorde bereits angewendet wurden. Dieses zweite Schachbrett ist Ihre Datenbank und die Tabelle ist django_migrations
Tabelle.
Diese Analogie ist ziemlich passend, da sie einige Verhaltensweisen von Django-Migrationen gut veranschaulicht:
-
Django-Migrationen versuchen effizient zu sein: So wie der Großmeister davon ausgeht, dass Sie die wenigsten Züge gemacht haben, wird Django versuchen, die effizientesten Migrationen zu erstellen. Wenn Sie ein Feld namens
A
hinzufügen zu einem Modell und benennen Sie es dann inB
um , und führen Sie dannmakemigrations
aus , dann erstellt Django eine neue Migration, um ein Feld mit dem NamenB
hinzuzufügen . -
Django-Migrationen haben ihre Grenzen: Wenn Sie viele Züge machen, bevor Sie den Großmeister auf das Schachbrett sehen lassen, kann er möglicherweise nicht die genauen Bewegungen jeder Figur nachvollziehen. Ebenso findet Django möglicherweise nicht die richtige Migration, wenn Sie zu viele Änderungen auf einmal vornehmen.
-
Die Django-Migration erwartet, dass Sie sich an die Regeln halten: Wenn Sie etwas Unerwartetes tun, wie z. B. ein zufälliges Stück vom Brett nehmen oder mit den Noten herumspielen, bemerkt der Großmeister es vielleicht zunächst nicht, aber früher oder später wird sie die Hände hochwerfen und sich weigern, weiterzumachen. Dasselbe passiert, wenn Sie mit
django_migrations
herumspielen Tabelle oder ändern Sie Ihr Datenbankschema außerhalb von Migrationen, indem Sie beispielsweise die Datenbanktabelle für ein Modell löschen.
SeparateDatabaseAndState
verstehen
Nachdem Sie nun den Projektstatus kennen, den Django erstellt, ist es an der Zeit, sich die Operation SeparateDatabaseAndState
genauer anzusehen . Diese Operation kann genau das tun, was der Name schon sagt:Sie kann den Projektstatus (das mentale Modell, das Django erstellt) von Ihrer Datenbank trennen.
SeparateDatabaseAndState
wird mit zwei Operationslisten instanziiert:
state_operations
enthält Operationen, die nur auf den Projektstatus angewendet werden.database_operations
enthält Operationen, die nur auf die Datenbank angewendet werden.
Mit dieser Operation können Sie jede Art von Änderung an Ihrer Datenbank vornehmen, aber es liegt in Ihrer Verantwortung, sicherzustellen, dass der Projektstatus anschließend zur Datenbank passt. Beispielanwendungsfälle für SeparateDatabaseAndState
ein Modell von einer App in eine andere verschieben oder einen Index in einer riesigen Datenbank ohne Ausfallzeiten erstellen.
SeparateDatabaseAndState
ist eine fortgeschrittene Operation und Sie werden an Ihrem ersten Tag nicht mit Migrationen arbeiten müssen und vielleicht auch nie. SeparateDatabaseAndState
ist vergleichbar mit einer Herzoperation. Es birgt ein ziemliches Risiko und ist nicht etwas, das Sie nur zum Spaß tun, aber manchmal ist es ein notwendiger Eingriff, um den Patienten am Leben zu erhalten.
Schlussfolgerung
Damit ist Ihr tiefer Einblick in die Django-Migrationen abgeschlossen. Herzliche Glückwünsche! Sie haben eine ganze Reihe fortgeschrittener Themen behandelt und wissen nun, was unter der Haube von Migrationen passiert.
Das haben Sie gelernt:
- Django verfolgt angewandte Migrationen in der Django-Migrationstabelle.
- Django-Migrationen bestehen aus einfachen Python-Dateien, die eine
Migration
enthalten Klasse. - Django weiß, welche Änderungen von den
operations
durchzuführen sind Liste in derMigration
Klassen. - Django vergleicht Ihre Modelle mit einem Projektstatus, den es aus den Migrationen erstellt.
Mit diesem Wissen sind Sie nun bereit, den dritten Teil der Serie über Django-Migrationen in Angriff zu nehmen, in dem Sie lernen, wie Sie Datenmigrationen verwenden, um einmalige Änderungen an Ihren Daten sicher vorzunehmen. Bleiben Sie dran!
In diesem Artikel wurde der bitcoin_tracker
verwendet Django-Projekt, das in Django Migrations:A Primer erstellt wurde. Sie können dieses Projekt entweder neu erstellen, indem Sie diesen Artikel durcharbeiten, oder Sie können den Quellcode herunterladen:
Quellcode herunterladen: Klicken Sie hier, um den Code für das Django-Migrationsprojekt herunterzuladen, das Sie in diesem Artikel verwenden werden.