Database
 sql >> Datenbank >  >> RDS >> Database

Tiefer in die Django-Migrationen eintauchen

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:

  1. dependencies
  2. 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:

  1. RunSQL ermöglicht es Ihnen, benutzerdefiniertes SQL in der Datenbank auszuführen.
  2. RunPython ermöglicht es Ihnen, beliebigen Python-Code auszuführen.
  3. 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 in B um , und führen Sie dann makemigrations aus , dann erstellt Django eine neue Migration, um ein Feld mit dem Namen B 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:

  1. state_operations enthält Operationen, die nur auf den Projektstatus angewendet werden.
  2. 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 der Migration 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.