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,183 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-30 01:23
|
||||
|
||||
import django.db.migrations.operations.special
|
||||
import django.db.models.deletion
|
||||
import opaque_keys.edx.django.models
|
||||
import openedx_learning.lib.fields
|
||||
import openedx_learning.lib.validators
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
from cms.djangoapps.contentstore.config.waffle import ENABLE_CHECKLISTS_QUALITY
|
||||
from cms.djangoapps.contentstore.toggles import ENABLE_REACT_MARKDOWN_EDITOR
|
||||
|
||||
|
||||
def create_checklists_quality_waffle_flag(apps, schema_editor):
|
||||
Flag = apps.get_model('waffle', 'Flag')
|
||||
# Replacement for flag_undefined_default=True on flag definition
|
||||
Flag.objects.get_or_create(name=ENABLE_CHECKLISTS_QUALITY.name, defaults={'everyone': True})
|
||||
|
||||
|
||||
def create_markdown_editor_waffle_flag(apps, schema_editor):
|
||||
Flag = apps.get_model('waffle', 'Flag')
|
||||
Flag.objects.get_or_create(
|
||||
name=ENABLE_REACT_MARKDOWN_EDITOR.name, defaults={'everyone': True}
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
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')]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('course_overviews', '0024_overview_adds_has_highlights'),
|
||||
('oel_components', '0003_remove_componentversioncontent_learner_downloadable'),
|
||||
('oel_publishing', '0002_alter_learningpackage_key_and_more'),
|
||||
('oel_publishing', '0003_containers'),
|
||||
('openedx_content', '0002_rename_tables_to_openedx_content'),
|
||||
('waffle', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VideoUploadConfig',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
|
||||
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
|
||||
('profile_whitelist', models.TextField(blank=True, help_text='A comma-separated list of names of profiles to include in video encoding downloads.')),
|
||||
('changed_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Changed by')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-change_date',),
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=create_checklists_quality_waffle_flag,
|
||||
reverse_code=django.db.migrations.operations.special.RunPython.noop,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CourseOutlineRegenerate',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('course_overviews.courseoverview',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BackfillCourseTabsConfig',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
|
||||
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
|
||||
('start_index', models.IntegerField(default=0, help_text='Index of first course to start backfilling (in an alphabetically sorted list of courses)')),
|
||||
('count', models.IntegerField(default=0, help_text='How many courses to backfill in this run (or zero for all courses)')),
|
||||
('changed_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Changed by')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Arguments for backfill_course_tabs',
|
||||
'verbose_name_plural': 'Arguments for backfill_course_tabs',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CleanStaleCertificateAvailabilityDatesConfig',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
|
||||
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
|
||||
('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.")),
|
||||
('changed_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Changed by')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': "Arguments for 'clean_stale_certificate_availability_dates'",
|
||||
'verbose_name_plural': "Arguments for 'clean_stale_certificate_availability_dates'",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LearningContextLinksStatus',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('context_key', opaque_keys.edx.django.models.CourseKeyField(help_text='Linking status for course context key', max_length=255, unique=True)),
|
||||
('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)),
|
||||
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
|
||||
('updated', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Learning Context Links status',
|
||||
'verbose_name_plural': 'Learning Context Links status',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ComponentLink',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
|
||||
('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)),
|
||||
('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)),
|
||||
('downstream_usage_key', opaque_keys.edx.django.models.UsageKeyField(max_length=255, unique=True)),
|
||||
('downstream_context_key', opaque_keys.edx.django.models.CourseKeyField(db_index=True, max_length=255)),
|
||||
('version_synced', models.IntegerField()),
|
||||
('version_declined', models.IntegerField(blank=True, null=True)),
|
||||
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
|
||||
('updated', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
|
||||
('upstream_block', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='links', to='openedx_content.component')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Component Link',
|
||||
'verbose_name_plural': 'Component Links',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContainerLink',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')),
|
||||
('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)),
|
||||
('downstream_usage_key', opaque_keys.edx.django.models.UsageKeyField(max_length=255, unique=True)),
|
||||
('downstream_context_key', opaque_keys.edx.django.models.CourseKeyField(db_index=True, max_length=255)),
|
||||
('version_synced', models.IntegerField()),
|
||||
('version_declined', models.IntegerField(blank=True, null=True)),
|
||||
('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
|
||||
('updated', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])),
|
||||
('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)),
|
||||
('upstream_container', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='links', to='openedx_content.container')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'verbose_name': 'Container Link',
|
||||
'verbose_name_plural': 'Container Links',
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=create_markdown_editor_waffle_flag,
|
||||
reverse_code=django.db.migrations.operations.special.RunPython.noop,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='componentlink',
|
||||
name='top_level_parent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contentstore.containerlink'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='containerlink',
|
||||
name='top_level_parent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contentstore.containerlink'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='componentlink',
|
||||
name='downstream_customized',
|
||||
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'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='containerlink',
|
||||
name='downstream_customized',
|
||||
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'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-25 21:52
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
from django.db.migrations.operations.special import SeparateDatabaseAndState
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contentstore', '0014_remove_componentlink_downstream_is_modified_and_more'),
|
||||
('openedx_content', '0002_rename_tables_to_openedx_content'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
SeparateDatabaseAndState(
|
||||
database_operations=[],
|
||||
state_operations=[
|
||||
migrations.AlterField(
|
||||
model_name='componentlink',
|
||||
name='upstream_block',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='links', to='openedx_content.component'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='containerlink',
|
||||
name='upstream_container',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='links', to='openedx_content.container'),
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,146 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-30 01:29
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import model_utils.fields
|
||||
import opaque_keys.edx.django.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('modulestore_migrator', '0001_initial'), ('modulestore_migrator', '0002_alter_modulestoremigration_task_status'), ('modulestore_migrator', '0003_modulestoremigration_is_failed'), ('modulestore_migrator', '0004_alter_modulestoreblockmigration_target_squashed_0005_modulestoreblockmigration_unsupported_reason'), ('modulestore_migrator', '0006_alter_modulestoreblocksource_forwarded_and_more'), ('modulestore_migrator', '0007_switch_to_openedx_content')]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('content_staging', '0006_alter_userclipboard_source_usage_key'),
|
||||
('oel_collections', '0005_alter_collection_options_alter_collection_enabled'),
|
||||
('oel_publishing', '0008_alter_draftchangelogrecord_options_and_more'),
|
||||
('openedx_content', '0002_rename_tables_to_openedx_content'),
|
||||
('user_tasks', '0004_url_textfield'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ModulestoreBlockMigration',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||
('change_log_record', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='openedx_content.draftchangelogrecord')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ModulestoreMigration',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('source_version', models.CharField(blank=True, help_text='Migrated content version, the hash of published content version', max_length=255, null=True)),
|
||||
('composition_level', models.CharField(choices=[('component', 'Component'), ('unit', 'Unit'), ('subsection', 'Subsection'), ('section', 'Section')], default='component', help_text='Maximum hierachy level at which content should be aggregated in target library', max_length=255)),
|
||||
('repeat_handling_strategy', models.CharField(choices=[('skip', 'Skip'), ('fork', 'Fork'), ('update', 'Update')], default='skip', help_text='If a piece of content already exists in the content library, choose how to handle it.', max_length=24)),
|
||||
('preserve_url_slugs', models.BooleanField(default=False, help_text='Should the migration preserve the location IDs of the existing blocks?If not, then new, unique human-readable IDs will be generated based on the block titles.')),
|
||||
('change_log', models.ForeignKey(help_text='Changelog entry in the target learning package which records this migration', null=True, on_delete=django.db.models.deletion.SET_NULL, to='openedx_content.draftchangelog')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ModulestoreSource',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', opaque_keys.edx.django.models.LearningContextKeyField(help_text='Key of the content source (a course or a legacy library)', max_length=255, unique=True)),
|
||||
('forwarded', models.OneToOneField(blank=True, help_text='If set, the system will forward references of this source over to the target of this migration', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='forwards', to='modulestore_migrator.modulestoremigration')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulestoremigration',
|
||||
name='source',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='migrations', to='modulestore_migrator.modulestoresource'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulestoremigration',
|
||||
name='staged_content',
|
||||
field=models.OneToOneField(help_text='Modulestore content is processed and staged before importing it to a learning packge. We temporarily save the staged content to allow for troubleshooting of failed migrations.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='content_staging.stagedcontent'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulestoremigration',
|
||||
name='target',
|
||||
field=models.ForeignKey(help_text='Content will be imported into this library', on_delete=django.db.models.deletion.CASCADE, to='openedx_content.learningpackage'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulestoremigration',
|
||||
name='target_collection',
|
||||
field=models.ForeignKey(blank=True, help_text='Optional - Collection (within the target library) into which imported content will be grouped', null=True, on_delete=django.db.models.deletion.SET_NULL, to='openedx_content.collection'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulestoremigration',
|
||||
name='task_status',
|
||||
field=models.ForeignKey(help_text='Tracks the status of the task which is executing this migration. In a bulk migration, the same task can be multiple migrations', on_delete=django.db.models.deletion.RESTRICT, related_name='migrations', to='user_tasks.usertaskstatus'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ModulestoreBlockSource',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||
('key', opaque_keys.edx.django.models.UsageKeyField(help_text='Original usage key of the XBlock that has been imported.', max_length=255)),
|
||||
('forwarded', models.OneToOneField(help_text='If set, the system will forward references of this block source over to the target of this block migration', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='forwards', to='modulestore_migrator.modulestoreblockmigration')),
|
||||
('overall_source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blocks', to='modulestore_migrator.modulestoresource')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulestoreblockmigration',
|
||||
name='overall_migration',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='block_migrations', to='modulestore_migrator.modulestoremigration'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulestoreblockmigration',
|
||||
name='source',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='modulestore_migrator.modulestoreblocksource'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulestoreblockmigration',
|
||||
name='target',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openedx_content.publishableentity'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='modulestoreblockmigration',
|
||||
unique_together={('overall_migration', 'source'), ('overall_migration', 'target')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulestoremigration',
|
||||
name='is_failed',
|
||||
field=models.BooleanField(default=False, help_text='is the migration failed?'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='modulestoreblockmigration',
|
||||
name='target',
|
||||
field=models.ForeignKey(blank=True, help_text='The target entity of this block migration, set to null if it fails to migrate', null=True, on_delete=django.db.models.deletion.CASCADE, to='openedx_content.publishableentity'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulestoreblockmigration',
|
||||
name='unsupported_reason',
|
||||
field=models.TextField(blank=True, help_text='Reason if the block is unsupported and target is set to null', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='modulestoreblocksource',
|
||||
name='forwarded',
|
||||
field=models.OneToOneField(help_text='If set, the system will forward references of this block source over to the target of this block migration', null=True, on_delete=django.db.models.deletion.SET_NULL, to='modulestore_migrator.modulestoreblockmigration'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='modulestoreblocksource',
|
||||
name='key',
|
||||
field=opaque_keys.edx.django.models.UsageKeyField(help_text='Original usage key of the XBlock that has been imported.', max_length=255, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='modulestoresource',
|
||||
name='forwarded',
|
||||
field=models.OneToOneField(blank=True, help_text='If set, the system will forward references of this source over to the target of this migration', null=True, on_delete=django.db.models.deletion.SET_NULL, to='modulestore_migrator.modulestoremigration'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='modulestoremigration',
|
||||
name='change_log',
|
||||
field=models.ForeignKey(help_text='Changelog entry in the target learning package which records this migration', null=True, on_delete=django.db.models.deletion.SET_NULL, to='openedx_content.draftchangelog'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,46 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-25 21:52
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
from django.db.migrations.operations.special import SeparateDatabaseAndState
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('modulestore_migrator', '0006_alter_modulestoreblocksource_forwarded_and_more'),
|
||||
('openedx_content', '0002_rename_tables_to_openedx_content'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
SeparateDatabaseAndState(
|
||||
database_operations=[],
|
||||
state_operations=[
|
||||
migrations.AlterField(
|
||||
model_name='modulestoreblockmigration',
|
||||
name='change_log_record',
|
||||
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='openedx_content.draftchangelogrecord'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='modulestoreblockmigration',
|
||||
name='target',
|
||||
field=models.ForeignKey(blank=True, help_text='The target entity of this block migration, set to null if it fails to migrate', null=True, on_delete=django.db.models.deletion.CASCADE, to='openedx_content.publishableentity'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='modulestoremigration',
|
||||
name='change_log',
|
||||
field=models.ForeignKey(help_text='Changelog entry in the target learning package which records this migration', null=True, on_delete=django.db.models.deletion.SET_NULL, to='openedx_content.draftchangelog'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='modulestoremigration',
|
||||
name='target',
|
||||
field=models.ForeignKey(help_text='Content will be imported into this library', on_delete=django.db.models.deletion.CASCADE, to='openedx_content.learningpackage'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='modulestoremigration',
|
||||
name='target_collection',
|
||||
field=models.ForeignKey(blank=True, help_text='Optional - Collection (within the target library) into which imported content will be grouped', null=True, on_delete=django.db.models.deletion.SET_NULL, to='openedx_content.collection'),
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
@@ -45,6 +45,7 @@ from corsheaders.defaults import default_headers as corsheaders_default_headers
|
||||
from datetime import timedelta
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from openedx_learning.api.django import openedx_learning_apps_to_install
|
||||
|
||||
from openedx.envs.common import * # pylint: disable=wildcard-import
|
||||
|
||||
@@ -897,14 +898,7 @@ INSTALLED_APPS = [
|
||||
|
||||
'openedx_events',
|
||||
|
||||
# Learning Core Apps, used by v2 content libraries (content_libraries app)
|
||||
"openedx_learning.apps.authoring.collections",
|
||||
"openedx_learning.apps.authoring.components",
|
||||
"openedx_learning.apps.authoring.contents",
|
||||
"openedx_learning.apps.authoring.publishing",
|
||||
"openedx_learning.apps.authoring.units",
|
||||
"openedx_learning.apps.authoring.subsections",
|
||||
"openedx_learning.apps.authoring.sections",
|
||||
*openedx_learning_apps_to_install(),
|
||||
]
|
||||
|
||||
### Apps only installed in some instances
|
||||
|
||||
@@ -60,6 +60,7 @@ from enterprise.constants import (
|
||||
PROVISIONING_PENDING_ENTERPRISE_CUSTOMER_ADMIN_ROLE,
|
||||
DEFAULT_ENTERPRISE_ENROLLMENT_INTENTIONS_ROLE,
|
||||
)
|
||||
from openedx_learning.api.django import openedx_learning_apps_to_install
|
||||
|
||||
from openedx.core.lib.derived import Derived
|
||||
from openedx.envs.common import * # pylint: disable=wildcard-import
|
||||
@@ -2019,14 +2020,8 @@ INSTALLED_APPS = [
|
||||
|
||||
'openedx_events',
|
||||
|
||||
# Learning Core Apps, used by v2 content libraries (content_libraries app)
|
||||
"openedx_learning.apps.authoring.collections",
|
||||
"openedx_learning.apps.authoring.components",
|
||||
"openedx_learning.apps.authoring.contents",
|
||||
"openedx_learning.apps.authoring.publishing",
|
||||
"openedx_learning.apps.authoring.units",
|
||||
"openedx_learning.apps.authoring.subsections",
|
||||
"openedx_learning.apps.authoring.sections",
|
||||
# Learning Core apps that power libraries
|
||||
*openedx_learning_apps_to_install(),
|
||||
]
|
||||
|
||||
# Add LMS specific optional apps
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
@@ -63,7 +63,7 @@ numpy<2.0.0
|
||||
# Date: 2023-09-18
|
||||
# pinning this version to avoid updates while the library is being developed
|
||||
# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35269
|
||||
openedx-learning==0.30.2
|
||||
openedx-learning==0.31.0
|
||||
|
||||
# Date: 2023-11-29
|
||||
# Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise.
|
||||
|
||||
@@ -834,7 +834,7 @@ openedx-filters==2.1.0
|
||||
# ora2
|
||||
openedx-forum==0.4.0
|
||||
# via -r requirements/edx/kernel.in
|
||||
openedx-learning==0.30.2
|
||||
openedx-learning==0.31.0
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
|
||||
@@ -1404,7 +1404,7 @@ openedx-forum==0.4.0
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
openedx-learning==0.30.2
|
||||
openedx-learning==0.31.0
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
|
||||
@@ -1013,7 +1013,7 @@ openedx-filters==2.1.0
|
||||
# ora2
|
||||
openedx-forum==0.4.0
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-learning==0.30.2
|
||||
openedx-learning==0.31.0
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
|
||||
@@ -1063,7 +1063,7 @@ openedx-filters==2.1.0
|
||||
# ora2
|
||||
openedx-forum==0.4.0
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-learning==0.30.2
|
||||
openedx-learning==0.31.0
|
||||
# via
|
||||
# -c requirements/constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
|
||||
Reference in New Issue
Block a user