Skip to content

Commit 69e4079

Browse files
committed
fix: Convert UUIDField columns to uuid type for Mariadb
The behavior of the MariaDB backend has changed behavior for UUIDField from a `CharField(32)` to an actual `uuid` type. This is not converted automatically, which results in all writes to the affected columns to error with a message about the data being too long. This is because the actual string being written is a UUID with the `-` included, resulting in a 36 character value which can't be inserted into a 32 character column. fix: renamed migration to 0016 Update cms/djangoapps/contentstore/migrations/0016_mariadb_uuid_conversion.py Co-authored-by: David Ormsbee <dave@axim.org> refactor: remove deprecated sidebar toggles (openedx#37983) DEPR ticket: openedx/public-engineering#316 feat: Add Waffle flag for AuthZ for Course Authoring (openedx#37985) build: Upgrade to `ora2==6.17.2` which removes loremipsum base dep (openedx#37991) We hope this will fix an error where loremipsum is using pkg_resources, which setuptools dropped support for as of v82 (released yesterday). fix: Nits on styles of library icon [FC-0114] (openedx#37980) - Fixes the issues described in openedx/frontend-app-authoring#2762 (comment): - Changed the background color for the library icon in the unit page. - Update punctuation for the library icon tooltip in the unit page. - Allows breaking the tooltip into multiple lines. refactor: xblock api upstream info and course details api (openedx#37971) - Returns top parent key instead of boolean in upstream info api - Adds edited_on raw time in course outline api - Adds has_changes to course details api feat: Add enable_authz_course_authoring flag to course_waffle_flags endpoint (openedx#37990) Discussion service to enable permission and access provider (openedx#37912) * chore: discussion service to enable permission and access provider
1 parent fd644d4 commit 69e4079

File tree

39 files changed

+334
-266
lines changed

39 files changed

+334
-266
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Generated migration for MariaDB UUID field conversion (Django 5.2)
2+
"""
3+
Migration to convert UUIDField from char(32) to uuid type for MariaDB compatibility.
4+
5+
This migration is necessary because Django 5 changed the behavior of UUIDField for MariaDB
6+
databases from using CharField(32) to using a proper UUID type. This change isn't managed
7+
automatically, so we need to generate migrations to safely convert the columns.
8+
9+
This migration only executes for MariaDB databases and is a no-op for other backends.
10+
11+
See: https://www.albertyw.com/note/django-5-mariadb-uuidfield
12+
"""
13+
14+
from django.db import migrations
15+
16+
17+
def apply_mariadb_migration(apps, schema_editor):
18+
"""Apply the migration only for MariaDB databases."""
19+
connection = schema_editor.connection
20+
21+
# Check if this is a MariaDB database
22+
if connection.vendor != 'mysql':
23+
return
24+
25+
# Additional check for MariaDB specifically (vs MySQL)
26+
with connection.cursor() as cursor:
27+
cursor.execute("SELECT VERSION()")
28+
version = cursor.fetchone()[0]
29+
if 'mariadb' not in version.lower():
30+
return
31+
32+
# Apply the field changes for MariaDB
33+
with connection.cursor() as cursor:
34+
cursor.execute(
35+
"ALTER TABLE oel_publishing_learningpackage "
36+
"MODIFY uuid uuid NOT NULL"
37+
)
38+
39+
40+
def reverse_mariadb_migration(apps, schema_editor):
41+
"""Reverse the migration only for MariaDB databases."""
42+
connection = schema_editor.connection
43+
44+
# Check if this is a MariaDB database
45+
if connection.vendor != 'mysql':
46+
return
47+
48+
# Additional check for MariaDB specifically (vs MySQL)
49+
with connection.cursor() as cursor:
50+
cursor.execute("SELECT VERSION()")
51+
version = cursor.fetchone()[0]
52+
if 'mariadb' not in version.lower():
53+
return
54+
55+
# Reverse the field changes for MariaDB
56+
with connection.cursor() as cursor:
57+
cursor.execute(
58+
"ALTER TABLE oel_publishing_learningpackage "
59+
"MODIFY uuid char(32) NOT NULL"
60+
)
61+
62+
63+
class Migration(migrations.Migration):
64+
65+
dependencies = [
66+
('contentstore', '0015_switch_to_openedx_content'),
67+
]
68+
69+
operations = [
70+
migrations.RunPython(
71+
code=apply_mariadb_migration,
72+
reverse_code=reverse_mariadb_migration,
73+
),
74+
]

cms/djangoapps/contentstore/rest_api/v1/serializers/course_details.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class CourseDetailsSerializer(serializers.Serializer):
5353
pre_requisite_courses = serializers.ListField(child=CourseKeyField())
5454
run = serializers.CharField()
5555
self_paced = serializers.BooleanField()
56+
has_changes = serializers.BooleanField()
5657
short_description = serializers.CharField(allow_blank=True)
5758
start_date = serializers.DateTimeField()
5859
subtitle = serializers.CharField(allow_blank=True)

cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from rest_framework import serializers
66

77
from cms.djangoapps.contentstore import toggles
8+
from openedx.core import toggles as core_toggles
89

910

1011
class CourseWaffleFlagsSerializer(serializers.Serializer):
@@ -31,6 +32,7 @@ class CourseWaffleFlagsSerializer(serializers.Serializer):
3132
use_react_markdown_editor = serializers.SerializerMethodField()
3233
use_video_gallery_flow = serializers.SerializerMethodField()
3334
enable_course_optimizer_check_prev_run_links = serializers.SerializerMethodField()
35+
enable_authz_course_authoring = serializers.SerializerMethodField()
3436

