From 452c13c1bdbd013a21c113a0c35f1b8f7f4a7a0b Mon Sep 17 00:00:00 2001 From: Kyle D McCormick Date: Tue, 21 Apr 2026 13:12:42 -0400 Subject: [PATCH 1/9] build: Upgrade openedx-core pin, 0.39.2 -> 0.44.0 --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index f051cc8f6114..73e9f6effa10 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -65,7 +65,7 @@ numpy<2.0.0 # breaking changes which openedx-core devs want to roll out manually. New patch versions # are OK to accept automatically. # Issue for unpinning: https://github.com/openedx/edx-platform/issues/35269 -openedx-core<0.40 +openedx-core<0.45 # Date: 2023-11-29 # Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise. diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 298f05e12aab..71790d659ff7 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -840,7 +840,7 @@ openedx-calc==5.0.0 # via # -r requirements/edx/kernel.in # xblocks-contrib -openedx-core==0.39.2 +openedx-core==0.44.0 # via # -c requirements/constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index ea0fd8e68eae..3ad4c94197fd 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -1394,7 +1394,7 @@ openedx-calc==5.0.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # xblocks-contrib -openedx-core==0.39.2 +openedx-core==0.44.0 # via # -c requirements/constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 194b5438ce1f..b7f29a68f1df 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -1018,7 +1018,7 @@ openedx-calc==5.0.0 # via # -r requirements/edx/base.txt # xblocks-contrib -openedx-core==0.39.2 +openedx-core==0.44.0 # via # -c requirements/constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index eb9732129f73..1e14484defc2 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -1065,7 +1065,7 @@ openedx-calc==5.0.0 # via # -r requirements/edx/base.txt # xblocks-contrib -openedx-core==0.39.2 +openedx-core==0.44.0 # via # -c requirements/constraints.txt # -r requirements/edx/base.txt From b8acc81f075876b158adc56197da9b2eb380a187 Mon Sep 17 00:00:00 2001 From: Kyle D McCormick Date: Tue, 21 Apr 2026 13:49:39 -0400 Subject: [PATCH 2/9] refactor: Rename key_field to ref_field for openedx-core 0.43.0 Renames the openedx_django_lib.fields import in EntityLinkBase from the removed key_field helper to ref_field. Co-Authored-By: Claude Opus 4.7 (1M context) --- cms/djangoapps/contentstore/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cms/djangoapps/contentstore/models.py b/cms/djangoapps/contentstore/models.py index c39e04c299dc..d80517c2a842 100644 --- a/cms/djangoapps/contentstore/models.py +++ b/cms/djangoapps/contentstore/models.py @@ -17,7 +17,7 @@ from opaque_keys.edx.locator import LibraryContainerLocator from openedx_content.api import get_published_version from openedx_content.models_api import Component, Container -from openedx_django_lib.fields import immutable_uuid_field, key_field, manual_date_time_field +from openedx_django_lib.fields import immutable_uuid_field, manual_date_time_field, ref_field logger = logging.getLogger(__name__) @@ -87,7 +87,7 @@ class EntityLinkBase(models.Model): """ uuid = immutable_uuid_field() # Search by library/upstream context key - upstream_context_key = key_field( + upstream_context_key = ref_field( help_text=_("Upstream context key i.e., learning_package/library key"), db_index=True, ) From 42e3861329f8a20b95a45e1ef46c3c7b669bd2eb Mon Sep 17 00:00:00 2001 From: Kyle D McCormick Date: Tue, 21 Apr 2026 14:02:22 -0400 Subject: [PATCH 3/9] refactor: Rename LearningPackage.key to package_ref for openedx-core 0.43.0 Updates callers of get_learning_package_by_key (renamed to get_learning_package_by_ref), create_learning_package, and update_learning_package to use the new package_ref kwarg, and switches .key attribute reads to .package_ref. Co-Authored-By: Claude Opus 4.7 (1M context) --- cms/djangoapps/modulestore_migrator/api/read_api.py | 2 +- openedx/core/djangoapps/content/search/tests/test_api.py | 2 +- openedx/core/djangoapps/content_libraries/api/libraries.py | 4 ++-- openedx/core/djangoapps/content_libraries/rest_api/blocks.py | 2 +- openedx/core/djangoapps/xblock/api.py | 2 +- .../core/djangoapps/xblock/runtime/openedx_content_runtime.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cms/djangoapps/modulestore_migrator/api/read_api.py b/cms/djangoapps/modulestore_migrator/api/read_api.py index 064223dc9633..e62a684476f6 100644 --- a/cms/djangoapps/modulestore_migrator/api/read_api.py +++ b/cms/djangoapps/modulestore_migrator/api/read_api.py @@ -209,7 +209,7 @@ def _block_migration_success( """ Build an instance of the migration success dataclass """ - target_library_key = LibraryLocatorV2.from_string(target.learning_package.key) + target_library_key = LibraryLocatorV2.from_string(target.learning_package.package_ref) target_key: LibraryUsageLocatorV2 | LibraryContainerLocator if hasattr(target, "component"): target_key = library_component_usage_key(target_library_key, target.component) diff --git a/openedx/core/djangoapps/content/search/tests/test_api.py b/openedx/core/djangoapps/content/search/tests/test_api.py index 8a29bb450326..3a4cdc32eafa 100644 --- a/openedx/core/djangoapps/content/search/tests/test_api.py +++ b/openedx/core/djangoapps/content/search/tests/test_api.py @@ -188,7 +188,7 @@ def setUp(self) -> None: tagging_api.add_tag_to_taxonomy(self.taxonomyB, "four") # Create a collection: - self.learning_package = content_api.get_learning_package_by_key(self.library.key) + self.learning_package = content_api.get_learning_package_by_ref(str(self.library.key)) with freeze_time(self.created_date): self.collection = content_api.create_collection( learning_package_id=self.learning_package.id, diff --git a/openedx/core/djangoapps/content_libraries/api/libraries.py b/openedx/core/djangoapps/content_libraries/api/libraries.py index bf91039b686b..90451da182d8 100644 --- a/openedx/core/djangoapps/content_libraries/api/libraries.py +++ b/openedx/core/djangoapps/content_libraries/api/libraries.py @@ -461,14 +461,14 @@ def create_library( # and also update its title/description in case they differ. content_api.update_learning_package( learning_package.id, - key=str(ref.library_key), + package_ref=str(ref.library_key), title=title, description=description, ) else: # We have to generate a new LearningPackage for this library. learning_package = content_api.create_learning_package( - key=str(ref.library_key), + package_ref=str(ref.library_key), title=title, description=description, ) diff --git a/openedx/core/djangoapps/content_libraries/rest_api/blocks.py b/openedx/core/djangoapps/content_libraries/rest_api/blocks.py index 518ac5b31fe3..a1471ea65a8e 100644 --- a/openedx/core/djangoapps/content_libraries/rest_api/blocks.py +++ b/openedx/core/djangoapps/content_libraries/rest_api/blocks.py @@ -379,7 +379,7 @@ def get_component_version_asset(request, component_version_uuid, asset_path): # Permissions check... learning_package = component_version.component.learning_package - library_key = LibraryLocatorV2.from_string(learning_package.key) + library_key = LibraryLocatorV2.from_string(learning_package.package_ref) api.require_permission_for_library_key( library_key, request.user, permissions.CAN_VIEW_THIS_CONTENT_LIBRARY, ) diff --git a/openedx/core/djangoapps/xblock/api.py b/openedx/core/djangoapps/xblock/api.py index 0ab620db9ab3..211b14871e7f 100644 --- a/openedx/core/djangoapps/xblock/api.py +++ b/openedx/core/djangoapps/xblock/api.py @@ -199,7 +199,7 @@ def get_component_from_usage_key(usage_key: UsageKeyV2) -> Component: This is a lower-level function that will return a Component even if there is no current draft version of that Component (because it's been soft-deleted). """ - learning_package = content_api.get_learning_package_by_key( + learning_package = content_api.get_learning_package_by_ref( str(usage_key.context_key) ) return content_api.get_component_by_key( diff --git a/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py b/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py index 3a90fb27a5f7..65de9c77332b 100644 --- a/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py +++ b/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py @@ -326,7 +326,7 @@ def _get_component_from_usage_key(self, usage_key): TODO: This is the third place where we're implementing this. Figure out where the definitive place should be and have everything else call that. """ - learning_package = content_api.get_learning_package_by_key(str(usage_key.lib_key)) + learning_package = content_api.get_learning_package_by_ref(str(usage_key.lib_key)) try: component = content_api.get_component_by_key( learning_package.id, From aa836c002125a1b75c4fc87a2830d2e5c5074522 Mon Sep 17 00:00:00 2001 From: Kyle D McCormick Date: Tue, 21 Apr 2026 14:14:19 -0400 Subject: [PATCH 4/9] refactor: Rename PublishableEntity.key to entity_ref for openedx-core 0.43.0 Switches callers of get_publishable_entity_by_key (now _by_ref) to the new name, and renames .key attribute reads on PublishableEntity, Component, and Container (via PublishableEntityMixin) to .entity_ref. Query filters using key__in/entity__key become entity_ref__in/ entity__entity_ref. The set_library_item_collections param entity_key is renamed to entity_ref for consistency. Collection.key reads are intentionally left for the collection_code rename in a later commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- openedx/core/djangoapps/content/search/api.py | 2 +- .../djangoapps/content/search/documents.py | 2 +- .../content/search/tests/test_api.py | 2 +- .../djangoapps/content_libraries/api/blocks.py | 8 ++++---- .../content_libraries/api/collections.py | 18 +++++++++--------- .../api/container_metadata.py | 3 ++- .../content_libraries/api/containers.py | 4 ++-- .../content_libraries/rest_api/blocks.py | 2 +- .../content_libraries/rest_api/containers.py | 2 +- .../core/djangoapps/content_libraries/tasks.py | 6 +++--- .../content_libraries/tests/test_api.py | 2 +- 11 files changed, 26 insertions(+), 25 deletions(-) diff --git a/openedx/core/djangoapps/content/search/api.py b/openedx/core/djangoapps/content/search/api.py index f6bfdf13be77..5c3c9e16b975 100644 --- a/openedx/core/djangoapps/content/search/api.py +++ b/openedx/core/djangoapps/content/search/api.py @@ -677,7 +677,7 @@ def index_container_batch(batch, num_done, library_key) -> int: doc.update(searchable_doc_containers(container_key, "sections")) docs.append(doc) except Exception as err: # pylint: disable=broad-except - status_cb(f"Error indexing container {container.key}: {err}") + status_cb(f"Error indexing container {container.entity_ref}: {err}") num_done += 1 if docs: diff --git a/openedx/core/djangoapps/content/search/documents.py b/openedx/core/djangoapps/content/search/documents.py index b986966ec42c..0ee3b4839287 100644 --- a/openedx/core/djangoapps/content/search/documents.py +++ b/openedx/core/djangoapps/content/search/documents.py @@ -450,7 +450,7 @@ def searchable_doc_collections(object_id: OpaqueKey) -> dict: component = lib_api.get_component_from_usage_key(object_id) collections = content_api.get_entity_collections( component.learning_package_id, - component.key, + component.entity_ref, ).values('key', 'title') elif isinstance(object_id, LibraryContainerLocator): container = lib_api.get_container(object_id, include_collections=True) diff --git a/openedx/core/djangoapps/content/search/tests/test_api.py b/openedx/core/djangoapps/content/search/tests/test_api.py index 3a4cdc32eafa..7f666ef071e8 100644 --- a/openedx/core/djangoapps/content/search/tests/test_api.py +++ b/openedx/core/djangoapps/content/search/tests/test_api.py @@ -536,7 +536,7 @@ def test_reindex_meilisearch_library_block_error(self, mock_meilisearch) -> None def mocked_from_component(lib_key, component): # Simulate an error when processing problem 1 - if component.key == 'xblock.v1:problem:p1': + if component.entity_ref == 'xblock.v1:problem:p1': raise Exception('Error') return orig_from_component(lib_key, component) diff --git a/openedx/core/djangoapps/content_libraries/api/blocks.py b/openedx/core/djangoapps/content_libraries/api/blocks.py index 2ddc06fde254..d969f9edbe8c 100644 --- a/openedx/core/djangoapps/content_libraries/api/blocks.py +++ b/openedx/core/djangoapps/content_libraries/api/blocks.py @@ -178,7 +178,7 @@ def get_library_block(usage_key: LibraryUsageLocatorV2, include_collections=Fals if include_collections: associated_collections = content_api.get_entity_collections( component.learning_package_id, - component.key, + component.entity_ref, ).values('key', 'title') else: associated_collections = None @@ -725,7 +725,7 @@ def send_block_deleted_signal(): send_block_deleted_signal() raise - affected_collections = content_api.get_entity_collections(component.learning_package_id, component.key) + affected_collections = content_api.get_entity_collections(component.learning_package_id, component.entity_ref) affected_containers = get_containers_contains_item(usage_key) content_api.soft_delete_draft(component.id, deleted_by=user_id) @@ -770,7 +770,7 @@ def restore_library_block(usage_key: LibraryUsageLocatorV2, user_id: int | None """ component = get_component_from_usage_key(usage_key) library_key = usage_key.context_key - affected_collections = content_api.get_entity_collections(component.learning_package_id, component.key) + affected_collections = content_api.get_entity_collections(component.learning_package_id, component.entity_ref) # Set draft version back to the latest available component version id. content_api.set_draft_version( @@ -985,7 +985,7 @@ def publish_component_changes(usage_key: LibraryUsageLocatorV2, user_id: int): learning_package = content_library.learning_package assert learning_package # The core publishing API is based on draft objects, so find the draft that corresponds to this component: - drafts_to_publish = content_api.get_all_drafts(learning_package.id).filter(entity__key=component.key) + drafts_to_publish = content_api.get_all_drafts(learning_package.id).filter(entity__entity_ref=component.entity_ref) # Publish the component and update anything that needs to be updated (e.g. search index): publish_log = content_api.publish_from_drafts( learning_package.id, draft_qset=drafts_to_publish, published_by=user_id, diff --git a/openedx/core/djangoapps/content_libraries/api/collections.py b/openedx/core/djangoapps/content_libraries/api/collections.py index 9d011bdae363..175304119716 100644 --- a/openedx/core/djangoapps/content_libraries/api/collections.py +++ b/openedx/core/djangoapps/content_libraries/api/collections.py @@ -127,8 +127,8 @@ def update_library_collection_items( assert content_library.learning_package_id assert content_library.library_key == library_key - # Fetch the Component.key values for the provided UsageKeys. - item_keys = [] + # Fetch the Component.entity_ref values for the provided UsageKeys. + item_refs = [] for opaque_key in opaque_keys: if isinstance(opaque_key, LibraryContainerLocator): try: @@ -139,7 +139,7 @@ def update_library_collection_items( except Collection.DoesNotExist as exc: raise ContentLibraryContainerNotFound(opaque_key) from exc - item_keys.append(container.key) + item_refs.append(container.entity_ref) elif isinstance(opaque_key, UsageKeyV2): # Parse the block_family from the key to use as namespace. block_type = BlockTypeKey.from_string(str(opaque_key)) @@ -153,13 +153,13 @@ def update_library_collection_items( except Component.DoesNotExist as exc: raise ContentLibraryBlockNotFound(opaque_key) from exc - item_keys.append(component.key) + item_refs.append(component.entity_ref) else: # This should never happen, but just in case. raise ValueError(f"Invalid opaque_key: {opaque_key}") entities_qset = PublishableEntity.objects.filter( - key__in=item_keys, + entity_ref__in=item_refs, ) if remove: @@ -181,7 +181,7 @@ def update_library_collection_items( def set_library_item_collections( library_key: LibraryLocatorV2, - entity_key: str, + entity_ref: str, *, collection_keys: list[str], created_by: int | None = None, @@ -207,12 +207,12 @@ def set_library_item_collections( assert content_library.learning_package_id assert content_library.library_key == library_key - publishable_entity = content_api.get_publishable_entity_by_key( + publishable_entity = content_api.get_publishable_entity_by_ref( content_library.learning_package_id, - key=entity_key, + entity_ref=entity_ref, ) - # Note: Component.key matches its PublishableEntity.key + # Note: Component.entity_ref matches its PublishableEntity.entity_ref collection_qs = content_api.get_collections(content_library.learning_package_id).filter( key__in=collection_keys ) diff --git a/openedx/core/djangoapps/content_libraries/api/container_metadata.py b/openedx/core/djangoapps/content_libraries/api/container_metadata.py index 5bb8bcd50ae4..ead5c6f051d6 100644 --- a/openedx/core/djangoapps/content_libraries/api/container_metadata.py +++ b/openedx/core/djangoapps/content_libraries/api/container_metadata.py @@ -366,7 +366,8 @@ def library_container_locator( if container_type_code not in LIBRARY_ALLOWED_CONTAINER_TYPES: raise ValueError(f"Unsupported container type for content libraries: {container!r}") - return LibraryContainerLocator(library_key, container_type=container_type_code, container_id=container.key) + # TODO: verify whether container_id should use entity_ref (opaque) or container_code (local slug). + return LibraryContainerLocator(library_key, container_type=container_type_code, container_id=container.entity_ref) def get_container_from_key(container_key: LibraryContainerLocator, include_deleted=False) -> Container: diff --git a/openedx/core/djangoapps/content_libraries/api/containers.py b/openedx/core/djangoapps/content_libraries/api/containers.py index 5db34dc753da..93d42b282717 100644 --- a/openedx/core/djangoapps/content_libraries/api/containers.py +++ b/openedx/core/djangoapps/content_libraries/api/containers.py @@ -226,7 +226,7 @@ def send_container_deleted_signal(): # Fetch related collections and containers before soft-delete affected_collections = content_api.get_entity_collections( container.publishable_entity.learning_package_id, - container.key, + container.entity_ref, ) affected_containers = get_containers_contains_item(container_key) # Get children containers or components to update their index data @@ -291,7 +291,7 @@ def restore_container(container_key: LibraryContainerLocator) -> None: affected_collections = content_api.get_entity_collections( container.publishable_entity.learning_package_id, - container.key, + container.entity_ref, ) content_api.set_draft_version(container.id, container.versioning.latest.pk) diff --git a/openedx/core/djangoapps/content_libraries/rest_api/blocks.py b/openedx/core/djangoapps/content_libraries/rest_api/blocks.py index a1471ea65a8e..81fc6bd2f1b6 100644 --- a/openedx/core/djangoapps/content_libraries/rest_api/blocks.py +++ b/openedx/core/djangoapps/content_libraries/rest_api/blocks.py @@ -272,7 +272,7 @@ def patch(self, request: RestRequest, usage_key_str) -> Response: collection_keys = serializer.validated_data['collection_keys'] api.set_library_item_collections( library_key=key.lib_key, - entity_key=component.publishable_entity.key, + entity_ref=component.publishable_entity.entity_ref, collection_keys=collection_keys, created_by=request.user.id, content_library=content_library, diff --git a/openedx/core/djangoapps/content_libraries/rest_api/containers.py b/openedx/core/djangoapps/content_libraries/rest_api/containers.py index 04dde384361e..12a4132920c3 100644 --- a/openedx/core/djangoapps/content_libraries/rest_api/containers.py +++ b/openedx/core/djangoapps/content_libraries/rest_api/containers.py @@ -346,7 +346,7 @@ def patch(self, request: RestRequest, container_key: LibraryContainerLocator) -> collection_keys = serializer.validated_data['collection_keys'] api.set_library_item_collections( library_key=container_key.lib_key, - entity_key=container_key.container_id, + entity_ref=container_key.container_id, collection_keys=collection_keys, created_by=request.user.id, content_library=content_library, diff --git a/openedx/core/djangoapps/content_libraries/tasks.py b/openedx/core/djangoapps/content_libraries/tasks.py index bb5db0a397c1..378ee9a19161 100644 --- a/openedx/core/djangoapps/content_libraries/tasks.py +++ b/openedx/core/djangoapps/content_libraries/tasks.py @@ -142,7 +142,7 @@ def send_events_after_publish(publish_log_pk: int, library_key_str: str) -> None pass else: log.warning( - f"PublishableEntity {record.entity.pk} / {record.entity.key} was modified during publish operation " + f"PublishableEntity {record.entity.pk} / {record.entity.entity_ref} was modified during publish operation " "but is of unknown type." ) @@ -246,13 +246,13 @@ def send_events_after_revert(draft_change_log_id: int, library_key_str: str) -> updated_container_keys.add(container_key) else: log.warning( - f"PublishableEntity {record.entity.pk} / {record.entity.key} was modified during publish operation " + f"PublishableEntity {record.entity.pk} / {record.entity.entity_ref} was modified during publish operation " "but is of unknown type." ) # If any collections contain this entity, their item count may need to be updated, e.g. if this was a # newly created component in the collection and is now deleted, or this was deleted and is now re-added. for parent_collection in content_api.get_entity_collections( - record.entity.learning_package_id, record.entity.key, + record.entity.learning_package_id, record.entity.entity_ref, ): collection_key = api.library_collection_locator( library_key=library_key, diff --git a/openedx/core/djangoapps/content_libraries/tests/test_api.py b/openedx/core/djangoapps/content_libraries/tests/test_api.py index 408d16618569..c62cf8eda861 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_api.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_api.py @@ -318,7 +318,7 @@ def test_set_library_component_collections(self) -> None: component = api.get_component_from_usage_key(UsageKeyV2.from_string(self.lib2_problem_block["id"])) api.set_library_item_collections( library_key=self.lib2.library_key, - entity_key=component.publishable_entity.key, + entity_ref=component.publishable_entity.entity_ref, collection_keys=[self.col2.key, self.col3.key], ) From 16e2563aacfd5af6dda31256f7368f9fbb3b95f1 Mon Sep 17 00:00:00 2001 From: Kyle D McCormick Date: Tue, 21 Apr 2026 14:16:09 -0400 Subject: [PATCH 5/9] refactor: Rename Component.local_key to component_code for openedx-core 0.43.0 Updates callers of get_component_by_key/component_exists_by_key (now _by_code) and switches the local_key kwarg on create_component, create_component_and_version, and related queries to component_code. Also renames component.local_key reads to component.component_code and adjusts a modulestore_migrator container query that filtered on publishable_entity__key (now entity_ref). Co-Authored-By: Claude Opus 4.7 (1M context) --- cms/djangoapps/modulestore_migrator/tasks.py | 8 ++++---- .../modulestore_migrator/tests/test_tasks.py | 12 ++++++------ .../core/djangoapps/content_libraries/api/blocks.py | 4 ++-- .../djangoapps/content_libraries/api/collections.py | 4 ++-- .../djangoapps/content_libraries/api/libraries.py | 2 +- .../djangoapps/content_libraries/library_context.py | 4 ++-- openedx/core/djangoapps/xblock/api.py | 4 ++-- .../xblock/runtime/openedx_content_runtime.py | 4 ++-- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cms/djangoapps/modulestore_migrator/tasks.py b/cms/djangoapps/modulestore_migrator/tasks.py index b54b9191e6ba..95048b92feb2 100644 --- a/cms/djangoapps/modulestore_migrator/tasks.py +++ b/cms/djangoapps/modulestore_migrator/tasks.py @@ -346,13 +346,13 @@ def _import_structure( LibraryUsageLocatorV2(target_library.key, block_type, block_id) # type: ignore[abstract] for block_type, block_id in content_api.get_components(migration.target.id).values_list( - "component_type__name", "local_key" + "component_type__name", "component_code" ) ), used_container_slugs=set( content_api.get_containers( migration.target.id - ).values_list("publishable_entity__key", flat=True) + ).values_list("publishable_entity__entity_ref", flat=True) ), previous_block_migrations=( get_migration_blocks(source_data.previous_migration.pk) @@ -932,7 +932,7 @@ def _migrate_component( try: component = content_api.get_components(context.target_package_id).get( component_type=component_type, - local_key=target_key.block_id, + component_code=target_key.block_id, ) component_existed = True # Do we have a specific method for this? @@ -953,7 +953,7 @@ def _migrate_component( component = content_api.create_component( context.target_package_id, component_type=component_type, - local_key=target_key.block_id, + component_code=target_key.block_id, created=context.created_at, created_by=context.created_by, ) diff --git a/cms/djangoapps/modulestore_migrator/tests/test_tasks.py b/cms/djangoapps/modulestore_migrator/tests/test_tasks.py index 2285dd7d77e8..76f94da16bc2 100644 --- a/cms/djangoapps/modulestore_migrator/tests/test_tasks.py +++ b/cms/djangoapps/modulestore_migrator/tests/test_tasks.py @@ -718,7 +718,7 @@ def test_migrate_container_creates_new_container(self): component_type=content_api.get_or_create_component_type( "xblock.v1", "problem" ), - local_key="child_problem_1", + component_code="child_problem_1", created=timezone.now(), created_by=self.user.id, ) @@ -734,7 +734,7 @@ def test_migrate_container_creates_new_container(self): component_type=content_api.get_or_create_component_type( "xblock.v1", "html" ), - local_key="child_html_1", + component_code="child_html_1", created=timezone.now(), created_by=self.user.id, ) @@ -906,7 +906,7 @@ def test_migrate_container_preserves_child_order(self): component_type=content_api.get_or_create_component_type( "xblock.v1", "problem" ), - local_key=f"child_problem_{i}", + component_code=f"child_problem_{i}", created=timezone.now(), created_by=self.user.id, ) @@ -946,7 +946,7 @@ def test_migrate_container_with_mixed_child_types(self): component_type=content_api.get_or_create_component_type( "xblock.v1", "problem" ), - local_key="mixed_problem", + component_code="mixed_problem", created=timezone.now(), created_by=self.user.id, ) @@ -962,7 +962,7 @@ def test_migrate_container_with_mixed_child_types(self): component_type=content_api.get_or_create_component_type( "xblock.v1", "html" ), - local_key="mixed_html", + component_code="mixed_html", created=timezone.now(), created_by=self.user.id, ) @@ -978,7 +978,7 @@ def test_migrate_container_with_mixed_child_types(self): component_type=content_api.get_or_create_component_type( "xblock.v1", "video" ), - local_key="mixed_video", + component_code="mixed_video", created=timezone.now(), created_by=self.user.id, ) diff --git a/openedx/core/djangoapps/content_libraries/api/blocks.py b/openedx/core/djangoapps/content_libraries/api/blocks.py index d969f9edbe8c..f2f415dfa2e0 100644 --- a/openedx/core/djangoapps/content_libraries/api/blocks.py +++ b/openedx/core/djangoapps/content_libraries/api/blocks.py @@ -425,7 +425,7 @@ def _import_staged_block( component = content_api.create_component( # noqa: F841 learning_package.id, component_type=component_type, - local_key=usage_key.block_id, + component_code=usage_key.block_id, created=now, created_by=user.id, ) @@ -1046,7 +1046,7 @@ def _create_component_for_block( component, component_version = content_api.create_component_and_version( learning_package.id, component_type=component_type, - local_key=usage_key.block_id, + component_code=usage_key.block_id, title=display_name, created=now, created_by=user_id, diff --git a/openedx/core/djangoapps/content_libraries/api/collections.py b/openedx/core/djangoapps/content_libraries/api/collections.py index 175304119716..6ab2a98c563b 100644 --- a/openedx/core/djangoapps/content_libraries/api/collections.py +++ b/openedx/core/djangoapps/content_libraries/api/collections.py @@ -144,11 +144,11 @@ def update_library_collection_items( # Parse the block_family from the key to use as namespace. block_type = BlockTypeKey.from_string(str(opaque_key)) try: - component = content_api.get_component_by_key( + component = content_api.get_component_by_code( content_library.learning_package_id, namespace=block_type.block_family, type_name=opaque_key.block_type, - local_key=opaque_key.block_id, + component_code=opaque_key.block_id, ) except Component.DoesNotExist as exc: raise ContentLibraryBlockNotFound(opaque_key) from exc diff --git a/openedx/core/djangoapps/content_libraries/api/libraries.py b/openedx/core/djangoapps/content_libraries/api/libraries.py index 90451da182d8..68d4258bb065 100644 --- a/openedx/core/djangoapps/content_libraries/api/libraries.py +++ b/openedx/core/djangoapps/content_libraries/api/libraries.py @@ -718,7 +718,7 @@ def library_component_usage_key( return LibraryUsageLocatorV2( # type: ignore[abstract] library_key, block_type=component.component_type.name, - usage_id=component.local_key, + usage_id=component.component_code, ) diff --git a/openedx/core/djangoapps/content_libraries/library_context.py b/openedx/core/djangoapps/content_libraries/library_context.py index bd91a3f89250..5d2c25c4286e 100644 --- a/openedx/core/djangoapps/content_libraries/library_context.py +++ b/openedx/core/djangoapps/content_libraries/library_context.py @@ -95,11 +95,11 @@ def block_exists(self, usage_key: LibraryUsageLocatorV2): if learning_package is None: return False - return content_api.component_exists_by_key( + return content_api.component_exists_by_code( learning_package.id, namespace='xblock.v1', type_name=usage_key.block_type, - local_key=usage_key.block_id, + component_code=usage_key.block_id, ) def send_block_updated_event(self, usage_key: UsageKeyV2): diff --git a/openedx/core/djangoapps/xblock/api.py b/openedx/core/djangoapps/xblock/api.py index 211b14871e7f..cc684e72b13f 100644 --- a/openedx/core/djangoapps/xblock/api.py +++ b/openedx/core/djangoapps/xblock/api.py @@ -202,11 +202,11 @@ def get_component_from_usage_key(usage_key: UsageKeyV2) -> Component: learning_package = content_api.get_learning_package_by_ref( str(usage_key.context_key) ) - return content_api.get_component_by_key( + return content_api.get_component_by_code( learning_package.id, namespace='xblock.v1', type_name=usage_key.block_type, - local_key=usage_key.block_id, + component_code=usage_key.block_id, ) diff --git a/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py b/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py index 65de9c77332b..53d2267e92a4 100644 --- a/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py +++ b/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py @@ -328,11 +328,11 @@ def _get_component_from_usage_key(self, usage_key): """ learning_package = content_api.get_learning_package_by_ref(str(usage_key.lib_key)) try: - component = content_api.get_component_by_key( + component = content_api.get_component_by_code( learning_package.id, namespace='xblock.v1', type_name=usage_key.block_type, - local_key=usage_key.block_id, + component_code=usage_key.block_id, ) except ObjectDoesNotExist as exc: raise NoSuchUsage(usage_key) from exc From 3210b37f626bd71b8729f98f7a816e52bb77cd25 Mon Sep 17 00:00:00 2001 From: Kyle D McCormick Date: Tue, 21 Apr 2026 14:18:06 -0400 Subject: [PATCH 6/9] refactor: Rename Container key and ComponentVersionMedia.key for openedx-core 0.43.0 Switches get_container_by_key to get_container_by_code and renames the container creation kwarg from key to container_code for both create_container_and_version and its platform wrapper. Updates ComponentVersionMedia accesses: the model field .key becomes .path, and create_component_version_media's key kwarg becomes path. Co-Authored-By: Claude Opus 4.7 (1M context) --- cms/djangoapps/modulestore_migrator/tasks.py | 2 +- .../core/djangoapps/content_libraries/api/blocks.py | 10 +++++----- .../djangoapps/content_libraries/api/collections.py | 4 ++-- .../content_libraries/api/container_metadata.py | 2 +- .../djangoapps/content_libraries/api/containers.py | 2 +- .../djangoapps/content_libraries/rest_api/blocks.py | 2 +- .../xblock/runtime/openedx_content_runtime.py | 6 +++--- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cms/djangoapps/modulestore_migrator/tasks.py b/cms/djangoapps/modulestore_migrator/tasks.py index 95048b92feb2..701a638b909b 100644 --- a/cms/djangoapps/modulestore_migrator/tasks.py +++ b/cms/djangoapps/modulestore_migrator/tasks.py @@ -971,7 +971,7 @@ def _migrate_component( continue new_path = f"static/{filename}" content_api.create_component_version_media( - component_version.pk, media_pk, key=new_path + component_version.pk, media_pk, path=new_path ) # Publish the component diff --git a/openedx/core/djangoapps/content_libraries/api/blocks.py b/openedx/core/djangoapps/content_libraries/api/blocks.py index f2f415dfa2e0..0476bd8829ac 100644 --- a/openedx/core/djangoapps/content_libraries/api/blocks.py +++ b/openedx/core/djangoapps/content_libraries/api/blocks.py @@ -490,7 +490,7 @@ def _import_staged_block( content_api.create_component_version_media( component_version.pk, content.id, - key=filename, + path=filename, ) # Emit library block created event @@ -852,7 +852,7 @@ def get_library_block_static_asset_files(usage_key: LibraryUsageLocatorV2) -> li component_version .componentversionmedia_set .filter(media__has_file=True) - .order_by('key') + .order_by('path') .select_related('media') ) @@ -860,13 +860,13 @@ def get_library_block_static_asset_files(usage_key: LibraryUsageLocatorV2) -> li return [ LibraryXBlockStaticFile( - path=cvm.key, + path=cvm.path, size=cvm.media.size, url=site_root_url + reverse( 'content_libraries:library-assets', kwargs={ 'component_version_uuid': component_version.uuid, - 'asset_path': cvm.key, + 'asset_path': cvm.path, } ), ) @@ -1061,7 +1061,7 @@ def _create_component_for_block( content_api.create_component_version_media( component_version.pk, content.id, - key="block.xml", + path="block.xml", ) return component_version diff --git a/openedx/core/djangoapps/content_libraries/api/collections.py b/openedx/core/djangoapps/content_libraries/api/collections.py index 6ab2a98c563b..e2efc5d250fa 100644 --- a/openedx/core/djangoapps/content_libraries/api/collections.py +++ b/openedx/core/djangoapps/content_libraries/api/collections.py @@ -132,9 +132,9 @@ def update_library_collection_items( for opaque_key in opaque_keys: if isinstance(opaque_key, LibraryContainerLocator): try: - container = content_api.get_container_by_key( + container = content_api.get_container_by_code( content_library.learning_package_id, - key=opaque_key.container_id, + container_code=opaque_key.container_id, ) except Collection.DoesNotExist as exc: raise ContentLibraryContainerNotFound(opaque_key) from exc diff --git a/openedx/core/djangoapps/content_libraries/api/container_metadata.py b/openedx/core/djangoapps/content_libraries/api/container_metadata.py index ead5c6f051d6..a0a73deea6cd 100644 --- a/openedx/core/djangoapps/content_libraries/api/container_metadata.py +++ b/openedx/core/djangoapps/content_libraries/api/container_metadata.py @@ -380,7 +380,7 @@ def get_container_from_key(container_key: LibraryContainerLocator, include_delet content_library = ContentLibrary.objects.get_by_key(container_key.lib_key) learning_package = content_library.learning_package assert learning_package is not None - container = content_api.get_container_by_key(learning_package.id, key=container_key.container_id) + container = content_api.get_container_by_code(learning_package.id, container_code=container_key.container_id) assert content_api.get_container_type_code_of(container) in LIBRARY_ALLOWED_CONTAINER_TYPES # We only return the container if it exists and either: # 1. the container has a draft version (which means it is not soft-deleted) OR diff --git a/openedx/core/djangoapps/content_libraries/api/containers.py b/openedx/core/djangoapps/content_libraries/api/containers.py index 93d42b282717..e33ef2fa373e 100644 --- a/openedx/core/djangoapps/content_libraries/api/containers.py +++ b/openedx/core/djangoapps/content_libraries/api/containers.py @@ -121,7 +121,7 @@ def create_container( # Then try creating the actual container: container, _initial_version = content_api.create_container_and_version( content_library.learning_package_id, - key=slug, + container_code=slug, title=title, container_cls=container_cls, entities=[], diff --git a/openedx/core/djangoapps/content_libraries/rest_api/blocks.py b/openedx/core/djangoapps/content_libraries/rest_api/blocks.py index 81fc6bd2f1b6..8dd70a069edb 100644 --- a/openedx/core/djangoapps/content_libraries/rest_api/blocks.py +++ b/openedx/core/djangoapps/content_libraries/rest_api/blocks.py @@ -402,7 +402,7 @@ def get_component_version_asset(request, component_version_uuid, asset_path): return redirect_response # If we got here, we know that the asset exists and it's okay to download. - cv_media = component_version.componentversionmedia_set.get(key=asset_path) + cv_media = component_version.componentversionmedia_set.get(path=asset_path) media = cv_media.media # Delete the re-direct part of the response headers. We'll copy the rest. diff --git a/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py b/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py index 53d2267e92a4..92caecccd32b 100644 --- a/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py +++ b/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py @@ -251,13 +251,13 @@ def get_block_assets(self, block, fetch_asset_data): .componentversionmedia_set .filter(media__has_file=True) .select_related('media') - .order_by('key') + .order_by('path') ) return [ StaticFile( - name=cvm.key, - url=self._absolute_url_for_asset(component_version, cvm.key), + name=cvm.path, + url=self._absolute_url_for_asset(component_version, cvm.path), data=cvm.media.read_file().read() if fetch_asset_data else None, ) for cvm in cvm_list From 00805b805296c1c261190439f8e6e82dbb4ae7d5 Mon Sep 17 00:00:00 2001 From: Kyle D McCormick Date: Tue, 21 Apr 2026 14:22:03 -0400 Subject: [PATCH 7/9] refactor: Rename Collection.key and update backup/restore for openedx-core 0.43.0 Renames all Collection APIs that took key/collection_key to use collection_code: create_collection, update_collection, delete_collection, restore_collection, add_to_collection, etc. Switches Collection.key attribute reads to .collection_code across tests, signal handlers, search indexers, and modulestore_migrator. Filters like target_collection__key become target_collection__collection_code. Also updates the library restore serializer to track the renamed lp_restored_data fields: archive_org_key -> archive_org_code, archive_slug -> archive_package_code, key -> package_ref, archive_lp_key -> archive_package_ref. The archive_org_code and archive_package_code fields now allow None, since openedx-core no longer raises ValueError when the archive_package_ref cannot be parsed as {prefix}:{org_code}:{package_code}. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../rest_api/v1/views/tests/test_home.py | 2 +- .../modulestore_migrator/api/read_api.py | 6 +-- cms/djangoapps/modulestore_migrator/tasks.py | 2 +- .../modulestore_migrator/tests/test_api.py | 10 ++-- openedx/core/djangoapps/content/search/api.py | 4 +- .../djangoapps/content/search/documents.py | 8 ++-- .../content/search/tests/test_api.py | 18 +++---- .../content/search/tests/test_documents.py | 4 +- .../content_libraries/api/blocks.py | 4 +- .../content_libraries/api/collections.py | 8 ++-- .../content_libraries/api/containers.py | 4 +- .../content_libraries/rest_api/collections.py | 2 +- .../content_libraries/rest_api/serializers.py | 10 ++-- .../content_libraries/signal_handlers.py | 6 +-- .../djangoapps/content_libraries/tasks.py | 2 +- .../content_libraries/tests/test_api.py | 48 +++++++++---------- .../tests/test_containers.py | 4 +- .../tests/test_views_collections.py | 42 ++++++++-------- 18 files changed, 93 insertions(+), 91 deletions(-) diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py index bebcdfd53dc5..7ac9e8e479ee 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py +++ b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py @@ -274,7 +274,7 @@ def setUp(self): collection_key = "test-collection" content_api.create_collection( learning_package_id=learning_package.id, - key=collection_key, + collection_code=collection_key, title="Test Collection", created_by=self.user.id, ) diff --git a/cms/djangoapps/modulestore_migrator/api/read_api.py b/cms/djangoapps/modulestore_migrator/api/read_api.py index e62a684476f6..9b5aa0b8b8ef 100644 --- a/cms/djangoapps/modulestore_migrator/api/read_api.py +++ b/cms/djangoapps/modulestore_migrator/api/read_api.py @@ -137,7 +137,7 @@ def get_migrations( if target_key: migrations = migrations.filter(target__key=str(target_key)) if target_collection_slug: - migrations = migrations.filter(target_collection__key=target_collection_slug) + migrations = migrations.filter(target_collection__collection_code=target_collection_slug) if task_uuid: migrations = migrations.filter(task_status__uuid=task_uuid) if is_failed is not None: @@ -176,9 +176,9 @@ def _migration(m: models.ModulestoreMigration) -> ModulestoreMigration: return ModulestoreMigration( pk=m.id, source_key=m.source.key, - target_key=LibraryLocatorV2.from_string(m.target.key), + target_key=LibraryLocatorV2.from_string(m.target.package_ref), target_title=m.target.title, - target_collection_slug=(m.target_collection.key if m.target_collection else None), + target_collection_slug=(m.target_collection.collection_code if m.target_collection else None), target_collection_title=(m.target_collection.title if m.target_collection else None), is_failed=m.is_failed, task_uuid=m.task_status.uuid, diff --git a/cms/djangoapps/modulestore_migrator/tasks.py b/cms/djangoapps/modulestore_migrator/tasks.py index 701a638b909b..c878d75d2c6c 100644 --- a/cms/djangoapps/modulestore_migrator/tasks.py +++ b/cms/djangoapps/modulestore_migrator/tasks.py @@ -409,7 +409,7 @@ def _populate_collection(user_id: int, migration: models.ModulestoreMigration) - if block_target_pks: content_api.add_to_collection( learning_package_id=migration.target.pk, - key=migration.target_collection.key, + collection_code=migration.target_collection.collection_code, entities_qset=PublishableEntity.objects.filter(id__in=block_target_pks), created_by=user_id, ) diff --git a/cms/djangoapps/modulestore_migrator/tests/test_api.py b/cms/djangoapps/modulestore_migrator/tests/test_api.py index 311d2b5b69ea..7e88e031a1ad 100644 --- a/cms/djangoapps/modulestore_migrator/tests/test_api.py +++ b/cms/djangoapps/modulestore_migrator/tests/test_api.py @@ -232,7 +232,7 @@ def test_start_migration_to_library_with_collection(self): collection_key = "test-collection" content_api.create_collection( learning_package_id=self.learning_package.id, - key=collection_key, + collection_code=collection_key, title="Test Collection", created_by=user.id, ) @@ -249,7 +249,7 @@ def test_start_migration_to_library_with_collection(self): ) modulestoremigration = ModulestoreMigration.objects.get() - assert modulestoremigration.target_collection.key == collection_key + assert modulestoremigration.target_collection.collection_code == collection_key def test_start_migration_to_library_with_strategy_skip(self): """ @@ -487,19 +487,19 @@ def test_migration_api_for_various_scenarios(self): # Lib 2 has Collection C content_api.create_collection( learning_package_id=self.learning_package.id, - key="test-collection-1a", + collection_code="test-collection-1a", title="Test Collection A in Lib 1", created_by=user.id, ) content_api.create_collection( learning_package_id=self.learning_package.id, - key="test-collection-1b", + collection_code="test-collection-1b", title="Test Collection B in Lib 1", created_by=user.id, ) content_api.create_collection( learning_package_id=self.learning_package_2.id, - key="test-collection-2c", + collection_code="test-collection-2c", title="Test Collection C in Lib 2", created_by=user.id, ) diff --git a/openedx/core/djangoapps/content/search/api.py b/openedx/core/djangoapps/content/search/api.py index 5c3c9e16b975..2d044cd4210d 100644 --- a/openedx/core/djangoapps/content/search/api.py +++ b/openedx/core/djangoapps/content/search/api.py @@ -641,7 +641,7 @@ def index_collection_batch(batch, num_done, library_key) -> int: docs = [] for collection in batch: try: - collection_key = lib_api.library_collection_locator(library_key, collection.key) + collection_key = lib_api.library_collection_locator(library_key, collection.collection_code) doc = searchable_doc_for_collection(collection_key, collection=collection) doc.update(searchable_doc_tags(collection_key)) docs.append(doc) @@ -1032,7 +1032,7 @@ def upsert_content_library_index_docs(library_key: LibraryLocatorV2, full_index: docs.append(doc) for collection in lib_api.get_library_collections(library_key): - collection_key = lib_api.library_collection_locator(library_key, collection.key) + collection_key = lib_api.library_collection_locator(library_key, collection.collection_code) doc = searchable_doc_for_collection(collection_key, collection=collection) docs.append(doc) diff --git a/openedx/core/djangoapps/content/search/documents.py b/openedx/core/djangoapps/content/search/documents.py index 0ee3b4839287..a98a02764bbe 100644 --- a/openedx/core/djangoapps/content/search/documents.py +++ b/openedx/core/djangoapps/content/search/documents.py @@ -33,7 +33,7 @@ class Fields: usage_key = "usage_key" type = "type" # DocType.course_block or DocType.library_block (see below) # The block_id part of the usage key for course or library blocks. - # If it's a collection, the collection.key is stored here. + # If it's a collection, the collection.collection_code is stored here. # Sometimes human-readable, sometimes a random hex ID # Is only unique within the given context_key. block_id = "block_id" @@ -64,7 +64,7 @@ class Fields: tags_level2 = "level2" tags_level3 = "level3" # Collections (dictionary) that this object belongs to. - # Similarly to tags above, we collect the collection.titles and collection.keys into hierarchical facets. + # Similarly to tags above, we collect the collection.titles and collection.collection_codes into hierarchical facets. collections = "collections" collections_display_name = "display_name" collections_key = "key" @@ -543,7 +543,7 @@ def searchable_doc_for_collection( pass if collection: - assert collection.key == collection_key.collection_id + assert collection.collection_code == collection_key.collection_id draft_num_children = content_api.filter_publishable_entities( collection.entities, @@ -558,7 +558,7 @@ def searchable_doc_for_collection( Fields.context_key: str(collection_key.context_key), Fields.org: str(collection_key.org), Fields.usage_key: str(collection_key), - Fields.block_id: collection.key, + Fields.block_id: collection.collection_code, Fields.type: DocType.collection, Fields.display_name: collection.title, Fields.description: collection.description, diff --git a/openedx/core/djangoapps/content/search/tests/test_api.py b/openedx/core/djangoapps/content/search/tests/test_api.py index 7f666ef071e8..981bb5858b80 100644 --- a/openedx/core/djangoapps/content/search/tests/test_api.py +++ b/openedx/core/djangoapps/content/search/tests/test_api.py @@ -192,7 +192,7 @@ def setUp(self) -> None: with freeze_time(self.created_date): self.collection = content_api.create_collection( learning_package_id=self.learning_package.id, - key="MYCOL", + collection_code="MYCOL", title="my_collection", created_by=None, description="my collection description" @@ -202,7 +202,7 @@ def setUp(self) -> None: ) self.collection_dict = { "id": "lib-collectionorg1libmycol-5b647617", - "block_id": self.collection.key, + "block_id": self.collection.collection_code, "usage_key": str(self.collection_key), "type": "collection", "display_name": "my_collection", @@ -722,7 +722,7 @@ def test_index_library_block_and_collections(self, mock_meilisearch) -> None: for collection in (collection2, collection1): library_api.update_library_collection_items( self.library.key, - collection_key=collection.key, + collection_key=collection.collection_code, opaque_keys=[ self.problem1.usage_key, ], @@ -904,7 +904,7 @@ def test_delete_collection(self, mock_meilisearch) -> None: with freeze_time(updated_date): library_api.update_library_collection_items( self.library.key, - collection_key=self.collection.key, + collection_key=self.collection.collection_code, opaque_keys=[ self.problem1.usage_key, self.unit.container_key @@ -918,14 +918,14 @@ def test_delete_collection(self, mock_meilisearch) -> None: "id": self.doc_problem1["id"], "collections": { "display_name": [self.collection.title], - "key": [self.collection.key], + "key": [self.collection.collection_code], }, } doc_unit_with_collection = { "id": self.unit_dict["id"], "collections": { "display_name": [self.collection.title], - "key": [self.collection.key], + "key": [self.collection.collection_code], }, } @@ -944,7 +944,7 @@ def test_delete_collection(self, mock_meilisearch) -> None: # Soft-delete the collection content_api.delete_collection( self.collection.learning_package_id, - self.collection.key, + self.collection.collection_code, ) doc_problem_without_collection = { @@ -979,7 +979,7 @@ def test_delete_collection(self, mock_meilisearch) -> None: with freeze_time(restored_date): content_api.restore_collection( self.collection.learning_package_id, - self.collection.key, + self.collection.collection_code, ) doc_collection = copy.deepcopy(self.collection_dict) @@ -1001,7 +1001,7 @@ def test_delete_collection(self, mock_meilisearch) -> None: # Hard-delete the collection content_api.delete_collection( self.collection.learning_package_id, - self.collection.key, + self.collection.collection_code, hard_delete=True, ) diff --git a/openedx/core/djangoapps/content/search/tests/test_documents.py b/openedx/core/djangoapps/content/search/tests/test_documents.py index a9aea3ab3cfb..ee4a1d613f61 100644 --- a/openedx/core/djangoapps/content/search/tests/test_documents.py +++ b/openedx/core/djangoapps/content/search/tests/test_documents.py @@ -492,7 +492,7 @@ def test_collection_with_library(self): assert doc == { "id": "lib-collectionedx2012_falltoy_collection-d1d907a4", - "block_id": self.collection.key, + "block_id": self.collection.collection_code, "usage_key": str(self.collection_key), "type": "collection", "org": "edX", @@ -521,7 +521,7 @@ def test_collection_with_published_library(self): assert doc == { "id": "lib-collectionedx2012_falltoy_collection-d1d907a4", - "block_id": self.collection.key, + "block_id": self.collection.collection_code, "usage_key": str(self.collection_key), "type": "collection", "org": "edX", diff --git a/openedx/core/djangoapps/content_libraries/api/blocks.py b/openedx/core/djangoapps/content_libraries/api/blocks.py index 0476bd8829ac..63327280d021 100644 --- a/openedx/core/djangoapps/content_libraries/api/blocks.py +++ b/openedx/core/djangoapps/content_libraries/api/blocks.py @@ -743,7 +743,7 @@ def send_block_deleted_signal(): library_collection=LibraryCollectionData( collection_key=library_collection_locator( library_key=library_key, - collection_key=collection.key, + collection_key=collection.collection_code, ), background=True, ) @@ -809,7 +809,7 @@ def restore_library_block(usage_key: LibraryUsageLocatorV2, user_id: int | None library_collection=LibraryCollectionData( collection_key=library_collection_locator( library_key=library_key, - collection_key=collection.key, + collection_key=collection.collection_code, ), background=True, ) diff --git a/openedx/core/djangoapps/content_libraries/api/collections.py b/openedx/core/djangoapps/content_libraries/api/collections.py index e2efc5d250fa..41cf425c111c 100644 --- a/openedx/core/djangoapps/content_libraries/api/collections.py +++ b/openedx/core/djangoapps/content_libraries/api/collections.py @@ -54,7 +54,7 @@ def create_library_collection( try: collection = content_api.create_collection( learning_package_id=content_library.learning_package_id, - key=collection_key, + collection_code=collection_key, title=title, description=description, created_by=created_by, @@ -86,7 +86,7 @@ def update_library_collection( try: collection = content_api.update_collection( learning_package_id=content_library.learning_package_id, - key=collection_key, + collection_code=collection_key, title=title, description=description, ) @@ -214,7 +214,7 @@ def set_library_item_collections( # Note: Component.entity_ref matches its PublishableEntity.entity_ref collection_qs = content_api.get_collections(content_library.learning_package_id).filter( - key__in=collection_keys + collection_code__in=collection_keys ) affected_collections = content_api.set_collections( @@ -232,7 +232,7 @@ def set_library_item_collections( library_collection=LibraryCollectionData( collection_key=library_collection_locator( library_key=library_key, - collection_key=collection.key, + collection_key=collection.collection_code, ), background=True, ) diff --git a/openedx/core/djangoapps/content_libraries/api/containers.py b/openedx/core/djangoapps/content_libraries/api/containers.py index e33ef2fa373e..d357cdbe78f7 100644 --- a/openedx/core/djangoapps/content_libraries/api/containers.py +++ b/openedx/core/djangoapps/content_libraries/api/containers.py @@ -249,7 +249,7 @@ def send_container_deleted_signal(): library_collection=LibraryCollectionData( collection_key=library_collection_locator( library_key=library_key, - collection_key=collection.key, + collection_key=collection.collection_code, ), background=True, ) @@ -333,7 +333,7 @@ def restore_container(container_key: LibraryContainerLocator) -> None: library_collection=LibraryCollectionData( collection_key=library_collection_locator( library_key=library_key, - collection_key=collection.key, + collection_key=collection.collection_code, ), ) ) diff --git a/openedx/core/djangoapps/content_libraries/rest_api/collections.py b/openedx/core/djangoapps/content_libraries/rest_api/collections.py index 3f67f5e777a8..4ccbae2ba36b 100644 --- a/openedx/core/djangoapps/content_libraries/rest_api/collections.py +++ b/openedx/core/djangoapps/content_libraries/rest_api/collections.py @@ -184,7 +184,7 @@ def destroy(self, request: RestRequest, *args, **kwargs) -> Response: assert collection.learning_package_id content_api.delete_collection( collection.learning_package_id, - collection.key, + collection.collection_code, hard_delete=False, ) return Response(None, status=HTTP_204_NO_CONTENT) diff --git a/openedx/core/djangoapps/content_libraries/rest_api/serializers.py b/openedx/core/djangoapps/content_libraries/rest_api/serializers.py index 72c59f695833..b30906d58d89 100644 --- a/openedx/core/djangoapps/content_libraries/rest_api/serializers.py +++ b/openedx/core/djangoapps/content_libraries/rest_api/serializers.py @@ -447,14 +447,16 @@ class RestoreSuccessDataSerializer(serializers.Serializer): """ learning_package_id = serializers.IntegerField(source="lp_restored_data.id") title = serializers.CharField(source="lp_restored_data.title") - org = serializers.CharField(source="lp_restored_data.archive_org_key") - slug = serializers.CharField(source="lp_restored_data.archive_slug") + # archive_org_code and archive_package_code may be None when archive_package_ref cannot be parsed + # as {prefix}:{org_code}:{package_code} (previously this raised ValueError in openedx-core). + org = serializers.CharField(source="lp_restored_data.archive_org_code", allow_null=True) + slug = serializers.CharField(source="lp_restored_data.archive_package_code", allow_null=True) # The `key` is a unique temporary key assigned to the learning package during the restore process, # whereas the `archive_key` is the original key of the learning package from the backup. # The temporary learning package key is replaced with a standard key once it is added to a content library. - key = serializers.CharField(source="lp_restored_data.key") - archive_key = serializers.CharField(source="lp_restored_data.archive_lp_key") + key = serializers.CharField(source="lp_restored_data.package_ref") + archive_key = serializers.CharField(source="lp_restored_data.archive_package_ref") containers = serializers.IntegerField(source="lp_restored_data.num_containers") components = serializers.IntegerField(source="lp_restored_data.num_components") diff --git a/openedx/core/djangoapps/content_libraries/signal_handlers.py b/openedx/core/djangoapps/content_libraries/signal_handlers.py index 041a49b473e0..fada1cb2f874 100644 --- a/openedx/core/djangoapps/content_libraries/signal_handlers.py +++ b/openedx/core/djangoapps/content_libraries/signal_handlers.py @@ -43,7 +43,7 @@ def library_collection_saved(sender, instance, created, **kwargs): library_collection=LibraryCollectionData( collection_key=library_collection_locator( library_key=library.library_key, - collection_key=instance.key, + collection_key=instance.collection_code, ), ) ) @@ -54,7 +54,7 @@ def library_collection_saved(sender, instance, created, **kwargs): library_collection=LibraryCollectionData( collection_key=library_collection_locator( library_key=library.library_key, - collection_key=instance.key, + collection_key=instance.collection_code, ), ) ) @@ -77,7 +77,7 @@ def library_collection_deleted(sender, instance, **kwargs): library_collection=LibraryCollectionData( collection_key=library_collection_locator( library_key=library.library_key, - collection_key=instance.key, + collection_key=instance.collection_code, ), ) ) diff --git a/openedx/core/djangoapps/content_libraries/tasks.py b/openedx/core/djangoapps/content_libraries/tasks.py index 378ee9a19161..2fe28c7476a8 100644 --- a/openedx/core/djangoapps/content_libraries/tasks.py +++ b/openedx/core/djangoapps/content_libraries/tasks.py @@ -651,7 +651,7 @@ def restore_library(self, user_id, storage_path): TASK_LOGGER.info( 'Restored learning package (id: %s) with key %s', learning_package_data.get('id'), - learning_package_data.get('key') + learning_package_data.get('package_ref') ) # Save the restore details as an artifact in JSON format diff --git a/openedx/core/djangoapps/content_libraries/tests/test_api.py b/openedx/core/djangoapps/content_libraries/tests/test_api.py index c62cf8eda861..7d2e84c10280 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_api.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_api.py @@ -115,7 +115,7 @@ def test_create_library_collection(self) -> None: description="Description for Collection 4", created_by=self.user.id, ) - assert collection.key == "COL4" + assert collection.collection_code == "COL4" assert collection.title == "Collection 4" assert collection.description == "Description for Collection 4" assert collection.created_by == self.user @@ -150,10 +150,10 @@ def test_update_library_collection(self) -> None: self.col1 = api.update_library_collection( self.lib1.library_key, - self.col1.key, + self.col1.collection_code, title="New title for Collection 1", ) - assert self.col1.key == "COL1" + assert self.col1.collection_code == "COL1" assert self.col1.title == "New title for Collection 1" assert self.col1.description == "Description for Collection 1" assert self.col1.created_by == self.user @@ -177,7 +177,7 @@ def test_update_library_collection_wrong_library(self) -> None: with self.assertRaises(api.ContentLibraryCollectionNotFound) as exc: # noqa: F841, PT027 api.update_library_collection( self.lib1.library_key, - self.col2.key, + self.col2.collection_code, ) def test_delete_library_collection(self) -> None: @@ -187,7 +187,7 @@ def test_delete_library_collection(self) -> None: assert self.lib1.learning_package_id is not None content_api.delete_collection( self.lib1.learning_package_id, - self.col1.key, + self.col1.collection_code, hard_delete=True, ) @@ -211,7 +211,7 @@ def test_update_library_collection_items(self) -> None: self.col1 = api.update_library_collection_items( self.lib1.library_key, - self.col1.key, + self.col1.collection_code, opaque_keys=[ LibraryUsageLocatorV2.from_string(self.lib1_problem_block["id"]), LibraryUsageLocatorV2.from_string(self.lib1_html_block["id"]), @@ -222,7 +222,7 @@ def test_update_library_collection_items(self) -> None: self.col1 = api.update_library_collection_items( self.lib1.library_key, - self.col1.key, + self.col1.collection_code, opaque_keys=[ LibraryUsageLocatorV2.from_string(self.lib1_html_block["id"]), ], @@ -240,7 +240,7 @@ def test_update_library_collection_components_event(self) -> None: api.update_library_collection_items( self.lib1.library_key, - self.col1.key, + self.col1.collection_code, opaque_keys=[ LibraryUsageLocatorV2.from_string(self.lib1_problem_block["id"]), LibraryUsageLocatorV2.from_string(self.lib1_html_block["id"]), @@ -300,7 +300,7 @@ def test_update_collection_components_from_wrong_library(self) -> None: with self.assertRaises(api.ContentLibraryBlockNotFound) as exc: # noqa: PT027 api.update_library_collection_items( self.lib2.library_key, - self.col2.key, + self.col2.collection_code, opaque_keys=[ LibraryUsageLocatorV2.from_string(self.lib1_problem_block["id"]), LibraryUsageLocatorV2.from_string(self.lib1_html_block["id"]), @@ -319,12 +319,12 @@ def test_set_library_component_collections(self) -> None: api.set_library_item_collections( library_key=self.lib2.library_key, entity_ref=component.publishable_entity.entity_ref, - collection_keys=[self.col2.key, self.col3.key], + collection_keys=[self.col2.collection_code, self.col3.collection_code], ) assert self.lib2.learning_package_id is not None - assert len(content_api.get_collection(self.lib2.learning_package_id, self.col2.key).entities.all()) == 1 - assert len(content_api.get_collection(self.lib2.learning_package_id, self.col3.key).entities.all()) == 1 + assert len(content_api.get_collection(self.lib2.learning_package_id, self.col2.collection_code).entities.all()) == 1 + assert len(content_api.get_collection(self.lib2.learning_package_id, self.col3.collection_code).entities.all()) == 1 self.assertDictContainsEntries( event_receiver.call_args_list[0].kwargs, @@ -343,11 +343,11 @@ def test_set_library_component_collections(self) -> None: assert all(event["signal"] == LIBRARY_COLLECTION_UPDATED for event in collection_update_events) assert {event["library_collection"] for event in collection_update_events} == { LibraryCollectionData( - collection_key=api.library_collection_locator(self.lib2.library_key, collection_key=self.col2.key), + collection_key=api.library_collection_locator(self.lib2.library_key, collection_key=self.col2.collection_code), background=True, ), LibraryCollectionData( - collection_key=api.library_collection_locator(self.lib2.library_key, collection_key=self.col3.key), + collection_key=api.library_collection_locator(self.lib2.library_key, collection_key=self.col3.collection_code), background=True, ) } @@ -355,7 +355,7 @@ def test_set_library_component_collections(self) -> None: def test_delete_library_block(self) -> None: api.update_library_collection_items( self.lib1.library_key, - self.col1.key, + self.col1.collection_code, opaque_keys=[ LibraryUsageLocatorV2.from_string(self.lib1_problem_block["id"]), LibraryUsageLocatorV2.from_string(self.lib1_html_block["id"]), @@ -376,7 +376,7 @@ def test_delete_library_block(self) -> None: "library_collection": LibraryCollectionData( collection_key=api.library_collection_locator( self.lib1.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), background=True, ), @@ -386,7 +386,7 @@ def test_delete_library_block(self) -> None: def test_delete_library_container(self) -> None: api.update_library_collection_items( self.lib1.library_key, - self.col1.key, + self.col1.collection_code, opaque_keys=[ LibraryUsageLocatorV2.from_string(self.lib1_problem_block["id"]), LibraryUsageLocatorV2.from_string(self.lib1_html_block["id"]), @@ -415,7 +415,7 @@ def test_delete_library_container(self) -> None: "library_collection": LibraryCollectionData( collection_key=api.library_collection_locator( self.lib1.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), background=True, ), @@ -499,7 +499,7 @@ def test_delete_library_block_when_component_does_not_exist(self) -> None: def test_restore_library_block(self) -> None: api.update_library_collection_items( self.lib1.library_key, - self.col1.key, + self.col1.collection_code, opaque_keys=[ LibraryUsageLocatorV2.from_string(self.lib1_problem_block["id"]), LibraryUsageLocatorV2.from_string(self.lib1_html_block["id"]), @@ -520,7 +520,7 @@ def test_restore_library_block(self) -> None: "library_collection": LibraryCollectionData( collection_key=api.library_collection_locator( self.lib1.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), background=True, ), @@ -539,7 +539,7 @@ def test_add_component_and_revert(self) -> None: # Add component. Note: collections are not part of the draft/publish cycle so this is not a draft change. api.update_library_collection_items( self.lib1.library_key, - self.col1.key, + self.col1.collection_code, opaque_keys=[ LibraryUsageLocatorV2.from_string(self.lib1_html_block["id"]), LibraryUsageLocatorV2.from_string(new_problem_block["id"]), @@ -560,7 +560,7 @@ def test_add_component_and_revert(self) -> None: "library_collection": LibraryCollectionData( collection_key=api.library_collection_locator( self.lib1.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), ), }, @@ -574,7 +574,7 @@ def test_delete_component_and_revert(self) -> None: # Add components and publish api.update_library_collection_items( self.lib1.library_key, - self.col1.key, + self.col1.collection_code, opaque_keys=[ LibraryUsageLocatorV2.from_string(self.lib1_problem_block["id"]), LibraryUsageLocatorV2.from_string(self.lib1_html_block["id"]) @@ -599,7 +599,7 @@ def test_delete_component_and_revert(self) -> None: "library_collection": LibraryCollectionData( collection_key=api.library_collection_locator( self.lib1.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), ), }, diff --git a/openedx/core/djangoapps/content_libraries/tests/test_containers.py b/openedx/core/djangoapps/content_libraries/tests/test_containers.py index 37ad621d26e6..a95b238b78a4 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_containers.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_containers.py @@ -591,7 +591,7 @@ def test_unit_collections(self) -> None: result = self._patch_container_collections( self.unit["id"], - collection_keys=[col1.key], + collection_keys=[col1.collection_code], ) assert result['count'] == 1 @@ -600,7 +600,7 @@ def test_unit_collections(self) -> None: unit_as_read = self._get_container(self.unit["id"]) # Verify the collections - assert unit_as_read['collections'] == [{"title": col1.title, "key": col1.key}] + assert unit_as_read['collections'] == [{"title": col1.title, "key": col1.collection_code}] def test_section_hierarchy(self): with self.assertNumQueries(126): diff --git a/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py b/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py index 270580cb2a61..a25b18dc3777 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py @@ -88,7 +88,7 @@ def test_get_library_collection(self): Test retrieving a Content Library Collection """ resp = self.client.get( - URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.key) + URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.collection_code) ) # Check that correct Content Library Collection data retrieved @@ -103,7 +103,7 @@ def test_get_library_collection(self): random_user = UserFactory.create(username="Random", email="random@example.com") with self.as_user(random_user): resp = self.client.get( - URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.key) + URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.collection_code) ) assert resp.status_code == 403 @@ -113,7 +113,7 @@ def test_get_invalid_library_collection(self): """ # Fetch collection that belongs to a different library, it should fail resp = self.client.get( - URL_LIB_COLLECTION.format(lib_key=self.lib1.library_key, collection_key=self.col3.key) + URL_LIB_COLLECTION.format(lib_key=self.lib1.library_key, collection_key=self.col3.collection_code) ) assert resp.status_code == 404 @@ -249,7 +249,7 @@ def test_create_invalid_library_collection(self): # Create collection with an existing collection.key; it should fail post_data_existing_key = { - "key": self.col1.key, + "key": self.col1.collection_code, "title": "Collection 4", } resp = self.client.post( @@ -275,7 +275,7 @@ def test_update_library_collection(self): "title": "Collection 3 Updated", } resp = self.client.patch( - URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.key), + URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.collection_code), patch_data, format="json" ) @@ -297,7 +297,7 @@ def test_update_library_collection(self): "title": "Collection 3 should not update", } resp = self.client.patch( - URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.key), + URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.collection_code), patch_data, format="json" ) @@ -313,7 +313,7 @@ def test_update_invalid_library_collection(self): } # Update collection that belongs to a different library, it should fail resp = self.client.patch( - URL_LIB_COLLECTION.format(lib_key=self.lib1.library_key, collection_key=self.col3.key), + URL_LIB_COLLECTION.format(lib_key=self.lib1.library_key, collection_key=self.col3.collection_code), patch_data, format="json" ) @@ -331,7 +331,7 @@ def test_update_invalid_library_collection(self): # Update collection with invalid library_key provided, it should fail resp = self.client.patch( - URL_LIB_COLLECTION.format(lib_key=123, collection_key=self.col3.key), + URL_LIB_COLLECTION.format(lib_key=123, collection_key=self.col3.collection_code), patch_data, format="json" ) @@ -342,22 +342,22 @@ def test_delete_library_collection(self): Test soft-deleting and restoring a Content Library Collection """ resp = self.client.delete( - URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.key) + URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.collection_code) ) assert resp.status_code == 204 resp = self.client.get( - URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.key) + URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.collection_code) ) assert resp.status_code == 404 resp = self.client.post( - URL_LIB_COLLECTION_RESTORE.format(lib_key=self.lib2.library_key, collection_key=self.col3.key) + URL_LIB_COLLECTION_RESTORE.format(lib_key=self.lib2.library_key, collection_key=self.col3.collection_code) ) assert resp.status_code == 204 resp = self.client.get( - URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.key) + URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.collection_code) ) # Check that correct Content Library Collection data retrieved expected_collection = { @@ -375,7 +375,7 @@ def test_get_components(self): resp = self.client.get( URL_LIB_COLLECTION_COMPONENTS.format( lib_key=self.lib1.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), ) assert resp.status_code == 405 @@ -388,7 +388,7 @@ def test_update_components(self): resp = self.client.patch( URL_LIB_COLLECTION_COMPONENTS.format( lib_key=self.lib1.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), data={ "usage_keys": [ @@ -404,7 +404,7 @@ def test_update_components(self): resp = self.client.delete( URL_LIB_COLLECTION_COMPONENTS.format( lib_key=self.lib1.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), data={ "usage_keys": [ @@ -423,7 +423,7 @@ def test_update_containers(self): resp = self.client.patch( URL_LIB_COLLECTION_COMPONENTS.format( lib_key=self.lib1.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), data={ "usage_keys": [ @@ -440,7 +440,7 @@ def test_update_containers(self): resp = self.client.delete( URL_LIB_COLLECTION_COMPONENTS.format( lib_key=self.lib1.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), data={ "usage_keys": [ @@ -460,7 +460,7 @@ def test_update_components_wrong_collection(self, method): resp = getattr(self.client, method)( URL_LIB_COLLECTION_COMPONENTS.format( lib_key=self.lib2.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), data={ "usage_keys": [ @@ -478,7 +478,7 @@ def test_update_components_missing_data(self, method): resp = getattr(self.client, method)( URL_LIB_COLLECTION_COMPONENTS.format( lib_key=self.lib2.library_key, - collection_key=self.col3.key, + collection_key=self.col3.collection_code, ), ) assert resp.status_code == 400 @@ -494,7 +494,7 @@ def test_update_components_from_another_library(self, method): resp = getattr(self.client, method)( URL_LIB_COLLECTION_COMPONENTS.format( lib_key=self.lib2.library_key, - collection_key=self.col3.key, + collection_key=self.col3.collection_code, ), data={ "usage_keys": [ @@ -515,7 +515,7 @@ def test_update_components_permissions(self, method): resp = getattr(self.client, method)( URL_LIB_COLLECTION_COMPONENTS.format( lib_key=self.lib1.library_key, - collection_key=self.col1.key, + collection_key=self.col1.collection_code, ), ) assert resp.status_code == 403 From 160081245523dc90c2b78d284875f624d589ff4c Mon Sep 17 00:00:00 2001 From: Kyle D McCormick Date: Tue, 21 Apr 2026 20:25:09 -0400 Subject: [PATCH 8/9] fix: various fixes to Claude's output... fix: build locator with container_code fix: pylint and mypy fix: queries for search index fix: some missed cvm.key -> cvm.path fix: undo breaking library changes fix: openedx-core no longer raises integrityerror on conflict fix: misses in modulestore_migrator fix: search tests docs: Improve collection_code/key TODO comment --- .../modulestore_migrator/api/read_api.py | 2 +- .../rest_api/v1/serializers.py | 4 +++ .../modulestore_migrator/rest_api/v1/views.py | 2 +- cms/djangoapps/modulestore_migrator/tasks.py | 2 +- .../modulestore_migrator/tests/test_tasks.py | 10 ++++---- .../djangoapps/content/search/documents.py | 9 +++++-- .../content/search/tests/test_api.py | 16 ++++++------ .../content_libraries/api/blocks.py | 7 ++++-- .../content_libraries/api/collections.py | 25 +++++++++---------- .../api/container_metadata.py | 6 ++--- .../content_libraries/api/containers.py | 6 ++++- .../content_libraries/rest_api/collections.py | 5 +++- .../content_libraries/rest_api/serializers.py | 5 +++- .../djangoapps/content_libraries/tasks.py | 12 ++++----- .../content_libraries/tests/test_api.py | 14 ++++++++--- .../tests/test_views_collections.py | 2 +- .../video_config/transcripts_utils.py | 2 +- openedx/core/djangoapps/xblock/api.py | 4 +-- .../xblock/runtime/openedx_content_runtime.py | 6 ++--- 19 files changed, 83 insertions(+), 56 deletions(-) diff --git a/cms/djangoapps/modulestore_migrator/api/read_api.py b/cms/djangoapps/modulestore_migrator/api/read_api.py index 9b5aa0b8b8ef..cd9962c4aa19 100644 --- a/cms/djangoapps/modulestore_migrator/api/read_api.py +++ b/cms/djangoapps/modulestore_migrator/api/read_api.py @@ -135,7 +135,7 @@ def get_migrations( if source_key: migrations = migrations.filter(source__key=source_key) if target_key: - migrations = migrations.filter(target__key=str(target_key)) + migrations = migrations.filter(target__package_ref=str(target_key)) if target_collection_slug: migrations = migrations.filter(target_collection__collection_code=target_collection_slug) if task_uuid: diff --git a/cms/djangoapps/modulestore_migrator/rest_api/v1/serializers.py b/cms/djangoapps/modulestore_migrator/rest_api/v1/serializers.py index 797c42d9a5b3..9dc0c5dda5d3 100644 --- a/cms/djangoapps/modulestore_migrator/rest_api/v1/serializers.py +++ b/cms/djangoapps/modulestore_migrator/rest_api/v1/serializers.py @@ -18,6 +18,10 @@ class LibraryMigrationCollectionSerializer(serializers.ModelSerializer): """ Serializer for the target collection of a library migration. """ + # Expose Collection.collection_code as "key" to preserve the REST API field name. + # This is temporary: https://github.com/openedx/openedx-platform/issues/38406 + key = serializers.CharField(source='collection_code') + class Meta: model = Collection fields = ["key", "title"] diff --git a/cms/djangoapps/modulestore_migrator/rest_api/v1/views.py b/cms/djangoapps/modulestore_migrator/rest_api/v1/views.py index 0b76c01cdaaa..594c9518a2ae 100644 --- a/cms/djangoapps/modulestore_migrator/rest_api/v1/views.py +++ b/cms/djangoapps/modulestore_migrator/rest_api/v1/views.py @@ -540,7 +540,7 @@ def get_queryset(self): self.request.user, lib_api.permissions.CAN_VIEW_THIS_CONTENT_LIBRARY ) - queryset = queryset.filter(target__key=library_key, source__key__startswith='course-v1') + queryset = queryset.filter(target__package_ref=str(library_key), source__key__startswith='course-v1') return queryset diff --git a/cms/djangoapps/modulestore_migrator/tasks.py b/cms/djangoapps/modulestore_migrator/tasks.py index c878d75d2c6c..15b25524b871 100644 --- a/cms/djangoapps/modulestore_migrator/tasks.py +++ b/cms/djangoapps/modulestore_migrator/tasks.py @@ -867,7 +867,7 @@ def _migrate_container( container_exists = False if PublishableEntity.objects.filter( learning_package_id=context.target_package_id, - key=target_key.container_id, + entity_ref=target_key.container_id, ).exists(): libraries_api.restore_container(container_key=target_key) container = libraries_api.get_container(target_key) diff --git a/cms/djangoapps/modulestore_migrator/tests/test_tasks.py b/cms/djangoapps/modulestore_migrator/tests/test_tasks.py index 76f94da16bc2..ae4ad1548937 100644 --- a/cms/djangoapps/modulestore_migrator/tests/test_tasks.py +++ b/cms/djangoapps/modulestore_migrator/tests/test_tasks.py @@ -89,12 +89,12 @@ def setUp(self): ) self.collection = Collection.objects.create( learning_package=self.learning_package, - key="test_collection", + collection_code="test_collection", title="Test Collection", ) self.collection2 = Collection.objects.create( learning_package=self.learning_package, - key="test_collection2", + collection_code="test_collection2", title="Test Collection 2", ) @@ -426,7 +426,7 @@ def test_migrate_component_with_static_content(self): self.assertIsNone(reason) # noqa: PT009 component_media = result.componentversion.componentversionmedia_set.filter( - key="static/test_image.png" + path="static/test_image.png" ).first() self.assertIsNotNone(component_media) # noqa: PT009 self.assertEqual(component_media.media.id, test_media.id) # noqa: PT009 @@ -673,12 +673,12 @@ def test_migrate_component_content_filename_not_in_olx(self): referenced_content_exists = ( result.componentversion.componentversionmedia_set.filter( - key="static/referenced.png" + path="static/referenced.png" ).exists() ) unreferenced_content_exists = ( result.componentversion.componentversionmedia_set.filter( - key="static/unreferenced.png" + path="static/unreferenced.png" ).exists() ) diff --git a/openedx/core/djangoapps/content/search/documents.py b/openedx/core/djangoapps/content/search/documents.py index a98a02764bbe..678281191eeb 100644 --- a/openedx/core/djangoapps/content/search/documents.py +++ b/openedx/core/djangoapps/content/search/documents.py @@ -7,6 +7,7 @@ from hashlib import blake2b from django.core.exceptions import ObjectDoesNotExist +from django.db.models import F from django.utils.text import slugify from opaque_keys.edx.keys import ContainerKey, LearningContextKey, OpaqueKey, UsageKey from opaque_keys.edx.locator import LibraryCollectionLocator, LibraryContainerLocator @@ -64,7 +65,8 @@ class Fields: tags_level2 = "level2" tags_level3 = "level3" # Collections (dictionary) that this object belongs to. - # Similarly to tags above, we collect the collection.titles and collection.collection_codes into hierarchical facets. + # Similarly to tags above, we collect the collection.titles and collection.collection_codes + # into hierarchical facets. collections = "collections" collections_display_name = "display_name" collections_key = "key" @@ -448,10 +450,13 @@ def searchable_doc_collections(object_id: OpaqueKey) -> dict: try: if isinstance(object_id, UsageKey): component = lib_api.get_component_from_usage_key(object_id) + # Temporarily alias collection_code to "key" so downstream consumers + # (search indexer, REST API) keep the same field name. We will update + # downstream consumers later: https://github.com/openedx/openedx-platform/issues/38406 collections = content_api.get_entity_collections( component.learning_package_id, component.entity_ref, - ).values('key', 'title') + ).values("title", key=F('collection_code')) elif isinstance(object_id, LibraryContainerLocator): container = lib_api.get_container(object_id, include_collections=True) collections = container.collections diff --git a/openedx/core/djangoapps/content/search/tests/test_api.py b/openedx/core/djangoapps/content/search/tests/test_api.py index 981bb5858b80..afa847748e89 100644 --- a/openedx/core/djangoapps/content/search/tests/test_api.py +++ b/openedx/core/djangoapps/content/search/tests/test_api.py @@ -732,8 +732,8 @@ def test_index_library_block_and_collections(self, mock_meilisearch) -> None: lib_access, _ = SearchAccess.objects.get_or_create(context_key=self.library.key) doc_collection1_created = { "id": "lib-collectionorg1libcol1-283a79c9", - "block_id": collection1.key, - "usage_key": f"lib-collection:org1:lib:{collection1.key}", + "block_id": collection1.collection_code, + "usage_key": f"lib-collection:org1:lib:{collection1.collection_code}", "type": "collection", "display_name": "Collection 1", "description": "First Collection", @@ -750,8 +750,8 @@ def test_index_library_block_and_collections(self, mock_meilisearch) -> None: } doc_collection2_created = { "id": "lib-collectionorg1libcol2-46823d4d", - "block_id": collection2.key, - "usage_key": f"lib-collection:org1:lib:{collection2.key}", + "block_id": collection2.collection_code, + "usage_key": f"lib-collection:org1:lib:{collection2.collection_code}", "type": "collection", "display_name": "Collection 2", "description": "Second Collection", @@ -768,8 +768,8 @@ def test_index_library_block_and_collections(self, mock_meilisearch) -> None: } doc_collection2_updated = { "id": "lib-collectionorg1libcol2-46823d4d", - "block_id": collection2.key, - "usage_key": f"lib-collection:org1:lib:{collection2.key}", + "block_id": collection2.collection_code, + "usage_key": f"lib-collection:org1:lib:{collection2.collection_code}", "type": "collection", "display_name": "Collection 2", "description": "Second Collection", @@ -786,8 +786,8 @@ def test_index_library_block_and_collections(self, mock_meilisearch) -> None: } doc_collection1_updated = { "id": "lib-collectionorg1libcol1-283a79c9", - "block_id": collection1.key, - "usage_key": f"lib-collection:org1:lib:{collection1.key}", + "block_id": collection1.collection_code, + "usage_key": f"lib-collection:org1:lib:{collection1.collection_code}", "type": "collection", "display_name": "Collection 1", "description": "First Collection", diff --git a/openedx/core/djangoapps/content_libraries/api/blocks.py b/openedx/core/djangoapps/content_libraries/api/blocks.py index 63327280d021..f8d45b78d5fe 100644 --- a/openedx/core/djangoapps/content_libraries/api/blocks.py +++ b/openedx/core/djangoapps/content_libraries/api/blocks.py @@ -15,7 +15,7 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.validators import validate_unicode_slug from django.db import transaction -from django.db.models import QuerySet +from django.db.models import F, QuerySet from django.urls import reverse from django.utils.text import slugify from django.utils.translation import gettext as _ @@ -176,10 +176,13 @@ def get_library_block(usage_key: LibraryUsageLocatorV2, include_collections=Fals raise ContentLibraryBlockNotFound(usage_key) if include_collections: + # Temporarily alias collection_code to "key" so downstream consumers + # (search indexer, REST API) keep the same field name. We will update + # downstream consumers later: https://github.com/openedx/openedx-platform/issues/38406 associated_collections = content_api.get_entity_collections( component.learning_package_id, component.entity_ref, - ).values('key', 'title') + ).values("title", key=F('collection_code')) else: associated_collections = None xblock_metadata = LibraryXBlockMetadata.from_component( diff --git a/openedx/core/djangoapps/content_libraries/api/collections.py b/openedx/core/djangoapps/content_libraries/api/collections.py index 41cf425c111c..1c87acd21232 100644 --- a/openedx/core/djangoapps/content_libraries/api/collections.py +++ b/openedx/core/djangoapps/content_libraries/api/collections.py @@ -2,7 +2,6 @@ Python API for library collections ================================== """ -from django.db import IntegrityError from opaque_keys import OpaqueKey from opaque_keys.edx.keys import BlockTypeKey, UsageKeyV2 from opaque_keys.edx.locator import LibraryCollectionLocator, LibraryContainerLocator, LibraryLocatorV2 @@ -51,18 +50,18 @@ def create_library_collection( assert content_library.learning_package_id assert content_library.library_key == library_key - try: - collection = content_api.create_collection( - learning_package_id=content_library.learning_package_id, - collection_code=collection_key, - title=title, - description=description, - created_by=created_by, - ) - except IntegrityError as err: - raise LibraryCollectionAlreadyExists from err - - return collection + if Collection.objects.filter( + learning_package_id=content_library.learning_package_id, + collection_code=collection_key, + ).exists(): + raise LibraryCollectionAlreadyExists(f"Collection {collection_key} already exists in {library_key}") + return content_api.create_collection( + learning_package_id=content_library.learning_package_id, + collection_code=collection_key, + title=title, + description=description, + created_by=created_by, + ) def update_library_collection( diff --git a/openedx/core/djangoapps/content_libraries/api/container_metadata.py b/openedx/core/djangoapps/content_libraries/api/container_metadata.py index a0a73deea6cd..98a5024ac674 100644 --- a/openedx/core/djangoapps/content_libraries/api/container_metadata.py +++ b/openedx/core/djangoapps/content_libraries/api/container_metadata.py @@ -365,9 +365,9 @@ def library_container_locator( container_type_code = content_api.get_container_type_code_of(container) if container_type_code not in LIBRARY_ALLOWED_CONTAINER_TYPES: raise ValueError(f"Unsupported container type for content libraries: {container!r}") - - # TODO: verify whether container_id should use entity_ref (opaque) or container_code (local slug). - return LibraryContainerLocator(library_key, container_type=container_type_code, container_id=container.entity_ref) + return LibraryContainerLocator( + library_key, container_type=container_type_code, container_id=container.container_code, + ) def get_container_from_key(container_key: LibraryContainerLocator, include_deleted=False) -> Container: diff --git a/openedx/core/djangoapps/content_libraries/api/containers.py b/openedx/core/djangoapps/content_libraries/api/containers.py index d357cdbe78f7..0b126e5665df 100644 --- a/openedx/core/djangoapps/content_libraries/api/containers.py +++ b/openedx/core/djangoapps/content_libraries/api/containers.py @@ -10,6 +10,7 @@ from uuid import uuid4 from django.db import transaction +from django.db.models import F from django.utils.text import slugify from opaque_keys.edx.locator import LibraryContainerLocator, LibraryLocatorV2, LibraryUsageLocatorV2 from openedx_content import api as content_api @@ -73,10 +74,13 @@ def get_container( """ container = get_container_from_key(container_key) if include_collections: + # Temporarily alias collection_code to "key" so downstream consumers + # (search indexer, REST API) keep the same field name. We will update + # downstream consumers later: https://github.com/openedx/openedx-platform/issues/38406 associated_collections = content_api.get_entity_collections( container.publishable_entity.learning_package_id, container_key.container_id, - ).values("key", "title") + ).values("title", key=F("collection_code")) else: associated_collections = None container_meta = ContainerMetadata.from_container( diff --git a/openedx/core/djangoapps/content_libraries/rest_api/collections.py b/openedx/core/djangoapps/content_libraries/rest_api/collections.py index 4ccbae2ba36b..9875f31d79d5 100644 --- a/openedx/core/djangoapps/content_libraries/rest_api/collections.py +++ b/openedx/core/djangoapps/content_libraries/rest_api/collections.py @@ -33,7 +33,10 @@ class LibraryCollectionsView(ModelViewSet): """ serializer_class = ContentLibraryCollectionSerializer - lookup_field = 'key' + # URL kwarg is `key` for backwards compatibility. + # https://github.com/openedx/openedx-platform/issues/38406 + lookup_field = 'collection_code' + lookup_url_kwarg = 'key' def __init__(self, *args, **kwargs) -> None: """ diff --git a/openedx/core/djangoapps/content_libraries/rest_api/serializers.py b/openedx/core/djangoapps/content_libraries/rest_api/serializers.py index b30906d58d89..425bb75c10d0 100644 --- a/openedx/core/djangoapps/content_libraries/rest_api/serializers.py +++ b/openedx/core/djangoapps/content_libraries/rest_api/serializers.py @@ -284,10 +284,13 @@ class ContentLibraryCollectionSerializer(serializers.ModelSerializer): """ Serializer for a Content Library Collection """ + # Expose Collection.collection_code as "key" to preserve the REST API field name. + # https://github.com/openedx/openedx-platform/issues/38406 + key = serializers.CharField(source='collection_code') class Meta: model = Collection - fields = '__all__' + exclude = ['collection_code'] class ContentLibraryCollectionUpdateSerializer(serializers.Serializer): diff --git a/openedx/core/djangoapps/content_libraries/tasks.py b/openedx/core/djangoapps/content_libraries/tasks.py index 2fe28c7476a8..ee972b248649 100644 --- a/openedx/core/djangoapps/content_libraries/tasks.py +++ b/openedx/core/djangoapps/content_libraries/tasks.py @@ -142,8 +142,8 @@ def send_events_after_publish(publish_log_pk: int, library_key_str: str) -> None pass else: log.warning( - f"PublishableEntity {record.entity.pk} / {record.entity.entity_ref} was modified during publish operation " - "but is of unknown type." + f"PublishableEntity {record.entity.pk} / {record.entity.entity_ref} " + "was modified during publish operation but is of unknown type." ) for container_key in affected_containers: @@ -246,8 +246,8 @@ def send_events_after_revert(draft_change_log_id: int, library_key_str: str) -> updated_container_keys.add(container_key) else: log.warning( - f"PublishableEntity {record.entity.pk} / {record.entity.entity_ref} was modified during publish operation " - "but is of unknown type." + f"PublishableEntity {record.entity.pk} / {record.entity.entity_ref} " + "was modified during publish operation but is of unknown type." ) # If any collections contain this entity, their item count may need to be updated, e.g. if this was a # newly created component in the collection and is now deleted, or this was deleted and is now re-added. @@ -256,7 +256,7 @@ def send_events_after_revert(draft_change_log_id: int, library_key_str: str) -> ): collection_key = api.library_collection_locator( library_key=library_key, - collection_key=parent_collection.key, + collection_key=parent_collection.collection_code, ) affected_collection_keys.add(collection_key) @@ -541,7 +541,7 @@ def backup_library(self, user_id: int, library_key_str: str) -> None: file_path = os.path.join(root_dir, filename) user = User.objects.get(id=user_id) origin_server = getattr(settings, 'CMS_BASE', None) - create_lib_zip_file(lp_key=str(library_key), path=file_path, user=user, origin_server=origin_server) + create_lib_zip_file(package_ref=str(library_key), path=file_path, user=user, origin_server=origin_server) set_custom_attribute("exporting_completed", str(library_key)) with open(file_path, 'rb') as zipfile: diff --git a/openedx/core/djangoapps/content_libraries/tests/test_api.py b/openedx/core/djangoapps/content_libraries/tests/test_api.py index 7d2e84c10280..cc0564964fb8 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_api.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_api.py @@ -323,8 +323,10 @@ def test_set_library_component_collections(self) -> None: ) assert self.lib2.learning_package_id is not None - assert len(content_api.get_collection(self.lib2.learning_package_id, self.col2.collection_code).entities.all()) == 1 - assert len(content_api.get_collection(self.lib2.learning_package_id, self.col3.collection_code).entities.all()) == 1 + col2 = content_api.get_collection(self.lib2.learning_package_id, self.col2.collection_code) + col3 = content_api.get_collection(self.lib2.learning_package_id, self.col3.collection_code) + assert len(col2.entities.all()) == 1 + assert len(col3.entities.all()) == 1 self.assertDictContainsEntries( event_receiver.call_args_list[0].kwargs, @@ -343,11 +345,15 @@ def test_set_library_component_collections(self) -> None: assert all(event["signal"] == LIBRARY_COLLECTION_UPDATED for event in collection_update_events) assert {event["library_collection"] for event in collection_update_events} == { LibraryCollectionData( - collection_key=api.library_collection_locator(self.lib2.library_key, collection_key=self.col2.collection_code), + collection_key=api.library_collection_locator( + self.lib2.library_key, collection_key=self.col2.collection_code, + ), background=True, ), LibraryCollectionData( - collection_key=api.library_collection_locator(self.lib2.library_key, collection_key=self.col3.collection_code), + collection_key=api.library_collection_locator( + self.lib2.library_key, collection_key=self.col3.collection_code, + ), background=True, ) } diff --git a/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py b/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py index a25b18dc3777..9f8e5dc1cde3 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py @@ -247,7 +247,7 @@ def test_create_invalid_library_collection(self): assert resp.status_code == 400 - # Create collection with an existing collection.key; it should fail + # Create collection with an existing collection.collection_code; it should fail post_data_existing_key = { "key": self.col1.collection_code, "title": "Collection 4", diff --git a/openedx/core/djangoapps/video_config/transcripts_utils.py b/openedx/core/djangoapps/video_config/transcripts_utils.py index c76a2b8b0377..6e6f0fde7b8c 100644 --- a/openedx/core/djangoapps/video_config/transcripts_utils.py +++ b/openedx/core/djangoapps/video_config/transcripts_utils.py @@ -1016,7 +1016,7 @@ def get_transcript_from_openedx_content(video_block, language, output_format, tr .componentversionmedia_set .filter(media__has_file=True) .select_related('media') - .get(key=file_path) + .get(path=file_path) .media ) data = media.read_file().read() diff --git a/openedx/core/djangoapps/xblock/api.py b/openedx/core/djangoapps/xblock/api.py index cc684e72b13f..a4661f67b164 100644 --- a/openedx/core/djangoapps/xblock/api.py +++ b/openedx/core/djangoapps/xblock/api.py @@ -232,9 +232,9 @@ def get_block_olx( raise NoSuchUsage(usage_key) # TODO: we should probably make a method on ComponentVersion that returns - # a content based on the name. Accessing by componentversionmedia__key is + # a content based on the name. Accessing by componentversionmedia__path is # awkward. - content = component_version.media.get(componentversionmedia__key="block.xml") + content = component_version.media.get(componentversionmedia__path="block.xml") return content.text diff --git a/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py b/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py index 92caecccd32b..1da6f048b1cc 100644 --- a/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py +++ b/openedx/core/djangoapps/xblock/runtime/openedx_content_runtime.py @@ -191,7 +191,7 @@ def get_block(self, usage_key, for_parent=None, *, version: int | LatestVersion raise NoSuchUsage(usage_key) content = component_version.media.get( - componentversionmedia__key="block.xml" + componentversionmedia__path="block.xml" ) xml_node = etree.fromstring(content.text) block_type = usage_key.block_type @@ -447,7 +447,7 @@ def _lookup_asset_url(self, block: XBlock, asset_path: str) -> str | None: component_version .componentversionmedia_set .filter(media__has_file=True) - .get(key=f"static/{asset_path}") + .get(path=f"static/{asset_path}") ) except ObjectDoesNotExist: try: @@ -458,7 +458,7 @@ def _lookup_asset_url(self, block: XBlock, asset_path: str) -> str | None: component_version .componentversionmedia_set .filter(media__has_file=True) - .get(key=f"static/{asset_path}") + .get(path=f"static/{asset_path}") ) except ObjectDoesNotExist: # This means we see a path that _looks_ like it should be a static From 85fbbdd3d23ec8aa3326ce2a24586ebdd79a643c Mon Sep 17 00:00:00 2001 From: Kyle D McCormick Date: Wed, 22 Apr 2026 11:54:47 -0400 Subject: [PATCH 9/9] fix: Return 'unknown/unknown' for un-parseable org/lib archive slugs This is necessary because we are no longer presuming that package_ref follows the same format as a Content Library. In the future, we may want a more graceful way of handling this. --- .../content_libraries/rest_api/serializers.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/openedx/core/djangoapps/content_libraries/rest_api/serializers.py b/openedx/core/djangoapps/content_libraries/rest_api/serializers.py index 425bb75c10d0..f8dd8b18a839 100644 --- a/openedx/core/djangoapps/content_libraries/rest_api/serializers.py +++ b/openedx/core/djangoapps/content_libraries/rest_api/serializers.py @@ -450,14 +450,14 @@ class RestoreSuccessDataSerializer(serializers.Serializer): """ learning_package_id = serializers.IntegerField(source="lp_restored_data.id") title = serializers.CharField(source="lp_restored_data.title") - # archive_org_code and archive_package_code may be None when archive_package_ref cannot be parsed - # as {prefix}:{org_code}:{package_code} (previously this raised ValueError in openedx-core). - org = serializers.CharField(source="lp_restored_data.archive_org_code", allow_null=True) - slug = serializers.CharField(source="lp_restored_data.archive_package_code", allow_null=True) - - # The `key` is a unique temporary key assigned to the learning package during the restore process, - # whereas the `archive_key` is the original key of the learning package from the backup. - # The temporary learning package key is replaced with a standard key once it is added to a content library. + org = serializers.SerializerMethodField() + slug = serializers.SerializerMethodField() + + # The `package_ref` is a unique temporary key assigned to the learning + # package during the restore process, whereas the `archive_package_ref` is + # the original key of the learning package from the backup. The temporary + # learning package_ref is replaced with a standard key once it is added to a + # content library. key = serializers.CharField(source="lp_restored_data.package_ref") archive_key = serializers.CharField(source="lp_restored_data.archive_package_ref") @@ -472,6 +472,18 @@ class RestoreSuccessDataSerializer(serializers.Serializer): created_at = serializers.DateTimeField(source="backup_metadata.created_at", format=DATETIME_FORMAT) created_by = serializers.SerializerMethodField() + def get_org(self, obj) -> str: + """ + The org code/slug, as parsed from archive_package_ref, or "unknown" if unparseable. + """ + return obj["lp_restored_data"]["archive_org_code"] or "unknown" + + def get_slug(self, obj) -> str: + """ + The library code/slug, as parsed from archive_package_ref, or "unknown" if unparseable. + """ + return obj["lp_restored_data"]["archive_package_code"] or "unknown" + def get_created_by(self, obj): """ Get the user information of the archive creator, if available.