diff --git a/lms/djangoapps/learner_home/mock_data.json b/lms/djangoapps/learner_home/mock_data.json index d252d16d9b..a505c67aa3 100644 --- a/lms/djangoapps/learner_home/mock_data.json +++ b/lms/djangoapps/learner_home/mock_data.json @@ -16,8 +16,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": true, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": true, "isAuditAccessExpired": false, "isEmailEnabled": false, @@ -103,8 +107,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": true, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": true, "isAuditAccessExpired": false, "isEmailEnabled": false, @@ -158,8 +166,12 @@ "enrollment": { "accessExpirationDate": "11/11/2000", "canUpgrade": true, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": true, "isAuditAccessExpired": true, "isEmailEnabled": false, @@ -205,8 +217,12 @@ "enrollment": { "accessExpirationDate": "11/11/2000", "canUpgrade": false, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": true, "isAuditAccessExpired": true, "isEmailEnabled": false, @@ -278,8 +294,12 @@ "enrollment": { "accessExpirationDate": "11/11/2000", "canUpgrade": true, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": true, "isAuditAccessExpired": true, "isEmailEnabled": false, @@ -340,8 +360,12 @@ "enrollment": { "accessExpirationDate": "11/11/2000", "canUpgrade": true, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": true, "isAuditAccessExpired": true, "isEmailEnabled": false, @@ -387,8 +411,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": true, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": true, "isAuditAccessExpired": false, "isEmailEnabled": false, @@ -464,8 +492,12 @@ "enrollment": { "accessExpirationDate": "11/11/2000", "canUpgrade": true, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": true, + "isStaff": false + }, "isAudit": true, "isAuditAccessExpired": true, "isEmailEnabled": false, @@ -530,8 +562,12 @@ "enrollment": { "accessExpirationDate": "11/11/2000", "canUpgrade": false, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": true, + "isStaff": false + }, "isAudit": true, "isAuditAccessExpired": true, "isEmailEnabled": false, @@ -582,8 +618,12 @@ "enrollment": { "accessExpirationDate": "11/11/2000", "canUpgrade": false, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": true, + "isStaff": false + }, "isAudit": true, "isAuditAccessExpired": true, "isEmailEnabled": false, @@ -664,8 +704,12 @@ "enrollment": { "accessExpirationDate": "11/11/2000", "canUpgrade": false, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": true, + "isStaff": false + }, "isAudit": true, "isAuditAccessExpired": true, "isEmailEnabled": false, @@ -716,8 +760,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": true, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -777,8 +825,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -850,8 +902,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": true, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -912,8 +968,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": true, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -945,8 +1005,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": true, "hasStarted": true, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1018,8 +1082,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": true, "hasStarted": true, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1081,8 +1149,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": true, "hasStarted": true, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1128,8 +1200,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": true, "hasStarted": true, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1202,8 +1278,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": true, "hasStarted": true, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1265,8 +1345,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": true, "hasStarted": true, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1348,8 +1432,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1456,8 +1544,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1553,8 +1645,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": true, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1635,8 +1731,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": true, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1717,8 +1817,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1788,8 +1892,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1845,8 +1953,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1927,8 +2039,12 @@ "enrollment": { "accessExpirationDate": "11/11/3030", "canUpgrade": null, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": null, "isEmailEnabled": false, @@ -1986,8 +2102,12 @@ "enrollment": { "accessExpirationDate": null, "canUpgrade": false, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": false, "isEmailEnabled": false, @@ -2074,8 +2194,12 @@ "enrollment": { "accessExpirationDate": null, "canUpgrade": false, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": false, "isEmailEnabled": false, @@ -2151,8 +2275,12 @@ "enrollment": { "accessExpirationDate": null, "canUpgrade": false, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": false, "isEmailEnabled": false, @@ -2213,8 +2341,12 @@ "enrollment": { "accessExpirationDate": null, "canUpgrade": false, - "hasFinished": false, "hasStarted": false, + "hasAccess": { + "hasUnmetPrerequisites": false, + "isTooEarly": false, + "isStaff": false + }, "isAudit": false, "isAuditAccessExpired": false, "isEmailEnabled": false, @@ -2275,21 +2407,9 @@ "isNeeded": true, "sendEmailUrl": "sendConfirmation@edx.org" }, - "enterpriseDashboards": { - "availableDashboards": [ - { - "label": "edX, Inc.", - "url": "/edx-dashboard" - }, - { - "label": "Harvard", - "url": "/harvard-dashboard" - } - ], - "mostRecentDashboard": { - "label": "edX, Inc.", - "url": "/edx-dashboard" - } + "enterpriseDashboard": { + "label": "edX, Inc.", + "url": "/edx-dashboard" }, "platformSettings": { "supportEmail": "support@example.com", diff --git a/lms/djangoapps/learner_home/serializers.py b/lms/djangoapps/learner_home/serializers.py index b2bed6eb59..41e6d2a3a0 100644 --- a/lms/djangoapps/learner_home/serializers.py +++ b/lms/djangoapps/learner_home/serializers.py @@ -94,6 +94,41 @@ class CourseRunSerializer(serializers.Serializer): return self.context.get("resume_course_urls", {}).get(instance.course_id) +class HasAccessSerializer(serializers.Serializer): + """ + Info determining whether a user should be able to view course material. + Mirrors logic in "show_courseware_links_for" from old dashboard.py + """ + + hasUnmetPrerequisites = serializers.SerializerMethodField() + isTooEarly = serializers.SerializerMethodField() + isStaff = serializers.SerializerMethodField() + + def _get_course_access_checks(self, enrollment): + """Internal helper to unpack access object for this particular enrollment""" + return self.context.get("course_access_checks", {}).get( + enrollment.course_id, {} + ) + + def get_hasUnmetPrerequisites(self, enrollment): + """Whether or not a course has unmet prerequisites""" + return self._get_course_access_checks(enrollment).get( + "has_unmet_prerequisites", False + ) + + def get_isTooEarly(self, enrollment): + """Determine if the course is open to a learner (course has started or user has early beta access)""" + return self._get_course_access_checks(enrollment).get( + "is_too_early_to_view", False + ) + + def get_isStaff(self, enrollment): + """Determine whether a user has staff access to this course""" + return self._get_course_access_checks(enrollment).get( + "user_has_staff_access", False + ) + + class EnrollmentSerializer(serializers.Serializer): """ Info about this particular enrollment. @@ -112,7 +147,7 @@ class EnrollmentSerializer(serializers.Serializer): accessExpirationDate = serializers.SerializerMethodField() isAudit = serializers.SerializerMethodField() hasStarted = serializers.SerializerMethodField() - hasFinished = serializers.SerializerMethodField() + hasAccess = HasAccessSerializer(source="*") isVerified = serializers.SerializerMethodField() canUpgrade = serializers.SerializerMethodField() isAuditAccessExpired = serializers.SerializerMethodField() @@ -138,10 +173,6 @@ class EnrollmentSerializer(serializers.Serializer): ) return resume_button_url is not None - def get_hasFinished(self, enrollment): - # TODO - AU-796 - return False - def get_isVerified(self, enrollment): return enrollment.is_verified_enrollment() @@ -329,22 +360,13 @@ class EnterpriseDashboardSerializer(serializers.Serializer): url = serializers.URLField() -class EnterpriseDashboardsSerializer(serializers.Serializer): - """Listing of available enterprise dashboards""" - - availableDashboards = serializers.ListField( - child=EnterpriseDashboardSerializer(), allow_empty=True - ) - mostRecentDashboard = EnterpriseDashboardSerializer() - - class LearnerDashboardSerializer(serializers.Serializer): """Serializer for all info required to render the Learner Dashboard""" requires_context = True emailConfirmation = EmailConfirmationSerializer() - enterpriseDashboards = EnterpriseDashboardsSerializer() + enterpriseDashboard = EnterpriseDashboardSerializer(allow_null=True) platformSettings = PlatformSettingsSerializer() courses = serializers.SerializerMethodField() suggestedCourses = serializers.ListField( diff --git a/lms/djangoapps/learner_home/test_serializers.py b/lms/djangoapps/learner_home/test_serializers.py index 3f8b36ac2e..b26c2f3eb4 100644 --- a/lms/djangoapps/learner_home/test_serializers.py +++ b/lms/djangoapps/learner_home/test_serializers.py @@ -22,9 +22,10 @@ from lms.djangoapps.learner_home.serializers import ( CourseSerializer, EmailConfirmationSerializer, EnrollmentSerializer, - EnterpriseDashboardsSerializer, + EnterpriseDashboardSerializer, EntitlementSerializer, GradeDataSerializer, + HasAccessSerializer, LearnerEnrollmentSerializer, PlatformSettingsSerializer, ProgramsSerializer, @@ -68,18 +69,6 @@ class LearnerDashboardBaseTest(SharedModuleStoreTestCase): return test_enrollment - @classmethod - def generate_base_test_context(cls): - """Base context object that can be used across tests""" - return { - "ecommerce_payment_page": random_url(), - "cert_statuses": {}, - "course_mode_info": {}, - "course_optouts": {}, - "resume_course_urls": {}, - "show_email_settings_for": {}, - } - class TestPlatformSettingsSerializer(TestCase): """Tests for the PlatformSettingsSerializer""" @@ -165,6 +154,94 @@ class TestCourseRunSerializer(LearnerDashboardBaseTest): assert output[key] is not None +@ddt.ddt +class TestHasAccessSerializer(LearnerDashboardBaseTest): + """Tests for the HasAccessSerializer""" + + def create_test_context(self, course): + return { + "course_access_checks": { + course.id: { + "has_unmet_prerequisites": False, + "is_too_early_to_view": False, + "user_has_staff_access": False, + } + } + } + + @ddt.data(True, False) + def test_unmet_prerequisites(self, has_unmet_prerequisites): + # Given an enrollment + input_data = self.create_test_enrollment() + input_context = self.create_test_context(input_data.course) + + # ... without unmet prerequisites + if has_unmet_prerequisites: + # ... or with unmet prerequisites + prerequisite_course = CourseFactory() + input_context.update( + { + "course_access_checks": { + input_data.course.id: { + "has_unmet_prerequisites": has_unmet_prerequisites, + } + } + } + ) + + # When I serialize + output_data = HasAccessSerializer(input_data, context=input_context).data + + # Then "hasUnmetPrerequisites" is outputs correctly + self.assertEqual(output_data["hasUnmetPrerequisites"], has_unmet_prerequisites) + + @ddt.data(True, False) + def test_is_staff(self, is_staff): + # Given an enrollment + input_data = self.create_test_enrollment() + input_context = self.create_test_context(input_data.course) + + # Where user has/hasn't staff access + input_context.update( + { + "course_access_checks": { + input_data.course.id: { + "user_has_staff_access": is_staff, + } + } + } + ) + + # When I serialize + output_data = HasAccessSerializer(input_data, context=input_context).data + + # Then "isStaff" serializes properly + self.assertEqual(output_data["isStaff"], is_staff) + + @ddt.data(True, False) + def test_is_too_early(self, is_too_early): + # Given an enrollment + input_data = self.create_test_enrollment() + input_context = self.create_test_context(input_data.course) + + # Where the course is/n't yet open for a learner + input_context.update( + { + "course_access_checks": { + input_data.course.id: { + "is_too_early_to_view": is_too_early, + } + } + } + ) + + # When I serialize + output_data = HasAccessSerializer(input_data, context=input_context).data + + # Then "isTooEarly" serializes properly + self.assertEqual(output_data["isTooEarly"], is_too_early) + + @ddt.ddt class TestEnrollmentSerializer(LearnerDashboardBaseTest): """Tests for the EnrollmentSerializer""" @@ -364,9 +441,7 @@ class TestCertificateSerializer(LearnerDashboardBaseTest): output_data = CertificateSerializer(input_data, context=input_context).data # Then the available date is the course end date - expected_available_date = datetime_to_django_format( - input_data.course.end - ) + expected_available_date = datetime_to_django_format(input_data.course.end) self.assertEqual(output_data["availableDate"], expected_available_date) @mock.patch.dict(settings.FEATURES, ENABLE_V2_CERT_DISPLAY_SETTINGS=True) @@ -759,36 +834,25 @@ class TestEmailConfirmationSerializer(TestCase): ) -class TestEnterpriseDashboardsSerializer(TestCase): - """High-level tests for EnterpriseDashboardsSerializer""" - - @classmethod - def generate_test_dashboard(cls): - return { - "label": f"{uuid4()}", - "url": random_url(), - } +class TestEnterpriseDashboardSerializer(TestCase): + """High-level tests for EnterpriseDashboardSerializer""" @classmethod def generate_test_data(cls): return { - "availableDashboards": [ - cls.generate_test_dashboard() for _ in range(randint(0, 3)) - ], - "mostRecentDashboard": cls.generate_test_dashboard() - if random_bool() - else None, + "label": f"{uuid4()}", + "url": random_url(), } def test_structure(self): """Test that nothing breaks and the output fields look correct""" input_data = self.generate_test_data() - output_data = EnterpriseDashboardsSerializer(input_data).data + output_data = EnterpriseDashboardSerializer(input_data).data expected_keys = [ - "availableDashboards", - "mostRecentDashboard", + "label", + "url", ] assert output_data.keys() == set(expected_keys) @@ -797,13 +861,13 @@ class TestEnterpriseDashboardsSerializer(TestCase): input_data = self.generate_test_data() - output_data = EnterpriseDashboardsSerializer(input_data).data + output_data = EnterpriseDashboardSerializer(input_data).data self.assertDictEqual( output_data, { - "availableDashboards": input_data["availableDashboards"], - "mostRecentDashboard": input_data["mostRecentDashboard"], + "label": input_data["label"], + "url": input_data["url"], }, ) @@ -819,7 +883,7 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest): input_data = { "emailConfirmation": None, - "enterpriseDashboards": None, + "enterpriseDashboard": None, "platformSettings": None, "enrollments": [], "unfulfilledEntitlements": [], @@ -831,7 +895,7 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest): output_data, { "emailConfirmation": None, - "enterpriseDashboards": None, + "enterpriseDashboard": None, "platformSettings": None, "courses": [], "suggestedCourses": [], @@ -857,7 +921,7 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest): input_data = { "emailConfirmation": None, - "enterpriseDashboards": None, + "enterpriseDashboard": None, "platformSettings": None, "enrollments": enrollments, "unfulfilledEntitlements": [], @@ -889,7 +953,7 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest): "lms.djangoapps.learner_home.serializers.PlatformSettingsSerializer.to_representation" ) @mock.patch( - "lms.djangoapps.learner_home.serializers.EnterpriseDashboardsSerializer.to_representation" + "lms.djangoapps.learner_home.serializers.EnterpriseDashboardSerializer.to_representation" ) @mock.patch( "lms.djangoapps.learner_home.serializers.EmailConfirmationSerializer.to_representation" @@ -897,7 +961,7 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest): def test_linkage( self, mock_email_confirmation_serializer, - mock_enterprise_dashboards_serializer, + mock_enterprise_dashboard_serializer, mock_platform_settings_serializer, mock_learner_enrollment_serializer, mock_entitlements_serializer, @@ -906,8 +970,8 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest): mock_email_confirmation_serializer.return_value = ( mock_email_confirmation_serializer ) - mock_enterprise_dashboards_serializer.return_value = ( - mock_enterprise_dashboards_serializer + mock_enterprise_dashboard_serializer.return_value = ( + mock_enterprise_dashboard_serializer ) mock_platform_settings_serializer.return_value = ( mock_platform_settings_serializer @@ -920,7 +984,7 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest): input_data = { "emailConfirmation": {}, - "enterpriseDashboards": [{}], + "enterpriseDashboard": {}, "platformSettings": {}, "enrollments": [{}], "unfulfilledEntitlements": [{}], @@ -932,7 +996,7 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest): output_data, { "emailConfirmation": mock_email_confirmation_serializer, - "enterpriseDashboards": mock_enterprise_dashboards_serializer, + "enterpriseDashboard": mock_enterprise_dashboard_serializer, "platformSettings": mock_platform_settings_serializer, "courses": [ mock_learner_enrollment_serializer, diff --git a/lms/djangoapps/learner_home/test_views.py b/lms/djangoapps/learner_home/test_views.py index 0f96002899..e3c48454ea 100644 --- a/lms/djangoapps/learner_home/test_views.py +++ b/lms/djangoapps/learner_home/test_views.py @@ -232,7 +232,7 @@ class TestDashboardView(SharedModuleStoreTestCase, APITestCase): expected_keys = set( [ "emailConfirmation", - "enterpriseDashboards", + "enterpriseDashboard", "platformSettings", "courses", "suggestedCourses", diff --git a/lms/djangoapps/learner_home/views.py b/lms/djangoapps/learner_home/views.py index 6ce0299333..a174afcb9a 100644 --- a/lms/djangoapps/learner_home/views.py +++ b/lms/djangoapps/learner_home/views.py @@ -14,9 +14,16 @@ from common.djangoapps.student.views.dashboard import ( get_course_enrollments, get_org_black_and_whitelist_for_site, ) +from common.djangoapps.util.milestones_helpers import ( + get_pre_requisite_courses_not_completed, +) from lms.djangoapps.bulk_email.models import Optout from lms.djangoapps.bulk_email.models_api import is_bulk_email_feature_enabled from lms.djangoapps.commerce.utils import EcommerceService +from lms.djangoapps.courseware.access import administrative_accesses_to_course_for_user +from lms.djangoapps.courseware.access_utils import ( + check_course_open_for_learner, +) from lms.djangoapps.learner_home.serializers import LearnerDashboardSerializer from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers @@ -125,6 +132,62 @@ def get_cert_statuses(user, course_enrollments): } +def _get_courses_with_unmet_prerequisites(user, course_enrollments): + """ + Determine which courses have unmet prerequisites. + NOTE: that courses w/out prerequisites, or with met prerequisites are not returned + in the output dict. That way we can do a simple "course_id in dict" check. + + Returns: { + : { "courses": [listing of unmet prerequisites] } + } + """ + + courses_having_prerequisites = frozenset( + enrollment.course_id + for enrollment in course_enrollments + if enrollment.course_overview.pre_requisite_courses + ) + + return get_pre_requisite_courses_not_completed(user, courses_having_prerequisites) + + +def check_course_access(user, course_enrollments): + """ + Wrapper for checks surrounding user ability to view courseware + + Returns: { + : { + "has_unmet_prerequisites": True/False, + "is_too_early_to_view": True/False, + "user_has_staff_access": True/False + } + } + """ + + course_access_dict = {} + + courses_with_unmet_prerequisites = _get_courses_with_unmet_prerequisites( + user, course_enrollments + ) + + for course_enrollment in course_enrollments: + course_access_dict[course_enrollment.course_id] = { + "has_unmet_prerequisites": course_enrollment.course_id + in courses_with_unmet_prerequisites, + "is_too_early_to_view": not check_course_open_for_learner( + user, course_enrollment.course + ), + "user_has_staff_access": any( + administrative_accesses_to_course_for_user( + user, course_enrollment.course_id + ) + ), + } + + return course_access_dict + + class InitializeView(RetrieveAPIView): # pylint: disable=unused-argument """List of courses a user is enrolled in or entitled to""" @@ -152,12 +215,11 @@ class InitializeView(RetrieveAPIView): # pylint: disable=unused-argument # Get cert status by course cert_statuses = get_cert_statuses(user, course_enrollments) - # TODO - Determine view access for courses (for showing courseware link or not) + # Determine view access for course, (for showing courseware link) involves: + course_access_checks = check_course_access(user, course_enrollments) # TODO - Get related programs - # TODO - Get user verification status - # e-commerce info ecommerce_payment_page = get_ecommerce_payment_page(user) @@ -178,6 +240,7 @@ class InitializeView(RetrieveAPIView): # pylint: disable=unused-argument "cert_statuses": cert_statuses, "course_mode_info": course_mode_info, "course_optouts": course_optouts, + "course_access_checks": course_access_checks, "resume_course_urls": resume_button_urls, "show_email_settings_for": show_email_settings_for, }