diff --git a/openedx_learning/__init__.py b/openedx_learning/__init__.py index b964256a2..e84267f13 100644 --- a/openedx_learning/__init__.py +++ b/openedx_learning/__init__.py @@ -2,4 +2,4 @@ Open edX Learning ("Learning Core"). """ -__version__ = "0.29.0" +__version__ = "0.29.1" diff --git a/openedx_learning/apps/authoring/backup_restore/zipper.py b/openedx_learning/apps/authoring/backup_restore/zipper.py index 59319c83d..a5bc18fd2 100644 --- a/openedx_learning/apps/authoring/backup_restore/zipper.py +++ b/openedx_learning/apps/authoring/backup_restore/zipper.py @@ -47,6 +47,7 @@ ) from openedx_learning.apps.authoring.collections import api as collections_api from openedx_learning.apps.authoring.components import api as components_api +from openedx_learning.apps.authoring.contents import api as contents_api from openedx_learning.apps.authoring.publishing import api as publishing_api from openedx_learning.apps.authoring.sections import api as sections_api from openedx_learning.apps.authoring.subsections import api as subsections_api @@ -493,6 +494,7 @@ def __init__(self, zipf: zipfile.ZipFile, key: str | None = None, user: UserType self.zipf = zipf self.user = user self.lp_key = key # If provided, use this key for the restored learning package + self.learning_package_id: int | None = None # Will be set upon restoration self.utc_now: datetime = datetime.now(timezone.utc) self.component_types_cache: dict[tuple[str, str], ComponentType] = {} self.errors: list[dict[str, Any]] = [] @@ -735,6 +737,7 @@ def _save( learning_package["key"] = self.lp_key learning_package_obj = publishing_api.create_learning_package(**learning_package) + self.learning_package_id = learning_package_obj.id with publishing_api.bulk_draft_changes_for(learning_package_obj.id): self._save_components(learning_package_obj, components, component_static_files) @@ -937,16 +940,31 @@ def _resolve_static_files( num_version: int, entity_key: str, static_files_map: dict[str, List[str]] - ) -> dict[str, bytes]: + ) -> dict[str, bytes | int]: """Resolve static file paths into their binary content.""" - resolved_files: dict[str, bytes] = {} + resolved_files: dict[str, bytes | int] = {} - static_file_key = f"{entity_key}:v{num_version}" # e.g., "my_component:123:v1" + static_file_key = f"{entity_key}:v{num_version}" # e.g., "xblock.v1:html:my_component_123456:v1" + block_type = entity_key.split(":")[1] # e.g., "html" static_files = static_files_map.get(static_file_key, []) for static_file in static_files: local_key = static_file.split(f"v{num_version}/")[-1] with self.zipf.open(static_file, "r") as f: - resolved_files[local_key] = f.read() + content_bytes = f.read() + if local_key == "block.xml": + # Special handling for block.xml to ensure + # storing the value as a content instance + if not self.learning_package_id: + raise ValueError("learning_package_id must be set before resolving static files.") + text_content = contents_api.get_or_create_text_content( + self.learning_package_id, + contents_api.get_or_create_media_type(f"application/vnd.openedx.xblock.v1.{block_type}+xml").id, + text=content_bytes.decode("utf-8"), + created=self.utc_now, + ) + resolved_files[local_key] = text_content.id + else: + resolved_files[local_key] = content_bytes return resolved_files def _resolve_children(self, entity_data: dict[str, Any], lookup_map: dict[str, Any]) -> list[Any]: diff --git a/tests/openedx_learning/apps/authoring/backup_restore/test_restore.py b/tests/openedx_learning/apps/authoring/backup_restore/test_restore.py index c53d10a89..de2d13c1b 100644 --- a/tests/openedx_learning/apps/authoring/backup_restore/test_restore.py +++ b/tests/openedx_learning/apps/authoring/backup_restore/test_restore.py @@ -82,6 +82,7 @@ def verify_containers(self, lp): assert False, f"Unexpected container key: {container.key}" def verify_components(self, lp): + # pylint: disable=too-many-statements """Verify the components and their versions were restored correctly.""" component_qs = components_api.get_components(lp.id) expected_component_keys = [ @@ -103,6 +104,13 @@ def verify_components(self, lp): assert draft_version is not None assert draft_version.version_num == 2 assert published_version is None + # Get the content associated with this component + contents = draft_version.componentversion.contents.all() + content = contents.first() if contents.exists() else None + assert content is not None + assert "