Django migration migraine

Het was een tijd en een plaats. Ik - oke, ik stop met dit flauwe gedoe. Het was ochtend en ik zat achter mijn bureau. Ik was bezig met een kleine refactor van wat code. Dacht ik.

Voor een nieuwe module waar ik mee bezig was, had ik een aantal zogeheten Models gemaakt. Dit is doorgaans Object Oriented jargon voor "database tabel".

Normaal hebben database tabellen een identificatie column, de primary key. Vaak een getal. "Dit is rij nr. 593 in de tabel". Maar mijn model moest over op willekeurige UUIDs, want de rest van de models in de code waren ook zo, en allerlei onderdelen berusten op die impliciete(!) standaard. Een UUID is ook een getal, maar dan van 32 hexadecimale cijfers (want 128 bits). En niet oplopend zoals de typische ID INT PRIMARY KEY is.

Een simpele klus, want het hele idee van een Object Relational Mapper zoals Django is dat je je Modellen kan uitdrukken in de object oriented programmeertaal waar je in werkt, en dat Django onder de motorkap met de database stoeit om het gewoon te laten werken. Ik zou gewoon een regeltje code aan kunnen passen en dan zou het werken. Dus ik pas die model aan zodat het oude nummertje een andere naam had en niet meer de primary key was. Daarnaast had ik een nieuw UUID veld, primary key, toegevoegd.

Django had andere plannen.

Als je de models verandert, moet je poetry run ./manage.py makemigrations doen. Daarmee maakt Django ergens in het project een bestandje met stappen waarmee de database daadwerkelijk omgekat zal worden. Dan doe je poetry run ./manage.py migrate, zodat hij die bestandjes 1 voor 1 afgaat, waarop hij accuut zeikt dat hij niet 2 primary keys tegelijk kan hebben.

Wat doet Django nou, die domme malware (een worm, wan-ORM): Dat kut ding probeert eerst een nieuw primary key UUID veld te maken en dan pas het oude veld niet-primary te maken. Natuurlijk kun je niet 2 primary keys tegelijk hebben. Ik heb ook maar 1 BSN. Op dat moment wil ik er vanaf zijn en draai ik de 2 operaties om, en hij gaat verder.

Vervolgens zeikt hij over een PROTECT: er waren andere models gelinkt aan de veranderde model. De database snapt dan niet dat de primaire identificatie momentaan verdwijnt - dat brengt alle relaties in het geding. Maar waarom is dit mijn probleem? Waarom regelt Django dit niet gewoon??

Aangezien al die models toch nog geen instances hadden (in database parlance: alle tabellen zijn nog leeg), en er dus in feite nog geen relaties waren, flikkerde ik die ganse foreign key gewoon weg. Hop, geen ON DELETE PROTECT gezeik.

En daarna maakte ik een tweede migraine die dan de foreign key weer terug zet. Oh, en nu hij vraagt een default omdat hij denkt dat er misschien bestaande rijen kunnen zijn, waarvan de relatie dan natuurlijk onbekend is? Zucht! Nee Django, die bestaan niet, want al die models zijn spiksplinternieuw en nog leeg, maar dat kan ik je niet vertellen op een manier die je begrijpt! Dan maar None als default.

Dat accepteert hij, ook al is die foreign key column als "not null" geregistreerd(??). Bijzonder. En niet erg intuïtief. En niet erg logisch. En niet erg gebruiksvriendelijk. En niet erg goed doordacht. En niet echt geschikt voor professionele software.

In SQL was dit makkelijker geweest, en had ik het direct goed gedaan:

  1. In de andere tabellen, verwijder de foreign key relatie op x en hernoem de column naar old_x_id.
  2. In tabel x, hernoem id naar old_id en verwijder primary
  3. In tabel x, maak een UUID id en maak die primary
  4. In de andere tabellen, maak de nieuwe foreign key x_id, door tabel.old_x_id te matchen aan x.old_id en dan x.id te selecten in tabel.x_id.

Maar dat snapt Django niet. Dat weet Django niet. Ook al is het the one job die Django heeft.

Al met al heb ik toch te veel verwacht van Django de worm. Had ik kunnen zien aankomen: in Django kun je niet eens filters toepassen op "calculated fields". (In database parlance, "view", niet te verwarren met een "view" in het Model-View-Controller model ("model" hier niet te verwarren met Model in het ...))

Het wordt nog beter: ik dus na dit gedoe mijn migraine bestandje pushen naar mijn git branch; merge request maken om mijn branch naar main te mergen. Wat blijkt: iemand anders had al een "migratie nr. 7" gemaakt. Want ja, zo houdt Django dat bij! Dus nu krijg je tijdens een poetry run ./manage.py migrate gezeik over een migration merge. Bellissimo.

Ik zou graag willen zien van mijn collega's in het veld dat ze iets hogere standaarden krijgen voor de tools waar ze mee werken. En dat ze verder kijken dan hun OOP lang is. Als dat de standaard houding was geweest, had ik dit gezeik niet gehad. Was mijn energie en focus voor de dag niet opgeslokt door een onnodig zwart gat. Had ik gewoon de taak die ik eigenlijk erna had willen doen, kunnen doen.

</rant>

2024-04-20 in blog