Django - migracje bazy, które nie zawsze działają

O kiepskim podejściu do migracji schematów baz danych w Django pisałem już w 2014r. Co jakiś czas jednak temat do mnie wraca, gdyż w niektórych projektach używam (niestety) tego rozwiązania. Powody są różne, a główny to "oszczędność czasu". No bo trudno zaprzeczyć, że automatyczne wygenerowanie plików z migracjami jest wolniejsze od klepania XML-i Liquibase lub plain SQL, prawda? Sam z tego przecież korzystam...

Jednak bywają takie momenty, gdzie zostaję z tymi migracjami w "czterech literach", gdzie nawet nie dochodzi ani jeden promyk światła. I taką sytuacją jest m.in. usuwanie atrybutu z modelu, co generuje operację RemoveField.

Bodaj wszystkie wbudowane w Django operacje posiadają mechanikę pozwalającą wycofać daną operację, RemoveField także. Ta akurat nie zadziała, gdy pole było zadeklarowane jako NOT NULL, ponieważ:

  • Django nie ustawia defaultów na poziomie baz danych, więc nigdy nie wygeneruje defaulta na poziomie SQL (co byłoby rozwiązaniem w sytuacjach, gdy default masz określony)
  • Implementacja backward w RemoveField jest oparta o pseudo-automat, tzw. schema editor, a ten "wie" że ma być NOT NULL (i nikt go nie przekona)
  • Sekwencja rollback dla przypadku usuwania pola jest bardziej złożona i zależna od kontekstu (dodanie pola nullable, wypełnienie danymi, zmiana na not null).

Często jest tak, że sekwencja backward (rollback) jest inna od sekwencji forward i nie zawsze każdej operacji forward odpowiada dokładnie jedna operacja backward (i odwrotnie). To skłania mnie do postawienia tezy:

Sekwencje rollback (backward operations) powinny być definiowane niezależne od forward operations, tj. plik migracji powinien mieć dwie oddzielne ścieżki, które mogą być oczywiście automatycznie generowane. 

To kolejny argument za stwierdzeniem, że wbudowane w Django migracje są zaimplementowane w oparciu o wadliwy koncept. Ale jest na to trochę nieczytelne obejście - operacja RunSQL i puste przebiegi forward.

Rozwiązanie tego konkretnego zagadnienia?

Odstawić czarodzieja schema editor, czyli zamienić operację RemoveField na RunSQL usuwającą kolumnę w forwardzie, ale przywracającą w backwardzie z dozwolonym null. Następnie zadeklarować RunSQL uzupełniający dane usuwanej kolumny w backwardzie, a w forwardzie nie robiący nic (''). Tę operację (lub ich sekwencję) należy umieścić przed alterem usuwającym kolumnę, aby w backwardzie wykonała się po przywróceniu tejże, a zakończyć (rozpocząć) od operacji RunSQL zakładającej w backwardzie not null. Partyzanckie, ale działa.

Czyli:

operations = [ 
    migrations.RemoveField('table', 'attribute'),
]

zamieniamy na:

operations = [
     migrations.RunSQL('', 'alter table <table> alter column <column> set not null'),
     migrations.RunSQL('', 'update <table> set <column> = ....'),
     migrations.RunSQL(
         'alter table <table> drop column <column>',
         'alter table <table> add column <column> [...] NULL'),
]

A na przyszłość usuwając pola z modeli należy wygenerować trzy oddzielne migracje:

  • pierwsza ma ustawiać nullable
  • druga ma być data migration, która w forwadzie nie robi nic (lub robi, jeśli dane przenosimy), a w backwardzie ma kod uzupełniający dane
  • trzecia usuwa kolumnę.

Odwracając proces uzyskamy:

  • dodanie kolumny nullable (schema editor będzie wówczas "mądrzejszy")
  • uzupełnienie kolumny danymi
  • założenie not null constraint.

Krótko o mnie

Jestem programistą i architektem systemów IT. Specjalizuję się w aplikacjach webowych, szczególnie w Python oraz Django, PostgreSQL oraz systemach wyszukiwana ElasticSearch.

Zajmuję się wsparciem istniejących systemów oraz projektowaniem i produkcją. W branży działam od 2001 roku. Oferuję doświadczenie, profesjonalizm oraz indywidualne podejście do zleceń.

Zainteresowanych moimi usługami zapraszam do wysłania zapytania.

Javascript logo PostgreSQL logo Cassandra logo Redis logo ElasticSearch logo Ansible logo HTML5 logo CSS3 logo NGINX logo Docker logo

Komentarze

2016-01-19 16:05:18 MaliniakGD:

Pamiętam jak na studiach robiłem projekt w Pythonie pod django i miałem ogromny problem z migracjami, dodatkowo robiliśmy wszystko równolegle na githubie na gałęziach więc jakoś to szło. Pomimo swoich niedoskonałości jest to rewelacyjny framework. Pozdrawiam

2016-01-19 16:05:36 MaliniakFG:

Pamiętam jak na studiach robiłem projekt w Pythonie pod django i miałem ogromny problem z migracjami, dodatkowo robiliśmy wszystko równolegle na githubie na gałęziach więc jakoś to szło. Pomimo swoich niedoskonałości jest to rewelacyjny framework. Pozdrawiam

2016-01-19 22:12:41 Autor:

Maliniak, jak jesteś botem od tej k..wy, która spamuje mi inny site, to oświadczam tu i teraz twojemu właścicielowi, że go znajdę, przyjadę i nakopię mu do jego obsranej rzyci. Jeśli jednak jesteś człowiekiem, który tylko chciał zareklamować swój biznes wciskając w moje sidła URL swojego site, to napisz do mnie jak człowiek i się dogadamy. Full legal - faktury wystawiam. A jak chcesz rzeczywiście podyskutować o Django, to odezwij się, zwrócę ci należny honor i pogadamy o konkretach.