feat: learner home contract updates (#30930)

* refactor: update enterprise dashboard data shape

* refactor: remove hasFinished

* refactor: allow enterprise dashboard to be null

* docs: update mock data

* feat: get if an enrollment has unmet prerequisites

* feat: get is staff for course

* feat: get is too early for course

Co-authored-by: nsprenkle <nsprenkle@2u.com>
This commit is contained in:
Nathan Sprenkle
2022-09-06 09:30:46 -04:00
committed by GitHub
parent 80b654676e
commit a12243e9b3
5 changed files with 382 additions and 113 deletions

View File

@@ -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",

View File

@@ -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(

View File

@@ -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,

View File

@@ -232,7 +232,7 @@ class TestDashboardView(SharedModuleStoreTestCase, APITestCase):
expected_keys = set(
[
"emailConfirmation",
"enterpriseDashboards",
"enterpriseDashboard",
"platformSettings",
"courses",
"suggestedCourses",

View File

@@ -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: {
<course_id>: { "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: {
<course_enrollment.id>: {
"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,
}