diff --git a/lms/djangoapps/courseware/course_tools.py b/lms/djangoapps/courseware/course_tools.py index b13116cd61..093f4ae55f 100644 --- a/lms/djangoapps/courseware/course_tools.py +++ b/lms/djangoapps/courseware/course_tools.py @@ -7,12 +7,14 @@ import datetime import pytz from crum import get_current_request +from django.conf import settings from django.utils.translation import ugettext as _ - +from django.urls import reverse from course_modes.models import CourseMode from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link from openedx.features.course_experience.course_tools import CourseTool from student.models import CourseEnrollment +from openedx.core.djangoapps.content.course_overviews.models import CourseOverview class VerifiedUpgradeTool(CourseTool): @@ -73,3 +75,70 @@ class VerifiedUpgradeTool(CourseTool): """ request = get_current_request() return verified_upgrade_deadline_link(request.user, course_id=course_key) + + +class FinancialAssistanceTool(CourseTool): + """ + The financial assistance tool. + """ + @classmethod + def analytics_id(cls): + """ + Returns an id to uniquely identify this tool in analytics events. + """ + return 'edx.tool.financial_assistance' + + @classmethod + def is_enabled(cls, request, course_key): + """ + Show this link for active courses where financial assistance is available, unless upgrade deadline has passed + """ + now = datetime.datetime.now(pytz.UTC) + feature_flags = None + try: + course_overview = CourseOverview.objects.get(id=course_key) + except CourseOverview.DoesNotExist: + course_overview = None + + # hide link if there's no ENABLE_FINANCIAL_ASSISTANCE_FORM setting (ex: Edge) or if it's False + subset_name = 'FEATURES' + feature_flags = getattr(settings, subset_name) + if feature_flags is None or not feature_flags.get('ENABLE_FINANCIAL_ASSISTANCE_FORM'): + return False + + # hide link for archived courses + if course_overview is not None and course_overview.end_date is not None and now > course_overview.end_date: + return False + + # hide link if not logged in or user not enrolled in the course + if not request.user or not CourseEnrollment.is_enrolled(request.user, course_key): + return False + + # hide if there's a course_upgrade_enrollment in the past + enrollment = CourseEnrollment.get_enrollment(request.user, course_key) + if enrollment.course_upgrade_deadline: + if now > enrollment.course_upgrade_deadline: + return False + + return bool(course_overview.eligible_for_financial_aid) + + @classmethod + def title(cls, course_key=None): + """ + Returns the title of this tool. + """ + return _('Financial Assistance') + + @classmethod + def icon_classes(cls, course_key=None): + """ + Returns the icon classes needed to represent this tool. + """ + return 'fa fa-info' + + @classmethod + def url(cls, course_key): + """ + Returns the URL for this tool for the specified course key. + """ + return reverse('financial_assistance') diff --git a/lms/djangoapps/courseware/tests/test_course_info.py b/lms/djangoapps/courseware/tests/test_course_info.py index 578cd8b7dc..59b535330e 100644 --- a/lms/djangoapps/courseware/tests/test_course_info.py +++ b/lms/djangoapps/courseware/tests/test_course_info.py @@ -431,8 +431,8 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest def test_num_queries_instructor_paced(self): # TODO: decrease query count as part of REVO-28 - self.fetch_course_info_with_queries(self.instructor_paced_course, 43, 3) + self.fetch_course_info_with_queries(self.instructor_paced_course, 44, 3) def test_num_queries_self_paced(self): # TODO: decrease query count as part of REVO-28 - self.fetch_course_info_with_queries(self.self_paced_course, 43, 3) + self.fetch_course_info_with_queries(self.self_paced_course, 44, 3) diff --git a/lms/djangoapps/courseware/tests/test_course_tools.py b/lms/djangoapps/courseware/tests/test_course_tools.py index 05115b68ba..a04ca01594 100644 --- a/lms/djangoapps/courseware/tests/test_course_tools.py +++ b/lms/djangoapps/courseware/tests/test_course_tools.py @@ -12,6 +12,7 @@ from mock import patch from course_modes.models import CourseMode from course_modes.tests.factories import CourseModeFactory +from lms.djangoapps.courseware.course_tools import FinancialAssistanceTool from lms.djangoapps.courseware.course_tools import VerifiedUpgradeTool from lms.djangoapps.courseware.models import DynamicUpgradeDeadlineConfiguration from openedx.core.djangoapps.content.course_overviews.models import CourseOverview @@ -103,3 +104,69 @@ class VerifiedUpgradeToolTest(SharedModuleStoreTestCase): self.course_verified_mode.expiration_datetime = None self.course_verified_mode.save() self.assertFalse(VerifiedUpgradeTool().is_enabled(self.request, self.course.id)) + + +class FinancialAssistanceToolTest(SharedModuleStoreTestCase): + """ + Tests for FinancialAssistanceTool + """ + @classmethod + def setUpClass(cls): + super(FinancialAssistanceToolTest, cls).setUpClass() + cls.now = datetime.datetime.now(pytz.UTC) + + cls.course = CourseFactory.create( + org='edX', + number='test', + display_name='Test Course', + self_paced=True, + ) + cls.course_overview = CourseOverview.get_from_id(cls.course.id) + + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) + def setUp(self): + super(FinancialAssistanceToolTest, self).setUp() + + self.course_financial_mode = CourseModeFactory( + course_id=self.course.id, + mode_slug=CourseMode.VERIFIED, + expiration_datetime=self.now + datetime.timedelta(days=1), + ) + DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True) + + self.request = RequestFactory().request() + crum.set_current_request(self.request) + self.addCleanup(crum.set_current_request, None) + self.enrollment = CourseEnrollmentFactory( + course_id=self.course.id, + mode=CourseMode.AUDIT, + course=self.course_overview, + ) + self.request.user = self.enrollment.user + self.enrollment.course_upgrade_deadline = self.now - datetime.timedelta(days=1) + self.enrollment.save() + + def test_tool_visible_logged_in(self): + self.course_financial_mode.save() + self.assertTrue(FinancialAssistanceTool().is_enabled(self.request, self.course.id)) + + def test_tool_not_visible_when_not_eligible(self): + self.course_overview.eligible_for_financial_aid = False + self.course_overview.save() + self.assertFalse(FinancialAssistanceTool().is_enabled(self.request, self.course_overview.id)) + + def test_tool_not_visible_when_user_not_unrolled(self): + self.course_financial_mode.save() + self.request.user = None + self.assertFalse(FinancialAssistanceTool().is_enabled(self.request, self.course.id)) + + # mock the response from get_enrollment to use enrollment with course_upgrade_deadline in the past + @patch('lms.djangoapps.courseware.course_tools.CourseEnrollment.get_enrollment') + def test_not_visible_when_upgrade_deadline_has_passed(self, get_enrollment_mock): + get_enrollment_mock.return_value = self.enrollment + self.assertFalse(FinancialAssistanceTool().is_enabled(self.request, self.course.id)) + + def test_tool_not_visible_when_end_date_passed(self): + self.course_overview.end_date = self.now - datetime.timedelta(days=30) + self.course_overview.save() + self.assertFalse(FinancialAssistanceTool().is_enabled(self.request, self.course_overview.id)) diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py index 6e56952bd1..70b256f03b 100644 --- a/openedx/features/course_experience/tests/views/test_course_home.py +++ b/openedx/features/course_experience/tests/views/test_course_home.py @@ -219,7 +219,7 @@ class TestCourseHomePage(CourseHomePageTestCase): # Fetch the view and verify the query counts # TODO: decrease query count as part of REVO-28 - with self.assertNumQueries(74, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): + with self.assertNumQueries(75, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): with check_mongo_calls(4): url = course_home_url(self.course) self.client.get(url) diff --git a/setup.py b/setup.py index cc5df8db8c..257131cb2e 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,7 @@ setup( "course_updates = openedx.features.course_experience.plugins:CourseUpdatesTool", "course_reviews = openedx.features.course_experience.plugins:CourseReviewsTool", "verified_upgrade = lms.djangoapps.courseware.course_tools:VerifiedUpgradeTool", + "financial_assistance = lms.djangoapps.courseware.course_tools:FinancialAssistanceTool", ], "openedx.user_partition_scheme": [ "random = openedx.core.djangoapps.user_api.partition_schemes:RandomUserPartitionScheme",