refactor: switch to use openedx_content app (#37924)
The openedx-learning repo was recently refactored to consolidate its authoring apps into a single openedx_content app: https://github.com/openedx/openedx-platform/pull/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.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-30 01:20
|
||||
|
||||
import django.db.models.deletion
|
||||
import opaque_keys.edx.django.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('content_libraries', '0001_initial'), ('content_libraries', '0002_group_permissions'), ('content_libraries', '0003_contentlibrary_type'), ('content_libraries', '0004_contentlibrary_license'), ('content_libraries', '0005_ltigradedresource_ltiprofile'), ('content_libraries', '0006_auto_20210615_1916'), ('content_libraries', '0005_contentlibraryblockimporttask'), ('content_libraries', '0007_merge_20210818_0614'), ('content_libraries', '0008_auto_20210818_2148'), ('content_libraries', '0009_alter_contentlibrary_authorized_lti_configs'), ('content_libraries', '0010_contentlibrary_learning_package_and_more'), ('content_libraries', '0011_remove_contentlibrary_bundle_uuid_and_more'), ('content_libraries', '0012_switch_to_openedx_content')]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0008_alter_user_username_max_length'),
|
||||
('lti1p3_tool_config', '0001_initial'),
|
||||
('oel_publishing', '0001_initial'),
|
||||
('openedx_content', '0002_rename_tables_to_openedx_content'),
|
||||
('organizations', '0007_historicalorganization'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ContentLibrary',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('slug', models.SlugField(allow_unicode=True)),
|
||||
('allow_public_learning', models.BooleanField(default=False, help_text='\n Allow any user (even unregistered users) to view and interact with\n content in this library (in the LMS; not in Studio). If this is not\n enabled, then the content in this library is not directly accessible\n in the LMS, and learners will only ever see this content if it is\n explicitly added to a course. If in doubt, leave this unchecked.\n ')),
|
||||
('allow_public_read', models.BooleanField(default=False, help_text="\n Allow any user with Studio access to view this library's content in\n Studio, use it in their courses, and copy content out of this\n library. If in doubt, leave this unchecked.\n ")),
|
||||
('org', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='organizations.organization')),
|
||||
('license', models.CharField(choices=[('', 'All Rights Reserved.'), ('CC:4.0:BY', 'Creative Commons Attribution 4.0'), ('CC:4.0:BY:NC', 'Creative Commons Attribution-NonCommercial 4.0'), ('CC:4.0:BY:NC:ND', 'Creative Commons Attribution-NonCommercial-NoDerivatives 4.0'), ('CC:4.0:BY:NC:SA', 'Creative Commons Attribution-NonCommercial-ShareAlike 4.0'), ('CC:4.0:BY:ND', 'Creative Commons Attribution-NoDerivatives 4.0'), ('CC:4.0:BY:SA', 'Creative Commons Attribution-ShareAlike 4.0')], default='', max_length=25)),
|
||||
('authorized_lti_configs', models.ManyToManyField(blank=True, help_text="List of authorized LTI tool configurations that can access this library's content through LTI launches, if empty no LTI launch is allowed.", related_name='content_libraries', to='lti1p3_tool_config.ltitool')),
|
||||
('learning_package', models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.learningpackage')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Content Libraries',
|
||||
'unique_together': {('org', 'slug')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContentLibraryPermission',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('access_level', models.CharField(choices=[('admin', 'Administer users and author content'), ('author', 'Author content'), ('read', 'Read-only')], max_length=30)),
|
||||
('library', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='permission_grants', to='content_libraries.contentlibrary')),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='auth.group')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('user__username', 'group__name'),
|
||||
'unique_together': {('library', 'group'), ('library', 'user')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LtiProfile',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('platform_id', models.CharField(help_text='The LTI platform identifier to which this profile belongs to.', max_length=255, verbose_name='lti platform identifier')),
|
||||
('client_id', models.CharField(help_text='The LTI client identifier generated by the LTI platform.', max_length=255, verbose_name='client identifier')),
|
||||
('subject_id', models.CharField(help_text='Identifies the entity that initiated the launch request, commonly a user.', max_length=255, verbose_name='subject identifier')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='contentlibraries_lti_profile', to=settings.AUTH_USER_MODEL, verbose_name='open edx user')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('platform_id', 'client_id', 'subject_id')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContentLibraryBlockImportTask',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('state', models.CharField(choices=[('created', 'Task was created, but not queued to run.'), ('pending', 'Task was created and queued to run.'), ('running', 'Task is running.'), ('failed', 'Task finished, but some blocks failed to import.'), ('successful', 'Task finished successfully.')], default='created', help_text='The state of the block import task.', max_length=30, verbose_name='state')),
|
||||
('progress', models.FloatField(default=0.0, help_text='A float from 0.0 to 1.0 representing the task progress.', verbose_name='progress')),
|
||||
('course_id', opaque_keys.edx.django.models.CourseKeyField(db_index=True, help_text='ID of the imported course.', max_length=255, verbose_name='course ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('library', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='import_tasks', to='content_libraries.contentlibrary')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at', '-updated_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LtiGradedResource',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('usage_key', opaque_keys.edx.django.models.UsageKeyField(help_text='The usage key string of the resource serving the content of this launch.', max_length=255)),
|
||||
('resource_id', models.CharField(help_text='The LTI platform unique identifier of this resource, also known as the "resource link id".', max_length=255)),
|
||||
('resource_title', models.CharField(help_text='The LTI platform descriptive title for this resource.', max_length=255, null=True)),
|
||||
('ags_lineitem', models.CharField(help_text='If AGS was enabled during launch, this should hold the lineitem ID.', max_length=255)),
|
||||
('profile', models.ForeignKey(help_text='The authorized LTI profile that launched the resource (identifies the user).', on_delete=django.db.models.deletion.CASCADE, related_name='lti_resources', to='content_libraries.ltiprofile')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('usage_key', 'profile')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 5.2.9 on 2026-01-25 19:44
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
from django.db.migrations.operations.special import SeparateDatabaseAndState
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('content_libraries', '0011_remove_contentlibrary_bundle_uuid_and_more'),
|
||||
('openedx_content', '0002_rename_tables_to_openedx_content'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
SeparateDatabaseAndState(
|
||||
database_operations=[],
|
||||
state_operations=[
|
||||
migrations.AlterField(
|
||||
model_name='contentlibrary',
|
||||
name='learning_package',
|
||||
field=models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.learningpackage'),
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user