55import hashlib
66import zipfile
77from pathlib import Path
8- from typing import Optional
8+ from typing import List , Optional
99
10- from django .db .models import QuerySet
10+ from django .db .models import Prefetch , QuerySet
1111from django .utils .text import slugify
1212
13- from openedx_learning .apps . authoring . backup_restore . toml import toml_learning_package , toml_publishable_entity
14- from openedx_learning . apps . authoring . components . models import ComponentVersion , ComponentVersionContent
15- from openedx_learning . apps . authoring . publishing import api as publishing_api
16- from openedx_learning . apps . authoring . publishing . models import (
13+ from openedx_learning .api . authoring_models import (
14+ ComponentVersion ,
15+ ComponentVersionContent ,
16+ Content ,
1717 LearningPackage ,
1818 PublishableEntity ,
1919 PublishableEntityVersion ,
2020)
21+ from openedx_learning .apps .authoring .backup_restore .toml import toml_learning_package , toml_publishable_entity
22+ from openedx_learning .apps .authoring .publishing import api as publishing_api
2123
2224TOML_PACKAGE_NAME = "package.toml"
2325
@@ -69,6 +71,49 @@ def create_folder(self, folder_path: Path, zip_file: zipfile.ZipFile) -> None:
6971 zip_file .writestr (zip_info , "" ) # Add explicit empty directory entry
7072 self .folders_already_created .add (folder_path )
7173
74+ def get_publishable_entities (self ) -> QuerySet [PublishableEntity ]:
75+ """
76+ Retrieve the publishable entities associated with the learning package.
77+ Prefetches related data for efficiency.
78+ """
79+ lp_id = self .learning_package .pk
80+ publishable_entities : QuerySet [PublishableEntity ] = publishing_api .get_publishable_entities (lp_id )
81+ return (
82+ publishable_entities
83+ .select_related (
84+ "container" ,
85+ "component__component_type" ,
86+ "draft__version__componentversion" ,
87+ "published__version__componentversion" ,
88+ )
89+ .prefetch_related (
90+ Prefetch (
91+ "draft__version__componentversion__componentversioncontent_set" ,
92+ queryset = ComponentVersionContent .objects .select_related ("content" ),
93+ to_attr = "prefetched_contents" ,
94+ ),
95+ Prefetch (
96+ "published__version__componentversion__componentversioncontent_set" ,
97+ queryset = ComponentVersionContent .objects .select_related ("content" ),
98+ to_attr = "prefetched_contents" ,
99+ ),
100+ )
101+ )
102+
103+ def get_versions_to_write (self , entity : PublishableEntity ):
104+ """
105+ Get the versions of a publishable entity that should be written to the zip file.
106+ It retrieves both draft and published versions.
107+ """
108+ draft_version : Optional [PublishableEntityVersion ] = publishing_api .get_draft_version (entity )
109+ published_version : Optional [PublishableEntityVersion ] = publishing_api .get_published_version (entity )
110+
111+ versions_to_write = [draft_version ] if draft_version else []
112+
113+ if published_version and published_version != draft_version :
114+ versions_to_write .append (published_version )
115+ return versions_to_write
116+
72117 def create_zip (self , path : str ) -> None :
73118 """
74119 Creates a zip file containing the learning package data.
@@ -77,7 +122,6 @@ def create_zip(self, path: str) -> None:
77122 Raises:
78123 Exception: If the learning package cannot be found or if the zip creation fails.
79124 """
80- lp_id = self .learning_package .pk
81125
82126 with zipfile .ZipFile (path , "w" , compression = zipfile .ZIP_DEFLATED ) as zipf :
83127 # Add the package.toml file
@@ -95,8 +139,7 @@ def create_zip(self, path: str) -> None:
95139 # ------ ENTITIES SERIALIZATION -------------
96140
97141 # get the publishable entities
98- publishable_entities : QuerySet [PublishableEntity ] = publishing_api .get_publishable_entities (lp_id )
99- publishable_entities = publishable_entities .select_related ("container" , "component__component_type" )
142+ publishable_entities : QuerySet [PublishableEntity ] = self .get_publishable_entities ()
100143
101144 for entity in publishable_entities :
102145 # entity: PublishableEntity = entity # Type hint for clarity
@@ -155,13 +198,7 @@ def create_zip(self, path: str) -> None:
155198 # Focusing on draft and published versions
156199
157200 # Get the draft and published versions
158- draft_version : Optional [PublishableEntityVersion ] = publishing_api .get_draft_version (entity )
159- published_version : Optional [PublishableEntityVersion ] = publishing_api .get_published_version (entity )
160-
161- versions_to_write = [draft_version ] if draft_version else []
162-
163- if published_version and published_version != draft_version :
164- versions_to_write .append (published_version )
201+ versions_to_write : List [PublishableEntityVersion ] = self .get_versions_to_write (entity )
165202
166203 for version in versions_to_write :
167204 # Create a folder for the version
@@ -174,24 +211,27 @@ def create_zip(self, path: str) -> None:
174211 self .create_folder (static_folder , zipf )
175212
176213 # ------ COMPONENT STATIC CONTENT -------------
177- # Get component version
178214 component_version : ComponentVersion = version .componentversion
179215
180216 # Get content data associated with this version
181- # content_list: QuerySet[Content] = component_version.contents.all()
182- content_list : QuerySet [ComponentVersionContent ] = component_version .componentversioncontent_set . all () # pylint: disable=line-too-long # noqa: E501
217+ # type: ignore[attr-defined]
218+ content_list : QuerySet [ComponentVersionContent ] = component_version .prefetched_contents
183219
184220 for component_version_content in content_list :
185- content = component_version_content .content
221+ content : Content = component_version_content .content
222+
223+ # Important: The component_version_content.key contains implicitly
224+ # the file name and the file extension
225+ file_path = version_folder / component_version_content .key
186226
187227 if content .has_file and content .path :
188- # Add the file to the static folder
189- # file_path = static_folder / content.path
190- file_path = static_folder / component_version_content .key
228+ # If has_file, we pull it from the file system
191229 with content .read_file () as f :
192230 file_data = f .read ()
193- zipf .writestr (str (file_path ), file_data )
194231 elif not content .has_file and content .text :
195- # Create file for the text file according to the mime_type attr
196- text_file_path = static_folder / component_version_content .key
197- zipf .writestr (str (text_file_path ), content .text )
232+ # Otherwise, we use the text content as the file data
233+ file_data = content .text
234+ else :
235+ # If no file and no text, we skip this content
236+ continue
237+ zipf .writestr (str (file_path ), file_data )
0 commit comments