From c9cbbacf0c21984cc0f818f16508eb09e1d6192d Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Sun, 16 Mar 2025 15:36:53 +1030 Subject: [PATCH 1/6] feat: adds get_containers and get_units to the authoring API --- .../apps/authoring/publishing/api.py | 20 +++++++++++++++++++ openedx_learning/apps/authoring/units/api.py | 12 +++++++++++ .../apps/authoring/units/test_api.py | 12 +++++++++++ 3 files changed, 44 insertions(+) diff --git a/openedx_learning/apps/authoring/publishing/api.py b/openedx_learning/apps/authoring/publishing/api.py index 231e42c4c..3a0852c4b 100644 --- a/openedx_learning/apps/authoring/publishing/api.py +++ b/openedx_learning/apps/authoring/publishing/api.py @@ -71,6 +71,7 @@ "create_container_version", "create_next_container_version", "get_container", + "get_containers", "ContainerEntityListEntry", "get_entities_in_container", "contains_unpublished_changes", @@ -837,6 +838,25 @@ def get_container(pk: int) -> Container: return Container.objects.get(pk=pk) +def get_containers( + learning_package_id: int, + container_cls: type[ContainerModel] = Container, # type: ignore[assignment] +) -> QuerySet[ContainerModel]: + """ + [ 🛑 UNSTABLE ] + Get all containers in the given learning package. + + Args: + learning_package_id: The primary key of the learning package + container_cls: The subclass of Container to use, if applicable + + Returns: + A queryset containing the container associated with the given learning package. + """ + assert issubclass(container_cls, Container) + return container_cls.objects.filter(publishable_entity__learning_package=learning_package_id) + + @dataclass(frozen=True) class ContainerEntityListEntry: """ diff --git a/openedx_learning/apps/authoring/units/api.py b/openedx_learning/apps/authoring/units/api.py index 93058dd38..e1d8df114 100644 --- a/openedx_learning/apps/authoring/units/api.py +++ b/openedx_learning/apps/authoring/units/api.py @@ -5,6 +5,7 @@ from dataclasses import dataclass from datetime import datetime +from django.db.models import QuerySet from django.db.transaction import atomic from openedx_learning.apps.authoring.components.models import Component, ComponentVersion @@ -21,6 +22,7 @@ "create_unit_and_version", "get_unit", "get_unit_version", + "get_units", "get_latest_unit_version", "UnitListEntry", "get_components_in_unit", @@ -211,6 +213,16 @@ def get_latest_unit_version(unit_pk: int) -> UnitVersion: return Unit.objects.get(pk=unit_pk).versioning.latest +def get_units(learning_package_id: int) -> QuerySet[Unit]: + """ + [ 🛑 UNSTABLE ] Get all units in a given learning package. + + Args: + learning_package_id: The learning package ID. + """ + return publishing_api.get_containers(learning_package_id, container_cls=Unit) + + @dataclass(frozen=True) class UnitListEntry: """ diff --git a/tests/openedx_learning/apps/authoring/units/test_api.py b/tests/openedx_learning/apps/authoring/units/test_api.py index 1556a34b8..5e31b12b5 100644 --- a/tests/openedx_learning/apps/authoring/units/test_api.py +++ b/tests/openedx_learning/apps/authoring/units/test_api.py @@ -90,6 +90,18 @@ def test_get_unit(self): with self.assertNumQueries(0): assert result.versioning.has_unpublished_changes + def test_get_units(self): + """ + Test get_units() + """ + unit = self.create_unit_with_components([]) + with self.assertNumQueries(1): + result = list(authoring_api.get_units(self.learning_package.id)) + assert result == [unit] + # Versioning data should be pre-loaded via select_related() + with self.assertNumQueries(0): + assert result[0].versioning.has_unpublished_changes + def test_get_container(self): """ Test get_container() From 59cdfe2a83f49d6e88fe3f62fcadf525225e5b0b Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Mon, 17 Mar 2025 10:51:38 +1030 Subject: [PATCH 2/6] feat: adds get_container_by_key to authoring api --- .../apps/authoring/publishing/api.py | 20 +++++++++++++++++++ .../apps/authoring/units/test_api.py | 15 ++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/openedx_learning/apps/authoring/publishing/api.py b/openedx_learning/apps/authoring/publishing/api.py index 3a0852c4b..4978b0efb 100644 --- a/openedx_learning/apps/authoring/publishing/api.py +++ b/openedx_learning/apps/authoring/publishing/api.py @@ -71,6 +71,7 @@ "create_container_version", "create_next_container_version", "get_container", + "get_container_by_key", "get_containers", "ContainerEntityListEntry", "get_entities_in_container", @@ -838,6 +839,25 @@ def get_container(pk: int) -> Container: return Container.objects.get(pk=pk) +def get_container_by_key(learning_package_id: int, /, key: str) -> Container: + """ + [ 🛑 UNSTABLE ] + Get a container by its learning package and primary key. + + Args: + learning_package_id: The ID of the learning package that contains the container. + key: The primary key of the container. + + Returns: + The container with the given primary key. + """ + entity = get_publishable_entity_by_key( + learning_package_id, + key=key, + ) + return get_container(entity.id) + + def get_containers( learning_package_id: int, container_cls: type[ContainerModel] = Container, # type: ignore[assignment] diff --git a/tests/openedx_learning/apps/authoring/units/test_api.py b/tests/openedx_learning/apps/authoring/units/test_api.py index 5e31b12b5..9c4910a53 100644 --- a/tests/openedx_learning/apps/authoring/units/test_api.py +++ b/tests/openedx_learning/apps/authoring/units/test_api.py @@ -114,6 +114,21 @@ def test_get_container(self): with self.assertNumQueries(0): assert result.versioning.has_unpublished_changes + def test_get_container_by_key(self): + """ + Test get_container_by_key() + """ + unit = self.create_unit_with_components([]) + with self.assertNumQueries(2): + result = authoring_api.get_container_by_key( + self.learning_package.id, + key=unit.publishable_entity.key, + ) + assert result == unit.container + # Versioning data should be pre-loaded via select_related() + with self.assertNumQueries(0): + assert result.versioning.has_unpublished_changes + def test_unit_container_versioning(self): """ Test that the .versioning helper of a Unit returns a UnitVersion, and From be2c53fa8006187a563855daa489786e182231da Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Tue, 18 Mar 2025 09:51:30 +1030 Subject: [PATCH 3/6] refactor: make get_component_by_key run with only 1 query --- openedx_learning/apps/authoring/publishing/api.py | 7 +++---- tests/openedx_learning/apps/authoring/units/test_api.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openedx_learning/apps/authoring/publishing/api.py b/openedx_learning/apps/authoring/publishing/api.py index 4978b0efb..6b27f0bb1 100644 --- a/openedx_learning/apps/authoring/publishing/api.py +++ b/openedx_learning/apps/authoring/publishing/api.py @@ -851,11 +851,10 @@ def get_container_by_key(learning_package_id: int, /, key: str) -> Container: Returns: The container with the given primary key. """ - entity = get_publishable_entity_by_key( - learning_package_id, - key=key, + return Container.objects.get( + publishable_entity__learning_package_id=learning_package_id, + publishable_entity__key=key, ) - return get_container(entity.id) def get_containers( diff --git a/tests/openedx_learning/apps/authoring/units/test_api.py b/tests/openedx_learning/apps/authoring/units/test_api.py index 9c4910a53..b5997dac2 100644 --- a/tests/openedx_learning/apps/authoring/units/test_api.py +++ b/tests/openedx_learning/apps/authoring/units/test_api.py @@ -119,7 +119,7 @@ def test_get_container_by_key(self): Test get_container_by_key() """ unit = self.create_unit_with_components([]) - with self.assertNumQueries(2): + with self.assertNumQueries(1): result = authoring_api.get_container_by_key( self.learning_package.id, key=unit.publishable_entity.key, From 35d686a9a455e388766f11ecd0e6147a4f977ba9 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Wed, 19 Mar 2025 09:41:35 +1030 Subject: [PATCH 4/6] revert: remove added get_units; use get_containers instead --- openedx_learning/apps/authoring/units/api.py | 12 ------------ .../apps/authoring/units/test_api.py | 8 ++++---- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/openedx_learning/apps/authoring/units/api.py b/openedx_learning/apps/authoring/units/api.py index e1d8df114..93058dd38 100644 --- a/openedx_learning/apps/authoring/units/api.py +++ b/openedx_learning/apps/authoring/units/api.py @@ -5,7 +5,6 @@ from dataclasses import dataclass from datetime import datetime -from django.db.models import QuerySet from django.db.transaction import atomic from openedx_learning.apps.authoring.components.models import Component, ComponentVersion @@ -22,7 +21,6 @@ "create_unit_and_version", "get_unit", "get_unit_version", - "get_units", "get_latest_unit_version", "UnitListEntry", "get_components_in_unit", @@ -213,16 +211,6 @@ def get_latest_unit_version(unit_pk: int) -> UnitVersion: return Unit.objects.get(pk=unit_pk).versioning.latest -def get_units(learning_package_id: int) -> QuerySet[Unit]: - """ - [ 🛑 UNSTABLE ] Get all units in a given learning package. - - Args: - learning_package_id: The learning package ID. - """ - return publishing_api.get_containers(learning_package_id, container_cls=Unit) - - @dataclass(frozen=True) class UnitListEntry: """ diff --git a/tests/openedx_learning/apps/authoring/units/test_api.py b/tests/openedx_learning/apps/authoring/units/test_api.py index b5997dac2..9db84fd82 100644 --- a/tests/openedx_learning/apps/authoring/units/test_api.py +++ b/tests/openedx_learning/apps/authoring/units/test_api.py @@ -90,14 +90,14 @@ def test_get_unit(self): with self.assertNumQueries(0): assert result.versioning.has_unpublished_changes - def test_get_units(self): + def test_get_containers(self): """ - Test get_units() + Test get_containers() """ unit = self.create_unit_with_components([]) with self.assertNumQueries(1): - result = list(authoring_api.get_units(self.learning_package.id)) - assert result == [unit] + result = list(authoring_api.get_containers(self.learning_package.id)) + assert result == [unit.container] # Versioning data should be pre-loaded via select_related() with self.assertNumQueries(0): assert result[0].versioning.has_unpublished_changes From ad73cbf9c9cf20907053c2b5f0fa30e300b0daf2 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Wed, 19 Mar 2025 09:42:52 +1030 Subject: [PATCH 5/6] test: improve test coverage of units api --- .../apps/authoring/units/test_api.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/openedx_learning/apps/authoring/units/test_api.py b/tests/openedx_learning/apps/authoring/units/test_api.py index 9db84fd82..e0fcb76c8 100644 --- a/tests/openedx_learning/apps/authoring/units/test_api.py +++ b/tests/openedx_learning/apps/authoring/units/test_api.py @@ -90,6 +90,26 @@ def test_get_unit(self): with self.assertNumQueries(0): assert result.versioning.has_unpublished_changes + def test_get_unit_version(self): + """ + Test get_unit_version() + """ + unit = self.create_unit_with_components([]) + draft = unit.versioning.draft + with self.assertNumQueries(1): + result = authoring_api.get_unit_version(draft.pk) + assert result == draft + + def test_get_latest_unit_version(self): + """ + Test test_get_latest_unit_version() + """ + unit = self.create_unit_with_components([]) + draft = unit.versioning.draft + with self.assertNumQueries(2): + result = authoring_api.get_latest_unit_version(unit.pk) + assert result == draft + def test_get_containers(self): """ Test get_containers() @@ -833,6 +853,8 @@ def test_snapshots_of_published_unit(self): # At first the unit has one component (unpinned): unit = self.create_unit_with_components([self.component_1]) self.modify_component(self.component_1, title="Component 1 as of checkpoint 1") + before_publish = authoring_api.get_components_in_published_unit_as_of(unit, 0) + assert before_publish is None # Publish everything, creating Checkpoint 1 checkpoint_1 = authoring_api.publish_all_drafts(self.learning_package.id, message="checkpoint 1") From 96ad3894ad94e4457e7b9569e6606a9b21e7b581 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Thu, 20 Mar 2025 06:12:50 +1030 Subject: [PATCH 6/6] chore: bumps package version --- openedx_learning/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openedx_learning/__init__.py b/openedx_learning/__init__.py index f2c501eeb..387eb5606 100644 --- a/openedx_learning/__init__.py +++ b/openedx_learning/__init__.py @@ -2,4 +2,4 @@ Open edX Learning ("Learning Core"). """ -__version__ = "0.19.0" +__version__ = "0.19.1"