diff --git a/openedx/core/djangoapps/content/search/documents.py b/openedx/core/djangoapps/content/search/documents.py index 4623b814d0a0..3041962da81c 100644 --- a/openedx/core/djangoapps/content/search/documents.py +++ b/openedx/core/djangoapps/content/search/documents.py @@ -627,42 +627,21 @@ def searchable_doc_for_container( log.error(f"Container {container_key} not found") return doc - draft_children = lib_api.get_container_children( - container_key, - published=False, - ) + draft_children = lib_api.get_container_children_v2(container_key, published=False) publish_status = PublishStatus.published if container.last_published is None: publish_status = PublishStatus.never elif container.has_unpublished_changes: publish_status = PublishStatus.modified - container_type_code = container_key.container_type - - def get_child_keys(children) -> list[str]: - match container_type_code: - case Unit.type_code: - return [ - str(child.usage_key) - for child in children - ] - case Subsection.type_code | Section.type_code: - return [ - str(child.container_key) - for child in children - ] - - def get_child_names(children) -> list[str]: - return [child.display_name for child in children] - doc.update({ Fields.display_name: container.display_name, Fields.created: container.created.timestamp(), Fields.modified: container.modified.timestamp(), Fields.num_children: len(draft_children), Fields.content: { - Fields.child_usage_keys: get_child_keys(draft_children), - Fields.child_display_names: get_child_names(draft_children), + Fields.child_usage_keys: [str(child.key) for child in draft_children], + Fields.child_display_names: [child.display_name for child in draft_children], }, Fields.publish_status: publish_status, Fields.last_published: container.last_published.timestamp() if container.last_published else None, @@ -672,16 +651,13 @@ def get_child_names(children) -> list[str]: doc[Fields.breadcrumbs] = [{"display_name": library.title}] if container.published_version_num is not None: - published_children = lib_api.get_container_children( - container_key, - published=True, - ) + published_children = lib_api.get_container_children_v2(container_key, published=True) doc[Fields.published] = { Fields.published_display_name: container.published_display_name, Fields.published_num_children: len(published_children), Fields.published_content: { - Fields.child_usage_keys: get_child_keys(published_children), - Fields.child_display_names: get_child_names(published_children), + Fields.child_usage_keys: [str(child.key) for child in published_children], + Fields.child_display_names: [child.display_name for child in published_children], }, } diff --git a/openedx/core/djangoapps/content_libraries/api/block_metadata.py b/openedx/core/djangoapps/content_libraries/api/block_metadata.py index cb76aee22ed1..463657097196 100644 --- a/openedx/core/djangoapps/content_libraries/api/block_metadata.py +++ b/openedx/core/djangoapps/content_libraries/api/block_metadata.py @@ -46,7 +46,12 @@ class LibraryXBlockMetadata(PublishableItem): usage_key: LibraryUsageLocatorV2 @classmethod - def from_component(cls, library_key, component, associated_collections=None): + def from_component( + cls, + library_key: LibraryLocatorV2, + component, + associated_collections=None, + ) -> LibraryXBlockMetadata: """ Construct a LibraryXBlockMetadata from a Component object. """ diff --git a/openedx/core/djangoapps/content_libraries/api/containers.py b/openedx/core/djangoapps/content_libraries/api/containers.py index 9bb54c5daba3..72ecec45d128 100644 --- a/openedx/core/djangoapps/content_libraries/api/containers.py +++ b/openedx/core/djangoapps/content_libraries/api/containers.py @@ -7,8 +7,10 @@ import logging import operator import typing +from dataclasses import dataclass from datetime import datetime, timezone from functools import cache +from typing_extensions import deprecated from uuid import UUID, uuid4 from django.core.exceptions import ObjectDoesNotExist @@ -28,6 +30,7 @@ LibraryXBlockMetadata, direct_published_entity_from_record, get_entity_item_type, + library_component_usage_key, make_contributor, resolve_change_action, ) @@ -48,9 +51,11 @@ # 🛑 UNSTABLE: All APIs related to containers are unstable until we've figured # out our approach to dynamic content (randomized, A/B tests, etc.) __all__ = [ + "ContainerChildMetadata", "get_container", "create_container", "get_container_children", + "get_container_children_v2", "get_container_children_count", "update_container", "delete_container", @@ -70,6 +75,15 @@ log = logging.getLogger(__name__) +@dataclass(frozen=True) +class ContainerChildMetadata: + """ + Class that represents limited metadata about a child entity of a container + """ + display_name: str + key: LibraryUsageLocatorV2 | LibraryContainerLocator + + def get_container( container_key: LibraryContainerLocator, *, @@ -196,14 +210,24 @@ def restore_container(container_key: LibraryContainerLocator) -> None: content_api.set_draft_version(container.id, container.versioning.latest.pk) +@deprecated("Use `get_container_children_v2` instead.") def get_container_children( container_key: LibraryContainerLocator, *, published=False, ) -> list[LibraryXBlockMetadata | ContainerMetadata]: """ - [ 🛑 UNSTABLE ] Get the entities contained in the given container - (e.g. the components/xblocks in a unit, units in a subsection, subsections in a section) + [ 🛑 DEPRECATED: use ``get_container_children_v2`` instead ] + + Get the entities contained in the given container (e.g. the components in a + unit, units in a subsection, subsections in a section) + + This deprecated API raises an exception if you use it to get the published + version of a container when some of the container's children have had their + draft versions deleted (but published still exists). + This is because the LibraryXBlockMetadata data structure is only designed to + hold information about components that have a draft version as well as an + optional published version. So use ``get_container_children_v2`` instead. """ container = get_container_from_key(container_key) @@ -218,6 +242,27 @@ def get_container_children( return result +def get_container_children_v2( + container_key: LibraryContainerLocator, *, + published: bool, +) -> list[ContainerChildMetadata]: + """ + [ 🛑 UNSTABLE ] Get the entities contained in the given container (e.g. the + components/xblocks in a unit, units in a subsection, subsections in a section) + """ + container = get_container_from_key(container_key) + result: list[ContainerChildMetadata] = [] + for entry in content_api.get_entities_in_container(container, published=published): + if hasattr(entry.entity, "component"): # the child is a Component + key = library_component_usage_key(container_key.lib_key, entry.entity.component) + else: + assert isinstance(entry.entity.container, Container) + key = library_container_locator(container_key.lib_key, entry.entity.container) + display_name = entry.entity_version.title + result.append(ContainerChildMetadata(display_name=display_name, key=key)) + return result + + def get_container_children_count( container_key: LibraryContainerLocator, published=False,