From f5eb025f63e1ed0f31764d51b039339ec6ffbeba Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:08:59 -0600 Subject: [PATCH] fix(migrations): make the ui_use_tailwind removal idempotent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0293_remove_usercontactinfo_ui_use_tailwind used a plain RemoveField, which emits an unconditional `ALTER TABLE ... DROP COLUMN` and fails with `column "ui_use_tailwind" of relation "dojo_usercontactinfo" does not exist` whenever the column is not physically present when the migration runs — which the OSS->Pro upgrade path hits (the upgrade fixture reaches 0293 without the column), and which any instance with migration-history/schema skew can hit too. Split the operation into SeparateDatabaseAndState: keep the state RemoveField so Django's model state is unchanged (no new migration is generated), and make the database drop idempotent with `DROP COLUMN IF EXISTS`. UserContactInfo is not pghistory-tracked, so no event table carries the column and a raw ALTER TABLE is sufficient. Mirrors the existing SeparateDatabaseAndState removal pattern in 0266_remove_credential_manager. The reverse re-adds the column with its original 0267 definition. Co-Authored-By: Claude Opus 4.8 --- ..._remove_usercontactinfo_ui_use_tailwind.py | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/dojo/db_migrations/0293_remove_usercontactinfo_ui_use_tailwind.py b/dojo/db_migrations/0293_remove_usercontactinfo_ui_use_tailwind.py index e691cbb78c..14deb432f9 100644 --- a/dojo/db_migrations/0293_remove_usercontactinfo_ui_use_tailwind.py +++ b/dojo/db_migrations/0293_remove_usercontactinfo_ui_use_tailwind.py @@ -1,15 +1,45 @@ +""" +Remove the ``ui_use_tailwind`` UI opt-in from UserContactInfo. + +Drops the classic-vs-Tailwind opt-in added in 0267. The database drop is made +idempotent (``DROP COLUMN IF EXISTS``): an instance can reach this migration +without the column physically present — the OSS->Pro upgrade fixtures, and any +database whose recorded migration history skews from its physical schema, hit +exactly that — and a plain ``RemoveField`` emits an unconditional +``ALTER TABLE ... DROP COLUMN`` that then fails with +``column "ui_use_tailwind" ... does not exist``. Splitting the state removal +from an ``IF EXISTS`` database drop keeps Django's model state correct while +tolerating the absent column. UserContactInfo is not pghistory-tracked, so no +event table carries the column and a raw ``ALTER TABLE`` is sufficient. +""" + from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('dojo', '0292_review_request_notes_public'), + ("dojo", "0292_review_request_notes_public"), ] operations = [ - migrations.RemoveField( - model_name='usercontactinfo', - name='ui_use_tailwind', + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name="usercontactinfo", + name="ui_use_tailwind", + ), + ], + database_operations=[ + migrations.RunSQL( + sql="ALTER TABLE dojo_usercontactinfo DROP COLUMN IF EXISTS ui_use_tailwind;", + # Reverse re-adds the column with its original definition (0267: + # BooleanField(default=False)) so a downgrade restores a working schema. + reverse_sql=( + "ALTER TABLE dojo_usercontactinfo " + "ADD COLUMN IF NOT EXISTS ui_use_tailwind boolean NOT NULL DEFAULT false;" + ), + ), + ], ), ]