From 9d919762ac854a59b51d1e5f37679524a8cc6cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ch=C3=A1vez?= <xnpiochv@gmail.com> Date: Tue, 12 Nov 2024 12:42:18 -0500 Subject: [PATCH] feat: add filter functions to publishing API [FC-0062] (#257) Adds filter_publishable_entities() to openedx.apps.authoring.publishing.api (and the public openedx.api.authoring package). This also bumps the version to 0.18.0 The motivation for this change is so that other apps can filter their PublishableEntity querysets without having to dig into the internals of the "publishing" app's data model relations. For instance, the "collections" app could already answer the question, "What Publishable Entities are in this Collection?" But to answer the question of, "What are the Publishable Entities in this Collection that have a published version?" requires filtering on "published__version__isnull", which is a level of detail that we don't want to burden other apps with. With this commit, they could call something like this instead: published_entities = filter_publishable_entities( collection.entities(), has_published=True, ) It's possible that this could be done in a more natural way with custom Managers/QuerySets. The main concern there would be to make sure that those come across correctly in various RelatedManagers, e.g. to make sure that something like "Collection.entities" returns the customized version. This is something we can follow up on in the future. --- openedx_learning/__init__.py | 2 +- .../apps/authoring/publishing/api.py | 20 +++++ .../apps/authoring/publishing/test_api.py | 74 +++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/openedx_learning/__init__.py b/openedx_learning/__init__.py index 23b5c7c2..f99dcab8 100644 --- a/openedx_learning/__init__.py +++ b/openedx_learning/__init__.py @@ -2,4 +2,4 @@ Open edX Learning ("Learning Core"). """ -__version__ = "0.17.0" +__version__ = "0.18.0" diff --git a/openedx_learning/apps/authoring/publishing/api.py b/openedx_learning/apps/authoring/publishing/api.py index 7e2c6ba9..3facc891 100644 --- a/openedx_learning/apps/authoring/publishing/api.py +++ b/openedx_learning/apps/authoring/publishing/api.py @@ -50,6 +50,7 @@ "soft_delete_draft", "reset_drafts_to_published", "register_content_models", + "filter_publishable_entities", ] @@ -493,3 +494,22 @@ def ready(self): return PublishableContentModelRegistry.register( content_model_cls, content_version_model_cls ) + + +def filter_publishable_entities( + entities: QuerySet[PublishableEntity], + has_draft=None, + has_published=None +) -> QuerySet[PublishableEntity]: + """ + Filter an entities query set. + + has_draft: You can filter by entities that has a draft or not. + has_published: You can filter by entities that has a published version or not. + """ + if has_draft is not None: + entities = entities.filter(draft__version__isnull=not has_draft) + if has_published is not None: + entities = entities.filter(published__version__isnull=not has_published) + + return entities diff --git a/tests/openedx_learning/apps/authoring/publishing/test_api.py b/tests/openedx_learning/apps/authoring/publishing/test_api.py index 421dd66c..119a5548 100644 --- a/tests/openedx_learning/apps/authoring/publishing/test_api.py +++ b/tests/openedx_learning/apps/authoring/publishing/test_api.py @@ -368,6 +368,80 @@ def test_get_entities_with_unpublished_changes(self) -> None: # should not return published soft-deleted entities. assert len(entities) == 0 + def test_filter_publishable_entities(self) -> None: + count_published = 7 + count_drafts = 6 + count_no_drafts = 3 + + for index in range(count_published): + # Create entities to publish + entity = publishing_api.create_publishable_entity( + self.learning_package.id, + f"entity_published_{index}", + created=self.now, + created_by=None, + ) + + publishing_api.create_publishable_entity_version( + entity.id, + version_num=1, + title=f"Entity_published_{index}", + created=self.now, + created_by=None, + ) + + publishing_api.publish_all_drafts(self.learning_package.id) + + for index in range(count_drafts): + # Create entities with drafts + entity = publishing_api.create_publishable_entity( + self.learning_package.id, + f"entity_draft_{index}", + created=self.now, + created_by=None, + ) + + publishing_api.create_publishable_entity_version( + entity.id, + version_num=1, + title=f"Entity_draft_{index}", + created=self.now, + created_by=None, + ) + + for index in range(count_no_drafts): + # Create entities without drafts + entity = publishing_api.create_publishable_entity( + self.learning_package.id, + f"entity_no_draft_{index}", + created=self.now, + created_by=None, + ) + + drafts = publishing_api.filter_publishable_entities( + PublishableEntity.objects.all(), + has_draft=True, + ) + assert drafts.count() == (count_published + count_drafts) + + no_drafts = publishing_api.filter_publishable_entities( + PublishableEntity.objects.all(), + has_draft=False, + ) + assert no_drafts.count() == count_no_drafts + + published = publishing_api.filter_publishable_entities( + PublishableEntity.objects.all(), + has_published=True, + ) + assert published.count() == count_published + + no_published = publishing_api.filter_publishable_entities( + PublishableEntity.objects.all(), + has_published=False, + ) + assert no_published.count() == (count_drafts + count_no_drafts) + def _get_published_version_num(self, entity: PublishableEntity) -> int | None: published_version = publishing_api.get_published_version(entity.id) if published_version is not None: