Skip to content
Merged
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
70 changes: 66 additions & 4 deletions openedx_learning/apps/authoring/backup_restore/zipper.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ class LearningPackageZipper:
def __init__(self, learning_package: LearningPackage):
self.learning_package = learning_package

def create_folder(self, folder_path: Path, zip_file: zipfile.ZipFile) -> None:
"""
Create a folder for the zip file structure.
Args:
folder_path (Path): The path of the folder to create.
"""
zip_info = zipfile.ZipInfo(str(folder_path) + "/")
zip_file.writestr(zip_info, "") # Add explicit empty directory entry

def create_zip(self, path: str) -> None:
"""
Creates a zip file containing the learning package data.
Expand All @@ -29,25 +38,78 @@ def create_zip(self, path: str) -> None:
Exception: If the learning package cannot be found or if the zip creation fails.
"""
package_toml_content: str = toml_learning_package(self.learning_package)
folders_already_created = set()

with zipfile.ZipFile(path, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
# Add the package.toml string
zipf.writestr(TOML_PACKAGE_NAME, package_toml_content)

# Add the entities directory
entities_folder = Path("entities")
zip_info = zipfile.ZipInfo(str(entities_folder) + "/") # Ensure trailing slash
zipf.writestr(zip_info, "") # Add explicit empty directory entry
self.create_folder(entities_folder, zipf)

# Add the collections directory
collections_folder = Path("collections")
collections_info = zipfile.ZipInfo(str(collections_folder) + "/") # Ensure trailing slash
zipf.writestr(collections_info, "") # Add explicit empty directory
self.create_folder(collections_folder, zipf)

# Add each entity's TOML file
for entity in publishing_api.get_entities(self.learning_package.pk):
# entity: PublishableEntity = entity # Type hint for clarity

# Create a TOML representation of the entity
entity_toml_content: str = toml_publishable_entity(entity)
entity_toml_filename = f"{entity.key}.toml"
entity_toml_path = entities_folder / entity_toml_filename
zipf.writestr(str(entity_toml_path), entity_toml_content)

if hasattr(entity, 'component'):
# Create the component folder structure for the entity. The structure is as follows:
# entities/
# xblock.v1/ (component namespace)
# html/ (component type)
# my_component.toml (entity TOML file)
# my_component/ (component id)
# component_versions/
# v1/
# static/

component_namespace_folder = entities_folder / entity.component.component_type.namespace
# Example of component namespace is: "xblock.v1"
if component_namespace_folder not in folders_already_created:
self.create_folder(component_namespace_folder, zipf)
folders_already_created.add(component_namespace_folder)

component_type_folder = component_namespace_folder / entity.component.component_type.name
# Example of component type is: "html"
if component_type_folder not in folders_already_created:
self.create_folder(component_type_folder, zipf)
folders_already_created.add(component_type_folder)

component_id_folder = component_type_folder / entity.component.local_key # entity.key
# Example of component id is: "i-dont-like-the-sidebar-aa1645ade4a7"
if component_id_folder not in folders_already_created:
self.create_folder(component_id_folder, zipf)
folders_already_created.add(component_id_folder)

# Add the entity TOML file inside the component type folder as well
component_entity_toml_path = component_type_folder / f"{entity.component.local_key}.toml"
zipf.writestr(str(component_entity_toml_path), entity_toml_content)

# Add component version folder into the component id folder
component_version_folder = component_id_folder / "component_versions"
if component_version_folder not in folders_already_created:
self.create_folder(component_version_folder, zipf)
folders_already_created.add(component_version_folder)

for entity_version in entity.component.versions.all():
component_number_version_folder = component_version_folder / f"v{entity_version.version_num}"
# Create a folder for each version of the component. Example: "v1", "v2", etc.
if component_number_version_folder not in folders_already_created:
self.create_folder(component_number_version_folder, zipf)
folders_already_created.add(component_number_version_folder)

# Add the static folder inside the component version folder
static_folder = component_number_version_folder / "static"
if static_folder not in folders_already_created:
self.create_folder(static_folder, zipf)
folders_already_created.add(static_folder)
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ def setUpTestData(cls):
created_by=cls.user.id,
)

# components = self.learning_package.publishable_entities.all()
components = api.get_components(cls.learning_package)
components = api.get_entities(cls.learning_package)
cls.all_components = components

def check_toml_file(self, zip_path: Path, zip_member_name: Path, content_to_check: list):
Expand All @@ -111,6 +110,17 @@ def check_zip_file_structure(self, zip_path: Path):
"package.toml",
"entities/",
"collections/",
"entities/xblock.v1/",
"entities/xblock.v1/html/",
"entities/xblock.v1/html/my_draft_example/",
"entities/xblock.v1/html/my_draft_example/component_versions/",
"entities/xblock.v1/html/my_draft_example/component_versions/v1/",
"entities/xblock.v1/html/my_draft_example/component_versions/v1/static/",
"entities/xblock.v1/problem/",
"entities/xblock.v1/problem/my_published_example/",
"entities/xblock.v1/problem/my_published_example/component_versions/",
"entities/xblock.v1/problem/my_published_example/component_versions/v1/",
"entities/xblock.v1/problem/my_published_example/component_versions/v1/static/",
]

# Add expected entity files
Expand Down Expand Up @@ -162,20 +172,22 @@ def test_lp_dump_command(self):

# Check the content of the entity TOML files
for entity in self.all_components:
current_draft_version = getattr(entity, "draft", None)
current_published_version = getattr(entity, "published", None)
expected_content = [
'[entity]',
f'uuid = "{entity.uuid}"',
'can_stand_alone = true',
'[entity.draft]',
f'version_num = {entity.versioning.draft.version_num}',
f'version_num = {current_draft_version.version.version_num}',
'[entity.published]',
]
if entity.versioning.published:
expected_content.append(f'version_num = {entity.versioning.published.version_num}')
if current_published_version:
expected_content.append(f'version_num = {current_published_version.version.version_num}')
else:
expected_content.append('# unpublished: no published_version_num')

for entity_version in entity.versioning.versions.all():
for entity_version in entity.versions.all():
expected_content.append(f'title = "{entity_version.title}"')
expected_content.append(f'uuid = "{entity_version.uuid}"')
expected_content.append(f'version_num = {entity_version.version_num}')
Expand Down