Skip to content

Commit 9bf7a72

Browse files
authored
refactor: switch to use openedx_content app (openedx#37924)
The openedx-learning repo was recently refactored to consolidate its authoring apps into a single openedx_content app: openedx#37924 This commit makes the following changes to accommodate this: - Bumps the openedx-learning version to 0.31.0 to get the changes. - Creates new migrations in content_libraries, contentstore, and modulestore_migrator to foreign key references to authoring apps to point to the new openedx_content app. This is done without actually making database changes, since the openedx_content app models are taking over the existing tables that the authoring apps once pointed to. - Creates new squashed migrations in these apps that create these foreign keys to reference openedx_content app models from the start. The full rationale for how and why this was done is in the following openedx-learning ADR: https://github.com/openedx/openedx-learning/blob/main/docs/decisions/0020-merge-authoring-apps-into-openedx-content.rst These migrations should run fine from either a from-scratch scenario (i.e. a new install or CI), or when upgrading from an Ulmo-or-later database state. If you have a database state that comes from the middle of the Ulmo development cycle (e.g. October 2025), you may encounter migration errors in content_libraries, contentstore, or modulestore_migrator, with an error message complaining about missing tables. If you receive this message, run the following command: python manage.py lms migrate openedx_content 0001 Then try to run the migrations for the app that failed. Repeat if necessary for multiple apps.
1 parent b6904be commit 9bf7a72

File tree

13 files changed

+541
-21
lines changed

13 files changed

+541
-21
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# Generated by Django 5.2.10 on 2026-01-30 01:23
2+
3+
import django.db.migrations.operations.special
4+
import django.db.models.deletion
5+
import opaque_keys.edx.django.models
6+
import openedx_learning.lib.fields
7+
import openedx_learning.lib.validators
8+
import uuid
9+
from django.conf import settings
10+
from django.db import migrations, models
11+
12+
from cms.djangoapps.contentstore.config.waffle import ENABLE_CHECKLISTS_QUALITY
13+
from cms.djangoapps.contentstore.toggles import ENABLE_REACT_MARKDOWN_EDITOR
14+
15+
16+
def create_checklists_quality_waffle_flag(apps, schema_editor):
17+
Flag = apps.get_model('waffle', 'Flag')
18+
# Replacement for flag_undefined_default=True on flag definition
19+
Flag.objects.get_or_create(name=ENABLE_CHECKLISTS_QUALITY.name, defaults={'everyone': True})
20+
21+
22+
def create_markdown_editor_waffle_flag(apps, schema_editor):
23+
Flag = apps.get_model('waffle', 'Flag')
24+
Flag.objects.get_or_create(
25+
name=ENABLE_REACT_MARKDOWN_EDITOR.name, defaults={'everyone': True}
26+
)
27+
28+
29+
class Migration(migrations.Migration):
30+
31+
replaces = [('contentstore', '0001_initial'), ('contentstore', '0002_add_assets_page_flag'), ('contentstore', '0003_remove_assets_page_flag'), ('contentstore', '0004_remove_push_notification_configmodel_table'), ('contentstore', '0005_add_enable_checklists_quality_waffle_flag'), ('contentstore', '0006_courseoutlineregenerate'), ('contentstore', '0007_backfillcoursetabsconfig'), ('contentstore', '0008_cleanstalecertificateavailabilitydatesconfig'), ('contentstore', '0009_learningcontextlinksstatus_publishableentitylink'), ('contentstore', '0010_container_link_models'), ('contentstore', '0011_enable_markdown_editor_flag_by_default'), ('contentstore', '0012_componentlink_top_level_parent_and_more'), ('contentstore', '0013_componentlink_downstream_is_modified_and_more'), ('contentstore', '0014_remove_componentlink_downstream_is_modified_and_more'), ('contentstore', '0015_switch_to_openedx_content')]
32+
33+
initial = True
34+
35+
dependencies = [
36+
('course_overviews', '0024_overview_adds_has_highlights'),
37+
('oel_components', '0003_remove_componentversioncontent_learner_downloadable'),
38+
('oel_publishing', '0002_alter_learningpackage_key_and_more'),
39+
('oel_publishing', '0003_containers'),
40+
('openedx_content', '0002_rename_tables_to_openedx_content'),
41+
('waffle', '0001_initial'),
42+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
43+
]
44+
45+
operations = [
46+
migrations.CreateModel(
47+
name='VideoUploadConfig',
48+
fields=[
49+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
50+
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
51+
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
52+
('profile_whitelist', models.TextField(blank=True, help_text='A comma-separated list of names of profiles to include in video encoding downloads.')),
53+
('changed_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Changed by')),
54+
],
55+
options={
56+
'ordering': ('-change_date',),
57+
'abstract': False,
58+
},
59+
),
60+
migrations.RunPython(
61+
code=create_checklists_quality_waffle_flag,
62+
reverse_code=django.db.migrations.operations.special.RunPython.noop,
63+
),
64+
migrations.CreateModel(
65+
name='CourseOutlineRegenerate',
66+
fields=[
67+
],
68+
options={
69+
'proxy': True,
70+
'indexes': [],
71+
'constraints': [],
72+
},
73+
bases=('course_overviews.courseoverview',),
74+
),
75+
migrations.CreateModel(
76+
name='BackfillCourseTabsConfig',
77+
fields=[
78+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
79+
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
80+
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
81+
('start_index', models.IntegerField(default=0, help_text='Index of first course to start backfilling (in an alphabetically sorted list of courses)')),
82+
('count', models.IntegerField(default=0, help_text='How many courses to backfill in this run (or zero for all courses)')),
83+
('changed_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Changed by')),
84+
],
85+
options={
86+
'verbose_name': 'Arguments for backfill_course_tabs',
87+
'verbose_name_plural': 'Arguments for backfill_course_tabs',
88+
},
89+
),
90+
migrations.CreateModel(
91+
name='CleanStaleCertificateAvailabilityDatesConfig',
92+
fields=[
93+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
94+
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
95+
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
96+
('arguments', models.TextField(blank=True, help_text="A space seperated collection of arguments to be used when running the `clean_stale_certificate_available_dates` management command.' See the management command for options.")),
97+
('changed_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Changed by')),
98+
],
99+
options={
100+
'verbose_name': "Arguments for 'clean_stale_certificate_availability_dates'",
101+
'verbose_name_plural': "Arguments for 'clean_stale_certificate_availability_dates'",
102+
},
103+
),
104+
migrations.CreateModel(
105+
name='LearningContextLinksStatus',
106+
fields=[
107+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
108+
('context_key', opaque_keys.edx.django.models.CourseKeyField(help_text='Linking status for course context key', max_length=255, unique=True)),
109+
('status', models.CharField(choices=[('pending', 'Pending'), ('processing', 'Processing'), ('failed', 'Failed'), ('completed', 'Completed')], help_text='Status of links in given learning context/course.', max_length=20)),
110+
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
111+
('updated', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
112+
],
113+
options={
114+
'verbose_name': 'Learning Context Links status',
115+
'verbose_name_plural': 'Learning Context Links status',
116+
},
117+
),
118+
migrations.CreateModel(
119+
name='ComponentLink',
120+
fields=[
121+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
122+
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
123+
('upstream_usage_key', opaque_keys.edx.django.models.UsageKeyField(help_text='Upstream block usage key, this value cannot be null and useful to track upstream library blocks that do not exist yet', max_length=255)),
124+
('upstream_context_key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_index=True, help_text='Upstream context key i.e., learning_package/library key', max_length=500)),
125+
('downstream_usage_key', opaque_keys.edx.django.models.UsageKeyField(max_length=255, unique=True)),
126+
('downstream_context_key', opaque_keys.edx.django.models.CourseKeyField(db_index=True, max_length=255)),
127+
('version_synced', models.IntegerField()),
128+
('version_declined', models.IntegerField(blank=True, null=True)),
129+
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
130+
('updated', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
131+
('upstream_block', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='links', to='openedx_content.component')),
132+
],
133+
options={
134+
'verbose_name': 'Component Link',
135+
'verbose_name_plural': 'Component Links',
136+
},
137+
),
138+
migrations.CreateModel(
139+
name='ContainerLink',
140+
fields=[
141+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
142+
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
143+
('upstream_context_key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_index=True, help_text='Upstream context key i.e., learning_package/library key', max_length=500)),
144+
('downstream_usage_key', opaque_keys.edx.django.models.UsageKeyField(max_length=255, unique=True)),
145+
('downstream_context_key', opaque_keys.edx.django.models.CourseKeyField(db_index=True, max_length=255)),
146+
('version_synced', models.IntegerField()),
147+
('version_declined', models.IntegerField(blank=True, null=True)),
148+
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
149+
('updated', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
150+
('upstream_container_key', opaque_keys.edx.django.models.ContainerKeyField(help_text='Upstream block key (e.g. lct:...), this value cannot be null and is useful to track upstream library blocks that do not exist yet or were deleted.', max_length=255)),
151+
('upstream_container', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='links', to='openedx_content.container')),
152+
],
153+
options={
154+
'abstract': False,
155+
'verbose_name': 'Container Link',
156+
'verbose_name_plural': 'Container Links',
157+
},
158+
),
159+
migrations.RunPython(
160+
code=create_markdown_editor_waffle_flag,
161+
reverse_code=django.db.migrations.operations.special.RunPython.noop,
162+
),
163+
migrations.AddField(
164+
model_name='componentlink',
165+
name='top_level_parent',
166+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contentstore.containerlink'),
167+
),
168+
migrations.AddField(
169+
model_name='containerlink',
170+
name='top_level_parent',
171+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contentstore.containerlink'),
172+
),
173+
migrations.AddField(
174+
model_name='componentlink',
175+
name='downstream_customized',
176+
field=models.JSONField(default=list, help_text='Names of the fields which have values set on the upstream block yet have been explicitly overridden on this downstream block'),
177+
),
178+
migrations.AddField(
179+
model_name='containerlink',
180+
name='downstream_customized',
181+
field=models.JSONField(default=list, help_text='Names of the fields which have values set on the upstream block yet have been explicitly overridden on this downstream block'),
182+
),
183+
]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Generated by Django 5.2.10 on 2026-01-25 21:52
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
from django.db.migrations.operations.special import SeparateDatabaseAndState
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('contentstore', '0014_remove_componentlink_downstream_is_modified_and_more'),
12+
('openedx_content', '0002_rename_tables_to_openedx_content'),
13+
]
14+
15+
operations = [
16+
SeparateDatabaseAndState(
17+
database_operations=[],
18+
state_operations=[
19+
migrations.AlterField(
20+
model_name='componentlink',
21+
name='upstream_block',
22+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='links', to='openedx_content.component'),
23+
),
24+
migrations.AlterField(
25+
model_name='containerlink',
26+
name='upstream_container',
27+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='links', to='openedx_content.container'),
28+
),
29+
]
30+
),
31+
]

0 commit comments

Comments
 (0)