From b5b6038c255e6ac1c6003962c676e3c5c84fccea Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Sun, 28 Dec 2025 00:57:48 -0500 Subject: [PATCH] refactor: combine authoring apps to openedx_content This commit makes the following major refactorings: - All apps previously under openedx_learning/apps/authoring have now effectively been merged together to one openedx_content app. - The models and logic for those apps continues to be encapsulated in what we're calling "applets", inside of openedx_content/applets/... - In order to facilitate smooth database migrations, the AppConfigs and database migrations for the old apps continue to be preserved in openedx_content/backcompat/... - A new openedx_learning.api.django.openedx_learning_apps_to_install() has been created to make it easier for openedx-platform to get all the necessary apps to install. The rationale for this change is detailed in docs/decisions/0020-authoring-as-one-app.rst, but the short version is that we hope this arrangement will allow us to keep many of the benefits of having small apps (easy to reason about), while also making it easier to refactor internally. Refactoring is currently hindered by the difficulty in moving models across apps. The name change away from "authoring" also reflects that we intend to use these APIs and models on the "learning" side of things as well, e.g. when reading content for rendering to a student. In the longer term, we will probably make openedx_content a top-level package in this repo, but that would be a later step. Most of this commit is just shuffling things around and renaming references, but the truly complicated bits are around the sequencing of database migrations, particularly the ones removing model state from the old apps, and transferring that state into the new openedx_content app without changing the actual database state. We also have some hacky logic in openedx_content/apps.py in order to properly patch migration dependencies so that we don't break existing openedx-platform migrations. This also bumps the version to 0.31.0. --- .annotation_safe_list.yml | 50 +- .gitignore | 9 +- .importlinter | 10 +- docs/decisions/0020-authoring-as-one-app.rst | 66 ++ .../management/commands/load_components.py | 6 +- openedx_learning/__init__.py | 2 +- openedx_learning/api/authoring.py | 11 +- openedx_learning/api/authoring_models.py | 14 +- openedx_learning/api/django.py | 24 + .../apps/authoring/components/apps.py | 24 - .../apps/authoring/publishing/apps.py | 25 - .../migrations/0010_backfill_dependencies.py | 149 ---- .../apps/authoring/sections/apps.py | 25 - .../apps/authoring/subsections/apps.py | 25 - openedx_learning/apps/authoring/units/apps.py | 25 - .../__init__.py | 0 .../apps/openedx_content/admin.py | 13 + openedx_learning/apps/openedx_content/api.py | 16 + .../applets}/__init__.py | 0 .../applets/backup_restore}/__init__.py | 0 .../applets}/backup_restore/admin.py | 0 .../applets}/backup_restore/api.py | 4 +- .../applets}/backup_restore/models.py | 0 .../applets}/backup_restore/serializers.py | 2 +- .../applets}/backup_restore/toml.py | 8 +- .../applets}/backup_restore/zipper.py | 19 +- .../applets/collections}/__init__.py | 0 .../applets}/collections/admin.py | 0 .../applets}/collections/api.py | 2 +- .../applets}/collections/models.py | 5 +- .../applets}/collections/readme.rst | 0 .../applets/components}/__init__.py | 0 .../applets}/components/admin.py | 2 +- .../applets}/components/api.py | 2 +- .../applets}/components/models.py | 6 +- .../applets}/components/readme.rst | 0 .../applets/contents}/__init__.py | 0 .../applets}/contents/admin.py | 0 .../applets}/contents/api.py | 5 +- .../applets}/contents/models.py | 10 +- .../applets/publishing}/__init__.py | 0 .../applets}/publishing/admin.py | 0 .../applets}/publishing/api.py | 2 +- .../applets}/publishing/contextmanagers.py | 0 .../applets}/publishing/models/__init__.py | 0 .../applets}/publishing/models/container.py | 0 .../applets}/publishing/models/draft_log.py | 0 .../applets}/publishing/models/entity_list.py | 1 + .../publishing/models/learning_package.py | 0 .../applets}/publishing/models/publish_log.py | 0 .../publishing/models/publishable_entity.py | 0 .../applets}/publishing/readme.rst | 0 .../applets/sections}/__init__.py | 0 .../applets}/sections/admin.py | 0 .../applets}/sections/api.py | 3 +- .../applets}/sections/models.py | 0 .../applets/subsections}/__init__.py | 0 .../applets}/subsections/admin.py | 0 .../applets}/subsections/api.py | 3 +- .../applets}/subsections/models.py | 0 .../applets/units}/__init__.py | 0 .../applets}/units/admin.py | 0 .../applets}/units/api.py | 3 +- .../applets}/units/models.py | 0 openedx_learning/apps/openedx_content/apps.py | 112 +++ .../backcompat}/__init__.py | 0 .../backcompat/backup_restore}/__init__.py | 0 .../backcompat}/backup_restore/apps.py | 2 +- .../backup_restore}/migrations/__init__.py | 0 .../backcompat/collections}/__init__.py | 0 .../backcompat}/collections/apps.py | 2 +- .../collections/migrations/0001_initial.py | 0 ...ion_name_collection_created_by_and_more.py | 0 .../migrations/0003_collection_entities.py | 0 .../migrations/0004_collection_key.py | 0 ...ection_options_alter_collection_enabled.py | 0 ...move_all_field_state_for_move_to_applet.py | 38 + .../collections}/migrations/__init__.py | 0 .../backcompat/components}/__init__.py | 0 .../backcompat/components/apps.py | 15 + .../components/migrations/0001_initial.py | 0 .../0002_alter_componentversioncontent_key.py | 0 ...nentversioncontent_learner_downloadable.py | 0 ...004_remove_componentversioncontent_uuid.py | 0 ...move_all_field_state_for_move_to_applet.py | 64 ++ .../components}/migrations/__init__.py | 0 .../backcompat/contents}/__init__.py | 0 .../backcompat}/contents/apps.py | 2 +- .../contents/migrations/0001_initial.py | 0 ...move_all_field_state_for_move_to_applet.py | 27 + .../contents}/migrations/__init__.py | 0 .../backcompat/publishing}/__init__.py | 0 .../backcompat/publishing/apps.py | 15 + .../publishing/migrations/0001_initial.py | 0 ...0002_alter_learningpackage_key_and_more.py | 0 .../publishing/migrations/0003_containers.py | 0 .../0004_publishableentity_can_stand_alone.py | 0 .../0005_alter_entitylistrow_options.py | 0 .../migrations/0006_draftchangelog.py | 0 .../0007_bootstrap_draftchangelog.py | 0 ...r_draftchangelogrecord_options_and_more.py | 0 .../0009_dependencies_and_hashing.py | 0 .../migrations/0010_backfill_dependencies.py | 425 ++++++++++++ ...move_all_field_state_for_move_to_applet.py | 214 ++++++ .../publishing}/migrations/__init__.py | 0 .../openedx_content/backcompat/readme.rst | 6 + .../backcompat/sections}/__init__.py | 0 .../backcompat/sections/apps.py | 16 + .../sections/migrations/0001_initial.py | 0 ...move_all_field_state_for_move_to_applet.py | 30 + .../sections/migrations}/__init__.py | 0 .../backcompat/subsections}/__init__.py | 0 .../backcompat/subsections/apps.py | 16 + .../subsections/migrations/0001_initial.py | 0 ...move_all_field_state_for_move_to_applet.py | 30 + .../subsections/migrations}/__init__.py | 0 .../backcompat/units}/__init__.py | 0 .../openedx_content/backcompat/units/apps.py | 16 + .../units/migrations/0001_initial.py | 0 ...move_all_field_state_for_move_to_applet.py | 30 + .../backcompat/units/migrations}/__init__.py | 0 .../openedx_content/management}/__init__.py | 0 .../management/commands}/__init__.py | 0 .../commands/add_assets_to_component.py | 3 +- .../management/commands/lp_dump.py | 4 +- .../management/commands/lp_load.py | 2 +- .../migrations/0001_initial.py | 654 ++++++++++++++++++ .../0002_rename_tables_to_openedx_content.py | 138 ++++ .../openedx_content/migrations/__init__.py | 0 .../apps/openedx_content/models.py | 17 + .../contrib/media_server/views.py | 2 +- projects/dev.py | 24 +- test_settings.py | 21 +- .../apps/authoring/applets/__init__.py | 0 .../applets/backup_restore/__init__.py | 0 .../collections/collection-test.toml | 0 .../entities/section1-8ca126.toml | 0 .../entities/subsection1-48afa3.toml | 0 .../library_backup/entities/unit1-b7eafb.toml | 0 .../4d1b2fac-8b30-42fb-872d-6b10ab580b27.toml | 0 .../component_versions/v2/block.xml | 0 .../c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2.toml | 0 .../component_versions/v2/block.xml | 0 .../e32d5479-9492-41f6-9222-550a7346bc37.toml | 0 .../component_versions/v4/block.xml | 0 .../component_versions/v4/static/me.png | Bin .../component_versions/v5/block.xml | 0 .../component_versions/v5/static/me.png | Bin .../1ee38208-a585-4455-a27e-4930aa541f53.toml | 0 .../component_versions/v2/block.xml | 0 .../256739e8-c2df-4ced-bd10-8156f6cfa90b.toml | 0 .../component_versions/v2/block.xml | 0 .../6681da3f-b056-4c6e-a8f9-040967907471.toml | 0 .../component_versions/v1/block.xml | 0 .../22601ebd-9da8-430b-9778-cfe059a98568.toml | 0 .../component_versions/v3/block.xml | 0 .../fixtures/library_backup/package.toml | 0 .../backup_restore/test_backup.py | 2 +- .../backup_restore/test_restore.py | 17 +- .../backup_restore/test_slug_hash.py | 2 +- .../authoring/applets/collections/__init__.py | 0 .../{ => applets}/collections/test_api.py | 2 +- .../authoring/applets/components/__init__.py | 0 .../{ => applets}/components/test_api.py | 16 +- .../{ => applets}/components/test_assets.py | 10 +- .../{ => applets}/components/test_models.py | 6 +- .../authoring/applets/contents/__init__.py | 0 .../contents/test_file_storage.py | 6 +- .../contents/test_media_types.py | 2 +- .../authoring/applets/publishing/__init__.py | 0 .../{ => applets}/publishing/test_api.py | 4 +- .../{ => applets}/publishing/test_models.py | 5 +- .../authoring/applets/sections/__init__.py | 0 .../{ => applets}/sections/test_api.py | 0 .../authoring/applets/subsections/__init__.py | 0 .../{ => applets}/subsections/test_api.py | 2 +- .../apps/authoring/applets/units/__init__.py | 0 .../authoring/{ => applets}/units/test_api.py | 2 +- .../tagging/test_system_defined_models.py | 2 +- 179 files changed, 2141 insertions(+), 433 deletions(-) create mode 100644 docs/decisions/0020-authoring-as-one-app.rst create mode 100644 openedx_learning/api/django.py delete mode 100644 openedx_learning/apps/authoring/components/apps.py delete mode 100644 openedx_learning/apps/authoring/publishing/apps.py delete mode 100644 openedx_learning/apps/authoring/publishing/migrations/0010_backfill_dependencies.py delete mode 100644 openedx_learning/apps/authoring/sections/apps.py delete mode 100644 openedx_learning/apps/authoring/subsections/apps.py delete mode 100644 openedx_learning/apps/authoring/units/apps.py rename openedx_learning/apps/{authoring => openedx_content}/__init__.py (100%) create mode 100644 openedx_learning/apps/openedx_content/admin.py create mode 100644 openedx_learning/apps/openedx_content/api.py rename openedx_learning/apps/{authoring/backup_restore => openedx_content/applets}/__init__.py (100%) rename openedx_learning/apps/{authoring/backup_restore/management => openedx_content/applets/backup_restore}/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/backup_restore/admin.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/backup_restore/api.py (84%) rename openedx_learning/apps/{authoring => openedx_content/applets}/backup_restore/models.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/backup_restore/serializers.py (98%) rename openedx_learning/apps/{authoring => openedx_content/applets}/backup_restore/toml.py (95%) rename openedx_learning/apps/{authoring => openedx_content/applets}/backup_restore/zipper.py (98%) rename openedx_learning/apps/{authoring/backup_restore/management/commands => openedx_content/applets/collections}/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/collections/admin.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/collections/api.py (99%) rename openedx_learning/apps/{authoring => openedx_content/applets}/collections/models.py (98%) rename openedx_learning/apps/{authoring => openedx_content/applets}/collections/readme.rst (100%) rename openedx_learning/apps/{authoring/backup_restore/migrations => openedx_content/applets/components}/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/components/admin.py (98%) rename openedx_learning/apps/{authoring => openedx_content/applets}/components/api.py (99%) rename openedx_learning/apps/{authoring => openedx_content/applets}/components/models.py (98%) rename openedx_learning/apps/{authoring => openedx_content/applets}/components/readme.rst (100%) rename openedx_learning/apps/{authoring/collections => openedx_content/applets/contents}/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/contents/admin.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/contents/api.py (99%) rename openedx_learning/apps/{authoring => openedx_content/applets}/contents/models.py (98%) rename openedx_learning/apps/{authoring/collections/migrations => openedx_content/applets/publishing}/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/publishing/admin.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/publishing/api.py (99%) rename openedx_learning/apps/{authoring => openedx_content/applets}/publishing/contextmanagers.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/publishing/models/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/publishing/models/container.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/publishing/models/draft_log.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/publishing/models/entity_list.py (99%) rename openedx_learning/apps/{authoring => openedx_content/applets}/publishing/models/learning_package.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/publishing/models/publish_log.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/publishing/models/publishable_entity.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/publishing/readme.rst (100%) rename openedx_learning/apps/{authoring/components => openedx_content/applets/sections}/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/sections/admin.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/sections/api.py (99%) rename openedx_learning/apps/{authoring => openedx_content/applets}/sections/models.py (100%) rename openedx_learning/apps/{authoring/components/management => openedx_content/applets/subsections}/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/subsections/admin.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/subsections/api.py (99%) rename openedx_learning/apps/{authoring => openedx_content/applets}/subsections/models.py (100%) rename openedx_learning/apps/{authoring/components/management/commands => openedx_content/applets/units}/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/units/admin.py (100%) rename openedx_learning/apps/{authoring => openedx_content/applets}/units/api.py (99%) rename openedx_learning/apps/{authoring => openedx_content/applets}/units/models.py (100%) create mode 100644 openedx_learning/apps/openedx_content/apps.py rename openedx_learning/apps/{authoring/components/migrations => openedx_content/backcompat}/__init__.py (100%) rename openedx_learning/apps/{authoring/contents => openedx_content/backcompat/backup_restore}/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/backup_restore/apps.py (78%) rename openedx_learning/apps/{authoring/contents => openedx_content/backcompat/backup_restore}/migrations/__init__.py (100%) rename openedx_learning/apps/{authoring/publishing => openedx_content/backcompat/collections}/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/collections/apps.py (82%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/collections/migrations/0001_initial.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/collections/migrations/0003_collection_entities.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/collections/migrations/0004_collection_key.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/collections/migrations/0006_remove_all_field_state_for_move_to_applet.py rename openedx_learning/apps/{authoring/publishing => openedx_content/backcompat/collections}/migrations/__init__.py (100%) rename openedx_learning/apps/{authoring/sections => openedx_content/backcompat/components}/__init__.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/components/apps.py rename openedx_learning/apps/{authoring => openedx_content/backcompat}/components/migrations/0001_initial.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/components/migrations/0002_alter_componentversioncontent_key.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/components/migrations/0004_remove_componentversioncontent_uuid.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/components/migrations/0005_remove_all_field_state_for_move_to_applet.py rename openedx_learning/apps/{authoring/sections => openedx_content/backcompat/components}/migrations/__init__.py (100%) rename openedx_learning/apps/{authoring/subsections => openedx_content/backcompat/contents}/__init__.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/contents/apps.py (82%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/contents/migrations/0001_initial.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/contents/migrations/0002_remove_all_field_state_for_move_to_applet.py rename openedx_learning/apps/{authoring/subsections => openedx_content/backcompat/contents}/migrations/__init__.py (100%) rename openedx_learning/apps/{authoring/units => openedx_content/backcompat/publishing}/__init__.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/publishing/apps.py rename openedx_learning/apps/{authoring => openedx_content/backcompat}/publishing/migrations/0001_initial.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/publishing/migrations/0002_alter_learningpackage_key_and_more.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/publishing/migrations/0003_containers.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/publishing/migrations/0004_publishableentity_can_stand_alone.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/publishing/migrations/0005_alter_entitylistrow_options.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/publishing/migrations/0006_draftchangelog.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/publishing/migrations/0007_bootstrap_draftchangelog.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py (100%) rename openedx_learning/apps/{authoring => openedx_content/backcompat}/publishing/migrations/0009_dependencies_and_hashing.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0010_backfill_dependencies.py create mode 100644 openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0011_remove_all_field_state_for_move_to_applet.py rename openedx_learning/apps/{authoring/units => openedx_content/backcompat/publishing}/migrations/__init__.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/readme.rst rename {tests/openedx_learning/apps/authoring/backup_restore => openedx_learning/apps/openedx_content/backcompat/sections}/__init__.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/sections/apps.py rename openedx_learning/apps/{authoring => openedx_content/backcompat}/sections/migrations/0001_initial.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/sections/migrations/0002_remove_all_field_state_for_move_to_applet.py rename {tests/openedx_learning/apps/authoring/collections => openedx_learning/apps/openedx_content/backcompat/sections/migrations}/__init__.py (100%) rename {tests/openedx_learning/apps/authoring/components => openedx_learning/apps/openedx_content/backcompat/subsections}/__init__.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/subsections/apps.py rename openedx_learning/apps/{authoring => openedx_content/backcompat}/subsections/migrations/0001_initial.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/subsections/migrations/0002_remove_all_field_state_for_move_to_applet.py rename {tests/openedx_learning/apps/authoring/contents => openedx_learning/apps/openedx_content/backcompat/subsections/migrations}/__init__.py (100%) rename {tests/openedx_learning/apps/authoring/publishing => openedx_learning/apps/openedx_content/backcompat/units}/__init__.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/units/apps.py rename openedx_learning/apps/{authoring => openedx_content/backcompat}/units/migrations/0001_initial.py (100%) create mode 100644 openedx_learning/apps/openedx_content/backcompat/units/migrations/0002_remove_all_field_state_for_move_to_applet.py rename {tests/openedx_learning/apps/authoring/sections => openedx_learning/apps/openedx_content/backcompat/units/migrations}/__init__.py (100%) rename {tests/openedx_learning/apps/authoring/subsections => openedx_learning/apps/openedx_content/management}/__init__.py (100%) rename {tests/openedx_learning/apps/authoring/units => openedx_learning/apps/openedx_content/management/commands}/__init__.py (100%) rename openedx_learning/apps/{authoring/components => openedx_content}/management/commands/add_assets_to_component.py (97%) rename openedx_learning/apps/{authoring/backup_restore => openedx_content}/management/commands/lp_dump.py (92%) rename openedx_learning/apps/{authoring/backup_restore => openedx_content}/management/commands/lp_load.py (95%) create mode 100644 openedx_learning/apps/openedx_content/migrations/0001_initial.py create mode 100644 openedx_learning/apps/openedx_content/migrations/0002_rename_tables_to_openedx_content.py create mode 100644 openedx_learning/apps/openedx_content/migrations/__init__.py create mode 100644 openedx_learning/apps/openedx_content/models.py create mode 100644 tests/openedx_learning/apps/authoring/applets/__init__.py create mode 100644 tests/openedx_learning/apps/authoring/applets/backup_restore/__init__.py rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/collections/collection-test.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/section1-8ca126.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/subsection1-48afa3.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/unit1-b7eafb.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27/component_versions/v2/block.xml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2/component_versions/v2/block.xml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/block.xml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/static/me.png (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/block.xml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/static/me.png (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53/component_versions/v2/block.xml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b/component_versions/v2/block.xml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471/component_versions/v1/block.xml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568/component_versions/v3/block.xml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/fixtures/library_backup/package.toml (100%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/test_backup.py (99%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/test_restore.py (96%) rename tests/openedx_learning/apps/authoring/{ => applets}/backup_restore/test_slug_hash.py (96%) create mode 100644 tests/openedx_learning/apps/authoring/applets/collections/__init__.py rename tests/openedx_learning/apps/authoring/{ => applets}/collections/test_api.py (99%) create mode 100644 tests/openedx_learning/apps/authoring/applets/components/__init__.py rename tests/openedx_learning/apps/authoring/{ => applets}/components/test_api.py (97%) rename tests/openedx_learning/apps/authoring/{ => applets}/components/test_assets.py (94%) rename tests/openedx_learning/apps/authoring/{ => applets}/components/test_models.py (95%) create mode 100644 tests/openedx_learning/apps/authoring/applets/contents/__init__.py rename tests/openedx_learning/apps/authoring/{ => applets}/contents/test_file_storage.py (92%) rename tests/openedx_learning/apps/authoring/{ => applets}/contents/test_media_types.py (90%) create mode 100644 tests/openedx_learning/apps/authoring/applets/publishing/__init__.py rename tests/openedx_learning/apps/authoring/{ => applets}/publishing/test_api.py (99%) rename tests/openedx_learning/apps/authoring/{ => applets}/publishing/test_models.py (82%) create mode 100644 tests/openedx_learning/apps/authoring/applets/sections/__init__.py rename tests/openedx_learning/apps/authoring/{ => applets}/sections/test_api.py (100%) create mode 100644 tests/openedx_learning/apps/authoring/applets/subsections/__init__.py rename tests/openedx_learning/apps/authoring/{ => applets}/subsections/test_api.py (99%) create mode 100644 tests/openedx_learning/apps/authoring/applets/units/__init__.py rename tests/openedx_learning/apps/authoring/{ => applets}/units/test_api.py (99%) diff --git a/.annotation_safe_list.yml b/.annotation_safe_list.yml index 54d8341fd..6b9f74d07 100644 --- a/.annotation_safe_list.yml +++ b/.annotation_safe_list.yml @@ -19,43 +19,43 @@ auth.User: ".. pii_retirement": "consumer_api" contenttypes.ContentType: ".. no_pii:": "This model has no PII" -oel_collections.Collection: +openedx_content.Collection: ".. no_pii:": "This model has no PII" -oel_collections.CollectionPublishableEntity: +openedx_content.CollectionPublishableEntity: ".. no_pii:": "This model has no PII" -oel_components.Component: +openedx_content.Component: ".. no_pii:": "This model has no PII" -oel_components.ComponentType: +openedx_content.ComponentType: ".. no_pii:": "This model has no PII" -oel_components.ComponentVersion: +openedx_content.ComponentVersion: ".. no_pii:": "This model has no PII" -oel_components.ComponentVersionContent: +openedx_content.ComponentVersionContent: ".. no_pii:": "This model has no PII" -oel_contents.Content: +openedx_content.Content: ".. no_pii:": "This model has no PII" -oel_contents.MediaType: +openedx_content.MediaType: ".. no_pii:": "This model has no PII" -oel_publishing.Container: +openedx_content.Container: ".. no_pii:": "This model has no PII" -oel_publishing.ContainerVersion: +openedx_content.ContainerVersion: ".. no_pii:": "This model has no PII" -oel_publishing.Draft: +openedx_content.Draft: ".. no_pii:": "This model has no PII" -oel_publishing.EntityList: +openedx_content.EntityList: ".. no_pii:": "This model has no PII" -oel_publishing.EntityListRow: +openedx_content.EntityListRow: ".. no_pii:": "This model has no PII" -oel_publishing.LearningPackage: +openedx_content.LearningPackage: ".. no_pii:": "This model has no PII" -oel_publishing.PublishLog: +openedx_content.PublishLog: ".. no_pii:": "This model has no PII" -oel_publishing.PublishLogRecord: +openedx_content.PublishLogRecord: ".. no_pii:": "This model has no PII" -oel_publishing.PublishableEntity: +openedx_content.PublishableEntity: ".. no_pii:": "This model has no PII" -oel_publishing.PublishableEntityVersion: +openedx_content.PublishableEntityVersion: ".. no_pii:": "This model has no PII" -oel_publishing.Published: +openedx_content.Published: ".. no_pii:": "This model has no PII" oel_tagging.ObjectTag: ".. no_pii:": "This model has no PII" @@ -65,17 +65,17 @@ oel_tagging.TagImportTask: ".. no_pii:": "This model has no PII" oel_tagging.Taxonomy: ".. no_pii:": "This model has no PII" -oel_sections.Section: +openedx_content.Section: ".. no_pii:": "This model has no PII" -oel_sections.SectionVersion: +openedx_content.SectionVersion: ".. no_pii:": "This model has no PII" -oel_subsections.Subsection: +openedx_content.Subsection: ".. no_pii:": "This model has no PII" -oel_subsections.SubsectionVersion: +openedx_content.SubsectionVersion: ".. no_pii:": "This model has no PII" -oel_units.Unit: +openedx_content.Unit: ".. no_pii:": "This model has no PII" -oel_units.UnitVersion: +openedx_content.UnitVersion: ".. no_pii:": "This model has no PII" social_django.Association: ".. no_pii:": "This model has no PII" diff --git a/.gitignore b/.gitignore index 6123dd4cf..faa881b9a 100644 --- a/.gitignore +++ b/.gitignore @@ -75,7 +75,12 @@ venv/ !.vscode/settings.json.example # Media files (for uploads) -media/ +/media/ # Media files generated during test runs -test_media/ +/test_media/ + +# uv stuff +.lock +CACHEDIR.TAG +pyvenv.cfg diff --git a/.importlinter b/.importlinter index 6c278d0dd..8125927ba 100644 --- a/.importlinter +++ b/.importlinter @@ -36,24 +36,24 @@ layers= openedx_learning.api.authoring # The "backup_restore" app handle the new export and import mechanism. - openedx_learning.apps.authoring.backup_restore + openedx_learning.apps.openedx_content.applets.backup_restore # The "components" app is responsible for storing versioned Components, # which is Open edX Studio terminology maps to things like individual # Problems, Videos, and blocks of HTML text. This is also the type we would # associate with a single "leaf" XBlock–one that is not a container type and # has no child elements. - openedx_learning.apps.authoring.components + openedx_learning.apps.openedx_content.applets.components # The "contents" app stores the simplest pieces of binary and text data, # without versioning information. These belong to a single Learning Package. - openedx_learning.apps.authoring.contents + openedx_learning.apps.openedx_content.applets.contents # The "collections" app stores arbitrary groupings of PublishableEntities. # Its only dependency should be the publishing app. - openedx_learning.apps.authoring.collections + openedx_learning.apps.openedx_content.applets.collections # The lowest layer is "publishing", which holds the basic primitives needed # to create Learning Packages and manage the draft and publish states for # various types of content. - openedx_learning.apps.authoring.publishing + openedx_learning.apps.openedx_content.applets.publishing diff --git a/docs/decisions/0020-authoring-as-one-app.rst b/docs/decisions/0020-authoring-as-one-app.rst new file mode 100644 index 000000000..81339a2cd --- /dev/null +++ b/docs/decisions/0020-authoring-as-one-app.rst @@ -0,0 +1,66 @@ +20. openedx_content as an Umbrella App of Smaller Applets +========================================================= + +Context +------- + +Up to this point, Learning Core has used many small apps with a narrow focus (e.g. ``components``, ``collections``, etc.) in order to make each individual app simpler to reason about. This has been useful overall, but it has made refactoring more cumbersome. For instance: + +#. Moving models between apps is tricky, requiring the use of Django's ``SeparateDatabaseAndState`` functionality to fake a deletion in one app and a creation in another without actually altering the database. It also requires doctoring the migration files for models in other repos that might have foreign key relations to the model being moved, so that they're pointing to the new ``app_label``. This will be an issue when we try to extract container-related models and logic out of publishing and into a new ``containers`` app. +#. Renaming an app is also cumbersome, because the process requires creating a new app and transitioning the models over. This came up when trying to rename the ``contents`` app to ``media``. + +There have also been minor inconveniences, like having a long list of ``INSTALLED_APPS`` to maintain in edx-platform over time, or not having these tables easily grouped together in the Django admin interface. + +Decisions +--------- + +1. Single openedx_content App +~~~~~~~~~~~~~~~~~~~~~~~ + +All existing authoring apps will be merged into one Django app (``openedx_learning.app.openedx_content``). Some consequences of this decision: + +- The tables will be renamed to have the ``openedx_content`` label prefix. +- All management commands will be moved to the ``openedx_content`` app. + +2. Logical Separation via Applets +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We will continue to keep internal API boundaries between individual applets, and use the ``api.py`` modules. This is both to insulate applets from implementation changes in other applets, as well as to provide a set of APIs that third-party plugins can utilize. As before, we will use Import Linter to enforce dependency ordering. + +3. Restructuring Specifics +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In one pull request, we are going to: + +#. Rename the ``openedx_learning.apps.authoring`` package to be ``openedx_learning.apps.openedx_content``. +#. Create bare shells of the existing ``authoring`` apps (``backup_restore``, ``collections``, ``components``, ``contents``, ``publishing``, ``sections``, ``subsections``, ``units``), and move them to the ``openedx_learning.apps.openedx_content.backcompat`` package. These shells will have an ``apps.py`` file and the ``migrations`` package for each existing app. This will allow for a smooth schema migration to transition the models from these individual apps to ``openedx_content``. +#. Move the actual models files and API logic for our existing authoring apps to the ``openedx_learning.apps.openedx_content.applets`` package. +#. Convert the top level ``openedx_learning.apps.openedx_content`` package to be a Django app. The top level ``admin.py``, ``api.py``, and ``models.py`` modules will do wildcard imports from the corresponding modules across all applet packages. + +In terms of model migrations, all existing apps will have a final migration that uses ``SeparateDatabaseAndState`` to remove all model state, but make no actual database changes. The initial ``openedx_content`` app migration will then also use ``SeparateDatabaseAndState`` to create the model state without doing any actual database operations. The next ``openedx_content`` app migration will rename all existing database tables to use the ``openedx_content`` prefix, for uniformity. + +The ordering of these migrations is important, and existing edx-platform migrations should remain unchanged. This is important to make sure that we do not introduce ordering inconsistencies for existing installations that are upgrading. + +Therefore, the migrations will happen in the following order: + +#. All ``backcompat.*`` apps migrations except for the final ones that delete model state. This takes us up to where migrations would already be before we make any changes. +#. The ``openedx_content`` app's ``0001_intial`` migration that adds model state without changing the database. At this point, model state exists for the same models in all the old ``backcompat.*`` apps as well as the new ``openedx_content`` app. +#. edx-platform apps that had foreign keys to old ``backcompat.*`` apps models will need to be switched to point to the new ``openedx_content`` app models. This will likewise be done without a database change, because they're still pointing to the same tables and columns. +#. Now that edx-platform references have been updated, we can delete the model state from the old ``backcompat.*`` apps and rename the underlying tables (in either order). + +The tricky part is to make sure that the old ``backcompat.*`` apps models still exist when the edx-platform migrations to move over the references runs. This is problematic because the edx-platform migrations can only specify that they run *after the new openedx_content models are created*. They cannot specify that they run *before the old backcompat models are dropped*. + +So in order to enforce this ordering, we do the following: + +* The ``openedx_content`` migration ``0001_initial`` requires that all ``backcompat.*`` migrations except the last ones removing model state are run. +* The ``openedx_content`` migration ``0002_rename_tables_to_openedx_content`` migration requires that the edx-platform migrations changing refrences over run. This is important anyway, because we want to make sure those reference changes happen before we change any table names. +* The final ``backcompat.*`` migrations that remove model field state will list ``openedx_content`` app's ``0002_rename_tables_to_openedx_content`` as a dependency. + +A further complication is that ``openedx_learning`` will often run its migrations without edx-platform present (e.g. for CI or standalone dev purposes), so we can't force ``0002_rename_tables_to_openedx_content`` in the ``openedx_content`` app to have references to edx-platform migrations. To get around this, we dynamically inject those migration dependencies only if we detect those edx-platform apps exist in the currently loaded Django project. This injection happens in the ``apps.py`` initialization for the ``openedx_content`` app. + +The final complication is that we want these migration dependencies to be the same regardless of whether you're running edx-platform migrations with the LMS or CMS (Studio) settings, or we run the risk of getting into an inconsistent state and dropping the old models before all the edx-platform apps can run their migrations to move their references. To do this, we have to make sure that the edx-platform apps that reference Learning Core models are present in the ``INSTALLED_APPS`` for both configurations. + +4. The Bigger Picture +~~~~~~~~~~~~~~~~~~~~~ + +This practice means that the ``openedx_content`` Django app corresponds to a Subdomain in Domain Driven Design terminology, with each applet being a Bounded Context. We call these "Applets" instead of "Bounded Contexts" because we don't want it to get confused for Django's notion of Contexts and Context Processors (or Python's notion of Context Managers). diff --git a/olx_importer/management/commands/load_components.py b/olx_importer/management/commands/load_components.py index 55cd268c8..f0d3ede4e 100644 --- a/olx_importer/management/commands/load_components.py +++ b/olx_importer/management/commands/load_components.py @@ -28,9 +28,9 @@ from django.db import transaction # Model references to remove -from openedx_learning.apps.authoring.components import api as components_api -from openedx_learning.apps.authoring.contents import api as contents_api -from openedx_learning.apps.authoring.publishing import api as publishing_api +from openedx_learning.apps.openedx_content.applets.components import api as components_api +from openedx_learning.apps.openedx_content.applets.contents import api as contents_api +from openedx_learning.apps.openedx_content.applets.publishing import api as publishing_api SUPPORTED_TYPES = ["problem", "video", "html"] logger = logging.getLogger(__name__) diff --git a/openedx_learning/__init__.py b/openedx_learning/__init__.py index c7fcd06b1..4457588a2 100644 --- a/openedx_learning/__init__.py +++ b/openedx_learning/__init__.py @@ -2,4 +2,4 @@ Open edX Learning ("Learning Core"). """ -__version__ = "0.30.2" +__version__ = "0.31.0" diff --git a/openedx_learning/api/authoring.py b/openedx_learning/api/authoring.py index 9082b33fb..5b1334ab4 100644 --- a/openedx_learning/api/authoring.py +++ b/openedx_learning/api/authoring.py @@ -2,21 +2,14 @@ This is the public API for content authoring in Learning Core. This is the single ``api`` module that code outside of the -``openedx_learning.apps.authoring.*`` package should import from. It will +``openedx_learning.apps.openedx_content.*`` package should import from. It will re-export the public functions from all api.py modules of all authoring apps. It may also implement its own convenience APIs that wrap calls to multiple app APIs. """ # These wildcard imports are okay because these api modules declare __all__. # pylint: disable=wildcard-import -from ..apps.authoring.backup_restore.api import * -from ..apps.authoring.collections.api import * -from ..apps.authoring.components.api import * -from ..apps.authoring.contents.api import * -from ..apps.authoring.publishing.api import * -from ..apps.authoring.sections.api import * -from ..apps.authoring.subsections.api import * -from ..apps.authoring.units.api import * +from ..apps.openedx_content.api import * # This was renamed after the authoring API refactoring pushed this and other # app APIs into the openedx_learning.api.authoring module. Here I'm aliasing to diff --git a/openedx_learning/api/authoring_models.py b/openedx_learning/api/authoring_models.py index 617d85dc4..25c90eeb3 100644 --- a/openedx_learning/api/authoring_models.py +++ b/openedx_learning/api/authoring_models.py @@ -7,10 +7,10 @@ """ # These wildcard imports are okay because these modules declare __all__. # pylint: disable=wildcard-import -from ..apps.authoring.collections.models import * -from ..apps.authoring.components.models import * -from ..apps.authoring.contents.models import * -from ..apps.authoring.publishing.models import * -from ..apps.authoring.sections.models import * -from ..apps.authoring.subsections.models import * -from ..apps.authoring.units.models import * +from ..apps.openedx_content.applets.collections.models import * +from ..apps.openedx_content.applets.components.models import * +from ..apps.openedx_content.applets.contents.models import * +from ..apps.openedx_content.applets.publishing.models import * +from ..apps.openedx_content.applets.sections.models import * +from ..apps.openedx_content.applets.subsections.models import * +from ..apps.openedx_content.applets.units.models import * diff --git a/openedx_learning/api/django.py b/openedx_learning/api/django.py new file mode 100644 index 000000000..267d0e038 --- /dev/null +++ b/openedx_learning/api/django.py @@ -0,0 +1,24 @@ +""" +Module for parts of the Learning Core API that exist to make it easier to use in +Django projects. +""" + + +def openedx_learning_apps_to_install(): + """ + Return all app names for appending to INSTALLED_APPS. + + This function exists to better insulate edx-platform and potential plugins + over time, as we eventually plan to remove the backcompat apps. + """ + return [ + "openedx_learning.apps.openedx_content", + "openedx_learning.apps.openedx_content.backcompat.backup_restore", + "openedx_learning.apps.openedx_content.backcompat.collections", + "openedx_learning.apps.openedx_content.backcompat.components", + "openedx_learning.apps.openedx_content.backcompat.contents", + "openedx_learning.apps.openedx_content.backcompat.publishing", + "openedx_learning.apps.openedx_content.backcompat.sections", + "openedx_learning.apps.openedx_content.backcompat.subsections", + "openedx_learning.apps.openedx_content.backcompat.units", + ] diff --git a/openedx_learning/apps/authoring/components/apps.py b/openedx_learning/apps/authoring/components/apps.py deleted file mode 100644 index 591e60f6c..000000000 --- a/openedx_learning/apps/authoring/components/apps.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Django metadata for the Components Django application. -""" -from django.apps import AppConfig - - -class ComponentsConfig(AppConfig): - """ - Configuration for the Components Django application. - """ - - name = "openedx_learning.apps.authoring.components" - verbose_name = "Learning Core > Authoring > Components" - default_auto_field = "django.db.models.BigAutoField" - label = "oel_components" - - def ready(self) -> None: - """ - Register Component and ComponentVersion. - """ - from ..publishing.api import register_publishable_models # pylint: disable=import-outside-toplevel - from .models import Component, ComponentVersion # pylint: disable=import-outside-toplevel - - register_publishable_models(Component, ComponentVersion) diff --git a/openedx_learning/apps/authoring/publishing/apps.py b/openedx_learning/apps/authoring/publishing/apps.py deleted file mode 100644 index d3aefd8a6..000000000 --- a/openedx_learning/apps/authoring/publishing/apps.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -publishing Django application initialization. -""" - -from django.apps import AppConfig - - -class PublishingConfig(AppConfig): - """ - Configuration for the publishing Django application. - """ - - name = "openedx_learning.apps.authoring.publishing" - verbose_name = "Learning Core > Authoring > Publishing" - default_auto_field = "django.db.models.BigAutoField" - label = "oel_publishing" - - def ready(self): - """ - Register Container and ContainerVersion. - """ - from .api import register_publishable_models # pylint: disable=import-outside-toplevel - from .models import Container, ContainerVersion # pylint: disable=import-outside-toplevel - - register_publishable_models(Container, ContainerVersion) diff --git a/openedx_learning/apps/authoring/publishing/migrations/0010_backfill_dependencies.py b/openedx_learning/apps/authoring/publishing/migrations/0010_backfill_dependencies.py deleted file mode 100644 index c274a401b..000000000 --- a/openedx_learning/apps/authoring/publishing/migrations/0010_backfill_dependencies.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -Backfill PublishableEntityVersionDependency entries based on ContainerVersions. - -We're introducing a lower-level publishing concept of a dependency that will be -used by Containers, but this means we have to backfill that dependency info for -existing Containers in the system. -""" -from django.db import migrations -from django.db.models import F - - -def create_backfill(apps, schema_editor): - """ - Create dependency entries and update dep hashes for Draft and Published. - """ - _create_dependencies(apps) - _update_drafts(apps) - _update_draft_dependencies_hashes(apps) - _update_published_dependencies_hashes(apps) - - -def _create_dependencies(apps): - """ - Populate the PublishableEntityVersion.dependencies relation. - - The only ones we should have in the system at this point are the ones from - containers, so we query ContainerVersion for that. - """ - PublishableEntityVersionDependency = apps.get_model( - "oel_publishing", "PublishableEntityVersionDependency" - ) - ContainerVersion = apps.get_model("oel_publishing", "ContainerVersion") - - for container_version in ContainerVersion.objects.all(): - # child_entity_ids is a set to de-dupe. This doesn't handle pinned - # child references yet, but you can't actually make those in a real - # library yet, so we shouldn't have that data lying around to migrate. - child_entity_ids = set( - container_version - .entity_list - .entitylistrow_set - .all() - .values_list("entity_id", flat=True) - ) - PublishableEntityVersionDependency.objects.bulk_create( - [ - PublishableEntityVersionDependency( - referring_version_id=container_version.pk, - referenced_entity_id=entity_id - ) - for entity_id in child_entity_ids - ], - ignore_conflicts=True, - ) - - -def _update_drafts(apps): - """ - Update Draft entries to point to their most recent DraftLogRecord. - - This is slow and expensive. - """ - Draft = apps.get_model("oel_publishing", "Draft") - DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") - for draft in Draft.objects.all(): - draft_log_record = ( - # Find the most recent DraftChangeLogRecord related to this Draft, - DraftChangeLogRecord.objects - .filter(entity_id=draft.pk) - .order_by('-pk') - .first() - ) - draft.draft_log_record = draft_log_record - draft.save() - - -def _update_draft_dependencies_hashes(apps): - """ - Update the dependency_hash_digest for all DraftChangeLogRecords. - - Backfill dependency state hashes. The important thing here is that things - without dependencies will have the default (blank) state hash, so we only - need to query for Draft entries for Containers. - - We are only backfilling the current DraftChangeLogRecords pointed to by the - Draft entries now. We are not backfilling all historical - DraftChangeLogRecords. Full historical reconstruction is probably possible, - but it's not really worth the cost and complexity. - """ - from ..api import update_dependencies_hash_digests_for_log - from ..models import DraftChangeLog - - # All DraftChangeLogs that have records that are pointed to by the current - # Draft and have a possibility of having dependencies. - change_logs = DraftChangeLog.objects.filter( - pk=F('records__entity__draft__draft_log_record__draft_change_log'), - records__entity__draft__version__isnull=False, - records__entity__container__isnull=False, - ).distinct() - for change_log in change_logs: - update_dependencies_hash_digests_for_log(change_log, backfill=True) - -def _update_published_dependencies_hashes(apps): - """ - Update all container Published.dependencies_hash_digest - - Backfill dependency state hashes. The important thing here is that things - without dependencies will have the default (blank) state hash, so we only - need to query for Published entries for Containers. - """ - from ..api import update_dependencies_hash_digests_for_log - from ..models import PublishLog - - # All PublishLogs that have records that are pointed to by the current - # Published and have a possibility of having dependencies. - change_logs = PublishLog.objects.filter( - pk=F('records__entity__published__publish_log_record__publish_log'), - records__entity__published__version__isnull=False, - records__entity__container__isnull=False, - ).distinct() - for change_log in change_logs: - update_dependencies_hash_digests_for_log(change_log, backfill=True) - -def remove_backfill(apps, schema_editor): - """ - Reset all dep hash values to default ('') and remove dependencies. - """ - Draft = apps.get_model("oel_publishing", "Draft") - DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") - PublishLogRecord = apps.get_model("oel_publishing", "PublishLogRecord") - PublishableEntityVersionDependency = apps.get_model( - "oel_publishing", "PublishableEntityVersionDependency" - ) - - PublishLogRecord.objects.all().update(dependencies_hash_digest='') - DraftChangeLogRecord.objects.all().update(dependencies_hash_digest='') - PublishableEntityVersionDependency.objects.all().delete() - Draft.objects.all().update(draft_log_record=None) - - -class Migration(migrations.Migration): - - dependencies = [ - ('oel_publishing', '0009_dependencies_and_hashing'), - ] - - operations = [ - migrations.RunPython(create_backfill, reverse_code=remove_backfill) - ] diff --git a/openedx_learning/apps/authoring/sections/apps.py b/openedx_learning/apps/authoring/sections/apps.py deleted file mode 100644 index f506229e2..000000000 --- a/openedx_learning/apps/authoring/sections/apps.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Sections Django application initialization. -""" - -from django.apps import AppConfig - - -class SectionsConfig(AppConfig): - """ - Configuration for the Sections Django application. - """ - - name = "openedx_learning.apps.authoring.sections" - verbose_name = "Learning Core > Authoring > Sections" - default_auto_field = "django.db.models.BigAutoField" - label = "oel_sections" - - def ready(self): - """ - Register Section and SectionVersion. - """ - from ..publishing.api import register_publishable_models # pylint: disable=import-outside-toplevel - from .models import Section, SectionVersion # pylint: disable=import-outside-toplevel - - register_publishable_models(Section, SectionVersion) diff --git a/openedx_learning/apps/authoring/subsections/apps.py b/openedx_learning/apps/authoring/subsections/apps.py deleted file mode 100644 index 950e14143..000000000 --- a/openedx_learning/apps/authoring/subsections/apps.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Subsection Django application initialization. -""" - -from django.apps import AppConfig - - -class SubsectionsConfig(AppConfig): - """ - Configuration for the subsections Django application. - """ - - name = "openedx_learning.apps.authoring.subsections" - verbose_name = "Learning Core > Authoring > Subsections" - default_auto_field = "django.db.models.BigAutoField" - label = "oel_subsections" - - def ready(self): - """ - Register Subsection and SubsectionVersion. - """ - from ..publishing.api import register_publishable_models # pylint: disable=import-outside-toplevel - from .models import Subsection, SubsectionVersion # pylint: disable=import-outside-toplevel - - register_publishable_models(Subsection, SubsectionVersion) diff --git a/openedx_learning/apps/authoring/units/apps.py b/openedx_learning/apps/authoring/units/apps.py deleted file mode 100644 index 17affcf25..000000000 --- a/openedx_learning/apps/authoring/units/apps.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Unit Django application initialization. -""" - -from django.apps import AppConfig - - -class UnitsConfig(AppConfig): - """ - Configuration for the units Django application. - """ - - name = "openedx_learning.apps.authoring.units" - verbose_name = "Learning Core > Authoring > Units" - default_auto_field = "django.db.models.BigAutoField" - label = "oel_units" - - def ready(self): - """ - Register Unit and UnitVersion. - """ - from ..publishing.api import register_publishable_models # pylint: disable=import-outside-toplevel - from .models import Unit, UnitVersion # pylint: disable=import-outside-toplevel - - register_publishable_models(Unit, UnitVersion) diff --git a/openedx_learning/apps/authoring/__init__.py b/openedx_learning/apps/openedx_content/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/__init__.py rename to openedx_learning/apps/openedx_content/__init__.py diff --git a/openedx_learning/apps/openedx_content/admin.py b/openedx_learning/apps/openedx_content/admin.py new file mode 100644 index 000000000..f603a5d54 --- /dev/null +++ b/openedx_learning/apps/openedx_content/admin.py @@ -0,0 +1,13 @@ +""" +This module aggregates all applet Django Admin modules. +""" +# pylint: disable=wildcard-import + +from .applets.backup_restore.admin import * +from .applets.collections.admin import * +from .applets.components.admin import * +from .applets.contents.admin import * +from .applets.publishing.admin import * +from .applets.sections.admin import * +from .applets.subsections.admin import * +from .applets.units.admin import * diff --git a/openedx_learning/apps/openedx_content/api.py b/openedx_learning/apps/openedx_content/api.py new file mode 100644 index 000000000..082b369e2 --- /dev/null +++ b/openedx_learning/apps/openedx_content/api.py @@ -0,0 +1,16 @@ +""" +This module aggregates all applet API modules. + +Question: Should this replace openedx_learning.api.authoring? +""" + +# pylint: disable=wildcard-import + +from .applets.backup_restore.api import * +from .applets.collections.api import * +from .applets.components.api import * +from .applets.contents.api import * +from .applets.publishing.api import * +from .applets.sections.api import * +from .applets.subsections.api import * +from .applets.units.api import * diff --git a/openedx_learning/apps/authoring/backup_restore/__init__.py b/openedx_learning/apps/openedx_content/applets/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/__init__.py rename to openedx_learning/apps/openedx_content/applets/__init__.py diff --git a/openedx_learning/apps/authoring/backup_restore/management/__init__.py b/openedx_learning/apps/openedx_content/applets/backup_restore/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/management/__init__.py rename to openedx_learning/apps/openedx_content/applets/backup_restore/__init__.py diff --git a/openedx_learning/apps/authoring/backup_restore/admin.py b/openedx_learning/apps/openedx_content/applets/backup_restore/admin.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/admin.py rename to openedx_learning/apps/openedx_content/applets/backup_restore/admin.py diff --git a/openedx_learning/apps/authoring/backup_restore/api.py b/openedx_learning/apps/openedx_content/applets/backup_restore/api.py similarity index 84% rename from openedx_learning/apps/authoring/backup_restore/api.py rename to openedx_learning/apps/openedx_content/applets/backup_restore/api.py index 802bf6ff3..b4cda8828 100644 --- a/openedx_learning/apps/authoring/backup_restore/api.py +++ b/openedx_learning/apps/openedx_content/applets/backup_restore/api.py @@ -5,8 +5,8 @@ from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user -from openedx_learning.apps.authoring.backup_restore.zipper import LearningPackageUnzipper, LearningPackageZipper -from openedx_learning.apps.authoring.publishing.api import get_learning_package_by_key +from ..publishing.api import get_learning_package_by_key +from .zipper import LearningPackageUnzipper, LearningPackageZipper def create_zip_file(lp_key: str, path: str, user: UserType | None = None, origin_server: str | None = None) -> None: diff --git a/openedx_learning/apps/authoring/backup_restore/models.py b/openedx_learning/apps/openedx_content/applets/backup_restore/models.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/models.py rename to openedx_learning/apps/openedx_content/applets/backup_restore/models.py diff --git a/openedx_learning/apps/authoring/backup_restore/serializers.py b/openedx_learning/apps/openedx_content/applets/backup_restore/serializers.py similarity index 98% rename from openedx_learning/apps/authoring/backup_restore/serializers.py rename to openedx_learning/apps/openedx_content/applets/backup_restore/serializers.py index c34e81059..d8f7a5c15 100644 --- a/openedx_learning/apps/authoring/backup_restore/serializers.py +++ b/openedx_learning/apps/openedx_content/applets/backup_restore/serializers.py @@ -5,7 +5,7 @@ from rest_framework import serializers -from openedx_learning.apps.authoring.components import api as components_api +from ..components import api as components_api class LearningPackageSerializer(serializers.Serializer): # pylint: disable=abstract-method diff --git a/openedx_learning/apps/authoring/backup_restore/toml.py b/openedx_learning/apps/openedx_content/applets/backup_restore/toml.py similarity index 95% rename from openedx_learning/apps/authoring/backup_restore/toml.py rename to openedx_learning/apps/openedx_content/applets/backup_restore/toml.py index a3ab9a03d..a75e7a0ad 100644 --- a/openedx_learning/apps/authoring/backup_restore/toml.py +++ b/openedx_learning/apps/openedx_content/applets/backup_restore/toml.py @@ -8,10 +8,10 @@ import tomlkit from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user -from openedx_learning.apps.authoring.collections.models import Collection -from openedx_learning.apps.authoring.publishing import api as publishing_api -from openedx_learning.apps.authoring.publishing.models import PublishableEntity, PublishableEntityVersion -from openedx_learning.apps.authoring.publishing.models.learning_package import LearningPackage +from ..collections.models import Collection +from ..publishing import api as publishing_api +from ..publishing.models import PublishableEntity, PublishableEntityVersion +from ..publishing.models.learning_package import LearningPackage def toml_learning_package( diff --git a/openedx_learning/apps/authoring/backup_restore/zipper.py b/openedx_learning/apps/openedx_content/applets/backup_restore/zipper.py similarity index 98% rename from openedx_learning/apps/authoring/backup_restore/zipper.py rename to openedx_learning/apps/openedx_content/applets/backup_restore/zipper.py index 27ddcacc3..5b9ce6fb3 100644 --- a/openedx_learning/apps/authoring/backup_restore/zipper.py +++ b/openedx_learning/apps/openedx_content/applets/backup_restore/zipper.py @@ -28,7 +28,15 @@ PublishableEntity, PublishableEntityVersion, ) -from openedx_learning.apps.authoring.backup_restore.serializers import ( + +from ..collections import api as collections_api +from ..components import api as components_api +from ..contents import api as contents_api +from ..publishing import api as publishing_api +from ..sections import api as sections_api +from ..subsections import api as subsections_api +from ..units import api as units_api +from .serializers import ( CollectionSerializer, ComponentSerializer, ComponentVersionSerializer, @@ -37,7 +45,7 @@ LearningPackageMetadataSerializer, LearningPackageSerializer, ) -from openedx_learning.apps.authoring.backup_restore.toml import ( +from .toml import ( parse_collection_toml, parse_learning_package_toml, parse_publishable_entity_toml, @@ -45,13 +53,6 @@ toml_learning_package, toml_publishable_entity, ) -from openedx_learning.apps.authoring.collections import api as collections_api -from openedx_learning.apps.authoring.components import api as components_api -from openedx_learning.apps.authoring.contents import api as contents_api -from openedx_learning.apps.authoring.publishing import api as publishing_api -from openedx_learning.apps.authoring.sections import api as sections_api -from openedx_learning.apps.authoring.subsections import api as subsections_api -from openedx_learning.apps.authoring.units import api as units_api TOML_PACKAGE_NAME = "package.toml" DEFAULT_USERNAME = "command" diff --git a/openedx_learning/apps/authoring/backup_restore/management/commands/__init__.py b/openedx_learning/apps/openedx_content/applets/collections/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/management/commands/__init__.py rename to openedx_learning/apps/openedx_content/applets/collections/__init__.py diff --git a/openedx_learning/apps/authoring/collections/admin.py b/openedx_learning/apps/openedx_content/applets/collections/admin.py similarity index 100% rename from openedx_learning/apps/authoring/collections/admin.py rename to openedx_learning/apps/openedx_content/applets/collections/admin.py diff --git a/openedx_learning/apps/authoring/collections/api.py b/openedx_learning/apps/openedx_content/applets/collections/api.py similarity index 99% rename from openedx_learning/apps/authoring/collections/api.py rename to openedx_learning/apps/openedx_content/applets/collections/api.py index e7e2acec1..c73c21acc 100644 --- a/openedx_learning/apps/authoring/collections/api.py +++ b/openedx_learning/apps/openedx_content/applets/collections/api.py @@ -12,7 +12,7 @@ from ..publishing.models import PublishableEntity from .models import Collection, CollectionPublishableEntity -# The public API that will be re-exported by openedx_learning.apps.authoring.api +# The public API that will be re-exported by openedx_learning.apps.openedx_content.api # is listed in the __all__ entries below. Internal helper functions that are # private to this module should start with an underscore. If a function does not # start with an underscore AND it is not in __all__, that function is considered diff --git a/openedx_learning/apps/authoring/collections/models.py b/openedx_learning/apps/openedx_content/applets/collections/models.py similarity index 98% rename from openedx_learning/apps/authoring/collections/models.py rename to openedx_learning/apps/openedx_content/applets/collections/models.py index 731f2cb73..22bc08c12 100644 --- a/openedx_learning/apps/authoring/collections/models.py +++ b/openedx_learning/apps/openedx_content/applets/collections/models.py @@ -180,7 +180,10 @@ class Meta: ), ] indexes = [ - models.Index(fields=["learning_package", "title"]), + models.Index( + fields=["learning_package", "title"], + name="openedx_content_coll_lp_title", + ), ] def __repr__(self) -> str: diff --git a/openedx_learning/apps/authoring/collections/readme.rst b/openedx_learning/apps/openedx_content/applets/collections/readme.rst similarity index 100% rename from openedx_learning/apps/authoring/collections/readme.rst rename to openedx_learning/apps/openedx_content/applets/collections/readme.rst diff --git a/openedx_learning/apps/authoring/backup_restore/migrations/__init__.py b/openedx_learning/apps/openedx_content/applets/components/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/migrations/__init__.py rename to openedx_learning/apps/openedx_content/applets/components/__init__.py diff --git a/openedx_learning/apps/authoring/components/admin.py b/openedx_learning/apps/openedx_content/applets/components/admin.py similarity index 98% rename from openedx_learning/apps/authoring/components/admin.py rename to openedx_learning/apps/openedx_content/applets/components/admin.py index 2fa501ddf..a7d51c82e 100644 --- a/openedx_learning/apps/authoring/components/admin.py +++ b/openedx_learning/apps/openedx_content/applets/components/admin.py @@ -27,7 +27,7 @@ class ComponentVersionInline(admin.TabularInline): def format_uuid(self, cv_obj): return format_html( '{}', - reverse("admin:oel_components_componentversion_change", args=(cv_obj.pk,)), + reverse("admin:openedx_content_componentversion_change", args=(cv_obj.pk,)), cv_obj.uuid, ) diff --git a/openedx_learning/apps/authoring/components/api.py b/openedx_learning/apps/openedx_content/applets/components/api.py similarity index 99% rename from openedx_learning/apps/authoring/components/api.py rename to openedx_learning/apps/openedx_content/applets/components/api.py index d8867b239..588bd40b9 100644 --- a/openedx_learning/apps/authoring/components/api.py +++ b/openedx_learning/apps/openedx_content/applets/components/api.py @@ -27,7 +27,7 @@ from ..publishing import api as publishing_api from .models import Component, ComponentType, ComponentVersion, ComponentVersionContent -# The public API that will be re-exported by openedx_learning.apps.authoring.api +# The public API that will be re-exported by openedx_learning.apps.openedx_content.api # is listed in the __all__ entries below. Internal helper functions that are # private to this module should start with an underscore. If a function does not # start with an underscore AND it is not in __all__, that function is considered diff --git a/openedx_learning/apps/authoring/components/models.py b/openedx_learning/apps/openedx_content/applets/components/models.py similarity index 98% rename from openedx_learning/apps/authoring/components/models.py rename to openedx_learning/apps/openedx_content/applets/components/models.py index b53077637..407fca2f8 100644 --- a/openedx_learning/apps/authoring/components/models.py +++ b/openedx_learning/apps/openedx_content/applets/components/models.py @@ -21,8 +21,9 @@ from django.db import models -from ....lib.fields import case_sensitive_char_field, key_field -from ....lib.managers import WithRelationsManager +from openedx_learning.lib.fields import case_sensitive_char_field, key_field +from openedx_learning.lib.managers import WithRelationsManager + from ..contents.models import Content from ..publishing.models import LearningPackage, PublishableEntityMixin, PublishableEntityVersionMixin @@ -63,6 +64,7 @@ class ComponentType(models.Model): # the UsageKey. name = case_sensitive_char_field(max_length=100, blank=True) + # TODO: this needs to go into a class Meta constraints = [ models.UniqueConstraint( fields=[ diff --git a/openedx_learning/apps/authoring/components/readme.rst b/openedx_learning/apps/openedx_content/applets/components/readme.rst similarity index 100% rename from openedx_learning/apps/authoring/components/readme.rst rename to openedx_learning/apps/openedx_content/applets/components/readme.rst diff --git a/openedx_learning/apps/authoring/collections/__init__.py b/openedx_learning/apps/openedx_content/applets/contents/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/collections/__init__.py rename to openedx_learning/apps/openedx_content/applets/contents/__init__.py diff --git a/openedx_learning/apps/authoring/contents/admin.py b/openedx_learning/apps/openedx_content/applets/contents/admin.py similarity index 100% rename from openedx_learning/apps/authoring/contents/admin.py rename to openedx_learning/apps/openedx_content/applets/contents/admin.py diff --git a/openedx_learning/apps/authoring/contents/api.py b/openedx_learning/apps/openedx_content/applets/contents/api.py similarity index 99% rename from openedx_learning/apps/authoring/contents/api.py rename to openedx_learning/apps/openedx_content/applets/contents/api.py index 319935882..82ce5e44c 100644 --- a/openedx_learning/apps/authoring/contents/api.py +++ b/openedx_learning/apps/openedx_content/applets/contents/api.py @@ -12,10 +12,11 @@ from django.core.files.base import ContentFile from django.db.transaction import atomic -from ....lib.fields import create_hash_digest +from openedx_learning.lib.fields import create_hash_digest + from .models import Content, MediaType -# The public API that will be re-exported by openedx_learning.apps.authoring.api +# The public API that will be re-exported by openedx_learning.apps.openedx_content.api # is listed in the __all__ entries below. Internal helper functions that are # private to this module should start with an underscore. If a function does not # start with an underscore AND it is not in __all__, that function is considered diff --git a/openedx_learning/apps/authoring/contents/models.py b/openedx_learning/apps/openedx_content/applets/contents/models.py similarity index 98% rename from openedx_learning/apps/authoring/contents/models.py rename to openedx_learning/apps/openedx_content/applets/contents/models.py index c087c122f..e90fd2b0a 100644 --- a/openedx_learning/apps/authoring/contents/models.py +++ b/openedx_learning/apps/openedx_content/applets/contents/models.py @@ -16,8 +16,14 @@ from django.db import models from django.utils.module_loading import import_string -from ....lib.fields import MultiCollationTextField, case_insensitive_char_field, hash_field, manual_date_time_field -from ....lib.managers import WithRelationsManager +from openedx_learning.lib.fields import ( + MultiCollationTextField, + case_insensitive_char_field, + hash_field, + manual_date_time_field, +) +from openedx_learning.lib.managers import WithRelationsManager + from ..publishing.models import LearningPackage logger = getLogger() diff --git a/openedx_learning/apps/authoring/collections/migrations/__init__.py b/openedx_learning/apps/openedx_content/applets/publishing/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/__init__.py rename to openedx_learning/apps/openedx_content/applets/publishing/__init__.py diff --git a/openedx_learning/apps/authoring/publishing/admin.py b/openedx_learning/apps/openedx_content/applets/publishing/admin.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/admin.py rename to openedx_learning/apps/openedx_content/applets/publishing/admin.py diff --git a/openedx_learning/apps/authoring/publishing/api.py b/openedx_learning/apps/openedx_content/applets/publishing/api.py similarity index 99% rename from openedx_learning/apps/authoring/publishing/api.py rename to openedx_learning/apps/openedx_content/applets/publishing/api.py index 72eccd428..375db9cc4 100644 --- a/openedx_learning/apps/authoring/publishing/api.py +++ b/openedx_learning/apps/openedx_content/applets/publishing/api.py @@ -47,7 +47,7 @@ ContainerModel = TypeVar('ContainerModel', bound=Container) ContainerVersionModel = TypeVar('ContainerVersionModel', bound=ContainerVersion) -# The public API that will be re-exported by openedx_learning.apps.authoring.api +# The public API that will be re-exported by openedx_learning.apps.openedx_content.api # is listed in the __all__ entries below. Internal helper functions that are # private to this module should start with an underscore. If a function does not # start with an underscore AND it is not in __all__, that function is considered diff --git a/openedx_learning/apps/authoring/publishing/contextmanagers.py b/openedx_learning/apps/openedx_content/applets/publishing/contextmanagers.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/contextmanagers.py rename to openedx_learning/apps/openedx_content/applets/publishing/contextmanagers.py diff --git a/openedx_learning/apps/authoring/publishing/models/__init__.py b/openedx_learning/apps/openedx_content/applets/publishing/models/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/__init__.py rename to openedx_learning/apps/openedx_content/applets/publishing/models/__init__.py diff --git a/openedx_learning/apps/authoring/publishing/models/container.py b/openedx_learning/apps/openedx_content/applets/publishing/models/container.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/container.py rename to openedx_learning/apps/openedx_content/applets/publishing/models/container.py diff --git a/openedx_learning/apps/authoring/publishing/models/draft_log.py b/openedx_learning/apps/openedx_content/applets/publishing/models/draft_log.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/draft_log.py rename to openedx_learning/apps/openedx_content/applets/publishing/models/draft_log.py diff --git a/openedx_learning/apps/authoring/publishing/models/entity_list.py b/openedx_learning/apps/openedx_content/applets/publishing/models/entity_list.py similarity index 99% rename from openedx_learning/apps/authoring/publishing/models/entity_list.py rename to openedx_learning/apps/openedx_content/applets/publishing/models/entity_list.py index 252ea2e37..37874acee 100644 --- a/openedx_learning/apps/authoring/publishing/models/entity_list.py +++ b/openedx_learning/apps/openedx_content/applets/publishing/models/entity_list.py @@ -18,6 +18,7 @@ class EntityList(models.Model): anonymous in a sense–they're pointed to by ContainerVersions and other models, rather than being looked up by their own identifiers. """ + @cached_property def rows(self): """ diff --git a/openedx_learning/apps/authoring/publishing/models/learning_package.py b/openedx_learning/apps/openedx_content/applets/publishing/models/learning_package.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/learning_package.py rename to openedx_learning/apps/openedx_content/applets/publishing/models/learning_package.py diff --git a/openedx_learning/apps/authoring/publishing/models/publish_log.py b/openedx_learning/apps/openedx_content/applets/publishing/models/publish_log.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/publish_log.py rename to openedx_learning/apps/openedx_content/applets/publishing/models/publish_log.py diff --git a/openedx_learning/apps/authoring/publishing/models/publishable_entity.py b/openedx_learning/apps/openedx_content/applets/publishing/models/publishable_entity.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/publishable_entity.py rename to openedx_learning/apps/openedx_content/applets/publishing/models/publishable_entity.py diff --git a/openedx_learning/apps/authoring/publishing/readme.rst b/openedx_learning/apps/openedx_content/applets/publishing/readme.rst similarity index 100% rename from openedx_learning/apps/authoring/publishing/readme.rst rename to openedx_learning/apps/openedx_content/applets/publishing/readme.rst diff --git a/openedx_learning/apps/authoring/components/__init__.py b/openedx_learning/apps/openedx_content/applets/sections/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/components/__init__.py rename to openedx_learning/apps/openedx_content/applets/sections/__init__.py diff --git a/openedx_learning/apps/authoring/sections/admin.py b/openedx_learning/apps/openedx_content/applets/sections/admin.py similarity index 100% rename from openedx_learning/apps/authoring/sections/admin.py rename to openedx_learning/apps/openedx_content/applets/sections/admin.py diff --git a/openedx_learning/apps/authoring/sections/api.py b/openedx_learning/apps/openedx_content/applets/sections/api.py similarity index 99% rename from openedx_learning/apps/authoring/sections/api.py rename to openedx_learning/apps/openedx_content/applets/sections/api.py index f6d58b858..05e0d6141 100644 --- a/openedx_learning/apps/authoring/sections/api.py +++ b/openedx_learning/apps/openedx_content/applets/sections/api.py @@ -7,9 +7,8 @@ from django.db.transaction import atomic -from openedx_learning.apps.authoring.subsections.models import Subsection, SubsectionVersion - from ..publishing import api as publishing_api +from ..subsections.models import Subsection, SubsectionVersion from .models import Section, SectionVersion # 🛑 UNSTABLE: All APIs related to containers are unstable until we've figured diff --git a/openedx_learning/apps/authoring/sections/models.py b/openedx_learning/apps/openedx_content/applets/sections/models.py similarity index 100% rename from openedx_learning/apps/authoring/sections/models.py rename to openedx_learning/apps/openedx_content/applets/sections/models.py diff --git a/openedx_learning/apps/authoring/components/management/__init__.py b/openedx_learning/apps/openedx_content/applets/subsections/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/components/management/__init__.py rename to openedx_learning/apps/openedx_content/applets/subsections/__init__.py diff --git a/openedx_learning/apps/authoring/subsections/admin.py b/openedx_learning/apps/openedx_content/applets/subsections/admin.py similarity index 100% rename from openedx_learning/apps/authoring/subsections/admin.py rename to openedx_learning/apps/openedx_content/applets/subsections/admin.py diff --git a/openedx_learning/apps/authoring/subsections/api.py b/openedx_learning/apps/openedx_content/applets/subsections/api.py similarity index 99% rename from openedx_learning/apps/authoring/subsections/api.py rename to openedx_learning/apps/openedx_content/applets/subsections/api.py index 02c1edf93..d39c5700a 100644 --- a/openedx_learning/apps/authoring/subsections/api.py +++ b/openedx_learning/apps/openedx_content/applets/subsections/api.py @@ -7,9 +7,8 @@ from django.db.transaction import atomic -from openedx_learning.apps.authoring.units.models import Unit, UnitVersion - from ..publishing import api as publishing_api +from ..units.models import Unit, UnitVersion from .models import Subsection, SubsectionVersion # 🛑 UNSTABLE: All APIs related to containers are unstable until we've figured diff --git a/openedx_learning/apps/authoring/subsections/models.py b/openedx_learning/apps/openedx_content/applets/subsections/models.py similarity index 100% rename from openedx_learning/apps/authoring/subsections/models.py rename to openedx_learning/apps/openedx_content/applets/subsections/models.py diff --git a/openedx_learning/apps/authoring/components/management/commands/__init__.py b/openedx_learning/apps/openedx_content/applets/units/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/components/management/commands/__init__.py rename to openedx_learning/apps/openedx_content/applets/units/__init__.py diff --git a/openedx_learning/apps/authoring/units/admin.py b/openedx_learning/apps/openedx_content/applets/units/admin.py similarity index 100% rename from openedx_learning/apps/authoring/units/admin.py rename to openedx_learning/apps/openedx_content/applets/units/admin.py diff --git a/openedx_learning/apps/authoring/units/api.py b/openedx_learning/apps/openedx_content/applets/units/api.py similarity index 99% rename from openedx_learning/apps/authoring/units/api.py rename to openedx_learning/apps/openedx_content/applets/units/api.py index ca9a2468d..779b5b3d0 100644 --- a/openedx_learning/apps/authoring/units/api.py +++ b/openedx_learning/apps/openedx_content/applets/units/api.py @@ -7,8 +7,7 @@ from django.db.transaction import atomic -from openedx_learning.apps.authoring.components.models import Component, ComponentVersion - +from ..components.models import Component, ComponentVersion from ..publishing import api as publishing_api from .models import Unit, UnitVersion diff --git a/openedx_learning/apps/authoring/units/models.py b/openedx_learning/apps/openedx_content/applets/units/models.py similarity index 100% rename from openedx_learning/apps/authoring/units/models.py rename to openedx_learning/apps/openedx_content/applets/units/models.py diff --git a/openedx_learning/apps/openedx_content/apps.py b/openedx_learning/apps/openedx_content/apps.py new file mode 100644 index 000000000..4806cd278 --- /dev/null +++ b/openedx_learning/apps/openedx_content/apps.py @@ -0,0 +1,112 @@ +""" +App Config for our umbrella openedx_content app. +""" +# pylint: disable=import-outside-toplevel +# +# Local imports in AppConfig.ready() are common and expected in Django, since +# Django needs to run initialization before before we can query for things like +# models, settings, and app config. + +from importlib import import_module + +from django.apps import AppConfig + + +class ContentConfig(AppConfig): + """ + Initialization for all applets must happen in here. + """ + + name = "openedx_learning.apps.openedx_content" + verbose_name = "Learning Core > Content" + default_auto_field = "django.db.models.BigAutoField" + label = "openedx_content" + + def patch_migration_dependencies(self): + """ + Hacky initialization to preserve migration order. + + We consolidated a number of smaller apps into a single openedx_content + app. In the process of doing so, we transitioned the models those apps + by using SeparateDatabaseAndState to remove models in the small apps and + add them to the new openedx_content app without actually doing any + database operations. + + One unfortunate consequence of this is that we need to make sure that + the openedx-platform migrations that switch their references from the + old authoring models to the new ones in openedx_content run *after* the + openedx_content 0001_initial migration, but *before* the backcompat + migrations that drop the model state for the old authoring apps. To do + this, we make it so that our 0002_rename_tables_to_openedx_content has + these openedx-platform migrations as dependencies, and then make it so + that the "000x_remove_all_field_state_for_move_to_applet" migrations all + list 0002_rename_tables_to_openedx_content as a dependency. + + Thus we force this ordering: + + 1. New model state created in openedx_content. + 2. openedx-platform apps switch model foreign key references from old + authoring apps models to openedx_content models. + 3. We rename the openedx_content tables to be properly prefixed with our + app label. + 4. Only at this point can the backcompat migrations dropping model field + state run on the old authoring apps. + + The only problem is that our migrations only sometimes run in the + openedx-platform project. When we're running in CI or other places, + having references to openedx-platform migrations would break. So that's + why we do this really sketchy looking migration dependency injection on + app initialization. + + For more details, see docs/decisions/0020-authoring-as-one-app.rst. + """ + from django.apps import apps + + # We can't directly import Python modules that start with a number using + # an import statement, so we have to use import_module to bring in the + # migration module. + migration_module = import_module( + ".migrations.0002_rename_tables_to_openedx_content", + package=__package__, + ) + deps_to_inject = [ + ('content_libraries', '0012_alter_contentlibrary_learning_package'), + ('contentstore', '0015_alter_componentlink_upstream_block_and_more'), + ('modulestore_migrator', '0007_alter_modulestoreblockmigration_change_log_record_and_more'), + ] + our_deps = migration_module.Migration.dependencies + for edx_platform_app, edx_platform_migration in deps_to_inject: + if edx_platform_app in apps.app_configs.keys(): + our_deps.append((edx_platform_app, edx_platform_migration)) + + def register_publishable_models(self): + """ + Register all Publishable -> Version model pairings in our app. + """ + from .api import register_publishable_models + from .models import ( + Component, + ComponentVersion, + Container, + ContainerVersion, + Section, + SectionVersion, + Subsection, + SubsectionVersion, + Unit, + UnitVersion, + ) + register_publishable_models(Component, ComponentVersion) + register_publishable_models(Container, ContainerVersion) + register_publishable_models(Section, SectionVersion) + register_publishable_models(Subsection, SubsectionVersion) + register_publishable_models(Unit, UnitVersion) + + def ready(self): + """ + Currently used to register publishable models and patch migrations. + + May later be used to register signal handlers as well. + """ + self.patch_migration_dependencies() + self.register_publishable_models() diff --git a/openedx_learning/apps/authoring/components/migrations/__init__.py b/openedx_learning/apps/openedx_content/backcompat/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/components/migrations/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/__init__.py diff --git a/openedx_learning/apps/authoring/contents/__init__.py b/openedx_learning/apps/openedx_content/backcompat/backup_restore/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/contents/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/backup_restore/__init__.py diff --git a/openedx_learning/apps/authoring/backup_restore/apps.py b/openedx_learning/apps/openedx_content/backcompat/backup_restore/apps.py similarity index 78% rename from openedx_learning/apps/authoring/backup_restore/apps.py rename to openedx_learning/apps/openedx_content/backcompat/backup_restore/apps.py index 7aa3f022b..b7517cf9c 100644 --- a/openedx_learning/apps/authoring/backup_restore/apps.py +++ b/openedx_learning/apps/openedx_content/backcompat/backup_restore/apps.py @@ -6,7 +6,7 @@ class BackupRestoreConfig(AppConfig): - name = 'openedx_learning.apps.authoring.backup_restore' + name = 'openedx_learning.apps.openedx_content.backcompat.backup_restore' verbose_name = "Learning Core > Authoring > Backup Restore" default_auto_field = 'django.db.models.BigAutoField' label = "oel_backup_restore" diff --git a/openedx_learning/apps/authoring/contents/migrations/__init__.py b/openedx_learning/apps/openedx_content/backcompat/backup_restore/migrations/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/contents/migrations/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/backup_restore/migrations/__init__.py diff --git a/openedx_learning/apps/authoring/publishing/__init__.py b/openedx_learning/apps/openedx_content/backcompat/collections/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/collections/__init__.py diff --git a/openedx_learning/apps/authoring/collections/apps.py b/openedx_learning/apps/openedx_content/backcompat/collections/apps.py similarity index 82% rename from openedx_learning/apps/authoring/collections/apps.py rename to openedx_learning/apps/openedx_content/backcompat/collections/apps.py index b1ca50c49..3f326bc84 100644 --- a/openedx_learning/apps/authoring/collections/apps.py +++ b/openedx_learning/apps/openedx_content/backcompat/collections/apps.py @@ -9,7 +9,7 @@ class CollectionsConfig(AppConfig): Configuration for the Collections Django application. """ - name = "openedx_learning.apps.authoring.collections" + name = "openedx_learning.apps.openedx_content.backcompat.collections" verbose_name = "Learning Core > Authoring > Collections" default_auto_field = "django.db.models.BigAutoField" label = "oel_collections" diff --git a/openedx_learning/apps/authoring/collections/migrations/0001_initial.py b/openedx_learning/apps/openedx_content/backcompat/collections/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/0001_initial.py rename to openedx_learning/apps/openedx_content/backcompat/collections/migrations/0001_initial.py diff --git a/openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py b/openedx_learning/apps/openedx_content/backcompat/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py rename to openedx_learning/apps/openedx_content/backcompat/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py diff --git a/openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py b/openedx_learning/apps/openedx_content/backcompat/collections/migrations/0003_collection_entities.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py rename to openedx_learning/apps/openedx_content/backcompat/collections/migrations/0003_collection_entities.py diff --git a/openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py b/openedx_learning/apps/openedx_content/backcompat/collections/migrations/0004_collection_key.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py rename to openedx_learning/apps/openedx_content/backcompat/collections/migrations/0004_collection_key.py diff --git a/openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py b/openedx_learning/apps/openedx_content/backcompat/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py rename to openedx_learning/apps/openedx_content/backcompat/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py diff --git a/openedx_learning/apps/openedx_content/backcompat/collections/migrations/0006_remove_all_field_state_for_move_to_applet.py b/openedx_learning/apps/openedx_content/backcompat/collections/migrations/0006_remove_all_field_state_for_move_to_applet.py new file mode 100644 index 000000000..5696d27c0 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/collections/migrations/0006_remove_all_field_state_for_move_to_applet.py @@ -0,0 +1,38 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('openedx_content', '0002_rename_tables_to_openedx_content'), + ('oel_collections', '0005_alter_collection_options_alter_collection_enabled'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='collectionpublishableentity', + name='collection', + ), + migrations.RemoveField( + model_name='collectionpublishableentity', + name='created_by', + ), + migrations.RemoveField( + model_name='collectionpublishableentity', + name='entity', + ), + migrations.DeleteModel( + name='Collection', + ), + migrations.DeleteModel( + name='CollectionPublishableEntity', + ), + ] + ) + ] \ No newline at end of file diff --git a/openedx_learning/apps/authoring/publishing/migrations/__init__.py b/openedx_learning/apps/openedx_content/backcompat/collections/migrations/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/collections/migrations/__init__.py diff --git a/openedx_learning/apps/authoring/sections/__init__.py b/openedx_learning/apps/openedx_content/backcompat/components/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/sections/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/components/__init__.py diff --git a/openedx_learning/apps/openedx_content/backcompat/components/apps.py b/openedx_learning/apps/openedx_content/backcompat/components/apps.py new file mode 100644 index 000000000..4a6ab9edf --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/components/apps.py @@ -0,0 +1,15 @@ +""" +Django metadata for the Components Django application. +""" +from django.apps import AppConfig + + +class ComponentsConfig(AppConfig): + """ + Configuration for the Components Django application. + """ + + name = "openedx_learning.apps.openedx_content.backcompat.components" + verbose_name = "Learning Core > Authoring > Components" + default_auto_field = "django.db.models.BigAutoField" + label = "oel_components" diff --git a/openedx_learning/apps/authoring/components/migrations/0001_initial.py b/openedx_learning/apps/openedx_content/backcompat/components/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/components/migrations/0001_initial.py rename to openedx_learning/apps/openedx_content/backcompat/components/migrations/0001_initial.py diff --git a/openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py b/openedx_learning/apps/openedx_content/backcompat/components/migrations/0002_alter_componentversioncontent_key.py similarity index 100% rename from openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py rename to openedx_learning/apps/openedx_content/backcompat/components/migrations/0002_alter_componentversioncontent_key.py diff --git a/openedx_learning/apps/authoring/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py b/openedx_learning/apps/openedx_content/backcompat/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py similarity index 100% rename from openedx_learning/apps/authoring/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py rename to openedx_learning/apps/openedx_content/backcompat/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py diff --git a/openedx_learning/apps/authoring/components/migrations/0004_remove_componentversioncontent_uuid.py b/openedx_learning/apps/openedx_content/backcompat/components/migrations/0004_remove_componentversioncontent_uuid.py similarity index 100% rename from openedx_learning/apps/authoring/components/migrations/0004_remove_componentversioncontent_uuid.py rename to openedx_learning/apps/openedx_content/backcompat/components/migrations/0004_remove_componentversioncontent_uuid.py diff --git a/openedx_learning/apps/openedx_content/backcompat/components/migrations/0005_remove_all_field_state_for_move_to_applet.py b/openedx_learning/apps/openedx_content/backcompat/components/migrations/0005_remove_all_field_state_for_move_to_applet.py new file mode 100644 index 000000000..a0f7447e0 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/components/migrations/0005_remove_all_field_state_for_move_to_applet.py @@ -0,0 +1,64 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('openedx_content', '0002_rename_tables_to_openedx_content'), + ('oel_components', '0004_remove_componentversioncontent_uuid'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='component', + name='component_type', + ), + migrations.RemoveField( + model_name='component', + name='learning_package', + ), + migrations.RemoveField( + model_name='component', + name='publishable_entity', + ), + migrations.RemoveField( + model_name='componentversion', + name='component', + ), + migrations.RemoveField( + model_name='componentversion', + name='contents', + ), + migrations.RemoveField( + model_name='componentversion', + name='publishable_entity_version', + ), + migrations.RemoveField( + model_name='componentversioncontent', + name='component_version', + ), + migrations.RemoveField( + model_name='componentversioncontent', + name='content', + ), + migrations.DeleteModel( + name='ComponentType', + ), + migrations.DeleteModel( + name='Component', + ), + migrations.DeleteModel( + name='ComponentVersion', + ), + migrations.DeleteModel( + name='ComponentVersionContent', + ), + ] + ) + ] diff --git a/openedx_learning/apps/authoring/sections/migrations/__init__.py b/openedx_learning/apps/openedx_content/backcompat/components/migrations/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/sections/migrations/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/components/migrations/__init__.py diff --git a/openedx_learning/apps/authoring/subsections/__init__.py b/openedx_learning/apps/openedx_content/backcompat/contents/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/subsections/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/contents/__init__.py diff --git a/openedx_learning/apps/authoring/contents/apps.py b/openedx_learning/apps/openedx_content/backcompat/contents/apps.py similarity index 82% rename from openedx_learning/apps/authoring/contents/apps.py rename to openedx_learning/apps/openedx_content/backcompat/contents/apps.py index 3c23bd4f5..38a1e4730 100644 --- a/openedx_learning/apps/authoring/contents/apps.py +++ b/openedx_learning/apps/openedx_content/backcompat/contents/apps.py @@ -9,7 +9,7 @@ class ContentsConfig(AppConfig): Configuration for the Contents Django application. """ - name = "openedx_learning.apps.authoring.contents" + name = "openedx_learning.apps.openedx_content.backcompat.contents" verbose_name = "Learning Core > Authoring > Contents" default_auto_field = "django.db.models.BigAutoField" label = "oel_contents" diff --git a/openedx_learning/apps/authoring/contents/migrations/0001_initial.py b/openedx_learning/apps/openedx_content/backcompat/contents/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/contents/migrations/0001_initial.py rename to openedx_learning/apps/openedx_content/backcompat/contents/migrations/0001_initial.py diff --git a/openedx_learning/apps/openedx_content/backcompat/contents/migrations/0002_remove_all_field_state_for_move_to_applet.py b/openedx_learning/apps/openedx_content/backcompat/contents/migrations/0002_remove_all_field_state_for_move_to_applet.py new file mode 100644 index 000000000..864a17160 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/contents/migrations/0002_remove_all_field_state_for_move_to_applet.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('openedx_content', '0002_rename_tables_to_openedx_content'), + ('oel_components', '0005_remove_all_field_state_for_move_to_applet'), + ('oel_contents', '0001_initial'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.DeleteModel( + name='Content', + ), + migrations.DeleteModel( + name='MediaType', + ), + ] + ) + ] diff --git a/openedx_learning/apps/authoring/subsections/migrations/__init__.py b/openedx_learning/apps/openedx_content/backcompat/contents/migrations/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/subsections/migrations/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/contents/migrations/__init__.py diff --git a/openedx_learning/apps/authoring/units/__init__.py b/openedx_learning/apps/openedx_content/backcompat/publishing/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/units/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/publishing/__init__.py diff --git a/openedx_learning/apps/openedx_content/backcompat/publishing/apps.py b/openedx_learning/apps/openedx_content/backcompat/publishing/apps.py new file mode 100644 index 000000000..22ff5f6c5 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/publishing/apps.py @@ -0,0 +1,15 @@ +""" +Publishing Django application initialization. +""" +from django.apps import AppConfig + + +class PublishingConfig(AppConfig): + """ + Configuration for the publishing Django application. + """ + + name = "openedx_learning.apps.openedx_content.backcompat.publishing" + verbose_name = "Learning Core > Authoring > Publishing" + default_auto_field = "django.db.models.BigAutoField" + label = "oel_publishing" diff --git a/openedx_learning/apps/authoring/publishing/migrations/0001_initial.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0001_initial.py rename to openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0001_initial.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0002_alter_learningpackage_key_and_more.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py rename to openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0002_alter_learningpackage_key_and_more.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0003_containers.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0003_containers.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0003_containers.py rename to openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0003_containers.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0004_publishableentity_can_stand_alone.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py rename to openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0004_publishableentity_can_stand_alone.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0005_alter_entitylistrow_options.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0005_alter_entitylistrow_options.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0005_alter_entitylistrow_options.py rename to openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0005_alter_entitylistrow_options.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0006_draftchangelog.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0006_draftchangelog.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0006_draftchangelog.py rename to openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0006_draftchangelog.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0007_bootstrap_draftchangelog.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0007_bootstrap_draftchangelog.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0007_bootstrap_draftchangelog.py rename to openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0007_bootstrap_draftchangelog.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py rename to openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0009_dependencies_and_hashing.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0009_dependencies_and_hashing.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0009_dependencies_and_hashing.py rename to openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0009_dependencies_and_hashing.py diff --git a/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0010_backfill_dependencies.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0010_backfill_dependencies.py new file mode 100644 index 000000000..a87032146 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0010_backfill_dependencies.py @@ -0,0 +1,425 @@ +""" +Backfill PublishableEntityVersionDependency entries based on ContainerVersions. + +We're introducing a lower-level publishing concept of a dependency that will be +used by Containers, but this means we have to backfill that dependency info for +existing Containers in the system. + +For more information, please see the PublishableEntityVersionDependency model. +""" +from django.db import migrations +from django.db.models import F, Prefetch + +from openedx_learning.lib.fields import create_hash_digest + + +def create_backfill(apps, schema_editor): + """ + Create dependency entries and update dep hashes for Draft and Published. + """ + _create_dependencies(apps) + _update_drafts(apps) + _update_draft_dependencies_hashes(apps) + _update_published_dependencies_hashes(apps) + + +def _create_dependencies(apps): + """ + Populate the PublishableEntityVersion.dependencies relation. + + The only ones we should have in the system at this point are the ones from + containers, so we query ContainerVersion for that. + """ + PublishableEntityVersionDependency = apps.get_model( + "oel_publishing", "PublishableEntityVersionDependency" + ) + ContainerVersion = apps.get_model("oel_publishing", "ContainerVersion") + + for container_version in ContainerVersion.objects.all(): + # child_entity_ids is a set to de-dupe. This doesn't handle pinned + # child references yet, but you can't actually make those in a real + # library yet, so we shouldn't have that data lying around to migrate. + child_entity_ids = set( + container_version + .entity_list + .entitylistrow_set + .all() + .values_list("entity_id", flat=True) + ) + PublishableEntityVersionDependency.objects.bulk_create( + [ + PublishableEntityVersionDependency( + referring_version_id=container_version.pk, + referenced_entity_id=entity_id + ) + for entity_id in child_entity_ids + ], + ignore_conflicts=True, + ) + + +def _update_drafts(apps): + """ + Update Draft entries to point to their most recent DraftLogRecord. + + This is slow and expensive. + """ + Draft = apps.get_model("oel_publishing", "Draft") + DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") + for draft in Draft.objects.all(): + draft_log_record = ( + # Find the most recent DraftChangeLogRecord related to this Draft, + DraftChangeLogRecord.objects + .filter(entity_id=draft.pk) + .order_by('-pk') + .first() + ) + draft.draft_log_record = draft_log_record + draft.save() + + +def _update_draft_dependencies_hashes(apps): + """ + Update the dependency_hash_digest for all DraftChangeLogRecords. + + Backfill dependency state hashes. The important thing here is that things + without dependencies will have the default (blank) state hash, so we only + need to query for Draft entries for Containers. + + We are only backfilling the current DraftChangeLogRecords pointed to by the + Draft entries now. We are not backfilling all historical + DraftChangeLogRecords. Full historical reconstruction is probably possible, + but it's not really worth the cost and complexity. + """ + DraftChangeLog = apps.get_model("oel_publishing", "DraftChangeLog") + + # All DraftChangeLogs that have records that are pointed to by the current + # Draft and have a possibility of having dependencies. + change_logs = DraftChangeLog.objects.filter( + pk=F('records__entity__draft__draft_log_record__draft_change_log'), + records__entity__draft__version__isnull=False, + records__entity__container__isnull=False, + ).distinct() + for change_log in change_logs: + update_dependencies_hash_digests_for_log(change_log, apps) + +def _update_published_dependencies_hashes(apps): + """ + Update all container Published.dependencies_hash_digest + + Backfill dependency state hashes. The important thing here is that things + without dependencies will have the default (blank) state hash, so we only + need to query for Published entries for Containers. + """ + PublishLog = apps.get_model("oel_publishing", "PublishLog") + + # All PublishLogs that have records that are pointed to by the current + # Published and have a possibility of having dependencies. + change_logs = PublishLog.objects.filter( + pk=F('records__entity__published__publish_log_record__publish_log'), + records__entity__published__version__isnull=False, + records__entity__container__isnull=False, + ).distinct() + for change_log in change_logs: + update_dependencies_hash_digests_for_log(change_log, apps) + +def remove_backfill(apps, schema_editor): + """ + Reset all dep hash values to default ('') and remove dependencies. + """ + Draft = apps.get_model("oel_publishing", "Draft") + DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") + PublishLogRecord = apps.get_model("oel_publishing", "PublishLogRecord") + PublishableEntityVersionDependency = apps.get_model( + "oel_publishing", "PublishableEntityVersionDependency" + ) + + PublishLogRecord.objects.all().update(dependencies_hash_digest='') + DraftChangeLogRecord.objects.all().update(dependencies_hash_digest='') + PublishableEntityVersionDependency.objects.all().delete() + Draft.objects.all().update(draft_log_record=None) + + +def update_dependencies_hash_digests_for_log( + change_log, # this is a historical DraftChangeLog or PublishLog + apps, + backfill=True, +) -> None: + """ + Update dependencies_hash_digest for Drafts or Published in a change log. + + This is copied from the publishing API to make sure we don't accidentally + break it with future changes as the data model evolves. It has also been + modified to use historical models, rather than having references to the new + ones that have been moved to the centralized authoring app. It has also been + modified to assume that it's being used as a backfill (the original makes it + optional). + + All the data for Draft/Published, DraftChangeLog/PublishLog, and + DraftChangeLogRecord/PublishLogRecord have been set at this point *except* + the dependencies_hash_digest of DraftChangeLogRecord/PublishLogRecord. Those + log records are newly created at this point, so dependencies_hash_digest are + set to their default values. + + Args: + change_log: A DraftChangeLog or PublishLog that already has all + side-effects added to it. The Draft and Published models should + already be updated to point to the post-change versions. + backfill: If this is true, we will not trust the hash values stored on + log records outside of our log, i.e. things that we would normally + expect to be pre-calculated. This will be important for the initial + data migration. + """ + DraftChangeLog = apps.get_model("oel_publishing", "DraftChangeLog") + DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") + PublishLog = apps.get_model("oel_publishing", "PublishLog") + PublishLogRecord = apps.get_model("oel_publishing", "PublishLogRecord") + PublishableEntity = apps.get_model("oel_publishing", "PublishableEntity") + + if isinstance(change_log, DraftChangeLog): + branch = "draft" + log_record_relation = "draft_log_record" + record_cls = DraftChangeLogRecord + elif isinstance(change_log, PublishLog): + branch = "published" + log_record_relation = "publish_log_record" + record_cls = PublishLogRecord # type: ignore[assignment] + else: + raise TypeError( + f"expected DraftChangeLog or PublishLog, not {type(change_log)}" + ) + + dependencies_prefetch = Prefetch( + "new_version__dependencies", + queryset=PublishableEntity.objects + .select_related( + f"{branch}__version", + f"{branch}__{log_record_relation}", + ) + .order_by(f"{branch}__version__uuid") + ) + changed_records = ( + change_log.records + .select_related("new_version", f"entity__{branch}") + .prefetch_related(dependencies_prefetch) + ) + + record_ids_to_hash_digests: dict[int, str | None] = {} + record_ids_to_live_deps: dict[int, list] = {} + records_that_need_hashes = [] + + for record in changed_records: + # This is a soft-deletion, so the dependency hash is default/blank. We + # set this value in our record_ids_to_hash_digests cache, but we don't + # need to write it to the database because it's just the default value. + if record.new_version is None: + record_ids_to_hash_digests[record.id] = '' + continue + + # Now check to see if the new version has "live" dependencies, i.e. + # dependencies that have not been deleted. + deps = list( + entity for entity in record.new_version.dependencies.all() + if hasattr(entity, branch) and getattr(entity, branch).version + ) + + # If there are no live dependencies, this log record also gets the + # default/blank value. + if not deps: + record_ids_to_hash_digests[record.id] = '' + continue + + # If we've gotten this far, it means that this record has dependencies + # and does need to get a hash computed for it. + records_that_need_hashes.append(record) + record_ids_to_live_deps[record.id] = deps + + if backfill: + untrusted_record_id_set = None + else: + untrusted_record_id_set = set(rec.id for rec in records_that_need_hashes) + + for record in records_that_need_hashes: + record.dependencies_hash_digest = hash_for_log_record( + apps, + record, + record_ids_to_hash_digests, + record_ids_to_live_deps, + untrusted_record_id_set, + ) + + _bulk_update_hashes(record_cls, records_that_need_hashes) + + +def _bulk_update_hashes(model_cls, records): + """ + bulk_update using the model class (PublishLogRecord or DraftChangeLogRecord) + + This is copied from the publishing API to make sure we don't accidentally + break it with future changes as the data model evolves. + """ + model_cls.objects.bulk_update(records, ['dependencies_hash_digest']) + + +def hash_for_log_record( + apps, + record, # historical DraftChangeLogRecord | PublishLogRecord, + record_ids_to_hash_digests: dict, + record_ids_to_live_deps: dict, + untrusted_record_id_set: set | None, +) -> str: + """ + The hash digest for a given change log record. + + This is copied from the publishing API to make sure we don't accidentally + break it with future changes as the data model evolves. It has also been + modified to use historical models, rather than having references to the new + ones that have been moved to the centralized authoring app. + + Note that this code is a little convoluted because we're working hard to + minimize the number of database requests. All the data we really need could + be derived from querying various relations off the record that's passed in + as the first parameter, but at a far higher cost. + + The hash calculated here will be used for the dependencies_hash_digest + attribute of DraftChangeLogRecord and PublishLogRecord. The hash is intended + to calculate the currently "live" (current draft or published) state of all + dependencies (and transitive dependencies) of the PublishableEntityVersion + pointed to by DraftChangeLogRecord.new_version/PublishLogRecord.new_version. + + The common case we have at the moment is when a container type like a Unit + has unpinned child Components as dependencies. In the data model, those + dependency relationships are represented by the "dependencies" M:M relation + on PublishableEntityVersion. Since the Unit version's references to its + child Components are unpinned, the draft Unit is always pointing to the + latest draft versions of those Components and the published Unit is always + pointing to the latest published versions of those Components. + + This means that the total draft or published state of any PublishableEntity + depends on the combination of: + + 1. The definition of the current draft/published version of that entity. + Example: Version 1 of a Unit would define that it had children [C1, C2]. + Version 2 of the same Unit might have children [C1, C2, C3]. + 2. The current draft/published versions of all dependencies. Example: What + are the current draft and published versions of C1, C2, and C3. + + This is why it makes sense to capture in a log record, since + PublishLogRecords or DraftChangeLogRecords are created whenever one of the + above two things changes. + + Here are the possible scenarios, including edge cases: + + EntityVersions with no dependencies + If record.new_version has no dependencies, dependencies_hash_digest is + set to the default value of ''. This will be the most common case. + + EntityVersions with dependencies + If an EntityVersion has dependencies, then its draft/published state + hash is based on the concatenation of, for each non-deleted dependency: + (i) the dependency's draft/published EntityVersion primary key, and + (ii) the dependency's own draft/published state hash, recursively re- + calculated if necessary. + + Soft-deletions + If the record.new_version is None, that means we've just soft-deleted + something (or published the soft-delete of something). We adopt the + convention that if something is soft-deleted, its dependencies_hash_digest + is reset to the default value of ''. This is not strictly necessary for + the recursive hash calculation, but deleted entities will not have their + hash updated even as their non-deleted dependencies are updated underneath + them, so we set to '' to avoid falsely implying that the deleted entity's + dep hash is up to date. + + EntityVersions with soft-deleted dependencies + A soft-deleted dependency isn't counted (it's as if the dependency were + removed). If all of an EntityVersion's dependencies are soft-deleted, + then it will go back to having to having the default blank string for its + dependencies_hash_digest. + """ + DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") + PublishLogRecord = apps.get_model("oel_publishing", "PublishLogRecord") + + # Case #1: We've already computed this, or it was bootstrapped for us in the + # cache because the record is a deletion or doesn't have dependencies. + if record.id in record_ids_to_hash_digests: + return record_ids_to_hash_digests[record.id] + + # Case #2: The log_record is a dependency of something that was affected by + # a change, but the dependency itself did not change in any way (neither + # directly, nor as a side-effect). + # + # Example: A Unit has two Components. One of the Components changed, forcing + # us to recalculate the dependencies_hash_digest for that Unit. Doing that + # recalculation requires us to fetch the dependencies_hash_digest of the + # unchanged child Component as well. + # + # If we aren't given an explicit untrusted_record_id_set, it means we can't + # trust anything. This would happen when we're bootstrapping things with an + # initial data migration. + if (untrusted_record_id_set is not None) and (record.id not in untrusted_record_id_set): + return record.dependencies_hash_digest + + # Normal recursive case starts here: + if isinstance(record, DraftChangeLogRecord): + branch = "draft" + elif isinstance(record, PublishLogRecord): + branch = "published" + else: + raise TypeError( + f"expected DraftChangeLogRecord or PublishLogRecord, not {type(record)}" + ) + + # This is extra work that only happens in case of a backfill, where we might + # need to compute dependency hashes for things outside of our log (because + # we don't trust them). + if record.id not in record_ids_to_live_deps: + if record.new_version is None: + record_ids_to_hash_digests[record.id] = '' + return '' + deps = list( + entity for entity in record.new_version.dependencies.all() + if hasattr(entity, branch) and getattr(entity, branch).version + ) + # If there are no live dependencies, this log record also gets the + # default/blank value. + if not deps: + record_ids_to_hash_digests[record.id] = '' + return '' + + record_ids_to_live_deps[record.id] = deps + # End special handling for backfill. + + # Begin normal + dependencies = sorted( + record_ids_to_live_deps[record.id], + key=lambda entity: getattr(entity, branch).log_record.new_version_id, + ) + dep_state_entries = [] + for dep_entity in dependencies: + new_version_id = getattr(dep_entity, branch).log_record.new_version_id + hash_digest = hash_for_log_record( + apps, + getattr(dep_entity, branch).log_record, + record_ids_to_hash_digests, + record_ids_to_live_deps, + untrusted_record_id_set, + ) + dep_state_entries.append(f"{new_version_id}:{hash_digest}") + summary_text = "\n".join(dep_state_entries) + + digest = create_hash_digest(summary_text.encode(), num_bytes=4) + record_ids_to_hash_digests[record.id] = digest + + return digest + + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_publishing', '0009_dependencies_and_hashing'), + ] + + operations = [ + migrations.RunPython(create_backfill, reverse_code=remove_backfill) + ] diff --git a/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0011_remove_all_field_state_for_move_to_applet.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0011_remove_all_field_state_for_move_to_applet.py new file mode 100644 index 000000000..5ae597071 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0011_remove_all_field_state_for_move_to_applet.py @@ -0,0 +1,214 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('openedx_content', '0002_rename_tables_to_openedx_content'), + ('oel_collections', '0006_remove_all_field_state_for_move_to_applet'), + ('oel_components', '0005_remove_all_field_state_for_move_to_applet'), + ('oel_contents', '0002_remove_all_field_state_for_move_to_applet'), + ('oel_publishing', '0010_backfill_dependencies'), + ('oel_sections', '0002_remove_all_field_state_for_move_to_applet'), + ('oel_subsections', '0002_remove_all_field_state_for_move_to_applet'), + ('oel_units', '0002_remove_all_field_state_for_move_to_applet'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='containerversion', + name='container', + ), + migrations.RemoveField( + model_name='containerversion', + name='entity_list', + ), + migrations.RemoveField( + model_name='containerversion', + name='publishable_entity_version', + ), + migrations.RemoveField( + model_name='draft', + name='draft_log_record', + ), + migrations.RemoveField( + model_name='draft', + name='entity', + ), + migrations.RemoveField( + model_name='draft', + name='version', + ), + migrations.RemoveField( + model_name='draftchangelog', + name='changed_by', + ), + migrations.RemoveField( + model_name='draftchangelog', + name='learning_package', + ), + migrations.RemoveField( + model_name='draftchangelogrecord', + name='draft_change_log', + ), + migrations.RemoveField( + model_name='draftchangelogrecord', + name='entity', + ), + migrations.RemoveField( + model_name='draftchangelogrecord', + name='new_version', + ), + migrations.RemoveField( + model_name='draftchangelogrecord', + name='old_version', + ), + migrations.RemoveField( + model_name='draftsideeffect', + name='effect', + ), + migrations.RemoveField( + model_name='draftsideeffect', + name='cause', + ), + migrations.RemoveField( + model_name='entitylistrow', + name='entity_list', + ), + migrations.RemoveField( + model_name='entitylistrow', + name='entity', + ), + migrations.RemoveField( + model_name='entitylistrow', + name='entity_version', + ), + migrations.RemoveField( + model_name='publishlog', + name='learning_package', + ), + migrations.RemoveField( + model_name='publishableentity', + name='learning_package', + ), + migrations.RemoveField( + model_name='publishableentity', + name='created_by', + ), + migrations.RemoveField( + model_name='published', + name='entity', + ), + migrations.RemoveField( + model_name='publishableentityversion', + name='dependencies', + ), + migrations.RemoveField( + model_name='publishableentityversion', + name='entity', + ), + migrations.RemoveField( + model_name='publishlogrecord', + name='entity', + ), + migrations.RemoveField( + model_name='publishableentityversiondependency', + name='referenced_entity', + ), + migrations.RemoveField( + model_name='publishableentityversion', + name='created_by', + ), + migrations.RemoveField( + model_name='published', + name='version', + ), + migrations.RemoveField( + model_name='publishlogrecord', + name='new_version', + ), + migrations.RemoveField( + model_name='publishlogrecord', + name='old_version', + ), + migrations.RemoveField( + model_name='publishableentityversiondependency', + name='referring_version', + ), + migrations.RemoveField( + model_name='published', + name='publish_log_record', + ), + migrations.RemoveField( + model_name='publishlog', + name='published_by', + ), + migrations.RemoveField( + model_name='publishlogrecord', + name='publish_log', + ), + migrations.RemoveField( + model_name='publishsideeffect', + name='cause', + ), + migrations.RemoveField( + model_name='publishsideeffect', + name='effect', + ), + migrations.DeleteModel( + name='Container', + ), + migrations.DeleteModel( + name='ContainerVersion', + ), + migrations.DeleteModel( + name='Draft', + ), + migrations.DeleteModel( + name='DraftChangeLog', + ), + migrations.DeleteModel( + name='DraftChangeLogRecord', + ), + migrations.DeleteModel( + name='DraftSideEffect', + ), + migrations.DeleteModel( + name='EntityList', + ), + migrations.DeleteModel( + name='EntityListRow', + ), + migrations.DeleteModel( + name='LearningPackage', + ), + migrations.DeleteModel( + name='PublishableEntity', + ), + migrations.DeleteModel( + name='PublishableEntityVersion', + ), + migrations.DeleteModel( + name='PublishableEntityVersionDependency', + ), + migrations.DeleteModel( + name='Published', + ), + migrations.DeleteModel( + name='PublishLog', + ), + migrations.DeleteModel( + name='PublishLogRecord', + ), + migrations.DeleteModel( + name='PublishSideEffect', + ), + ] + ) + ] diff --git a/openedx_learning/apps/authoring/units/migrations/__init__.py b/openedx_learning/apps/openedx_content/backcompat/publishing/migrations/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/units/migrations/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/publishing/migrations/__init__.py diff --git a/openedx_learning/apps/openedx_content/backcompat/readme.rst b/openedx_learning/apps/openedx_content/backcompat/readme.rst new file mode 100644 index 000000000..1f844641e --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/readme.rst @@ -0,0 +1,6 @@ +Backwards Compatibility App Package +=================================== + +The apps in this package should not be modified. They are a byproduct of our migration from having a bunch of little authoring apps to having one unified app. They exist to provide backwards compatibilty for database migrations (see `<0020-authoring-as-one-app.rst>`_). + +At some point in the future, we will remove this package and modify the initial migration for the ``authoring`` app to actually create the models for real, instead of using ``SeparateDatabaseAndState`` to fake the database side of the migration. For anyone who has already run the ``openedx_content`` migrations, the modified initial migration won't run anyway. Anyone setting things up for the first time would get the ``openedx_content`` models created without the intermediate steps of creating all the smaller app models first and renaming them. We should not do this before the Willow release, but there's no real downside to doing it later. diff --git a/tests/openedx_learning/apps/authoring/backup_restore/__init__.py b/openedx_learning/apps/openedx_content/backcompat/sections/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/sections/__init__.py diff --git a/openedx_learning/apps/openedx_content/backcompat/sections/apps.py b/openedx_learning/apps/openedx_content/backcompat/sections/apps.py new file mode 100644 index 000000000..35a922773 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/sections/apps.py @@ -0,0 +1,16 @@ +""" +Sections Django application initialization. +""" + +from django.apps import AppConfig + + +class SectionsConfig(AppConfig): + """ + Configuration for the Sections Django application. + """ + + name = "openedx_learning.apps.openedx_content.backcompat.sections" + verbose_name = "Learning Core > Authoring > Sections" + default_auto_field = "django.db.models.BigAutoField" + label = "oel_sections" diff --git a/openedx_learning/apps/authoring/sections/migrations/0001_initial.py b/openedx_learning/apps/openedx_content/backcompat/sections/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/sections/migrations/0001_initial.py rename to openedx_learning/apps/openedx_content/backcompat/sections/migrations/0001_initial.py diff --git a/openedx_learning/apps/openedx_content/backcompat/sections/migrations/0002_remove_all_field_state_for_move_to_applet.py b/openedx_learning/apps/openedx_content/backcompat/sections/migrations/0002_remove_all_field_state_for_move_to_applet.py new file mode 100644 index 000000000..3bd0264a1 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/sections/migrations/0002_remove_all_field_state_for_move_to_applet.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('openedx_content', '0002_rename_tables_to_openedx_content'), + ('oel_sections', '0001_initial'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='sectionversion', + name='container_version', + ), + migrations.DeleteModel( + name='Section', + ), + migrations.DeleteModel( + name='SectionVersion', + ), + ] + ) + ] diff --git a/tests/openedx_learning/apps/authoring/collections/__init__.py b/openedx_learning/apps/openedx_content/backcompat/sections/migrations/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/collections/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/sections/migrations/__init__.py diff --git a/tests/openedx_learning/apps/authoring/components/__init__.py b/openedx_learning/apps/openedx_content/backcompat/subsections/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/components/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/subsections/__init__.py diff --git a/openedx_learning/apps/openedx_content/backcompat/subsections/apps.py b/openedx_learning/apps/openedx_content/backcompat/subsections/apps.py new file mode 100644 index 000000000..a0f946b96 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/subsections/apps.py @@ -0,0 +1,16 @@ +""" +Subsection Django application initialization. +""" + +from django.apps import AppConfig + + +class SubsectionsConfig(AppConfig): + """ + Configuration for the subsections Django application. + """ + + name = "openedx_learning.apps.openedx_content.backcompat.subsections" + verbose_name = "Learning Core > Authoring > Subsections" + default_auto_field = "django.db.models.BigAutoField" + label = "oel_subsections" diff --git a/openedx_learning/apps/authoring/subsections/migrations/0001_initial.py b/openedx_learning/apps/openedx_content/backcompat/subsections/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/subsections/migrations/0001_initial.py rename to openedx_learning/apps/openedx_content/backcompat/subsections/migrations/0001_initial.py diff --git a/openedx_learning/apps/openedx_content/backcompat/subsections/migrations/0002_remove_all_field_state_for_move_to_applet.py b/openedx_learning/apps/openedx_content/backcompat/subsections/migrations/0002_remove_all_field_state_for_move_to_applet.py new file mode 100644 index 000000000..21ff3c1f2 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/subsections/migrations/0002_remove_all_field_state_for_move_to_applet.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('openedx_content', '0002_rename_tables_to_openedx_content'), + ('oel_subsections', '0001_initial'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='subsectionversion', + name='container_version', + ), + migrations.DeleteModel( + name='Subsection', + ), + migrations.DeleteModel( + name='SubsectionVersion', + ), + ] + ) + ] diff --git a/tests/openedx_learning/apps/authoring/contents/__init__.py b/openedx_learning/apps/openedx_content/backcompat/subsections/migrations/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/contents/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/subsections/migrations/__init__.py diff --git a/tests/openedx_learning/apps/authoring/publishing/__init__.py b/openedx_learning/apps/openedx_content/backcompat/units/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/publishing/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/units/__init__.py diff --git a/openedx_learning/apps/openedx_content/backcompat/units/apps.py b/openedx_learning/apps/openedx_content/backcompat/units/apps.py new file mode 100644 index 000000000..7296798f2 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/units/apps.py @@ -0,0 +1,16 @@ +""" +Unit Django application initialization. +""" + +from django.apps import AppConfig + + +class UnitsConfig(AppConfig): + """ + Configuration for the units Django application. + """ + + name = "openedx_learning.apps.openedx_content.backcompat.units" + verbose_name = "Learning Core > Authoring > Units" + default_auto_field = "django.db.models.BigAutoField" + label = "oel_units" diff --git a/openedx_learning/apps/authoring/units/migrations/0001_initial.py b/openedx_learning/apps/openedx_content/backcompat/units/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/units/migrations/0001_initial.py rename to openedx_learning/apps/openedx_content/backcompat/units/migrations/0001_initial.py diff --git a/openedx_learning/apps/openedx_content/backcompat/units/migrations/0002_remove_all_field_state_for_move_to_applet.py b/openedx_learning/apps/openedx_content/backcompat/units/migrations/0002_remove_all_field_state_for_move_to_applet.py new file mode 100644 index 000000000..04acef950 --- /dev/null +++ b/openedx_learning/apps/openedx_content/backcompat/units/migrations/0002_remove_all_field_state_for_move_to_applet.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('openedx_content', '0002_rename_tables_to_openedx_content'), + ('oel_units', '0001_initial'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='unitversion', + name='container_version', + ), + migrations.DeleteModel( + name='Unit', + ), + migrations.DeleteModel( + name='UnitVersion', + ), + ] + ) + ] diff --git a/tests/openedx_learning/apps/authoring/sections/__init__.py b/openedx_learning/apps/openedx_content/backcompat/units/migrations/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/sections/__init__.py rename to openedx_learning/apps/openedx_content/backcompat/units/migrations/__init__.py diff --git a/tests/openedx_learning/apps/authoring/subsections/__init__.py b/openedx_learning/apps/openedx_content/management/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/subsections/__init__.py rename to openedx_learning/apps/openedx_content/management/__init__.py diff --git a/tests/openedx_learning/apps/authoring/units/__init__.py b/openedx_learning/apps/openedx_content/management/commands/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/units/__init__.py rename to openedx_learning/apps/openedx_content/management/commands/__init__.py diff --git a/openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py b/openedx_learning/apps/openedx_content/management/commands/add_assets_to_component.py similarity index 97% rename from openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py rename to openedx_learning/apps/openedx_content/management/commands/add_assets_to_component.py index 5e6518a99..d4306210c 100644 --- a/openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py +++ b/openedx_learning/apps/openedx_content/management/commands/add_assets_to_component.py @@ -9,8 +9,7 @@ from django.core.management.base import BaseCommand -from ....publishing.api import get_learning_package_by_key -from ...api import create_next_component_version, get_component_by_key +from ...api import create_next_component_version, get_component_by_key, get_learning_package_by_key class Command(BaseCommand): diff --git a/openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py b/openedx_learning/apps/openedx_content/management/commands/lp_dump.py similarity index 92% rename from openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py rename to openedx_learning/apps/openedx_content/management/commands/lp_dump.py index b1fb52b4e..dc20fd083 100644 --- a/openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py +++ b/openedx_learning/apps/openedx_content/management/commands/lp_dump.py @@ -8,8 +8,8 @@ from django.core.management import CommandError from django.core.management.base import BaseCommand -from openedx_learning.apps.authoring.backup_restore.api import create_zip_file -from openedx_learning.apps.authoring.publishing.api import LearningPackage +from openedx_learning.apps.openedx_content.applets.backup_restore.api import create_zip_file +from openedx_learning.apps.openedx_content.applets.publishing.api import LearningPackage logger = logging.getLogger(__name__) diff --git a/openedx_learning/apps/authoring/backup_restore/management/commands/lp_load.py b/openedx_learning/apps/openedx_content/management/commands/lp_load.py similarity index 95% rename from openedx_learning/apps/authoring/backup_restore/management/commands/lp_load.py rename to openedx_learning/apps/openedx_content/management/commands/lp_load.py index a326a0850..f658db390 100644 --- a/openedx_learning/apps/authoring/backup_restore/management/commands/lp_load.py +++ b/openedx_learning/apps/openedx_content/management/commands/lp_load.py @@ -8,7 +8,7 @@ from django.core.management import CommandError from django.core.management.base import BaseCommand -from openedx_learning.apps.authoring.backup_restore.api import load_learning_package +from openedx_learning.apps.openedx_content.applets.backup_restore.api import load_learning_package logger = logging.getLogger(__name__) diff --git a/openedx_learning/apps/openedx_content/migrations/0001_initial.py b/openedx_learning/apps/openedx_content/migrations/0001_initial.py new file mode 100644 index 000000000..7670b6903 --- /dev/null +++ b/openedx_learning/apps/openedx_content/migrations/0001_initial.py @@ -0,0 +1,654 @@ +""" +This migration has two modes it needs to run in: + +1. Existing installs that have migration data that is current through 0.30.2 + (bundled with the Ulmo release). +2. New installs. +""" +import uuid + +import django.core.validators +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models +from django.db.migrations.operations.special import SeparateDatabaseAndState + +import openedx_learning.lib.fields +import openedx_learning.lib.validators + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('oel_collections', '0005_alter_collection_options_alter_collection_enabled'), + ('oel_components', '0004_remove_componentversioncontent_uuid'), + ('oel_contents', '0001_initial'), + ('oel_publishing', '0010_backfill_dependencies'), + ('oel_sections', '0001_initial'), + ('oel_subsections', '0001_initial'), + ('oel_units', '0001_initial'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.CreateModel( + name='PublishableEntity', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')), + ('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500)), + ('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('can_stand_alone', models.BooleanField(default=True, help_text='Set to True when created independently, False when created as part of a container.')), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Publishable Entity', + 'verbose_name_plural': 'Publishable Entities', + 'db_table': 'oel_publishing_publishableentity', + }, + ), + migrations.CreateModel( + name='ComponentType', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('namespace', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)), + ('name', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)), + ], + options={ + 'db_table': 'oel_components_componenttype', + }, + ), + migrations.CreateModel( + name='PublishableEntityVersion', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')), + ('title', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=500)), + ('version_num', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1)])), + ('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ('entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='openedx_content.publishableentity')), + ], + options={ + 'verbose_name': 'Publishable Entity Version', + 'verbose_name_plural': 'Publishable Entity Versions', + 'db_table': 'oel_publishing_publishableentityversion', + }, + ), + migrations.CreateModel( + name='Content', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('size', models.PositiveBigIntegerField(validators=[django.core.validators.MaxValueValidator(50000000)])), + ('hash_digest', models.CharField(editable=False, max_length=40)), + ('has_file', models.BooleanField()), + ('text', openedx_learning.lib.fields.MultiCollationTextField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=50000, null=True)), + ('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ], + options={ + 'verbose_name': 'Content', + 'verbose_name_plural': 'Contents', + 'db_table': 'oel_contents_content', + }, + ), + migrations.CreateModel( + name='EntityList', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'db_table': 'oel_publishing_entitylist', + }, + ), + migrations.CreateModel( + name='LearningPackage', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')), + ('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500)), + ('title', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=500)), + ('description', openedx_learning.lib.fields.MultiCollationTextField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=10000)), + ('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 Package', + 'verbose_name_plural': 'Learning Packages', + 'db_table': 'oel_publishing_learningpackage', + }, + ), + migrations.CreateModel( + name='MediaType', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('type', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)), + ('sub_type', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)), + ('suffix', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)), + ], + options={ + 'db_table': "oel_contents_mediatype", + }, + ), + migrations.CreateModel( + name='PublishableEntityVersionDependency', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'db_table': 'oel_publishing_publishableentityversiondependency', + }, + ), + migrations.CreateModel( + name='PublishLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')), + ('message', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=500)), + ('published_at', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ], + options={ + 'verbose_name': 'Publish Log', + 'verbose_name_plural': 'Publish Logs', + 'db_table': 'oel_publishing_publishlog', + }, + ), + migrations.CreateModel( + name='PublishLogRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('dependencies_hash_digest', models.CharField(blank=True, default='', editable=False, max_length=8)), + ], + options={ + 'verbose_name': 'Publish Log Record', + 'verbose_name_plural': 'Publish Log Records', + 'db_table': 'oel_publishing_publishlogrecord', + }, + ), + migrations.CreateModel( + name='PublishSideEffect', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'verbose_name': 'Publish Side Effect', + 'verbose_name_plural': 'Publish Side Effects', + 'db_table': 'oel_publishing_publishsideeffect', + }, + ), + migrations.CreateModel( + name='Collection', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500)), + ('title', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, help_text='The title of the collection.', max_length=500)), + ('description', openedx_learning.lib.fields.MultiCollationTextField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', help_text='Provides extra information for the user about this collection.', max_length=10000)), + ('enabled', models.BooleanField(default=True, help_text='Disabled collections are "soft deleted", and should be re-enabled before use, or be deleted.')), + ('created', models.DateTimeField(auto_now_add=True, validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('modified', models.DateTimeField(auto_now=True, validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'Collections', + 'db_table': 'oel_collections_collection', + }, + ), + migrations.CreateModel( + name='Component', + fields=[ + ('publishable_entity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='openedx_content.publishableentity')), + ('local_key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=500)), + ], + options={ + 'verbose_name': 'Component', + 'verbose_name_plural': 'Components', + 'db_table': 'oel_components_component', + }, + ), + migrations.CreateModel( + name='Container', + fields=[ + ('publishable_entity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='openedx_content.publishableentity')), + ], + options={ + 'db_table': 'oel_publishing_container', + }, + ), + migrations.CreateModel( + name='Draft', + fields=[ + ('entity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='openedx_content.publishableentity')), + ], + options={ + 'db_table': 'oel_publishing_draft', + }, + ), + migrations.CreateModel( + name='Published', + fields=[ + ('entity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='openedx_content.publishableentity')), + ], + options={ + 'verbose_name': 'Published Entity', + 'verbose_name_plural': 'Published Entities', + 'db_table': 'oel_publishing_published', + }, + ), + migrations.CreateModel( + name='CollectionPublishableEntity', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True, validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openedx_content.collection')), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ('entity', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.publishableentity')), + ], + options={ + 'db_table': 'oel_collections_collectionpublishableentity', + }, + ), + migrations.AddField( + model_name='collection', + name='entities', + field=models.ManyToManyField(related_name='collections', through='openedx_content.CollectionPublishableEntity', to='openedx_content.publishableentity'), + ), + migrations.CreateModel( + name='ComponentVersion', + fields=[ + ('publishable_entity_version', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='openedx_content.publishableentityversion')), + ], + options={ + 'verbose_name': 'Component Version', + 'verbose_name_plural': 'Component Versions', + 'db_table': 'oel_components_componentversion', + }, + ), + migrations.CreateModel( + name='ContainerVersion', + fields=[ + ('publishable_entity_version', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='openedx_content.publishableentityversion')), + ], + options={ + 'db_table': 'oel_publishing_containerversion', + }, + ), + migrations.CreateModel( + name='ComponentVersionContent', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500)), + ('content', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.content')), + ], + options={ + 'db_table': 'oel_components_componentversioncontent', + }, + ), + migrations.CreateModel( + name='DraftChangeLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')), + ('changed_at', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('changed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Draft Change Log', + 'verbose_name_plural': 'Draft Change Logs', + 'db_table': 'oel_publishing_draftchangelog', + }, + ), + migrations.CreateModel( + name='DraftChangeLogRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('dependencies_hash_digest', models.CharField(blank=True, default='', editable=False, max_length=8)), + ('draft_change_log', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records', to='openedx_content.draftchangelog')), + ('entity', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.publishableentity')), + ('new_version', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.publishableentityversion')), + ('old_version', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='+', to='openedx_content.publishableentityversion')), + ], + options={ + 'verbose_name': 'Draft Change Log Record', + 'verbose_name_plural': 'Draft Change Log Records', + 'db_table': 'oel_publishing_draftchangelogrecord', + }, + ), + migrations.CreateModel( + name='DraftSideEffect', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('cause', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='causes', to='openedx_content.draftchangelogrecord')), + ('effect', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='affected_by', to='openedx_content.draftchangelogrecord')), + ], + options={ + 'verbose_name': 'Draft Side Effect', + 'verbose_name_plural': 'Draft Side Effects', + 'db_table': 'oel_publishing_draftsideeffect', + }, + ), + migrations.CreateModel( + name='EntityListRow', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order_num', models.PositiveIntegerField()), + ('entity', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.publishableentity')), + ('entity_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openedx_content.entitylist')), + ('entity_version', models.ForeignKey(null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='+', to='openedx_content.publishableentityversion')), + ], + options={ + 'db_table': 'oel_publishing_entitylistrow', + 'ordering': ['order_num'], + }, + ), + migrations.AddField( + model_name='publishableentity', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='publishable_entities', to='openedx_content.learningpackage'), + ), + migrations.AddConstraint( + model_name='learningpackage', + constraint=models.UniqueConstraint(fields=('key',), name='oel_publishing_lp_uniq_key'), + ), + migrations.AddField( + model_name='draftchangelog', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openedx_content.learningpackage'), + ), + migrations.AddField( + model_name='content', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openedx_content.learningpackage'), + ), + migrations.AddField( + model_name='collection', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openedx_content.learningpackage'), + ), + migrations.AddConstraint( + model_name='mediatype', + constraint=models.UniqueConstraint(fields=('type', 'sub_type', 'suffix'), name='oel_contents_uniq_t_st_sfx'), + ), + migrations.AddField( + model_name='content', + name='media_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='openedx_content.mediatype'), + ), + migrations.AddField( + model_name='publishableentityversiondependency', + name='referenced_entity', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.publishableentity'), + ), + migrations.AddField( + model_name='publishableentityversiondependency', + name='referring_version', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openedx_content.publishableentityversion'), + ), + migrations.AddField( + model_name='publishableentityversion', + name='dependencies', + field=models.ManyToManyField(related_name='affects', through='openedx_content.PublishableEntityVersionDependency', to='openedx_content.publishableentity'), + ), + migrations.AddField( + model_name='publishlog', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openedx_content.learningpackage'), + ), + migrations.AddField( + model_name='publishlog', + name='published_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='publishlogrecord', + name='entity', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.publishableentity'), + ), + migrations.AddField( + model_name='publishlogrecord', + name='new_version', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.publishableentityversion'), + ), + migrations.AddField( + model_name='publishlogrecord', + name='old_version', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='+', to='openedx_content.publishableentityversion'), + ), + migrations.AddField( + model_name='publishlogrecord', + name='publish_log', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records', to='openedx_content.publishlog'), + ), + migrations.AddField( + model_name='publishsideeffect', + name='cause', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='causes', to='openedx_content.publishlogrecord'), + ), + migrations.AddField( + model_name='publishsideeffect', + name='effect', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='affected_by', to='openedx_content.publishlogrecord'), + ), + migrations.AddField( + model_name='component', + name='component_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='openedx_content.componenttype'), + ), + migrations.AddField( + model_name='component', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openedx_content.learningpackage'), + ), + migrations.CreateModel( + name='Section', + fields=[ + ('container', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='openedx_content.container')), + ], + options={ + 'abstract': False, + 'db_table': "oel_sections_section", + }, + bases=('openedx_content.container',), + ), + migrations.CreateModel( + name='Subsection', + fields=[ + ('container', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='openedx_content.container')), + ], + options={ + 'abstract': False, + 'db_table': "oel_subsections_subsection", + }, + bases=('openedx_content.container',), + ), + migrations.CreateModel( + name='Unit', + fields=[ + ('container', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='openedx_content.container')), + ], + options={ + 'abstract': False, + 'db_table': "oel_units_unit", + }, + bases=('openedx_content.container',), + ), + migrations.AddField( + model_name='draft', + name='draft_log_record', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='openedx_content.draftchangelogrecord'), + ), + migrations.AddField( + model_name='draft', + name='version', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.publishableentityversion'), + ), + migrations.AddField( + model_name='published', + name='publish_log_record', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.publishlogrecord'), + ), + migrations.AddField( + model_name='published', + name='version', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.RESTRICT, to='openedx_content.publishableentityversion'), + ), + migrations.AddConstraint( + model_name='collectionpublishableentity', + constraint=models.UniqueConstraint(fields=('collection', 'entity'), name='oel_collections_cpe_uniq_col_ent'), + ), + migrations.AddField( + model_name='componentversioncontent', + name='component_version', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openedx_content.componentversion'), + ), + migrations.AddField( + model_name='componentversion', + name='component', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='openedx_content.component'), + ), + migrations.AddField( + model_name='componentversion', + name='contents', + field=models.ManyToManyField(related_name='component_versions', through='openedx_content.ComponentVersionContent', to='openedx_content.content'), + ), + migrations.CreateModel( + name='SectionVersion', + fields=[ + ('container_version', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='openedx_content.containerversion')), + ], + options={ + 'abstract': False, + 'db_table': 'oel_sections_sectionversion', + }, + bases=('openedx_content.containerversion',), + ), + migrations.CreateModel( + name='SubsectionVersion', + fields=[ + ('container_version', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='openedx_content.containerversion')), + ], + options={ + 'abstract': False, + 'db_table': 'oel_subsections_subsectionversion', + }, + bases=('openedx_content.containerversion',), + ), + migrations.CreateModel( + name='UnitVersion', + fields=[ + ('container_version', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='openedx_content.containerversion')), + ], + options={ + 'abstract': False, + 'db_table': 'oel_units_unitversion', + }, + bases=('openedx_content.containerversion',), + ), + migrations.AddField( + model_name='containerversion', + name='container', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='openedx_content.container'), + ), + migrations.AddField( + model_name='containerversion', + name='entity_list', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='container_versions', to='openedx_content.entitylist'), + ), + migrations.AddIndex( + model_name='draftchangelogrecord', + index=models.Index(fields=['entity', '-draft_change_log'], name='oel_dlr_idx_entity_rdcl'), + ), + migrations.AddConstraint( + model_name='draftchangelogrecord', + constraint=models.UniqueConstraint(fields=('draft_change_log', 'entity'), name='oel_dlr_uniq_dcl'), + ), + migrations.AddConstraint( + model_name='draftsideeffect', + constraint=models.UniqueConstraint(fields=('cause', 'effect'), name='oel_pub_dse_uniq_c_e'), + ), + migrations.AddConstraint( + model_name='entitylistrow', + constraint=models.UniqueConstraint(fields=('entity_list', 'order_num'), name='oel_publishing_elist_row_order'), + ), + migrations.AddIndex( + model_name='publishableentity', + index=models.Index(fields=['key'], name='oel_pub_ent_idx_key'), + ), + migrations.AddIndex( + model_name='publishableentity', + index=models.Index(fields=['learning_package', '-created'], name='oel_pub_ent_idx_lp_rcreated'), + ), + migrations.AddConstraint( + model_name='publishableentity', + constraint=models.UniqueConstraint(fields=('learning_package', 'key'), name='oel_pub_ent_uniq_lp_key'), + ), + migrations.AddIndex( + model_name='collection', + index=models.Index(fields=['learning_package', 'title'], name='oel_collect_learnin_dfaf89_idx'), + ), + migrations.AddConstraint( + model_name='collection', + constraint=models.UniqueConstraint(fields=('learning_package', 'key'), name='oel_coll_uniq_lp_key'), + ), + migrations.AddIndex( + model_name='content', + index=models.Index(fields=['learning_package', '-size'], name='oel_content_idx_lp_rsize'), + ), + migrations.AddConstraint( + model_name='content', + constraint=models.UniqueConstraint(fields=('learning_package', 'media_type', 'hash_digest'), name='oel_content_uniq_lc_media_type_hash_digest'), + ), + migrations.AddConstraint( + model_name='publishableentityversiondependency', + constraint=models.UniqueConstraint(fields=('referring_version', 'referenced_entity'), name='oel_pevd_uniq_rv_re'), + ), + migrations.AddIndex( + model_name='publishableentityversion', + index=models.Index(fields=['entity', '-created'], name='oel_pv_idx_entity_rcreated'), + ), + migrations.AddIndex( + model_name='publishableentityversion', + index=models.Index(fields=['title'], name='oel_pv_idx_title'), + ), + migrations.AddConstraint( + model_name='publishableentityversion', + constraint=models.UniqueConstraint(fields=('entity', 'version_num'), name='oel_pv_uniq_entity_version_num'), + ), + migrations.AddIndex( + model_name='publishlogrecord', + index=models.Index(fields=['entity', '-publish_log'], name='oel_plr_idx_entity_rplr'), + ), + migrations.AddConstraint( + model_name='publishlogrecord', + constraint=models.UniqueConstraint(fields=('publish_log', 'entity'), name='oel_plr_uniq_pl_publishable'), + ), + migrations.AddConstraint( + model_name='publishsideeffect', + constraint=models.UniqueConstraint(fields=('cause', 'effect'), name='oel_pub_pse_uniq_c_e'), + ), + migrations.AddIndex( + model_name='component', + index=models.Index(fields=['component_type', 'local_key'], name='oel_component_idx_ct_lk'), + ), + migrations.AddConstraint( + model_name='component', + constraint=models.UniqueConstraint(fields=('learning_package', 'component_type', 'local_key'), name='oel_component_uniq_lc_ct_lk'), + ), + migrations.AddIndex( + model_name='componentversioncontent', + index=models.Index(fields=['content', 'component_version'], name='oel_cvcontent_c_cv'), + ), + migrations.AddIndex( + model_name='componentversioncontent', + index=models.Index(fields=['component_version', 'content'], name='oel_cvcontent_cv_d'), + ), + migrations.AddConstraint( + model_name='componentversioncontent', + constraint=models.UniqueConstraint(fields=('component_version', 'key'), name='oel_cvcontent_uniq_cv_key'), + ), + ] + ) + ] diff --git a/openedx_learning/apps/openedx_content/migrations/0002_rename_tables_to_openedx_content.py b/openedx_learning/apps/openedx_content/migrations/0002_rename_tables_to_openedx_content.py new file mode 100644 index 000000000..6fbf320f7 --- /dev/null +++ b/openedx_learning/apps/openedx_content/migrations/0002_rename_tables_to_openedx_content.py @@ -0,0 +1,138 @@ +# Generated by Django 5.2.9 on 2025-12-29 07:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('openedx_content', '0001_initial'), + ] + + operations = [ + migrations.RenameIndex( + model_name='collection', + new_name='openedx_content_coll_lp_title', + old_name='oel_collect_learnin_dfaf89_idx', + ), + migrations.AlterModelTable( + name='collection', + table=None, + ), + migrations.AlterModelTable( + name='collectionpublishableentity', + table=None, + ), + migrations.AlterModelTable( + name='content', + table=None, + ), + migrations.AlterModelTable( + name='component', + table=None, + ), + migrations.AlterModelTable( + name='componenttype', + table=None, + ), + migrations.AlterModelTable( + name='componentversion', + table=None, + ), + migrations.AlterModelTable( + name='componentversioncontent', + table=None, + ), + migrations.AlterModelTable( + name='container', + table=None, + ), + migrations.AlterModelTable( + name='containerversion', + table=None, + ), + migrations.AlterModelTable( + name='draft', + table=None, + ), + migrations.AlterModelTable( + name='draftchangelog', + table=None, + ), + migrations.AlterModelTable( + name='draftchangelogrecord', + table=None, + ), + migrations.AlterModelTable( + name='draftsideeffect', + table=None, + ), + migrations.AlterModelTable( + name='entitylist', + table=None, + ), + migrations.AlterModelTable( + name='entitylistrow', + table=None, + ), + migrations.AlterModelTable( + name='learningpackage', + table=None, + ), + migrations.AlterModelTable( + name='mediatype', + table=None, + ), + migrations.AlterModelTable( + name='publishableentity', + table=None, + ), + migrations.AlterModelTable( + name='publishableentityversion', + table=None, + ), + migrations.AlterModelTable( + name='publishableentityversiondependency', + table=None, + ), + migrations.AlterModelTable( + name='published', + table=None, + ), + migrations.AlterModelTable( + name='publishlog', + table=None, + ), + migrations.AlterModelTable( + name='publishlogrecord', + table=None, + ), + migrations.AlterModelTable( + name='publishsideeffect', + table=None, + ), + migrations.AlterModelTable( + name='section', + table=None, + ), + migrations.AlterModelTable( + name='sectionversion', + table=None, + ), + migrations.AlterModelTable( + name='subsection', + table=None, + ), + migrations.AlterModelTable( + name='subsectionversion', + table=None, + ), + migrations.AlterModelTable( + name='unit', + table=None, + ), + migrations.AlterModelTable( + name='unitversion', + table=None, + ), + ] diff --git a/openedx_learning/apps/openedx_content/migrations/__init__.py b/openedx_learning/apps/openedx_content/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openedx_learning/apps/openedx_content/models.py b/openedx_learning/apps/openedx_content/models.py new file mode 100644 index 000000000..56f672a67 --- /dev/null +++ b/openedx_learning/apps/openedx_content/models.py @@ -0,0 +1,17 @@ +""" +This module aggregates all applet model modules. + +I experimented with creating a utility to auto-detect applets and magically +import their modules, but that broke code introspection. +""" + +# pylint: disable=wildcard-import + +from .applets.backup_restore.models import * +from .applets.collections.models import * +from .applets.components.models import * +from .applets.contents.models import * +from .applets.publishing.models import * +from .applets.sections.models import * +from .applets.subsections.models import * +from .applets.units.models import * diff --git a/openedx_learning/contrib/media_server/views.py b/openedx_learning/contrib/media_server/views.py index d7088879e..bfc5a4b6d 100644 --- a/openedx_learning/contrib/media_server/views.py +++ b/openedx_learning/contrib/media_server/views.py @@ -8,7 +8,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.http import FileResponse, Http404 -from openedx_learning.apps.authoring.components.api import look_up_component_version_content +from openedx_learning.apps.openedx_content.applets.components.api import look_up_component_version_content def component_asset( diff --git a/projects/dev.py b/projects/dev.py index 1b3b42f47..ebc3e94e6 100644 --- a/projects/dev.py +++ b/projects/dev.py @@ -4,6 +4,8 @@ from __future__ import annotations from pathlib import Path +from openedx_learning.api.django import openedx_learning_apps_to_install + # Build paths inside the project like this: BASE_DIR / {dir_name} / BASE_DIR = Path(__file__).resolve().parents[1] @@ -21,40 +23,32 @@ } } -INSTALLED_APPS = ( +INSTALLED_APPS = [ "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.messages", "django.contrib.sessions", "django.contrib.staticfiles", + # Admin "django.contrib.admin", "django.contrib.admindocs", + # Learning Core Apps - "openedx_learning.apps.authoring.collections.apps.CollectionsConfig", - "openedx_learning.apps.authoring.components.apps.ComponentsConfig", - "openedx_learning.apps.authoring.contents.apps.ContentsConfig", - "openedx_learning.apps.authoring.publishing.apps.PublishingConfig", - "openedx_learning.apps.authoring.sections.apps.SectionsConfig", - "openedx_learning.apps.authoring.subsections.apps.SubsectionsConfig", - "openedx_learning.apps.authoring.units.apps.UnitsConfig", - "openedx_learning.apps.authoring.backup_restore.apps.BackupRestoreConfig", - # Learning Contrib Apps - "openedx_learning.contrib.media_server.apps.MediaServerConfig", - # Apps that don't belong in this repo in the long term, but are here to make - # testing/iteration easier until the APIs stabilize. - "olx_importer.apps.OLXImporterConfig", + *openedx_learning_apps_to_install(), + # REST API "rest_framework", # django-rules based authorization 'rules.apps.AutodiscoverRulesConfig', + # Tagging Core Apps "openedx_tagging.core.tagging.apps.TaggingConfig", # Debugging "debug_toolbar", -) +] AUTHENTICATION_BACKENDS = [ 'rules.permissions.ObjectPermissionBackend', diff --git a/test_settings.py b/test_settings.py index c4b22926d..73c97bc47 100644 --- a/test_settings.py +++ b/test_settings.py @@ -7,6 +7,8 @@ from os.path import abspath, dirname, join +from openedx_learning.api.django import openedx_learning_apps_to_install + def root(*args): """ @@ -18,18 +20,14 @@ def root(*args): DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": "default.db", - "USER": "", - "PASSWORD": "", - "HOST": "", - "PORT": "", + "NAME": ":memory:", } } # If you provision the 'oel'@'%' with broad permissions on your MySQL instance, # running the tests will auto-generate a database for running tests. This is # slower than the default sqlite3 setup above, but it's sometimes helpful for -# finding things that only break in CI. +# finding things that only break in CI. # # DATABASES = { # "default": { @@ -55,15 +53,8 @@ def root(*args): # django-rules based authorization 'rules.apps.AutodiscoverRulesConfig', # Our own apps - "openedx_learning.apps.authoring.collections.apps.CollectionsConfig", - "openedx_learning.apps.authoring.components.apps.ComponentsConfig", - "openedx_learning.apps.authoring.contents.apps.ContentsConfig", - "openedx_learning.apps.authoring.publishing.apps.PublishingConfig", - "openedx_tagging.core.tagging.apps.TaggingConfig", - "openedx_learning.apps.authoring.sections.apps.SectionsConfig", - "openedx_learning.apps.authoring.subsections.apps.SubsectionsConfig", - "openedx_learning.apps.authoring.units.apps.UnitsConfig", - "openedx_learning.apps.authoring.backup_restore.apps.BackupRestoreConfig", + *openedx_learning_apps_to_install(), + "openedx_tagging.core.tagging", ] AUTHENTICATION_BACKENDS = [ diff --git a/tests/openedx_learning/apps/authoring/applets/__init__.py b/tests/openedx_learning/apps/authoring/applets/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/applets/backup_restore/__init__.py b/tests/openedx_learning/apps/authoring/applets/backup_restore/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/collections/collection-test.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/collections/collection-test.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/collections/collection-test.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/collections/collection-test.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/section1-8ca126.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/section1-8ca126.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/section1-8ca126.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/section1-8ca126.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/subsection1-48afa3.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/subsection1-48afa3.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/subsection1-48afa3.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/subsection1-48afa3.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/unit1-b7eafb.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/unit1-b7eafb.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/unit1-b7eafb.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/unit1-b7eafb.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27/component_versions/v2/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27/component_versions/v2/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27/component_versions/v2/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27/component_versions/v2/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2/component_versions/v2/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2/component_versions/v2/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2/component_versions/v2/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2/component_versions/v2/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/static/me.png b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/static/me.png similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/static/me.png rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/static/me.png diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/static/me.png b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/static/me.png similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/static/me.png rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/static/me.png diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53/component_versions/v2/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53/component_versions/v2/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53/component_versions/v2/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53/component_versions/v2/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b/component_versions/v2/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b/component_versions/v2/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b/component_versions/v2/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b/component_versions/v2/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471/component_versions/v1/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471/component_versions/v1/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471/component_versions/v1/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471/component_versions/v1/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568/component_versions/v3/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568/component_versions/v3/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568/component_versions/v3/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568/component_versions/v3/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/package.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/package.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/package.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/package.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/test_backup.py b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_backup.py similarity index 99% rename from tests/openedx_learning/apps/authoring/backup_restore/test_backup.py rename to tests/openedx_learning/apps/authoring/applets/backup_restore/test_backup.py index 312f289f1..ba11d93ed 100644 --- a/tests/openedx_learning/apps/authoring/backup_restore/test_backup.py +++ b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_backup.py @@ -12,7 +12,7 @@ from openedx_learning.api import authoring as api from openedx_learning.api.authoring_models import Collection, Component, Content, LearningPackage, PublishableEntity -from openedx_learning.apps.authoring.backup_restore.zipper import LearningPackageZipper +from openedx_learning.apps.openedx_content.applets.backup_restore.zipper import LearningPackageZipper from openedx_learning.lib.test_utils import TestCase User = get_user_model() diff --git a/tests/openedx_learning/apps/authoring/backup_restore/test_restore.py b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_restore.py similarity index 96% rename from tests/openedx_learning/apps/authoring/backup_restore/test_restore.py rename to tests/openedx_learning/apps/authoring/applets/backup_restore/test_restore.py index e34a0b5f7..07436b174 100644 --- a/tests/openedx_learning/apps/authoring/backup_restore/test_restore.py +++ b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_restore.py @@ -7,10 +7,13 @@ from django.contrib.auth import get_user_model from django.core.management import call_command -from openedx_learning.apps.authoring.backup_restore.zipper import LearningPackageUnzipper, generate_staged_lp_key -from openedx_learning.apps.authoring.collections import api as collections_api -from openedx_learning.apps.authoring.components import api as components_api -from openedx_learning.apps.authoring.publishing import api as publishing_api +from openedx_learning.apps.openedx_content.applets.backup_restore.zipper import ( + LearningPackageUnzipper, + generate_staged_lp_key, +) +from openedx_learning.apps.openedx_content.applets.collections import api as collections_api +from openedx_learning.apps.openedx_content.applets.components import api as components_api +from openedx_learning.apps.openedx_content.applets.publishing import api as publishing_api from openedx_learning.lib.test_utils import TestCase from test_utils.zip_file_utils import folder_to_inmemory_zip @@ -31,7 +34,7 @@ def setUp(self): class RestoreLearningPackageCommandTest(RestoreTestCase): """Tests for the lp_load management command.""" - @patch("openedx_learning.apps.authoring.backup_restore.api.load_learning_package") + @patch("openedx_learning.apps.openedx_content.applets.backup_restore.api.load_learning_package") def test_restore_command(self, mock_load_learning_package): # Mock load_learning_package to return our in-memory zip file restore_result = LearningPackageUnzipper(self.zip_file, user=self.user).load() @@ -284,7 +287,7 @@ def test_error_learning_package_missing_key(self): # Mock parse_learning_package_toml to return a dict without 'key' with patch( - "openedx_learning.apps.authoring.backup_restore.zipper.parse_learning_package_toml", + "openedx_learning.apps.openedx_content.applets.backup_restore.zipper.parse_learning_package_toml", return_value={ "learning_package": { "title": "Library test", @@ -315,7 +318,7 @@ def test_error_no_metadata_section(self): # Mock parse_learning_package_toml to return a dict without 'meta' with patch( - "openedx_learning.apps.authoring.backup_restore.zipper.parse_learning_package_toml", + "openedx_learning.apps.openedx_content.applets.backup_restore.zipper.parse_learning_package_toml", return_value={ "learning_package": { "title": "Library test", diff --git a/tests/openedx_learning/apps/authoring/backup_restore/test_slug_hash.py b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_slug_hash.py similarity index 96% rename from tests/openedx_learning/apps/authoring/backup_restore/test_slug_hash.py rename to tests/openedx_learning/apps/authoring/applets/backup_restore/test_slug_hash.py index 292d08af1..582510488 100644 --- a/tests/openedx_learning/apps/authoring/backup_restore/test_slug_hash.py +++ b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_slug_hash.py @@ -5,7 +5,7 @@ generating slugified, hash-based filenames. """ -from openedx_learning.apps.authoring.backup_restore.zipper import slugify_hashed_filename +from openedx_learning.apps.openedx_content.applets.backup_restore.zipper import slugify_hashed_filename from openedx_learning.lib.test_utils import TestCase diff --git a/tests/openedx_learning/apps/authoring/applets/collections/__init__.py b/tests/openedx_learning/apps/authoring/applets/collections/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/collections/test_api.py b/tests/openedx_learning/apps/authoring/applets/collections/test_api.py similarity index 99% rename from tests/openedx_learning/apps/authoring/collections/test_api.py rename to tests/openedx_learning/apps/authoring/applets/collections/test_api.py index b70a8abd3..97b2f2ba4 100644 --- a/tests/openedx_learning/apps/authoring/collections/test_api.py +++ b/tests/openedx_learning/apps/authoring/applets/collections/test_api.py @@ -17,8 +17,8 @@ ComponentType, LearningPackage, PublishableEntity, + Unit, ) -from openedx_learning.apps.authoring.units.models import Unit from openedx_learning.lib.test_utils import TestCase User = get_user_model() diff --git a/tests/openedx_learning/apps/authoring/applets/components/__init__.py b/tests/openedx_learning/apps/authoring/applets/components/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/components/test_api.py b/tests/openedx_learning/apps/authoring/applets/components/test_api.py similarity index 97% rename from tests/openedx_learning/apps/authoring/components/test_api.py rename to tests/openedx_learning/apps/authoring/applets/components/test_api.py index 8a4dd44c5..851a4e755 100644 --- a/tests/openedx_learning/apps/authoring/components/test_api.py +++ b/tests/openedx_learning/apps/authoring/applets/components/test_api.py @@ -7,14 +7,14 @@ from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user from django.core.exceptions import ObjectDoesNotExist -from openedx_learning.apps.authoring.collections import api as collection_api -from openedx_learning.apps.authoring.collections.models import Collection -from openedx_learning.apps.authoring.components import api as components_api -from openedx_learning.apps.authoring.components.models import Component, ComponentType -from openedx_learning.apps.authoring.contents import api as contents_api -from openedx_learning.apps.authoring.contents.models import MediaType -from openedx_learning.apps.authoring.publishing import api as publishing_api -from openedx_learning.apps.authoring.publishing.models import LearningPackage +from openedx_learning.apps.openedx_content.applets.collections import api as collection_api +from openedx_learning.apps.openedx_content.applets.collections.models import Collection +from openedx_learning.apps.openedx_content.applets.components import api as components_api +from openedx_learning.apps.openedx_content.applets.components.models import Component, ComponentType +from openedx_learning.apps.openedx_content.applets.contents import api as contents_api +from openedx_learning.apps.openedx_content.applets.contents.models import MediaType +from openedx_learning.apps.openedx_content.applets.publishing import api as publishing_api +from openedx_learning.apps.openedx_content.applets.publishing.models import LearningPackage from openedx_learning.lib.test_utils import TestCase User = get_user_model() diff --git a/tests/openedx_learning/apps/authoring/components/test_assets.py b/tests/openedx_learning/apps/authoring/applets/components/test_assets.py similarity index 94% rename from tests/openedx_learning/apps/authoring/components/test_assets.py rename to tests/openedx_learning/apps/authoring/applets/components/test_assets.py index f9cbf0643..8132263d4 100644 --- a/tests/openedx_learning/apps/authoring/components/test_assets.py +++ b/tests/openedx_learning/apps/authoring/applets/components/test_assets.py @@ -5,11 +5,11 @@ from pathlib import Path from uuid import uuid4 -from openedx_learning.apps.authoring.components import api as components_api -from openedx_learning.apps.authoring.components.api import AssetError -from openedx_learning.apps.authoring.contents import api as contents_api -from openedx_learning.apps.authoring.publishing import api as publishing_api -from openedx_learning.apps.authoring.publishing.models import LearningPackage +from openedx_learning.apps.openedx_content.applets.components import api as components_api +from openedx_learning.apps.openedx_content.applets.components.api import AssetError +from openedx_learning.apps.openedx_content.applets.contents import api as contents_api +from openedx_learning.apps.openedx_content.applets.publishing import api as publishing_api +from openedx_learning.apps.openedx_content.applets.publishing.models import LearningPackage from openedx_learning.lib.test_utils import TestCase diff --git a/tests/openedx_learning/apps/authoring/components/test_models.py b/tests/openedx_learning/apps/authoring/applets/components/test_models.py similarity index 95% rename from tests/openedx_learning/apps/authoring/components/test_models.py rename to tests/openedx_learning/apps/authoring/applets/components/test_models.py index ca786d281..9562920da 100644 --- a/tests/openedx_learning/apps/authoring/components/test_models.py +++ b/tests/openedx_learning/apps/authoring/applets/components/test_models.py @@ -6,13 +6,13 @@ from freezegun import freeze_time -from openedx_learning.apps.authoring.components.api import ( +from openedx_learning.apps.openedx_content.applets.components.api import ( create_component_and_version, get_component, get_or_create_component_type, ) -from openedx_learning.apps.authoring.components.models import Component, ComponentType, ComponentVersion -from openedx_learning.apps.authoring.publishing.api import ( +from openedx_learning.apps.openedx_content.applets.components.models import Component, ComponentType, ComponentVersion +from openedx_learning.apps.openedx_content.applets.publishing.api import ( LearningPackage, create_learning_package, create_publishable_entity_version, diff --git a/tests/openedx_learning/apps/authoring/applets/contents/__init__.py b/tests/openedx_learning/apps/authoring/applets/contents/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/contents/test_file_storage.py b/tests/openedx_learning/apps/authoring/applets/contents/test_file_storage.py similarity index 92% rename from tests/openedx_learning/apps/authoring/contents/test_file_storage.py rename to tests/openedx_learning/apps/authoring/applets/contents/test_file_storage.py index a94b31571..5d38c3bb6 100644 --- a/tests/openedx_learning/apps/authoring/contents/test_file_storage.py +++ b/tests/openedx_learning/apps/authoring/applets/contents/test_file_storage.py @@ -7,9 +7,9 @@ from django.core.exceptions import ImproperlyConfigured from django.test import override_settings -from openedx_learning.apps.authoring.contents import api as contents_api -from openedx_learning.apps.authoring.contents.models import get_storage -from openedx_learning.apps.authoring.publishing import api as publishing_api +from openedx_learning.apps.openedx_content.applets.contents import api as contents_api +from openedx_learning.apps.openedx_content.applets.contents.models import get_storage +from openedx_learning.apps.openedx_content.applets.publishing import api as publishing_api from openedx_learning.lib.test_utils import TestCase diff --git a/tests/openedx_learning/apps/authoring/contents/test_media_types.py b/tests/openedx_learning/apps/authoring/applets/contents/test_media_types.py similarity index 90% rename from tests/openedx_learning/apps/authoring/contents/test_media_types.py rename to tests/openedx_learning/apps/authoring/applets/contents/test_media_types.py index 6f9b16b30..4e439c46e 100644 --- a/tests/openedx_learning/apps/authoring/contents/test_media_types.py +++ b/tests/openedx_learning/apps/authoring/applets/contents/test_media_types.py @@ -1,7 +1,7 @@ """ A few tests to make sure our MediaType lookups are working as expected. """ -from openedx_learning.apps.authoring.contents import api as contents_api +from openedx_learning.apps.openedx_content.applets.contents import api as contents_api from openedx_learning.lib.test_utils import TestCase diff --git a/tests/openedx_learning/apps/authoring/applets/publishing/__init__.py b/tests/openedx_learning/apps/authoring/applets/publishing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/publishing/test_api.py b/tests/openedx_learning/apps/authoring/applets/publishing/test_api.py similarity index 99% rename from tests/openedx_learning/apps/authoring/publishing/test_api.py rename to tests/openedx_learning/apps/authoring/applets/publishing/test_api.py index f44667178..ee4a54f7b 100644 --- a/tests/openedx_learning/apps/authoring/publishing/test_api.py +++ b/tests/openedx_learning/apps/authoring/applets/publishing/test_api.py @@ -10,8 +10,8 @@ from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError -from openedx_learning.apps.authoring.publishing import api as publishing_api -from openedx_learning.apps.authoring.publishing.models import ( +from openedx_learning.apps.openedx_content.applets.publishing import api as publishing_api +from openedx_learning.apps.openedx_content.applets.publishing.models import ( Container, ContainerVersion, Draft, diff --git a/tests/openedx_learning/apps/authoring/publishing/test_models.py b/tests/openedx_learning/apps/authoring/applets/publishing/test_models.py similarity index 82% rename from tests/openedx_learning/apps/authoring/publishing/test_models.py rename to tests/openedx_learning/apps/authoring/applets/publishing/test_models.py index 60b2d0840..254f312a3 100644 --- a/tests/openedx_learning/apps/authoring/publishing/test_models.py +++ b/tests/openedx_learning/apps/authoring/applets/publishing/test_models.py @@ -3,7 +3,10 @@ """ from typing import TYPE_CHECKING, assert_type -from openedx_learning.apps.authoring.publishing.models import PublishableEntityMixin, PublishableEntityVersionMixin +from openedx_learning.apps.openedx_content.applets.publishing.models import ( + PublishableEntityMixin, + PublishableEntityVersionMixin, +) from openedx_learning.lib.managers import WithRelationsManager if TYPE_CHECKING: diff --git a/tests/openedx_learning/apps/authoring/applets/sections/__init__.py b/tests/openedx_learning/apps/authoring/applets/sections/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/sections/test_api.py b/tests/openedx_learning/apps/authoring/applets/sections/test_api.py similarity index 100% rename from tests/openedx_learning/apps/authoring/sections/test_api.py rename to tests/openedx_learning/apps/authoring/applets/sections/test_api.py diff --git a/tests/openedx_learning/apps/authoring/applets/subsections/__init__.py b/tests/openedx_learning/apps/authoring/applets/subsections/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/subsections/test_api.py b/tests/openedx_learning/apps/authoring/applets/subsections/test_api.py similarity index 99% rename from tests/openedx_learning/apps/authoring/subsections/test_api.py rename to tests/openedx_learning/apps/authoring/applets/subsections/test_api.py index 577881a9a..5af38c33c 100644 --- a/tests/openedx_learning/apps/authoring/subsections/test_api.py +++ b/tests/openedx_learning/apps/authoring/applets/subsections/test_api.py @@ -262,7 +262,7 @@ def test_adding_external_units(self): created_by=None, ) - @patch('openedx_learning.apps.authoring.subsections.api._pub_entities_for_units') + @patch('openedx_learning.apps.openedx_content.applets.subsections.api._pub_entities_for_units') def test_adding_mismatched_versions(self, mock_entities_for_units): # pylint: disable=arguments-renamed """ Test that versioned units must match their entities. diff --git a/tests/openedx_learning/apps/authoring/applets/units/__init__.py b/tests/openedx_learning/apps/authoring/applets/units/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/units/test_api.py b/tests/openedx_learning/apps/authoring/applets/units/test_api.py similarity index 99% rename from tests/openedx_learning/apps/authoring/units/test_api.py rename to tests/openedx_learning/apps/authoring/applets/units/test_api.py index ebef4810a..2f908e1b8 100644 --- a/tests/openedx_learning/apps/authoring/units/test_api.py +++ b/tests/openedx_learning/apps/authoring/applets/units/test_api.py @@ -250,7 +250,7 @@ def test_adding_external_components(self): created_by=None, ) - @patch('openedx_learning.apps.authoring.units.api._pub_entities_for_components') + @patch('openedx_learning.apps.openedx_content.applets.units.api._pub_entities_for_components') def test_adding_mismatched_versions(self, mock_entities_for_components): """ Test that versioned components must match their entities. diff --git a/tests/openedx_tagging/core/tagging/test_system_defined_models.py b/tests/openedx_tagging/core/tagging/test_system_defined_models.py index f771d82ee..5e519bfa9 100644 --- a/tests/openedx_tagging/core/tagging/test_system_defined_models.py +++ b/tests/openedx_tagging/core/tagging/test_system_defined_models.py @@ -9,7 +9,7 @@ import pytest from django.test import TestCase, override_settings -from openedx_learning.apps.authoring.publishing.models import LearningPackage +from openedx_learning.apps.openedx_content.applets.publishing.models import LearningPackage from openedx_tagging.core.tagging import api from openedx_tagging.core.tagging.models import Taxonomy from openedx_tagging.core.tagging.models.system_defined import ModelSystemDefinedTaxonomy, UserSystemDefinedTaxonomy