diff --git a/cms/api.py b/cms/api.py index 75e30fc911..3f82fcd27e 100644 --- a/cms/api.py +++ b/cms/api.py @@ -18,6 +18,7 @@ from cms.constants import CERTIFICATE_INDEX_SLUG, INSTRUCTOR_INDEX_SLUG from cms.exceptions import WagtailSpecificPageError from cms.models import Page +from courses.constants import HOMEPAGE_CACHE_AGE from courses.models import Course, Program from courses.utils import ( get_enrollable_courseruns_qs, @@ -39,7 +40,6 @@ ] RESOURCE_PAGE_SLUGS = [slugify(title) for title in RESOURCE_PAGE_TITLES] PROGRAM_INDEX_PAGE_PROPERTIES = dict(title="Programs") # noqa: C408 -HOMEPAGE_CACHE_AGE = 86400 # 24 hours def get_home_page(raise_if_missing=True, check_specific=False) -> Page: # noqa: FBT002 diff --git a/cms/models.py b/cms/models.py index b0eab6f3c0..da6302f672 100644 --- a/cms/models.py +++ b/cms/models.py @@ -728,8 +728,7 @@ def get_cached_featured_products(self): Retrieves teh featured products that were generated using cms/api/create_featured_items either from the management command or the daily cron job. This is used to display the featured products on the home page. """ - start_of_day = now_in_utc() - timedelta(days=1) - end_of_day = now_in_utc() + timedelta(days=1) + now = now_in_utc() redis_cache = caches["redis"] cached_featured_products = redis_cache.get("CMS_homepage_featured_courses") if len(cached_featured_products) > 0: @@ -737,9 +736,12 @@ def get_cached_featured_products(self): relevant_run_course_ids = ( CourseRun.objects.filter(live=True) .filter( - course__id__in=featured_product_ids, - enrollment_start__lte=start_of_day, - enrollment_end__gte=end_of_day, + models.Q(course__id__in=featured_product_ids) + & models.Q(enrollment_start__lte=now) + & ( + models.Q(enrollment_end__gte=now) + | models.Q(enrollment_end__isnull=True) + ) ) .values_list("course__id", flat=True) ) diff --git a/cms/models_test.py b/cms/models_test.py index 52305709d6..d25c1e254c 100644 --- a/cms/models_test.py +++ b/cms/models_test.py @@ -8,17 +8,20 @@ import pytest from django.contrib.auth.models import AnonymousUser, Group from django.contrib.sessions.middleware import SessionMiddleware +from django.core.cache import caches from django.test.client import RequestFactory from django.urls import resolve from mitol.common.factories import UserFactory from mitol.common.utils.datetime import now_in_utc from mitol.olposthog.features import is_enabled +from cms.api import create_featured_items from cms.constants import CMS_EDITORS_GROUP_NAME from cms.factories import ( CertificatePageFactory, CoursePageFactory, FlexiblePricingFormFactory, + HomePageFactory, InstructorPageFactory, ProgramPageFactory, ResourcePageFactory, @@ -45,6 +48,7 @@ from flexiblepricing.constants import FlexiblePriceStatus from flexiblepricing.factories import FlexiblePriceFactory, FlexiblePriceTierFactory from flexiblepricing.models import FlexiblePrice +from main import features pytestmark = [pytest.mark.django_db] @@ -681,3 +685,80 @@ def test_flexible_pricing_request_form_context(flex_form_for_course): else: assert context["product"] is None assert context["product_page"] == course_page.url + + +def test_homepage__featured_products(settings, mocker): + settings.FEATURES[features.ENABLE_NEW_DESIGN] = True + now = now_in_utc() + future_date = now + timedelta(days=1) + past_date = now - timedelta(days=1) + further_future_date = future_date + timedelta(days=1) + further_past_date = past_date - timedelta(days=1) + furthest_future_date = further_future_date + timedelta(days=1) + redis_cache = caches["redis"] + # Ensure the key is empty since pytest doesn't clear the cache between tests + featured_courses = redis_cache.get("CMS_homepage_featured_courses") + if featured_courses is not None: + redis_cache.delete("CMS_homepage_featured_courses") + assert redis_cache.get("CMS_homepage_featured_courses") is None + + enrollable_future_course = CourseFactory.create(page=None, live=True) + enrollable_future_course_page = CoursePageFactory.create( + course=enrollable_future_course, live=True + ) + enrollable_future_courserun = CourseRunFactory.create( + course=enrollable_future_course, + live=True, + start_date=future_date, + enrollment_start=further_past_date, + enrollment_end=further_future_date, + end_date=furthest_future_date, + ) + create_featured_items() + assert len(redis_cache.get("CMS_homepage_featured_courses")) == 1 + hf = HomePageFactory.create() + assert hf.get_cached_featured_products == [ + { + "title": enrollable_future_course_page.title, + "description": enrollable_future_course_page.description, + "feature_image": enrollable_future_course_page.feature_image, + "start_date": enrollable_future_courserun.start_date, + "url_path": enrollable_future_course_page.get_url(), + "is_program": enrollable_future_course_page.is_program_page, + "is_self_paced": False, + "program_type": None, + } + ] + # Remove the previous course from the potential courses to be featured, this will also test a course as unenrollable + enrollable_future_course.live = False + enrollable_future_course.save() + + enrollable_future_course_with_no_enrollment_end = CourseFactory.create( + page=None, live=True + ) + enrollable_future_course_with_no_enrollment_end_page = CoursePageFactory.create( + course=enrollable_future_course_with_no_enrollment_end, live=True + ) + enrollable_future_courserun_with_no_enrollment_end = CourseRunFactory.create( + course=enrollable_future_course_with_no_enrollment_end, + live=True, + start_date=future_date, + enrollment_start=further_past_date, + enrollment_end=None, + end_date=furthest_future_date, + ) + create_featured_items() + assert len(redis_cache.get("CMS_homepage_featured_courses")) == 1 + hf = HomePageFactory.create() + assert hf.get_cached_featured_products == [ + { + "title": enrollable_future_course_with_no_enrollment_end_page.title, + "description": enrollable_future_course_with_no_enrollment_end_page.description, + "feature_image": enrollable_future_course_with_no_enrollment_end_page.feature_image, + "start_date": enrollable_future_courserun_with_no_enrollment_end.start_date, + "url_path": enrollable_future_course_with_no_enrollment_end_page.get_url(), + "is_program": enrollable_future_course_with_no_enrollment_end_page.is_program_page, + "is_self_paced": False, + "program_type": None, + } + ] diff --git a/courses/constants.py b/courses/constants.py index 81deed25e5..9285edc7ec 100644 --- a/courses/constants.py +++ b/courses/constants.py @@ -28,4 +28,6 @@ zip(ALL_ENROLL_CHANGE_STATUSES, ALL_ENROLL_CHANGE_STATUSES) ) +HOMEPAGE_CACHE_AGE = 86400 # 24 hours + SYNCED_COURSE_RUN_FIELD_MSG = "This value is synced automatically with edX studio."