.. _problems_migrations: Probleme bei der Versionierung von Migrationen ************************************************ Bei Arbeiten im Team bzw. bei einem Workflow mit sogenannten Feature-Branches mit einem Versionierungsystem wie ``git`` kommt es immer wieder zu Problemen mit den Migrationen, die von Django erstellt werden. Wenn zum Beispiel in einem Feature-Branch die Datenbank durch eine Migrationen verändert wurde, ist diese Veränderung nicht in einem anderen Branch, zum Beispiel dem Main-Branch verfügbar. Ähnliche Probleme ergeben sich, wenn zwei Personen an der gleichen App arbeiten und Model-Felder ändern. Dann kann es zum Beispiel plötzlich zwei Migrationsdateien mit dem Präfix ``0002`` haben. Strategien mit Migrationsdateien ================================= Das Problem ist nicht neu und die Lösung nicht trivial. Es gibt eigentlich auch keine gute, allgemeingültige Lösung dafür, nur ein paar Regeln, wie man mit dem Problem umgeht. Vermeidet, gleichzeitig an einer App zu arbeiten --------------------------------------------------- oder zumindest sollte nur einer von den Beteiligten Änderungen an einem Model bzw. einer View vornehmen. Gute Absprachen sind wichtig, um später Komplikationen zu vermeiden. Umbennen der Migrationsdateien -------------------------------- Falls doch mal zwei Migrationsdateien mit dem selben numerischen Präfix im Code landen, kann umbenannt werden. Das ``Dependencies``-Attribut nicht vergessen, anzupassen. Migration Rollback --------------------- Falls man in den wichtigen Main-Branch wechselt (checkout), könnte man auf dem Feature-Branch einen Rollback der Migration machen. Das geht in Django relativ einfach, in dem man einfach die Nummer der Migration bei ``migrate`` angibt. .. code-block:: bash python manage.py migrate events 0001 Hier migrieren wir gezielt die Migration ``0001``, die Datenbank wird daraufhin in den Zustand zurückgesetzt. Jetzt könnte man in den Main-branch wechseln, und dort mit dem Zustand der Datenbank wie vorher, weiterarbeiten. Branch Datenbanken ------------------------- Falls im Feature-Branch Arbeiten anstehen, die das Datenbank-Schema massiv verändern und vieleicht sogar Daten-Migrationen durchgeführt werden müssen, ist es zu überlgen, ob man nicht eine eigene Datenbank im Feature-Branch anlegt. Mehr zu dem Thema hier: ``_ Daten-Migration ----------------- Falls ein eindeutiger Slug in schon bestehende Daten eingefügt werden muss, aber die Daten nicht gelöscht werden können, können wir nach folgendem Muster vorgehen. Es sind drei Makemigrations und eine Migration nötig, um das Ziel zu erreichen. 1) Beispiel: Ein Slug-Field erstellen mit null=True .......................................... Erstmal setzen wir das Slugfield weder auf unique noch machen wir es mandatory. Das etwas seltsam anmutende Konstrukt macht aber was es soll, und zwar Null Values und doppelte Vorkommen erstmal zu erlauben. .. code-block:: python slug = models.SlugField(null=True) Dann führen wir die Migration für die App ``pets`` aus: .. code-block:: bash python manage.py makemigrations pets An dieser Stelle sollte nichts schiefgehen, da wir einfach eine neue Spalte in die Tabelle eingetragen haben. Es wurde eine Migrationsdatei erstellt. 2. Erstelle eine Daten-Migration .................................... und sorge dafür, dass die Daten eindeutig eingetragen werden. .. admonition:: Daten-Migration Django ``Datenmigrationen`` sind eine bequeme Möglichkeit, Daten in einer Datenbank bei Änderung des Datenbank-Schemas gleich mit zu ändern. Sie funktionieren im Grunde wie normale Migrationen, verändern aber statt des DB-Schemas Daten. So könnte man neue Daten auf Basis schon bestehender Daten in ein Feld einfügen. Ein häufiger Anwendungsfall für Datenmigrationen ist die Implementierung neuer Felder, die nicht optional sind und mit eindeutigen Werten gefüllt werden müssen, wie zum Beispiel ein eindeutiges Pflicht-Slug-Feld in einer bereits bestehenden Tabelle. Man könnte das zwar mühselig und händisch selber machen, aber die Team-Kollegen stehen dann vor dem gleichen Problem. Zum Erstellen von Datenmigrationen wird eine leere Migrationsdatei mit dem Argument ``--empty`` erstellt. Über den ``RunPython``-Befehl wird Django mitgeteilt, welche Funktion beim Migrieren ausgeführt werden soll. Hier hat man Zugriff auf das Model und kann wie gewohnt mit dem ``ORM`` arbeiten. Die zweite Funktionsreferenz in ``RunPython`` gibt an, was bei einem Rollback passieren soll. Wird hier nichts angegeben, scheitert die Migration mit einer Exception. ``migrations.RunPython.noop`` verhindert das Scheitern und macht einfach gar nichts. Mehr zum Thema Datenmigrationen in der Doku: ``_ Dafür erstellen wir eine leere Migrationsdatei mit ``empty``... .. code-block:: bash python manage.py makemigrations pets --empty und tragen in die neu erstelle Migrationsdatei folgenden Code ein: .. code-block:: python from django.db import migrations from django.utils.text import slugify def populate_slug_field(apps, schema_editor): petmodel = apps.get_model("pets", "Pet") for pet in petmodel.objects.all(): pet.slug = slugify(pet) pet.save() class Migration(migrations.Migration): dependencies = [ ("pets", "0002_pet_slug"), ] operations = [ migrations.RunPython( populate_slug_field, migrations.RunPython.noop ), ] Die Operations-Liste sorgt mit dem Befehl ``migrations.RunPython`` dafür, dass auf der Datenbank später beim Migrieren das Skript ``populate_slug_field`` ausgeführt wird. Ansonsten macht diese Migration nichts weiter. ``migrations.RunPython.noop`` ist die Funktion, die bei Rollback ausgeführt würde, sie macht einfach nichts. ``populate_slug_field`` macht aus den schon vorhandenen Einträgen jeweils einen Slug und füllt damit die entsprechende Spalte. Hier sollte darauf geachtet werden, dass alle Werte eindeutig sind, da es ansonsten später zu einem Fehler kommt. Schließlich soll das Slug-Feld ``unique=True`` sein. 3. der letzte Schritt .................................... Verändere das Model und erstelle die dritte Migration. Dann erst können wir migrieren. Wir nehmen also die ``null=True`` - Eigenschaft wieder aus dem Slugfield und setzen das Feld jetzt endlich auf ``unique=True``. .. code-block:: python slug = models.SlugField(unique=True) und führen die dritte MakeMigration für diese App aus: .. code-block:: bash python manage.py makemigrations pets Django merkt, dass wir ein Pflichtfeld im Model haben (slug), und will nun wissen, wie die schon vorhandenen Objekte in der Tabelle damit umgehen sollen. Da wir eine Datenmigration geschrieben haben, die unsere Datenbank-Tabelle füllen wird, ignorieren wir diese Nachricht und lassen RunPython den Rest erledigen. Deshalb wählen wir ``2) Ignore for now`` .. code-block:: bash It is impossible to change a nullable field 'slug' on tag to non-nullable without providing a default. This is because the database needs something to populate existing rows. Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Ignore for now. Existing rows that contain NULL values will have to be handled manually, for example with a RunPython or RunSQL operation. 3) Quit and manually define a default value in models.py. Select an option: 2 python manage.py migrate pets Wenn die Migrationen anstandslos durchgelaufen sind, prüfen wir das Ergebnis gleich mal in der Datenbank.