Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 6 additions & 30 deletions openedx/core/djangoapps/content/search/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from opaque_keys.edx.keys import ContainerKey, LearningContextKey, OpaqueKey, UsageKey
from opaque_keys.edx.locator import LibraryCollectionLocator, LibraryContainerLocator
from openedx_content import api as content_api
from openedx_content.models_api import Collection, Section, Subsection, Unit

Check failure on line 15 in openedx/core/djangoapps/content/search/documents.py

View workflow job for this annotation

GitHub Actions / Quality Others (ubuntu-24.04, 3.12, 20)

ruff (F401)

openedx/core/djangoapps/content/search/documents.py:15:73: F401 `openedx_content.models_api.Unit` imported but unused help: Remove unused import

Check failure on line 15 in openedx/core/djangoapps/content/search/documents.py

View workflow job for this annotation

GitHub Actions / Quality Others (ubuntu-24.04, 3.12, 20)

ruff (F401)

openedx/core/djangoapps/content/search/documents.py:15:61: F401 `openedx_content.models_api.Subsection` imported but unused help: Remove unused import

Check failure on line 15 in openedx/core/djangoapps/content/search/documents.py

View workflow job for this annotation

GitHub Actions / Quality Others (ubuntu-24.04, 3.12, 20)

ruff (F401)

openedx/core/djangoapps/content/search/documents.py:15:52: F401 `openedx_content.models_api.Section` imported but unused help: Remove unused import
from rest_framework.exceptions import NotFound

from openedx.core.djangoapps.content.search.models import SearchAccess
Expand Down Expand Up @@ -627,42 +627,21 @@
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,
Expand All @@ -672,16 +651,13 @@
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],
},
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand Down
49 changes: 47 additions & 2 deletions openedx/core/djangoapps/content_libraries/api/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,47 @@
API for containers (Sections, Subsections, Units) in Content Libraries
"""

from __future__ import annotations

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
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
from openedx_content.models_api import Container, PublishLogRecord
from openedx_events.content_authoring.data import LibraryContainerData
from openedx_events.content_authoring.signals import LIBRARY_CONTAINER_DELETED

from ..models import ContentLibrary
from .block_metadata import (
LibraryHistoryContributor,
LibraryHistoryEntry,
LibraryPublishHistoryGroup,
LibraryXBlockMetadata,
direct_published_entity_from_record,
get_entity_item_type,
library_component_usage_key,
make_contributor,
resolve_change_action,
)
from .container_metadata import (
LIBRARY_ALLOWED_CONTAINER_TYPES,
ContainerHierarchy,
ContainerMetadata,
get_container_from_key,
get_entity_from_key,
library_container_locator,
)
from .serializers import ContainerSerializer

Check failure on line 45 in openedx/core/djangoapps/content_libraries/api/containers.py

View workflow job for this annotation

GitHub Actions / Quality Others (ubuntu-24.04, 3.12, 20)

ruff (I001)

openedx/core/djangoapps/content_libraries/api/containers.py:5:1: I001 Import block is un-sorted or un-formatted help: Organize imports

if typing.TYPE_CHECKING:
from openedx.core.djangoapps.content_staging.api import UserClipboardData
Expand All @@ -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",
Expand All @@ -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,
*,
Expand Down Expand Up @@ -196,14 +210,24 @@
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)

Expand All @@ -218,6 +242,27 @@
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,
Expand Down
Loading