From 9dbfc3e58ab71781ed1861053afa6ae24a73e2c1 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Mon, 15 Jun 2026 09:10:38 -0600 Subject: [PATCH 1/2] refactor(migrations): update removal of Stub Findings and Credential Manager features to preserve database state --- .../db_migrations/0265_remove_stub_finding.py | 21 +++-- .../0266_remove_credential_manager.py | 82 +++++++++++-------- 2 files changed, 61 insertions(+), 42 deletions(-) diff --git a/dojo/db_migrations/0265_remove_stub_finding.py b/dojo/db_migrations/0265_remove_stub_finding.py index a9432846d8f..65d813cb211 100644 --- a/dojo/db_migrations/0265_remove_stub_finding.py +++ b/dojo/db_migrations/0265_remove_stub_finding.py @@ -1,8 +1,10 @@ -"""Remove the Stub Findings feature. +"""Remove the Stub Findings feature (state only). -Drops the ``Stub_Finding`` model. Stub Findings was deprecated in 2.57.0 and -is end-of-life in 2.59. The model has no inbound foreign keys, so the -deletion is self-contained. +Drops the ``Stub_Finding`` model from Django's state but leaves the +``dojo_stub_finding`` table in place so a downgrade to a release that still +defines the model keeps its data. Stub Findings was deprecated in 2.57.0 and +is end-of-life in 2.59. The model has no inbound foreign keys, so the removal +is self-contained. Note: rebase the filename and the ``dependencies`` tuple to point at whatever the latest migration is at merge time if another migration has @@ -19,7 +21,14 @@ class Migration(migrations.Migration): ] operations = [ - migrations.DeleteModel( - name="Stub_Finding", + migrations.SeparateDatabaseAndState( + # State only: forget the model so it no longer has to be defined + # in dojo/models.py. database_operations is intentionally empty so + # the dojo_stub_finding table is preserved for downgrades. + state_operations=[ + migrations.DeleteModel( + name="Stub_Finding", + ), + ], ), ] diff --git a/dojo/db_migrations/0266_remove_credential_manager.py b/dojo/db_migrations/0266_remove_credential_manager.py index ba04cf317db..5bda915af8a 100644 --- a/dojo/db_migrations/0266_remove_credential_manager.py +++ b/dojo/db_migrations/0266_remove_credential_manager.py @@ -1,9 +1,11 @@ -"""Remove the Credential Manager feature. +"""Remove the Credential Manager feature (state only). -Drops the `Cred_User`, `Cred_Mapping`, and `Cred_UserEvent` models, removes -the pghistory triggers that wrote into the latter, and removes the -`enable_credentials` switch from System_Settings. The Credential Manager -feature was deprecated in 2.57.0 and is end-of-life in 2.59. +Removes the `Cred_User`, `Cred_Mapping`, and `Cred_UserEvent` models, their +pghistory triggers, and the `enable_credentials` switch from System_Settings +from Django's state, but leaves the underlying tables, columns, and triggers +in the database so a downgrade to a release that still defines them keeps its +data. The Credential Manager feature was deprecated in 2.57.0 and is +end-of-life in 2.59. """ import pgtrigger.migrations @@ -17,36 +19,44 @@ class Migration(migrations.Migration): ] operations = [ - # Remove pghistory triggers that mirror Cred_User changes into - # Cred_UserEvent. Triggers must be dropped before the source / event - # tables can be removed. - pgtrigger.migrations.RemoveTrigger( - model_name="cred_user", - name="insert_insert", - ), - pgtrigger.migrations.RemoveTrigger( - model_name="cred_user", - name="update_update", - ), - pgtrigger.migrations.RemoveTrigger( - model_name="cred_user", - name="delete_delete", - ), - # Drop the audit/event table (FKs from Cred_UserEvent → Cred_User get - # cleaned up automatically as part of DeleteModel). - migrations.DeleteModel( - name="Cred_UserEvent", - ), - # Cred_Mapping holds an FK to Cred_User and must be dropped first. - migrations.DeleteModel( - name="Cred_Mapping", - ), - migrations.DeleteModel( - name="Cred_User", - ), - # The UI toggle no longer has anything to gate. - migrations.RemoveField( - model_name="system_settings", - name="enable_credentials", + migrations.SeparateDatabaseAndState( + # State only: forget the models, triggers, and field so they no + # longer have to be defined in dojo/models.py. database_operations + # is intentionally empty so the dojo_cred_user, dojo_cred_mapping, + # and dojo_cred_userevent tables, the cred_user pghistory triggers, + # and the system_settings.enable_credentials column are all + # preserved for downgrades. + state_operations=[ + # Drop the pghistory triggers from state before the model they + # hang off of is removed. + pgtrigger.migrations.RemoveTrigger( + model_name="cred_user", + name="insert_insert", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="cred_user", + name="update_update", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="cred_user", + name="delete_delete", + ), + # Cred_UserEvent FKs Cred_User; Cred_Mapping FKs Cred_User too, + # so both come out of state before Cred_User itself. + migrations.DeleteModel( + name="Cred_UserEvent", + ), + migrations.DeleteModel( + name="Cred_Mapping", + ), + migrations.DeleteModel( + name="Cred_User", + ), + # The UI toggle no longer has anything to gate. + migrations.RemoveField( + model_name="system_settings", + name="enable_credentials", + ), + ], ), ] From 873c32865c72cb82caf9a369973cf5188321da89 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Mon, 15 Jun 2026 09:49:55 -0600 Subject: [PATCH 2/2] fix(migrations): keep enable_credentials column insertable after state-only removal The state-only removal in 0266 left dojo_system_settings.enable_credentials in place (NOT NULL, no DB default) for downgrade safety, but the model no longer supplies a value on INSERT. New System_Settings rows then failed with a NotNullViolation, surfacing as 28 errors in unittests.test_apply_finding_template. Split the field handling into its own SeparateDatabaseAndState: drop the field from Django state while a database_operations RunSQL sets a server-side default of true (matching the field's original default) on the retained column, so inserts that omit it still satisfy the NOT NULL constraint. Verified locally: the 28 test_apply_finding_template errors reproduce before the change and pass after; makemigrations --check reports no drift; and the cred_*/stub_finding tables, the enable_credentials column, and the cred_user pghistory triggers all remain in the database. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../0266_remove_credential_manager.py | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/dojo/db_migrations/0266_remove_credential_manager.py b/dojo/db_migrations/0266_remove_credential_manager.py index 5bda915af8a..ddcd088fcd6 100644 --- a/dojo/db_migrations/0266_remove_credential_manager.py +++ b/dojo/db_migrations/0266_remove_credential_manager.py @@ -19,13 +19,12 @@ class Migration(migrations.Migration): ] operations = [ + # State only: forget the models and their triggers so they no longer + # have to be defined in dojo/models.py. There are no database_operations + # so the dojo_cred_user, dojo_cred_mapping, and dojo_cred_userevent + # tables and the cred_user pghistory triggers are preserved for + # downgrades. migrations.SeparateDatabaseAndState( - # State only: forget the models, triggers, and field so they no - # longer have to be defined in dojo/models.py. database_operations - # is intentionally empty so the dojo_cred_user, dojo_cred_mapping, - # and dojo_cred_userevent tables, the cred_user pghistory triggers, - # and the system_settings.enable_credentials column are all - # preserved for downgrades. state_operations=[ # Drop the pghistory triggers from state before the model they # hang off of is removed. @@ -52,11 +51,24 @@ class Migration(migrations.Migration): migrations.DeleteModel( name="Cred_User", ), - # The UI toggle no longer has anything to gate. + ], + ), + # Drop the enable_credentials field from state but keep the column for + # downgrades. The model no longer supplies a value on INSERT, so give + # the column a server-side default (the field defaulted to True) to + # keep new System_Settings rows satisfying its NOT NULL constraint. + migrations.SeparateDatabaseAndState( + state_operations=[ migrations.RemoveField( model_name="system_settings", name="enable_credentials", ), ], + database_operations=[ + migrations.RunSQL( + sql="ALTER TABLE dojo_system_settings ALTER COLUMN enable_credentials SET DEFAULT true;", + reverse_sql="ALTER TABLE dojo_system_settings ALTER COLUMN enable_credentials DROP DEFAULT;", + ), + ], ), ]