From e48bdee8dca456cc1bc59897b64325677a885036 Mon Sep 17 00:00:00 2001 From: Kyle D McCormick Date: Tue, 22 Apr 2025 19:26:41 -0400 Subject: [PATCH 1/2] feat: Explicit mapping from container_types to OLX tags --- .../content_libraries/api/containers.py | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/openedx/core/djangoapps/content_libraries/api/containers.py b/openedx/core/djangoapps/content_libraries/api/containers.py index 3a0a89550cc4..1eb4d58286ac 100644 --- a/openedx/core/djangoapps/content_libraries/api/containers.py +++ b/openedx/core/djangoapps/content_libraries/api/containers.py @@ -7,6 +7,7 @@ from datetime import datetime from enum import Enum import logging +import typing as t from uuid import uuid4 from django.utils.text import slugify @@ -60,7 +61,47 @@ class ContainerType(Enum): + """ + The container types supported by content_libraries, and logic to map them to OLX. + """ Unit = "unit" + Subsection = "subsection" + Section = "section" + + @property + def olx_tag(self) -> str: + """ + Canonical XML tag to use when representing this container as OLX. + + For example, Units are encoded as .... + + These tag names are historical. We keep them around for the backwards compatibility of OLX + and for easier interaction with legacy modulestore-powered structural XBlocks + (e.g., copy-paste of Units between courses and V2 libraries). + """ + match self: + case self.Unit: + return "vertical" + case self.Subsection: + return "sequential" + case self.Section: + return "chapter" + raise TypeError(f"unexpected ContainerType: {self!r}") + + @classmethod + def from_source_olx_tag(cls, olx_tag: str) -> t.Self: + """ + Get the ContainerType that this OLX tag maps to. + """ + if olx_tag == "unit": + # There is an alternative implementation to VerticalBlock called UnitBlock whose + # OLX tag is . When converting from OLX, we want to handle both + # and as Unit containers, although the canonical serialization is still . + return cls.Unit + try: + return next(ct for ct in cls if olx_tag == ct.olx_tag) + except StopIteration: + raise ValueError(f"no container_type for XML tag: <{olx_tag}>") from None @dataclass(frozen=True, kw_only=True) @@ -83,7 +124,6 @@ def from_container(cls, library_key, container: Container, associated_collection container=container, ) container_type = ContainerType(container_key.container_type) - published_by = "" if last_publish_log and last_publish_log.published_by: published_by = last_publish_log.published_by.username @@ -209,7 +249,7 @@ def create_container( created_by=user_id, ) case _: - raise ValueError(f"Invalid container type: {container_type}") + raise NotImplementedError(f"Library support for {container_type} is in progress") LIBRARY_CONTAINER_CREATED.send_event( library_container=LibraryContainerData( From aa30452df251d63ee5222d491ae512e9812893d3 Mon Sep 17 00:00:00 2001 From: Kyle D McCormick Date: Tue, 22 Apr 2025 19:52:29 -0400 Subject: [PATCH 2/2] fix: make mypy happy --- openedx/core/djangoapps/content_libraries/api/containers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openedx/core/djangoapps/content_libraries/api/containers.py b/openedx/core/djangoapps/content_libraries/api/containers.py index 1eb4d58286ac..393755ed1c91 100644 --- a/openedx/core/djangoapps/content_libraries/api/containers.py +++ b/openedx/core/djangoapps/content_libraries/api/containers.py @@ -7,7 +7,6 @@ from datetime import datetime from enum import Enum import logging -import typing as t from uuid import uuid4 from django.utils.text import slugify @@ -89,7 +88,7 @@ def olx_tag(self) -> str: raise TypeError(f"unexpected ContainerType: {self!r}") @classmethod - def from_source_olx_tag(cls, olx_tag: str) -> t.Self: + def from_source_olx_tag(cls, olx_tag: str) -> 'ContainerType': """ Get the ContainerType that this OLX tag maps to. """