From 48bb3bb821011a3c298795a9da86404a12394836 Mon Sep 17 00:00:00 2001 From: Daniel Wong Date: Thu, 31 Jul 2025 14:30:56 -0600 Subject: [PATCH 1/3] feat: pull entity data from PublishableEntity instances --- .../apps/authoring/backup_restore/toml.py | 31 ++++++++++++------- .../apps/authoring/backup_restore/zipper.py | 4 +-- .../apps/authoring/publishing/api.py | 8 +++++ 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/openedx_learning/apps/authoring/backup_restore/toml.py b/openedx_learning/apps/authoring/backup_restore/toml.py index 05ede1df3..8017bf15c 100644 --- a/openedx_learning/apps/authoring/backup_restore/toml.py +++ b/openedx_learning/apps/authoring/backup_restore/toml.py @@ -6,11 +6,13 @@ import tomlkit -from openedx_learning.apps.authoring.publishing.models.learning_package import LearningPackage -from openedx_learning.apps.authoring.publishing.models.publishable_entity import ( - PublishableEntityMixin, - PublishableEntityVersionMixin, +from openedx_learning.apps.authoring.publishing.models import ( + Draft, + PublishableEntity, + PublishableEntityVersion, + Published, ) +from openedx_learning.apps.authoring.publishing.models.learning_package import LearningPackage def toml_learning_package(learning_package: LearningPackage) -> str: @@ -27,20 +29,25 @@ def toml_learning_package(learning_package: LearningPackage) -> str: return tomlkit.dumps(doc) -def toml_publishable_entity(entity: PublishableEntityMixin) -> str: +def toml_publishable_entity(entity: PublishableEntity) -> str: """Create a TOML representation of a publishable entity.""" + + current_draft_version = getattr(entity, "draft", None) + current_published_version = getattr(entity, "published", None) + doc = tomlkit.document() entity_table = tomlkit.table() entity_table.add("uuid", str(entity.uuid)) entity_table.add("can_stand_alone", entity.can_stand_alone) - draft = tomlkit.table() - draft.add("version_num", entity.versioning.draft.version_num) - entity_table.add("draft", draft) + if current_draft_version: + draft = tomlkit.table() + draft.add("version_num", current_draft_version.version.version_num) + entity_table.add("draft", draft) published = tomlkit.table() - if entity.versioning.published: - published.add("version_num", entity.versioning.published.version_num) + if current_published_version: + published.add("version_num", current_published_version.version.version_num) else: published.add(tomlkit.comment("unpublished: no published_version_num")) entity_table.add("published", published) @@ -49,7 +56,7 @@ def toml_publishable_entity(entity: PublishableEntityMixin) -> str: doc.add(tomlkit.nl()) doc.add(tomlkit.comment("### Versions")) - for entity_version in entity.versioning.versions.all(): + for entity_version in entity.versions.all(): version = tomlkit.aot() version_table = toml_publishable_entity_version(entity_version) version.append(version_table) @@ -58,7 +65,7 @@ def toml_publishable_entity(entity: PublishableEntityMixin) -> str: return tomlkit.dumps(doc) -def toml_publishable_entity_version(version: PublishableEntityVersionMixin) -> tomlkit.items.Table: +def toml_publishable_entity_version(version: PublishableEntityVersion) -> tomlkit.items.Table: """Create a TOML representation of a publishable entity version.""" version_table = tomlkit.table() version_table.add("title", version.title) diff --git a/openedx_learning/apps/authoring/backup_restore/zipper.py b/openedx_learning/apps/authoring/backup_restore/zipper.py index 2c07401e5..8ea73c71e 100644 --- a/openedx_learning/apps/authoring/backup_restore/zipper.py +++ b/openedx_learning/apps/authoring/backup_restore/zipper.py @@ -6,7 +6,7 @@ from pathlib import Path from openedx_learning.apps.authoring.backup_restore.toml import toml_learning_package, toml_publishable_entity -from openedx_learning.apps.authoring.components import api as components_api +from openedx_learning.apps.authoring.publishing import api as publishing_api from openedx_learning.apps.authoring.publishing.models.learning_package import LearningPackage TOML_PACKAGE_NAME = "package.toml" @@ -45,7 +45,7 @@ def create_zip(self, path: str) -> None: zipf.writestr(collections_info, "") # Add explicit empty directory # Add each entity's TOML file - for entity in components_api.get_components(self.learning_package.pk): + for entity in publishing_api.get_entities(self.learning_package.pk): # Create a TOML representation of the entity entity_toml_content: str = toml_publishable_entity(entity) entity_toml_filename = f"{entity.key}.toml" diff --git a/openedx_learning/apps/authoring/publishing/api.py b/openedx_learning/apps/authoring/publishing/api.py index 388f52cba..871df8e25 100644 --- a/openedx_learning/apps/authoring/publishing/api.py +++ b/openedx_learning/apps/authoring/publishing/api.py @@ -60,6 +60,7 @@ "get_publishable_entity_by_key", "get_last_publish", "get_all_drafts", + "get_entities", "get_entities_with_unpublished_changes", "get_entities_with_unpublished_deletes", "publish_all_drafts", @@ -261,6 +262,13 @@ def get_all_drafts(learning_package_id: int, /) -> QuerySet[Draft]: ) +def get_entities(learning_package_id: int, /) -> QuerySet[PublishableEntity]: + """ + Get all entities in a learning package. + """ + return PublishableEntity.objects.filter(learning_package_id=learning_package_id) + + def get_entities_with_unpublished_changes( learning_package_id: int, /, From 06c8cd7a77dbe7e13bae7d23ccfd51bc4eb24d4a Mon Sep 17 00:00:00 2001 From: Daniel Wong Date: Fri, 1 Aug 2025 15:44:02 -0600 Subject: [PATCH 2/3] test: add unit test for publishable.get_entities --- .../apps/authoring/publishing/test_api.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/openedx_learning/apps/authoring/publishing/test_api.py b/tests/openedx_learning/apps/authoring/publishing/test_api.py index b9e45ec91..fe9206e02 100644 --- a/tests/openedx_learning/apps/authoring/publishing/test_api.py +++ b/tests/openedx_learning/apps/authoring/publishing/test_api.py @@ -1075,3 +1075,66 @@ def test_multiple_layers_of_containers(self): assert unit_change.affected_by.first().cause == component_change assert subsection_change.affected_by.count() == 1 assert subsection_change.affected_by.first().cause == unit_change + + +class GeneralTestCase(TestCase): + """ + Test general operations with the publishing API. + """ + now: datetime + learning_package_1: LearningPackage + learning_package_2: LearningPackage + + @classmethod + def setUpTestData(cls) -> None: + cls.now = datetime(2024, 1, 28, 16, 45, 30, tzinfo=timezone.utc) + cls.learning_package_1 = publishing_api.create_learning_package( + "my_package_key_1", + "Draft Testing LearningPackage 🔥 1", + created=cls.now, + ) + cls.learning_package_2 = publishing_api.create_learning_package( + "my_package_key_2", + "Draft Testing LearningPackage 🔥 2", + created=cls.now, + ) + + def test_get_entities(self) -> None: + """ + Simplest test that multiple entities can be created and fetched. + 2 entities should be created, and both should be returned. + """ + with publishing_api.bulk_draft_changes_for(self.learning_package_1.id): + entity = publishing_api.create_publishable_entity( + self.learning_package_1.id, + "my_entity", + created=self.now, + created_by=None, + ) + publishing_api.create_publishable_entity_version( + entity.id, + version_num=1, + title="An Entity 🌴", + created=self.now, + created_by=None, + ) + entity2 = publishing_api.create_publishable_entity( + self.learning_package_1.id, + "my_entity2", + created=self.now, + created_by=None, + ) + publishing_api.create_publishable_entity_version( + entity2.id, + version_num=1, + title="An Entity 🌴 2", + created=self.now, + created_by=None, + ) + entities = publishing_api.get_entities(self.learning_package_1.id) + assert entities.count() == 2 + for entity in entities: + assert isinstance(entity, PublishableEntity) + assert entity.learning_package_id == self.learning_package_1.id + assert entity.created == self.now + assert entity.created_by is None From 7999ace0ade0282078c826856a9a2c28e740ef8b Mon Sep 17 00:00:00 2001 From: Daniel Wong Date: Mon, 4 Aug 2025 12:28:47 -0600 Subject: [PATCH 3/3] test: adjust docstrings and variable names in entities tests --- .../apps/authoring/backup_restore/toml.py | 21 +++++++------------ .../apps/authoring/publishing/test_api.py | 13 ++++++------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/openedx_learning/apps/authoring/backup_restore/toml.py b/openedx_learning/apps/authoring/backup_restore/toml.py index 8017bf15c..0551201ba 100644 --- a/openedx_learning/apps/authoring/backup_restore/toml.py +++ b/openedx_learning/apps/authoring/backup_restore/toml.py @@ -6,12 +6,7 @@ import tomlkit -from openedx_learning.apps.authoring.publishing.models import ( - Draft, - PublishableEntity, - PublishableEntityVersion, - Published, -) +from openedx_learning.apps.authoring.publishing.models import PublishableEntity, PublishableEntityVersion from openedx_learning.apps.authoring.publishing.models.learning_package import LearningPackage @@ -41,16 +36,16 @@ def toml_publishable_entity(entity: PublishableEntity) -> str: entity_table.add("can_stand_alone", entity.can_stand_alone) if current_draft_version: - draft = tomlkit.table() - draft.add("version_num", current_draft_version.version.version_num) - entity_table.add("draft", draft) + draft_table = tomlkit.table() + draft_table.add("version_num", current_draft_version.version.version_num) + entity_table.add("draft", draft_table) - published = tomlkit.table() + published_table = tomlkit.table() if current_published_version: - published.add("version_num", current_published_version.version.version_num) + published_table.add("version_num", current_published_version.version.version_num) else: - published.add(tomlkit.comment("unpublished: no published_version_num")) - entity_table.add("published", published) + published_table.add(tomlkit.comment("unpublished: no published_version_num")) + entity_table.add("published", published_table) doc.add("entity", entity_table) doc.add(tomlkit.nl()) diff --git a/tests/openedx_learning/apps/authoring/publishing/test_api.py b/tests/openedx_learning/apps/authoring/publishing/test_api.py index fe9206e02..94c328f22 100644 --- a/tests/openedx_learning/apps/authoring/publishing/test_api.py +++ b/tests/openedx_learning/apps/authoring/publishing/test_api.py @@ -1077,9 +1077,9 @@ def test_multiple_layers_of_containers(self): assert subsection_change.affected_by.first().cause == unit_change -class GeneralTestCase(TestCase): +class EntitiesQueryTestCase(TestCase): """ - Test general operations with the publishing API. + Tests for querying PublishableEntity objects. """ now: datetime learning_package_1: LearningPackage @@ -1087,22 +1087,21 @@ class GeneralTestCase(TestCase): @classmethod def setUpTestData(cls) -> None: - cls.now = datetime(2024, 1, 28, 16, 45, 30, tzinfo=timezone.utc) + cls.now = datetime(2025, 8, 4, 12, 00, 00, tzinfo=timezone.utc) cls.learning_package_1 = publishing_api.create_learning_package( "my_package_key_1", - "Draft Testing LearningPackage 🔥 1", + "Entities Testing LearningPackage 🔥 1", created=cls.now, ) cls.learning_package_2 = publishing_api.create_learning_package( "my_package_key_2", - "Draft Testing LearningPackage 🔥 2", + "Entities Testing LearningPackage 🔥 2", created=cls.now, ) def test_get_entities(self) -> None: """ - Simplest test that multiple entities can be created and fetched. - 2 entities should be created, and both should be returned. + Test that get_entities returns all entities for a learning package. """ with publishing_api.bulk_draft_changes_for(self.learning_package_1.id): entity = publishing_api.create_publishable_entity(