From d2e5e3bfe2a3ee4a5fc79f4fa102590a9754193b Mon Sep 17 00:00:00 2001 From: Jillian Date: Thu, 1 Aug 2024 02:41:46 +0930 Subject: [PATCH] feat: get_last_publish log for PublishableEntity [FC-0059] (#204) * feat: adds VersioningHelper.last_publish_log property and adds test Adds freezegun to the test requirements so we can check published_at dates. * perf: prefetches fields for VersioningHelper.last_publish_log and adds test * chore: bumps version to 0.10.1 --- openedx_learning/__init__.py | 2 +- .../apps/authoring/components/models.py | 2 + .../apps/authoring/publishing/model_mixins.py | 12 +++ requirements/dev.txt | 3 + requirements/doc.txt | 3 + requirements/quality.txt | 3 + requirements/test.in | 1 + requirements/test.txt | 3 + .../apps/authoring/components/test_api.py | 1 + .../apps/authoring/components/test_models.py | 86 ++++++++++++++++++- 10 files changed, 114 insertions(+), 2 deletions(-) diff --git a/openedx_learning/__init__.py b/openedx_learning/__init__.py index bcc19e54..7262add0 100644 --- a/openedx_learning/__init__.py +++ b/openedx_learning/__init__.py @@ -1,4 +1,4 @@ """ Open edX Learning ("Learning Core"). """ -__version__ = "0.10.0" +__version__ = "0.10.1" diff --git a/openedx_learning/apps/authoring/components/models.py b/openedx_learning/apps/authoring/components/models.py index 1e6f5215..df17a999 100644 --- a/openedx_learning/apps/authoring/components/models.py +++ b/openedx_learning/apps/authoring/components/models.py @@ -134,6 +134,8 @@ class Component(PublishableEntityMixin): # type: ignore[django-manager-missing] 'publishable_entity__draft__version__componentversion', 'publishable_entity__published__version', 'publishable_entity__published__version__componentversion', + 'publishable_entity__published__publish_log_record', + 'publishable_entity__published__publish_log_record__publish_log', ) # This foreign key is technically redundant because we're already locked to diff --git a/openedx_learning/apps/authoring/publishing/model_mixins.py b/openedx_learning/apps/authoring/publishing/model_mixins.py index ab5672f1..1f0210da 100644 --- a/openedx_learning/apps/authoring/publishing/model_mixins.py +++ b/openedx_learning/apps/authoring/publishing/model_mixins.py @@ -243,6 +243,18 @@ def has_unpublished_changes(self): return draft_version_id != published_version_id + @property + def last_publish_log(self): + """ + Return the most recent PublishLog for this component. + + Return None if the component is not published. + """ + pub_entity = self.content_obj.publishable_entity + if hasattr(pub_entity, 'published'): + return pub_entity.published.publish_log_record.publish_log + return None + @property def versions(self): """ diff --git a/requirements/dev.txt b/requirements/dev.txt index db165641..59ae351a 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -178,6 +178,8 @@ filelock==3.15.4 # -r requirements/ci.txt # tox # virtualenv +freezegun==1.5.1 + # via -r requirements/quality.txt grimp==3.4.1 # via # -r requirements/ci.txt @@ -391,6 +393,7 @@ python-dateutil==2.9.0.post0 # via # -r requirements/quality.txt # celery + # freezegun python-slugify==8.0.4 # via # -r requirements/quality.txt diff --git a/requirements/doc.txt b/requirements/doc.txt index c2614e16..993591bb 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -145,6 +145,8 @@ edx-opaque-keys==2.10.0 # via # -r requirements/test.txt # edx-drf-extensions +freezegun==1.5.1 + # via -r requirements/test.txt grimp==3.4.1 # via # -r requirements/test.txt @@ -253,6 +255,7 @@ python-dateutil==2.9.0.post0 # via # -r requirements/test.txt # celery + # freezegun python-slugify==8.0.4 # via # -r requirements/test.txt diff --git a/requirements/quality.txt b/requirements/quality.txt index 4dabc758..9bad017d 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -144,6 +144,8 @@ edx-opaque-keys==2.10.0 # via # -r requirements/test.txt # edx-drf-extensions +freezegun==1.5.1 + # via -r requirements/test.txt grimp==3.4.1 # via # -r requirements/test.txt @@ -292,6 +294,7 @@ python-dateutil==2.9.0.post0 # via # -r requirements/test.txt # celery + # freezegun python-slugify==8.0.4 # via # -r requirements/test.txt diff --git a/requirements/test.in b/requirements/test.in index 0d3126ae..164affcf 100644 --- a/requirements/test.in +++ b/requirements/test.in @@ -18,3 +18,4 @@ mypy # static type checking django-stubs # Typing stubs for Django, so it works with mypy djangorestframework-stubs # Typing stubs for DRF django-debug-toolbar # provides a debug toolbar for Django +freezegun # Allows tests to mock the output of assorted datetime module functions diff --git a/requirements/test.txt b/requirements/test.txt index 66f222d4..89ed3eb8 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -122,6 +122,8 @@ edx-opaque-keys==2.10.0 # via # -r requirements/base.txt # edx-drf-extensions +freezegun==1.5.1 + # via -r requirements/test.in grimp==3.4.1 # via import-linter idna==3.7 @@ -198,6 +200,7 @@ python-dateutil==2.9.0.post0 # via # -r requirements/base.txt # celery + # freezegun python-slugify==8.0.4 # via code-annotations pyyaml==6.0.1 diff --git a/tests/openedx_learning/apps/authoring/components/test_api.py b/tests/openedx_learning/apps/authoring/components/test_api.py index a6fca7d8..eb5a0978 100644 --- a/tests/openedx_learning/apps/authoring/components/test_api.py +++ b/tests/openedx_learning/apps/authoring/components/test_api.py @@ -73,6 +73,7 @@ def test_component_num_queries(self) -> None: draft = component.versioning.draft published = component.versioning.published assert draft.title == published.title + assert component.versioning.last_publish_log.published_at == self.now class GetComponentsTestCase(ComponentTestCase): diff --git a/tests/openedx_learning/apps/authoring/components/test_models.py b/tests/openedx_learning/apps/authoring/components/test_models.py index f087d4f3..99b43daf 100644 --- a/tests/openedx_learning/apps/authoring/components/test_models.py +++ b/tests/openedx_learning/apps/authoring/components/test_models.py @@ -3,13 +3,20 @@ """ from datetime import datetime, timezone +from freezegun import freeze_time + from openedx_learning.apps.authoring.components.api import ( create_component_and_version, get_component, get_or_create_component_type, ) from openedx_learning.apps.authoring.components.models import ComponentType -from openedx_learning.apps.authoring.publishing.api import LearningPackage, create_learning_package, publish_all_drafts +from openedx_learning.apps.authoring.publishing.api import ( + LearningPackage, + create_learning_package, + create_publishable_entity_version, + publish_all_drafts, +) from openedx_learning.lib.test_utils import TestCase @@ -53,3 +60,80 @@ def test_latest_version(self) -> None: # Grabbing the list of versions for this component assert list(component.versioning.versions) == [component_version] + + def test_last_publish_log(self): + """ + Test last_publish_log versioning property for published Components. + """ + # This Component will get a couple of Published versions + component_with_changes, _ = create_component_and_version( + self.learning_package.id, + component_type=self.problem_type, + local_key="with_changes", + title="Component with changes v1", + created=self.now, + created_by=None, + ) + + # This Component will only be Published once. + component_with_no_changes, _ = create_component_and_version( + self.learning_package.id, + component_type=self.problem_type, + local_key="with_no_changes", + title="Component with no changes v1", + created=self.now, + created_by=None, + ) + + # Publish first time. + published_first_time = datetime(2024, 5, 6, 7, 8, 9, tzinfo=timezone.utc) + with freeze_time(published_first_time): + publish_all_drafts(self.learning_package.id) + + # Refetch the entities to get latest versions + component_with_changes = get_component(component_with_changes.pk) + component_with_no_changes = get_component(component_with_no_changes.pk) + + # Fetch the most recent PublishLog for these components + first_publish_log_for_component_with_changes = component_with_changes.versioning.last_publish_log + first_publish_log_for_component_with_no_changes = component_with_no_changes.versioning.last_publish_log + + # PublishLog for library + both entities should match each other + assert ( + published_first_time == + first_publish_log_for_component_with_changes.published_at == + first_publish_log_for_component_with_no_changes.published_at + ) + + # Modify component_with_changes + create_publishable_entity_version( + component_with_changes.publishable_entity.id, + version_num=2, + title="Component with changes v2", + created=self.now, + created_by=None, + ) + + # Publish second time + published_second_time = datetime(2024, 5, 6, 7, 8, 9, tzinfo=timezone.utc) + with freeze_time(published_second_time): + publish_all_drafts(self.learning_package.id) + + # Refetch the entities to get latest versions + component_with_changes = get_component(component_with_changes.pk) + component_with_no_changes = get_component(component_with_no_changes.pk) + + # Re-fetch the most recent PublishLog for these components + next_publish_log_for_component_with_changes = component_with_changes.versioning.last_publish_log + next_publish_log_for_component_with_no_changes = component_with_no_changes.versioning.last_publish_log + + # PublishLog for component_with_changes should have been updated + assert ( + published_second_time == + next_publish_log_for_component_with_changes.published_at + ) + # But the component_with_no_changes should still be on the original publish log + assert ( + first_publish_log_for_component_with_no_changes == + next_publish_log_for_component_with_no_changes + )