1- # Generated by Django 5.2.5 on 2026-06-16 22:01
1+ # Generated by Django 5.2.5 on 2026-06-16 20:35
22
3- import django .db .models .deletion
4- from django .db import migrations , models
3+ from django .db import migrations
54
6- from indicatorsets .odp_migration import ensure_original_data_provider_schema
75
6+ INDICATOR_SET_TABLE = "indicatorsets_indicatorset"
7+ PROVIDER_TABLE = "indicatorsets_originaldataprovider"
8+ CHAR_COLUMN = "original_data_provider"
9+ FK_COLUMN = "original_data_provider_id"
810
9- def drop_orphan_original_data_provider_table (apps , schema_editor ):
10- """Clean up partial/failed deploys before CreateModel runs."""
11- with schema_editor .connection .cursor () as cursor :
12- cursor .execute ("SET FOREIGN_KEY_CHECKS=0" )
11+
12+ def _get_indicator_set_columns (cursor ):
13+ cursor .execute (
14+ """
15+ SELECT COLUMN_NAME, DATA_TYPE
16+ FROM information_schema.COLUMNS
17+ WHERE TABLE_SCHEMA = DATABASE()
18+ AND TABLE_NAME = %s
19+ """ ,
20+ [INDICATOR_SET_TABLE ],
21+ )
22+ return {row [0 ]: row [1 ] for row in cursor .fetchall ()}
23+
24+
25+ def _table_exists (cursor , table_name ):
26+ cursor .execute ("SHOW TABLES LIKE %s" , [table_name ])
27+ return cursor .fetchone () is not None
28+
29+
30+ def _drop_foreign_keys (cursor ):
31+ cursor .execute (
32+ """
33+ SELECT CONSTRAINT_NAME
34+ FROM information_schema.KEY_COLUMN_USAGE
35+ WHERE TABLE_SCHEMA = DATABASE()
36+ AND TABLE_NAME = %s
37+ AND COLUMN_NAME = %s
38+ AND REFERENCED_TABLE_NAME IS NOT NULL
39+ """ ,
40+ [INDICATOR_SET_TABLE , FK_COLUMN ],
41+ )
42+ for (constraint_name ,) in cursor .fetchall ():
1343 cursor .execute (
14- """
15- SELECT CONSTRAINT_NAME
16- FROM information_schema.KEY_COLUMN_USAGE
17- WHERE TABLE_SCHEMA = DATABASE()
18- AND TABLE_NAME = 'indicatorsets_indicatorset'
19- AND COLUMN_NAME = 'original_data_provider_id'
20- AND REFERENCED_TABLE_NAME IS NOT NULL
21- """
44+ f"ALTER TABLE `{ INDICATOR_SET_TABLE } ` DROP FOREIGN KEY `{ constraint_name } `"
2245 )
23- for (constraint_name ,) in cursor .fetchall ():
46+
47+
48+ def revert_original_data_provider_schema (apps , schema_editor ):
49+ """
50+ Ensure DB matches CharField original_data_provider with no ODP lookup table.
51+
52+ Handles fresh DBs, orphan ODP tables from failed deploys, and FK-based schemas.
53+ """
54+ with schema_editor .connection .cursor () as cursor :
55+ if not _table_exists (cursor , INDICATOR_SET_TABLE ):
56+ return
57+
58+ columns = _get_indicator_set_columns (cursor )
59+ has_char = CHAR_COLUMN in columns
60+ has_fk = FK_COLUMN in columns
61+ has_provider_table = _table_exists (cursor , PROVIDER_TABLE )
62+
63+ cursor .execute ("SET FOREIGN_KEY_CHECKS=0" )
64+
65+ if has_fk :
66+ if has_provider_table :
67+ if not has_char :
68+ cursor .execute (
69+ f"ALTER TABLE `{ INDICATOR_SET_TABLE } ` "
70+ f"ADD COLUMN `{ CHAR_COLUMN } ` varchar(255) NOT NULL DEFAULT ''"
71+ )
72+ has_char = True
73+
74+ cursor .execute (
75+ f"""
76+ UPDATE `{ INDICATOR_SET_TABLE } ` iset
77+ LEFT JOIN `{ PROVIDER_TABLE } ` odp
78+ ON iset.`{ FK_COLUMN } ` = odp.`id`
79+ SET iset.`{ CHAR_COLUMN } ` = COALESCE(odp.`name`, '')
80+ """
81+ )
82+
83+ _drop_foreign_keys (cursor )
2484 cursor .execute (
25- f"ALTER TABLE `indicatorsets_indicatorset` "
26- f"DROP FOREIGN KEY `{ constraint_name } `"
85+ f"ALTER TABLE `{ INDICATOR_SET_TABLE } ` DROP COLUMN `{ FK_COLUMN } `"
2786 )
28- cursor .execute ("DROP TABLE IF EXISTS `indicatorsets_originaldataprovider`" )
87+
88+ if has_provider_table :
89+ cursor .execute (f"DROP TABLE IF EXISTS `{ PROVIDER_TABLE } `" )
90+
2991 cursor .execute ("SET FOREIGN_KEY_CHECKS=1" )
3092
3193
@@ -37,60 +99,7 @@ class Migration(migrations.Migration):
3799
38100 operations = [
39101 migrations .RunPython (
40- drop_orphan_original_data_provider_table ,
102+ revert_original_data_provider_schema ,
41103 migrations .RunPython .noop ,
42104 ),
43- migrations .CreateModel (
44- name = "OriginalDataProvider" ,
45- fields = [
46- (
47- "id" ,
48- models .BigAutoField (
49- auto_created = True ,
50- primary_key = True ,
51- serialize = False ,
52- verbose_name = "ID" ,
53- ),
54- ),
55- (
56- "name" ,
57- models .CharField (max_length = 255 , unique = True , verbose_name = "Name" ),
58- ),
59- (
60- "group" ,
61- models .CharField (
62- choices = [
63- ("us_government" , "U.S. Government" ),
64- ("us_states" , "U.S. States" ),
65- ("individual" , "Individual providers" ),
66- ],
67- "constraints" : [
68- models .UniqueConstraint (
69- fields = ("name" , "group" ),
70- name = "unique_data_provider_name_group" ,
71- )
72- ],
73- },
74- ),
75- migrations .AlterField (
76- model_name = "indicatorset" ,
77- name = "original_data_provider" ,
78- field = models .ForeignKey (
79- blank = True ,
80- help_text = "Original data provider of the Indicator Set" ,
81- null = True ,
82- on_delete = django .db .models .deletion .SET_NULL ,
83- related_name = "indicator_sets" ,
84- to = "indicatorsets.originaldataprovider" ,
85- verbose_name = "Original Data Provider" ,
86- ),
87- ),
88- ],
89- database_operations = [
90- migrations .RunPython (
91- ensure_original_data_provider_schema ,
92- migrations .RunPython .noop ,
93- ),
94- ],
95- ),
96105 ]
0 commit comments