.. _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.