3537
def get_course_key(self):
3638
"""
@@ -201,3 +203,10 @@ def get_enable_course_optimizer_check_prev_run_links(self, obj):
201203
"""
202204
course_key = self.get_course_key()
203205
return toggles.enable_course_optimizer_check_prev_run_links(course_key)
206+
207+
def get_enable_authz_course_authoring(self, obj):
208+
"""
209+
Method to get the authz.enable_course_authoring waffle flag
210+
"""
211+
course_key = self.get_course_key()
212+
return core_toggles.enable_authz_course_authoring(course_key)

cms/djangoapps/contentstore/rest_api/v1/serializers/vertical_block.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class UpstreamLinkSerializer(serializers.Serializer):
125125
error_message = serializers.CharField(allow_null=True)
126126
ready_to_sync = serializers.BooleanField()
127127
downstream_customized = serializers.ListField(child=serializers.CharField(), allow_empty=True)
128-
has_top_level_parent = serializers.BooleanField()
128+
top_level_parent_key = serializers.CharField(allow_null=True)
129129
ready_to_sync_children = UpstreamChildrenInfoSerializer(many=True, required=False)
130130

131131

cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_waffle_flags.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class CourseWaffleFlagsViewTest(CourseTestCase):
3838
"use_react_markdown_editor": False,
3939
"use_video_gallery_flow": False,
4040
"enable_course_optimizer_check_prev_run_links": False,
41+
"enable_authz_course_authoring": False,
4142
}
4243

4344
def setUp(self):

cms/djangoapps/contentstore/rest_api/v1/views/tests/test_vertical_block.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ def test_children_content(self):
323323
"version_declined": None,
324324
"error_message": None,
325325
"ready_to_sync": True,
326-
"has_top_level_parent": False,
326+
"top_level_parent_key": None,
327327
"downstream_customized": [],
328328
},
329329
"user_partition_info": expected_user_partition_info,

cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstreams.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def _get_upstream_link_good_and_syncable(downstream):
5050
version_declined=downstream.upstream_version_declined,
5151
error_message=None,
5252
downstream_customized=[],
53-
has_top_level_parent=False,
53+
top_level_parent_key=None,
5454
upstream_name=downstream.upstream_display_name,
5555
)
5656

cms/djangoapps/contentstore/tasks.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from olxcleaner.reporting import report_error_summary, report_errors
3636
from opaque_keys import InvalidKeyError
3737
from opaque_keys.edx.keys import CourseKey, UsageKey
38-
from opaque_keys.edx.locator import LibraryContainerLocator, LibraryLocator, BlockUsageLocator
38+
from opaque_keys.edx.locator import LibraryContainerLocator, LibraryLocator
3939
from openedx_events.content_authoring.data import CourseData
4040
from openedx_events.content_authoring.signals import COURSE_RERUN_COMPLETED
4141
from organizations.api import add_organization_course, ensure_organization
@@ -1641,11 +1641,7 @@ def handle_create_xblock_upstream_link(usage_key):
16411641
return
16421642
if xblock.top_level_downstream_parent_key is not None:
16431643
block_key = BlockKey.from_string(xblock.top_level_downstream_parent_key)
1644-
top_level_parent_usage_key = BlockUsageLocator(
1645-
xblock.course_id,
1646-
block_key.type,
1647-
block_key.id,
1648-
)
1644+
top_level_parent_usage_key = block_key.to_usage_key(xblock.course_id)
16491645
try:
16501646
ContainerLink.get_by_downstream_usage_key(top_level_parent_usage_key)
16511647
except ContainerLink.DoesNotExist:

cms/djangoapps/contentstore/views/preview.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from xblock.runtime import KvsFieldData
2020

2121
from openedx.core.djangoapps.video_config.services import VideoConfigService
22+
from openedx.core.djangoapps.discussions.services import DiscussionConfigService
2223
from xmodule.contentstore.django import contentstore
2324
from xmodule.exceptions import NotFoundError as XModuleNotFoundError
2425
from xmodule.modulestore.django import XBlockI18nService, modulestore
@@ -228,6 +229,7 @@ def _prepare_runtime_for_preview(request, block):
228229
"cache": CacheService(cache),
229230
'replace_urls': ReplaceURLService,
230231
'video_config': VideoConfigService(),
232+
'discussion_config_service': DiscussionConfigService(),
231233
}
232234

233235
block.runtime.get_block_for_descriptor = partial(_load_preview_block, request)

cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,9 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements
12021202
"edited_on": get_default_time_display(xblock.subtree_edited_on)
12031203
if xblock.subtree_edited_on
12041204
else None,
1205+
"edited_on_raw": str(xblock.subtree_edited_on)
1206+
if xblock.subtree_edited_on
1207+
else None,
12051208
"published": published,
12061209
"published_on": published_on,
12071210
"studio_url": xblock_studio_url(xblock, parent_xblock),
@@ -1331,7 +1334,7 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements
13311334
# Disable adding or removing children component if xblock is imported from library
13321335
xblock_actions["childAddable"] = False
13331336
# Enable unlinking only for top level imported components
1334-
xblock_actions["unlinkable"] = not upstream_info["has_top_level_parent"]
1337+
xblock_actions["unlinkable"] = not upstream_info["top_level_parent_key"]
13351338

13361339
if is_xblock_unit:
13371340
# if xblock is a Unit we add the discussion_enabled option

0 commit comments

Comments
 (0)