From 182fbee36ce3864af3b6189f206c24839297677f Mon Sep 17 00:00:00 2001 From: Tobias Macey Date: Wed, 30 Sep 2026 15:26:41 -0400 Subject: [PATCH 1/2] fix: convert this package's own UUID columns in the MariaDB migrations blackboard 0025, canvas 0041 and integrated_channel 0037 altered the enterprise-integrated-channels tables (blackboard_channel_*, canvas_channel_*, channel_integration_*) instead of this package's own. Nothing orders those tables before these migrations, so on a fresh MariaDB database migrate fails with "Table ... doesn't exist" at blackboard 0025. On existing databases the other package's columns were converted (they already are, by that package's own migrations) and this package's columns were left as char(32). The three migrations now name this package's tables, and 0026, 0042 and 0038 convert those tables on databases that applied the old versions. MODIFY to the type a column already has changes nothing, so the new migrations are safe after the corrected ones too. integrated_channel_contentmetadataitemtransmission.enterprise_customer_catalog_uuid is nullable in this package, so it is converted as NULL rather than the NOT NULL of the channel_integration copy. Claude-Session: https://claude.ai/code/session_01LjapgaiiGBFwNW9PgQ59jv --- CHANGELOG.rst | 10 ++++ enterprise/__init__.py | 2 +- .../0025_mariadb_uuid_conversion.py | 4 +- .../0026_mariadb_uuid_conversion_own_table.py | 46 +++++++++++++++++++ .../0041_mariadb_uuid_conversion.py | 4 +- .../0042_mariadb_uuid_conversion_own_table.py | 46 +++++++++++++++++++ .../0037_mariadb_uuid_conversion.py | 8 ++-- ...0038_mariadb_uuid_conversion_own_tables.py | 46 +++++++++++++++++++ 8 files changed, 157 insertions(+), 9 deletions(-) create mode 100644 integrated_channels/blackboard/migrations/0026_mariadb_uuid_conversion_own_table.py create mode 100644 integrated_channels/canvas/migrations/0042_mariadb_uuid_conversion_own_table.py create mode 100644 integrated_channels/integrated_channel/migrations/0038_mariadb_uuid_conversion_own_tables.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b4f7feae96..6c26d95bd8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,16 @@ Unreleased ---------- * nothing unreleased +[8.17.1] - 2026-09-30 +---------------------- +* fix: The MariaDB UUID conversions in ``blackboard`` 0025, ``canvas`` 0041 and + ``integrated_channel`` 0037 altered the enterprise-integrated-channels tables + (``blackboard_channel_*``, ``canvas_channel_*``, ``channel_integration_*``) + instead of this package's own, so ``migrate`` failed on a fresh MariaDB + database and left these apps' UUID columns as char(32). They now name this + package's tables, and new migrations convert those tables on databases that + already applied the old versions. + [8.17.0] - 2026-09-29 ---------------------- * feat: Add ``enterprise_learner_generic_name`` and ``enterprise_learner_portal_link`` diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 0da8a68fca..7c1a016393 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "8.17.0" +__version__ = "8.17.1" diff --git a/integrated_channels/blackboard/migrations/0025_mariadb_uuid_conversion.py b/integrated_channels/blackboard/migrations/0025_mariadb_uuid_conversion.py index 981829b966..2dbeb097ac 100644 --- a/integrated_channels/blackboard/migrations/0025_mariadb_uuid_conversion.py +++ b/integrated_channels/blackboard/migrations/0025_mariadb_uuid_conversion.py @@ -21,7 +21,7 @@ def apply_mariadb_migration(apps, schema_editor): return with connection.cursor() as cursor: - cursor.execute("ALTER TABLE blackboard_channel_blackboardenterprisecustomerconfiguration MODIFY uuid uuid NOT NULL") + cursor.execute("ALTER TABLE blackboard_blackboardenterprisecustomerconfiguration MODIFY uuid uuid NOT NULL") def reverse_mariadb_migration(apps, schema_editor): @@ -37,7 +37,7 @@ def reverse_mariadb_migration(apps, schema_editor): return with connection.cursor() as cursor: - cursor.execute("ALTER TABLE blackboard_channel_blackboardenterprisecustomerconfiguration MODIFY uuid char(32) NOT NULL") + cursor.execute("ALTER TABLE blackboard_blackboardenterprisecustomerconfiguration MODIFY uuid char(32) NOT NULL") class Migration(migrations.Migration): diff --git a/integrated_channels/blackboard/migrations/0026_mariadb_uuid_conversion_own_table.py b/integrated_channels/blackboard/migrations/0026_mariadb_uuid_conversion_own_table.py new file mode 100644 index 0000000000..ee9f8260b8 --- /dev/null +++ b/integrated_channels/blackboard/migrations/0026_mariadb_uuid_conversion_own_table.py @@ -0,0 +1,46 @@ +""" +Convert blackboard_blackboardenterprisecustomerconfiguration.uuid from char(32) to uuid on MariaDB. + +0025_mariadb_uuid_conversion was released altering +blackboard_channel_blackboardenterprisecustomerconfiguration, the +enterprise-integrated-channels copy of this table, which that package converts +in its own migrations. On a fresh database 0025 failed because nothing creates +that table first, and on existing databases this table kept its char(32) +column. 0025 now names this table; this migration converts it on databases that +applied the old version. MODIFY to the type a column already has changes +nothing, so it is also safe after the corrected 0025. + +See: https://docs.djangoproject.com/en/5.2/releases/5.0/#migrating-uuidfield +""" + +from django.db import migrations + + +def _is_mariadb(connection): + if connection.vendor != 'mysql': + return False + with connection.cursor() as cursor: + cursor.execute("SELECT VERSION()") + return 'mariadb' in cursor.fetchone()[0].lower() + + +def apply_mariadb_migration(apps, schema_editor): + if not _is_mariadb(schema_editor.connection): + return + with schema_editor.connection.cursor() as cursor: + cursor.execute("ALTER TABLE blackboard_blackboardenterprisecustomerconfiguration MODIFY uuid uuid NOT NULL") + + +class Migration(migrations.Migration): + + dependencies = [ + ('blackboard', '0025_mariadb_uuid_conversion'), + ] + + operations = [ + # Reversing to char(32) is left to 0025_mariadb_uuid_conversion, which this repeats. + migrations.RunPython( + code=apply_mariadb_migration, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/integrated_channels/canvas/migrations/0041_mariadb_uuid_conversion.py b/integrated_channels/canvas/migrations/0041_mariadb_uuid_conversion.py index c90968cf8e..bfee48c823 100644 --- a/integrated_channels/canvas/migrations/0041_mariadb_uuid_conversion.py +++ b/integrated_channels/canvas/migrations/0041_mariadb_uuid_conversion.py @@ -21,7 +21,7 @@ def apply_mariadb_migration(apps, schema_editor): return with connection.cursor() as cursor: - cursor.execute("ALTER TABLE canvas_channel_canvasenterprisecustomerconfiguration MODIFY uuid uuid NOT NULL") + cursor.execute("ALTER TABLE canvas_canvasenterprisecustomerconfiguration MODIFY uuid uuid NOT NULL") def reverse_mariadb_migration(apps, schema_editor): @@ -37,7 +37,7 @@ def reverse_mariadb_migration(apps, schema_editor): return with connection.cursor() as cursor: - cursor.execute("ALTER TABLE canvas_channel_canvasenterprisecustomerconfiguration MODIFY uuid char(32) NOT NULL") + cursor.execute("ALTER TABLE canvas_canvasenterprisecustomerconfiguration MODIFY uuid char(32) NOT NULL") class Migration(migrations.Migration): diff --git a/integrated_channels/canvas/migrations/0042_mariadb_uuid_conversion_own_table.py b/integrated_channels/canvas/migrations/0042_mariadb_uuid_conversion_own_table.py new file mode 100644 index 0000000000..5cf1078f8a --- /dev/null +++ b/integrated_channels/canvas/migrations/0042_mariadb_uuid_conversion_own_table.py @@ -0,0 +1,46 @@ +""" +Convert canvas_canvasenterprisecustomerconfiguration.uuid from char(32) to uuid on MariaDB. + +0041_mariadb_uuid_conversion was released altering +canvas_channel_canvasenterprisecustomerconfiguration, the +enterprise-integrated-channels copy of this table, which that package converts +in its own migrations. On a fresh database 0041 failed because nothing creates +that table first, and on existing databases this table kept its char(32) +column. 0041 now names this table; this migration converts it on databases that +applied the old version. MODIFY to the type a column already has changes +nothing, so it is also safe after the corrected 0041. + +See: https://docs.djangoproject.com/en/5.2/releases/5.0/#migrating-uuidfield +""" + +from django.db import migrations + + +def _is_mariadb(connection): + if connection.vendor != 'mysql': + return False + with connection.cursor() as cursor: + cursor.execute("SELECT VERSION()") + return 'mariadb' in cursor.fetchone()[0].lower() + + +def apply_mariadb_migration(apps, schema_editor): + if not _is_mariadb(schema_editor.connection): + return + with schema_editor.connection.cursor() as cursor: + cursor.execute("ALTER TABLE canvas_canvasenterprisecustomerconfiguration MODIFY uuid uuid NOT NULL") + + +class Migration(migrations.Migration): + + dependencies = [ + ('canvas', '0041_mariadb_uuid_conversion'), + ] + + operations = [ + # Reversing to char(32) is left to 0041_mariadb_uuid_conversion, which this repeats. + migrations.RunPython( + code=apply_mariadb_migration, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/integrated_channels/integrated_channel/migrations/0037_mariadb_uuid_conversion.py b/integrated_channels/integrated_channel/migrations/0037_mariadb_uuid_conversion.py index ff0cd4f587..14517a8535 100644 --- a/integrated_channels/integrated_channel/migrations/0037_mariadb_uuid_conversion.py +++ b/integrated_channels/integrated_channel/migrations/0037_mariadb_uuid_conversion.py @@ -21,8 +21,8 @@ def apply_mariadb_migration(apps, schema_editor): return with connection.cursor() as cursor: - cursor.execute("ALTER TABLE channel_integration_genericlearnerdatatransmissionaudit MODIFY enterprise_customer_uuid uuid NULL") - cursor.execute("ALTER TABLE channel_integration_contentmetadataitemtransmission MODIFY enterprise_customer_catalog_uuid uuid NOT NULL") + cursor.execute("ALTER TABLE integrated_channel_genericlearnerdatatransmissionaudit MODIFY enterprise_customer_uuid uuid NULL") + cursor.execute("ALTER TABLE integrated_channel_contentmetadataitemtransmission MODIFY enterprise_customer_catalog_uuid uuid NULL") def reverse_mariadb_migration(apps, schema_editor): @@ -38,8 +38,8 @@ def reverse_mariadb_migration(apps, schema_editor): return with connection.cursor() as cursor: - cursor.execute("ALTER TABLE channel_integration_genericlearnerdatatransmissionaudit MODIFY enterprise_customer_uuid char(32) NULL") - cursor.execute("ALTER TABLE channel_integration_contentmetadataitemtransmission MODIFY enterprise_customer_catalog_uuid char(32) NOT NULL") + cursor.execute("ALTER TABLE integrated_channel_genericlearnerdatatransmissionaudit MODIFY enterprise_customer_uuid char(32) NULL") + cursor.execute("ALTER TABLE integrated_channel_contentmetadataitemtransmission MODIFY enterprise_customer_catalog_uuid char(32) NULL") class Migration(migrations.Migration): diff --git a/integrated_channels/integrated_channel/migrations/0038_mariadb_uuid_conversion_own_tables.py b/integrated_channels/integrated_channel/migrations/0038_mariadb_uuid_conversion_own_tables.py new file mode 100644 index 0000000000..6ec62c4ff6 --- /dev/null +++ b/integrated_channels/integrated_channel/migrations/0038_mariadb_uuid_conversion_own_tables.py @@ -0,0 +1,46 @@ +""" +Convert this app's UUIDField columns from char(32) to uuid on MariaDB. + +0037_mariadb_uuid_conversion was released altering the channel_integration_* +tables, the enterprise-integrated-channels copies of this app's tables, which +that package converts in its own migrations. Those tables have no ordering +guarantee against 0037, and this app's own columns kept char(32). 0037 now +names this app's tables; this migration converts them on databases that +applied the old version. MODIFY to the type a column already has changes +nothing, so it is also safe after the corrected 0037. + +See: https://docs.djangoproject.com/en/5.2/releases/5.0/#migrating-uuidfield +""" + +from django.db import migrations + + +def _is_mariadb(connection): + if connection.vendor != 'mysql': + return False + with connection.cursor() as cursor: + cursor.execute("SELECT VERSION()") + return 'mariadb' in cursor.fetchone()[0].lower() + + +def apply_mariadb_migration(apps, schema_editor): + if not _is_mariadb(schema_editor.connection): + return + with schema_editor.connection.cursor() as cursor: + cursor.execute("ALTER TABLE integrated_channel_genericlearnerdatatransmissionaudit MODIFY enterprise_customer_uuid uuid NULL") + cursor.execute("ALTER TABLE integrated_channel_contentmetadataitemtransmission MODIFY enterprise_customer_catalog_uuid uuid NULL") + + +class Migration(migrations.Migration): + + dependencies = [ + ('integrated_channel', '0037_mariadb_uuid_conversion'), + ] + + operations = [ + # Reversing to char(32) is left to 0037_mariadb_uuid_conversion, which this repeats. + migrations.RunPython( + code=apply_mariadb_migration, + reverse_code=migrations.RunPython.noop, + ), + ] From bbb6ada0224c2799406a0d8c0c5b9835f06c16c4 Mon Sep 17 00:00:00 2001 From: Tobias Macey Date: Wed, 30 Sep 2026 15:30:02 -0400 Subject: [PATCH 2/2] docs: state the 0037 failure mode concretely in 0038's docstring Claude-Session: https://claude.ai/code/session_01LjapgaiiGBFwNW9PgQ59jv --- .../migrations/0038_mariadb_uuid_conversion_own_tables.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/integrated_channels/integrated_channel/migrations/0038_mariadb_uuid_conversion_own_tables.py b/integrated_channels/integrated_channel/migrations/0038_mariadb_uuid_conversion_own_tables.py index 6ec62c4ff6..caa23a45c3 100644 --- a/integrated_channels/integrated_channel/migrations/0038_mariadb_uuid_conversion_own_tables.py +++ b/integrated_channels/integrated_channel/migrations/0038_mariadb_uuid_conversion_own_tables.py @@ -3,8 +3,9 @@ 0037_mariadb_uuid_conversion was released altering the channel_integration_* tables, the enterprise-integrated-channels copies of this app's tables, which -that package converts in its own migrations. Those tables have no ordering -guarantee against 0037, and this app's own columns kept char(32). 0037 now +that package converts in its own migrations. Nothing creates those tables +before 0037, so a fresh migrate only succeeds when Django happens to order +them first, and this app's own columns kept char(32). 0037 now names this app's tables; this migration converts them on databases that applied the old version. MODIFY to the type a column already has changes nothing, so it is also safe after the corrected 0037.