feat: learner home course provider and grade data (#30959)

* feat: get course provider info

* feat: get grade data

* feat: get course provider for entitlements

* style: run black

Co-authored-by: nsprenkle <nsprenkle@2u.com>
This commit is contained in:
Nathan Sprenkle
2022-09-13 12:24:49 -04:00
committed by GitHub
parent 6ebc9b3888
commit 1673b1d3b9
4 changed files with 290 additions and 102 deletions

View File

@@ -6,9 +6,11 @@ from urllib.parse import urljoin
from django.conf import settings
from django.urls import reverse
from opaque_keys.edx.keys import CourseKey
from rest_framework import serializers
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.helpers import user_has_passing_grade_in_course
from openedx.features.course_experience import course_home_url
from xmodule.data import CertificatesDisplayBehaviors
@@ -17,6 +19,7 @@ class LiteralField(serializers.Field):
"""
Custom Field for use with fields that will always intentionally serialize to the same static value.
"""
def __init__(self, literal_value):
super().__init__()
self.literal_value = literal_value
@@ -37,9 +40,9 @@ class PlatformSettingsSerializer(serializers.Serializer):
class CourseProviderSerializer(serializers.Serializer):
"""Info about a course provider (institution/business)"""
"""Info about a course provider (institution/business) from a CourseOverview"""
name = serializers.CharField()
name = serializers.CharField(source="display_org_with_default")
class CourseSerializer(serializers.Serializer):
@@ -223,7 +226,10 @@ class EnrollmentSerializer(serializers.Serializer):
class GradeDataSerializer(serializers.Serializer):
"""Info about grades for this enrollment"""
isPassing = serializers.BooleanField()
isPassing = serializers.SerializerMethodField()
def get_isPassing(self, enrollment):
return user_has_passing_grade_in_course(enrollment)
class CertificateSerializer(serializers.Serializer):
@@ -287,9 +293,9 @@ class CertificateSerializer(serializers.Serializer):
class AvailableEntitlementSessionSerializer(serializers.Serializer):
"""An available entitlement session"""
startDate = serializers.DateTimeField(source='start')
endDate = serializers.DateTimeField(source='end')
courseId = serializers.CharField(source='key')
startDate = serializers.DateTimeField(source="start")
endDate = serializers.DateTimeField(source="end")
courseId = serializers.CharField(source="key")
class EntitlementSerializer(serializers.Serializer):
@@ -298,7 +304,7 @@ class EntitlementSerializer(serializers.Serializer):
availableSessions = serializers.SerializerMethodField()
uuid = serializers.UUIDField()
isRefundable = serializers.BooleanField(source='is_entitlement_refundable')
isRefundable = serializers.BooleanField(source="is_entitlement_refundable")
isFulfilled = serializers.SerializerMethodField()
changeDeadline = serializers.SerializerMethodField()
isExpired = serializers.SerializerMethodField()
@@ -314,8 +320,10 @@ class EntitlementSerializer(serializers.Serializer):
return bool(instance.expired_at)
def get_availableSessions(self, instance):
avaialableSessions = self.context['course_entitlement_available_sessions'].get(str(instance.uuid))
return AvailableEntitlementSessionSerializer(avaialableSessions, many=True).data
availableSessions = self.context["course_entitlement_available_sessions"].get(
str(instance.uuid)
)
return AvailableEntitlementSessionSerializer(availableSessions, many=True).data
def get_expirationDate(self, instance):
if instance.expired_at is not None:
@@ -327,7 +335,7 @@ class EntitlementSerializer(serializers.Serializer):
return self.get_expirationDate(instance)
def get_enrollmentUrl(self, instance):
return reverse('entitlements_api:v1:enrollments', args=[str(instance.uuid)])
return reverse("entitlements_api:v1:enrollments", args=[str(instance.uuid)])
class RelatedProgramSerializer(serializers.Serializer):
@@ -356,24 +364,27 @@ class LearnerEnrollmentSerializer(serializers.Serializer):
Info for displaying an enrollment on the learner dashboard.
Derived from a CourseEnrollment with added context.
"""
requires_context = True
course = CourseSerializer()
courseProvider = CourseProviderSerializer(source="course_overview")
courseRun = CourseRunSerializer(source="*")
enrollment = EnrollmentSerializer(source="*")
certificate = CertificateSerializer(source="*")
entitlement = serializers.SerializerMethodField()
gradeData = GradeDataSerializer(source="*")
# TODO - remove "allow_null" as each of these are implemented, temp for testing.
courseProvider = CourseProviderSerializer(allow_null=True)
gradeData = GradeDataSerializer(allow_null=True)
programs = ProgramsSerializer(allow_null=True)
def get_entitlement(self, instance):
"""
If this enrollment is the fulfillment of an entitlement, include information about the entitlement
"""
entitlement = self.context['fulfilled_entitlements'].get(str(instance.course_id))
entitlement = self.context["fulfilled_entitlements"].get(
str(instance.course_id)
)
if entitlement:
return EntitlementSerializer(entitlement, context=self.context).data
else:
@@ -391,23 +402,24 @@ class UnfulfilledEntitlementSerializer(serializers.Serializer):
# This is the static constant data returned as the 'enrollment' key for all unfulfilled enrollments.
STATIC_ENTITLEMENT_ENROLLMENT_DATA = {
'accessExpirationDate': None,
'isAudit': False,
'hasStarted': False,
'hasAccess': True,
'isVerified': False,
'canUpgrade': False,
'isAuditAccessExpired': False,
'isEmailEnabled': False,
'hasOptedOutOfEmail': False,
'lastEnrolled': None,
'isEnrolled': False,
"accessExpirationDate": None,
"isAudit": False,
"hasStarted": False,
"hasAccess": True,
"isVerified": False,
"canUpgrade": False,
"isAuditAccessExpired": False,
"isEmailEnabled": False,
"hasOptedOutOfEmail": False,
"lastEnrolled": None,
"isEnrolled": False,
}
class _PseudoSessionCourseSerializer(serializers.Serializer):
"""
'Private' Serilizer for the 'course' key data. This data comes from the pseudo session
'Private' Serializer for the 'course' key data. This data comes from the pseudo session
"""
bannerImgSrc = serializers.URLField(source="image.src")
courseName = serializers.CharField(source="title")
courseNumber = serializers.CharField(source="key")
@@ -415,9 +427,9 @@ class UnfulfilledEntitlementSerializer(serializers.Serializer):
# These fields contain all real data and will be serialized
entitlement = EntitlementSerializer(source="*")
course = serializers.SerializerMethodField()
courseProvider = serializers.SerializerMethodField()
# Change after data is implemented. This data is required
courseProvider = CourseProviderSerializer(allow_null=True)
programs = ProgramsSerializer(allow_null=True)
# These fields are literal values that do not change
@@ -427,8 +439,27 @@ class UnfulfilledEntitlementSerializer(serializers.Serializer):
enrollment = LiteralField(STATIC_ENTITLEMENT_ENROLLMENT_DATA)
def get_course(self, instance):
pseudo_session = self.context['unfulfilled_entitlement_pseudo_sessions'].get(str(instance.uuid))
return UnfulfilledEntitlementSerializer._PseudoSessionCourseSerializer(pseudo_session).data
pseudo_session = self.context["unfulfilled_entitlement_pseudo_sessions"].get(
str(instance.uuid)
)
return UnfulfilledEntitlementSerializer._PseudoSessionCourseSerializer(
pseudo_session
).data
def get_courseProvider(self, entitlement):
"""Look up course provider from CourseOverview matching the pseudo session"""
pseudo_session = self.context["unfulfilled_entitlement_pseudo_sessions"].get(
str(entitlement.uuid)
)
course_overview = None
if pseudo_session:
course_key = CourseKey.from_string(pseudo_session["key"])
course_overview = self.context.get("pseudo_session_course_overviews").get(
course_key
)
return CourseProviderSerializer(course_overview, allow_null=True).data
class SuggestedCourseSerializer(serializers.Serializer):
@@ -450,11 +481,11 @@ class EmailConfirmationSerializer(serializers.Serializer):
class EnterpriseDashboardSerializer(serializers.Serializer):
"""Serializer for individual enterprise dashboard data"""
label = serializers.CharField(source='name')
label = serializers.CharField(source="name")
url = serializers.SerializerMethodField()
def get_url(self, instance):
return urljoin(settings.ENTERPRISE_LEARNER_PORTAL_BASE_URL, instance['uuid'])
return urljoin(settings.ENTERPRISE_LEARNER_PORTAL_BASE_URL, instance["uuid"])
class LearnerDashboardSerializer(serializers.Serializer):

View File

@@ -9,6 +9,8 @@ from uuid import uuid4
from django.conf import settings
from django.test import TestCase
import ddt
from opaque_keys.edx.keys import CourseKey
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
@@ -17,7 +19,12 @@ from common.djangoapps.student.tests.factories import (
CourseEnrollmentFactory,
UserFactory,
)
from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory as CatalogCourseRunFactory
from openedx.core.djangoapps.catalog.tests.factories import (
CourseRunFactory as CatalogCourseRunFactory,
)
from openedx.core.djangoapps.content.course_overviews.tests.factories import (
CourseOverviewFactory,
)
from lms.djangoapps.learner_home.serializers import (
CertificateSerializer,
CourseProviderSerializer,
@@ -72,6 +79,31 @@ class LearnerDashboardBaseTest(SharedModuleStoreTestCase):
return test_enrollment
def create_test_entitlement_and_sessions(self):
"""
Create a test entitlement
Returns: (unfulfilled_entitlement, pseudo_sessions, available_sessions)
"""
unfulfilled_entitlement = CourseEntitlementFactory.create()
# Create pseudo-sessions
pseudo_sessions = {
str(unfulfilled_entitlement.uuid): CatalogCourseRunFactory.create()
}
# Create available sessions
available_sessions = {
str(unfulfilled_entitlement.uuid): CatalogCourseRunFactory.create_batch(3)
}
# Create related course overviews
course_key_str = pseudo_sessions[str(unfulfilled_entitlement.uuid)]["key"]
course_key = CourseKey.from_string(course_key_str)
course_overview = CourseOverviewFactory.create(id=course_key)
return unfulfilled_entitlement, pseudo_sessions, available_sessions
def _assert_all_keys_equal(self, dicts):
element_0 = dicts[0]
for element in dicts[1:]:
@@ -102,7 +134,7 @@ class TestPlatformSettingsSerializer(TestCase):
}
class TestCourseProviderSerializer(TestCase):
class TestCourseProviderSerializer(LearnerDashboardBaseTest):
"""Tests for the CourseProviderSerializer"""
@classmethod
@@ -113,12 +145,12 @@ class TestCourseProviderSerializer(TestCase):
}
def test_happy_path(self):
input_data = self.generate_test_provider_info()
test_enrollment = self.create_test_enrollment()
input_data = test_enrollment.course_overview
output_data = CourseProviderSerializer(input_data).data
assert output_data == {
"name": input_data["name"],
}
self.assertEqual(output_data["name"], test_enrollment.course_overview.org)
class TestCourseSerializer(LearnerDashboardBaseTest):
@@ -344,23 +376,29 @@ class TestEnrollmentSerializer(LearnerDashboardBaseTest):
self.assertFalse(output["hasStarted"])
class TestGradeDataSerializer(TestCase):
@ddt.ddt
class TestGradeDataSerializer(LearnerDashboardBaseTest):
"""Tests for the GradeDataSerializer"""
@classmethod
def generate_test_grade_data(cls):
"""Util to generate test grade data"""
return {
"isPassing": random_bool(),
}
@mock.patch(
"lms.djangoapps.learner_home.serializers.user_has_passing_grade_in_course"
)
@ddt.data(True, False, None)
def test_happy_path(self, is_passing, mock_get_grade_data):
# Given a course where I am/not passing
input_data = self.create_test_enrollment()
mock_get_grade_data.return_value = is_passing
def test_happy_path(self):
input_data = self.generate_test_grade_data()
# When I serialize grade data
output_data = GradeDataSerializer(input_data).data
assert output_data == {
"isPassing": input_data["isPassing"],
}
# Then I get the correct data shape out
self.assertDictEqual(
output_data,
{
"isPassing": is_passing,
},
)
@ddt.ddt
@@ -581,13 +619,13 @@ class TestCertificateSerializer(LearnerDashboardBaseTest):
class TestEntitlementSerializer(TestCase):
"""Tests for the EntitlementSerializer"""
def _assert_availale_sessions(self, input_sessions, output_sessions):
def _assert_available_sessions(self, input_sessions, output_sessions):
assert len(output_sessions) == len(input_sessions)
for input_session, output_session in zip(input_sessions, output_sessions):
assert output_session == {
'startDate': input_session['start'],
'endDate': input_session['end'],
'courseId': input_session['key']
"startDate": input_session["start"],
"endDate": input_session["end"],
"courseId": input_session["key"],
}
@ddt.unpack
@@ -595,26 +633,33 @@ class TestEntitlementSerializer(TestCase):
def test_serialize_entitlement(self, isExpired, isEnrolled):
entitlement_kwargs = {}
if isExpired:
entitlement_kwargs['expired_at'] = datetime.now()
entitlement_kwargs["expired_at"] = datetime.now()
if isEnrolled:
entitlement_kwargs['enrollment_course_run'] = CourseEnrollmentFactory.create()
entitlement_kwargs[
"enrollment_course_run"
] = CourseEnrollmentFactory.create()
entitlement = CourseEntitlementFactory.create(**entitlement_kwargs)
available_sessions = CatalogCourseRunFactory.create_batch(4)
course_entitlement_available_sessions = {
str(entitlement.uuid): available_sessions
}
output_data = EntitlementSerializer(entitlement, context={
'course_entitlement_available_sessions': course_entitlement_available_sessions
}).data
output_data = EntitlementSerializer(
entitlement,
context={
"course_entitlement_available_sessions": course_entitlement_available_sessions
},
).data
output_sessions = output_data.pop('availableSessions')
self._assert_availale_sessions(available_sessions, output_sessions)
output_sessions = output_data.pop("availableSessions")
self._assert_available_sessions(available_sessions, output_sessions)
if isExpired:
expected_expiration_date = entitlement.expired_at
else:
expected_expiration_date = date.today() + timedelta(days=entitlement.get_days_until_expiration())
expected_expiration_date = date.today() + timedelta(
days=entitlement.get_days_until_expiration()
)
assert output_data == {
"isRefundable": entitlement.is_entitlement_refundable(),
@@ -623,7 +668,7 @@ class TestEntitlementSerializer(TestCase):
"isExpired": bool(entitlement.expired_at),
"expirationDate": expected_expiration_date,
"uuid": str(entitlement.uuid),
"enrollmentUrl": f"/api/entitlements/v1/entitlements/{entitlement.uuid}/enrollments"
"enrollmentUrl": f"/api/entitlements/v1/entitlements/{entitlement.uuid}/enrollments",
}
@@ -736,14 +781,29 @@ class TestUnfulfilledEntitlementSerializer(LearnerDashboardBaseTest):
def test_happy_path(self):
"""Test that nothing breaks and the output fields look correct"""
unfulfilled_entitlement = CourseEntitlementFactory.create()
pseudo_sessions = {str(unfulfilled_entitlement.uuid): CatalogCourseRunFactory.create()}
available_sessions = {str(unfulfilled_entitlement.uuid): CatalogCourseRunFactory.create_batch(3)}
context = {
'unfulfilled_entitlement_pseudo_sessions': pseudo_sessions,
'course_entitlement_available_sessions': available_sessions,
pseudo_sessions = {
str(unfulfilled_entitlement.uuid): CatalogCourseRunFactory.create()
}
available_sessions = {
str(unfulfilled_entitlement.uuid): CatalogCourseRunFactory.create_batch(3)
}
output_data = UnfulfilledEntitlementSerializer(unfulfilled_entitlement, context=context).data
# create course overview for course provider info
course_key_str = pseudo_sessions[str(unfulfilled_entitlement.uuid)]["key"]
course_key = CourseKey.from_string(course_key_str)
course_overview = CourseOverviewFactory.create(id=course_key)
pseudo_session_course_overviews = {course_key: course_overview}
context = {
"unfulfilled_entitlement_pseudo_sessions": pseudo_sessions,
"course_entitlement_available_sessions": available_sessions,
"pseudo_session_course_overviews": pseudo_session_course_overviews,
}
output_data = UnfulfilledEntitlementSerializer(
unfulfilled_entitlement, context=context
).data
expected_keys = [
"courseProvider",
@@ -753,14 +813,18 @@ class TestUnfulfilledEntitlementSerializer(LearnerDashboardBaseTest):
"courseRun",
"gradeData",
"certificate",
"enrollment"
"enrollment",
]
assert output_data.keys() == set(expected_keys)
assert output_data['courseRun'] is None
assert output_data['gradeData'] is None
assert output_data['certificate'] is None
assert output_data['enrollment'] == UnfulfilledEntitlementSerializer.STATIC_ENTITLEMENT_ENROLLMENT_DATA
assert output_data["courseProvider"] is not None
assert output_data["courseRun"] is None
assert output_data["gradeData"] is None
assert output_data["certificate"] is None
assert (
output_data["enrollment"]
== UnfulfilledEntitlementSerializer.STATIC_ENTITLEMENT_ENROLLMENT_DATA
)
def test_static_enrollment_data(self):
"""
@@ -768,7 +832,9 @@ class TestUnfulfilledEntitlementSerializer(LearnerDashboardBaseTest):
This test is to ensure that that dict has the same keys as returned by the LearnerEnrollmentSerializer
"""
output_data = TestEnrollmentSerializer().serialize_test_enrollment()
expected_keys = UnfulfilledEntitlementSerializer.STATIC_ENTITLEMENT_ENROLLMENT_DATA.keys()
expected_keys = (
UnfulfilledEntitlementSerializer.STATIC_ENTITLEMENT_ENROLLMENT_DATA.keys()
)
actual_keys = output_data.keys()
assert expected_keys == actual_keys
@@ -888,7 +954,9 @@ class TestEnterpriseDashboardSerializer(TestCase):
output_data,
{
"label": input_data["name"],
"url": settings.ENTERPRISE_LEARNER_PORTAL_BASE_URL + '/' + input_data["uuid"],
"url": settings.ENTERPRISE_LEARNER_PORTAL_BASE_URL
+ "/"
+ input_data["uuid"],
},
)
@@ -899,9 +967,14 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest):
# Show full diff for serialization issues
maxDiff = None
def make_test_context(self, enrollments=None, enrollments_with_entitlements=None, unfulfilled_entitlements=None):
def make_test_context(
self,
enrollments=None,
enrollments_with_entitlements=None,
unfulfilled_entitlements=None,
):
"""
Given enrollments and entitlements, generate a mathing serializer context
Given enrollments and entitlements, generate a matching serializer context
"""
enrollments = enrollments or []
enrollments_with_entitlements = enrollments_with_entitlements or []
@@ -938,6 +1011,17 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest):
for entitlement in all_entitlements
}
# Create related course overviews for entitlement pseudo sessions
pseudo_session_course_overviews = {}
for unfulfilled_entitlement in unfulfilled_entitlement_pseudo_sessions:
course_key_str = unfulfilled_entitlement_pseudo_sessions[
unfulfilled_entitlement
]["key"]
course_key = CourseKey.from_string(course_key_str)
course_overview = CourseOverviewFactory.create(id=course_key)
pseudo_session_course_overviews[course_key] = course_overview
input_context = {
"resume_course_urls": resume_course_urls,
"ecommerce_payment_page": random_url(),
@@ -945,6 +1029,7 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest):
"fulfilled_entitlements": fulfilled_entitlements,
"unfulfilled_entitlement_pseudo_sessions": unfulfilled_entitlement_pseudo_sessions,
"course_entitlement_available_sessions": course_entitlement_available_sessions,
"pseudo_session_course_overviews": pseudo_session_course_overviews,
}
return input_context
@@ -998,10 +1083,7 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest):
def test_entitlements(self):
# One standard enrollment, one fulfilled entitlement, one unfulfilled enrollment
enrollments = [
self.create_test_enrollment(),
self.create_test_enrollment()
]
enrollments = [self.create_test_enrollment(), self.create_test_enrollment()]
unfulfilled_entitlements = [CourseEntitlementFactory.create()]
input_context = self.make_test_context(
@@ -1027,7 +1109,7 @@ class TestLearnerDashboardSerializer(LearnerDashboardBaseTest):
self._assert_all_keys_equal(courses)
# Non-entitlement enrollment should have no entitlement info
assert not courses[0]['entitlement']
# Fulfuilled and Unfulfilled entitlement should have identical keys
# Fulfilled and Unfulfilled entitlement should have identical keys
fulfilled_entitlement = courses[1]['entitlement']
unfulfilled_entitlement = courses[2]['entitlement']
assert fulfilled_entitlement

View File

@@ -10,9 +10,9 @@ import ddt
from django.conf import settings
from django.urls import reverse
from django.utils import timezone
from opaque_keys.edx.keys import CourseKey
from rest_framework.test import APITestCase
from lms.djangoapps.learner_home.test_utils import create_test_enrollment
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.entitlements.tests.factories import CourseEntitlementFactory
from common.djangoapps.student.tests.factories import (
@@ -20,7 +20,9 @@ from common.djangoapps.student.tests.factories import (
UserFactory,
)
from lms.djangoapps.bulk_email.models import Optout
from lms.djangoapps.learner_home.test_utils import create_test_enrollment
from lms.djangoapps.learner_home.views import (
get_course_overviews_for_pseudo_sessions,
get_email_settings_info,
get_enrollments,
get_platform_settings,
@@ -28,7 +30,12 @@ from lms.djangoapps.learner_home.views import (
get_entitlements,
)
from lms.djangoapps.learner_home.test_serializers import random_url
from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory as CatalogCourseRunFactory
from openedx.core.djangoapps.catalog.tests.factories import (
CourseRunFactory as CatalogCourseRunFactory,
)
from openedx.core.djangoapps.content.course_overviews.tests.factories import (
CourseOverviewFactory,
)
from xmodule.modulestore.tests.django_utils import (
TEST_DATA_SPLIT_MODULESTORE,
SharedModuleStoreTestCase,
@@ -36,7 +43,7 @@ from xmodule.modulestore.tests.django_utils import (
from xmodule.modulestore.tests.factories import CourseFactory
ENTERPRISE_ENABLED = 'ENABLE_ENTERPRISE_INTEGRATION'
ENTERPRISE_ENABLED = "ENABLE_ENTERPRISE_INTEGRATION"
class TestGetPlatformSettings(TestCase):
@@ -173,7 +180,7 @@ class TestGetEntitlements(SharedModuleStoreTestCase):
self,
filtered_entitlements,
course_entitlement_available_sessions,
unfulfilled_entitlement_pseudo_sessions
unfulfilled_entitlement_pseudo_sessions,
):
"""
Context manager utility for mocking get_filtered_course_entitlements.
@@ -184,7 +191,10 @@ class TestGetEntitlements(SharedModuleStoreTestCase):
course_entitlement_available_sessions,
unfulfilled_entitlement_pseudo_sessions,
)
with patch('lms.djangoapps.learner_home.views.get_filtered_course_entitlements', return_value=return_value):
with patch(
"lms.djangoapps.learner_home.views.get_filtered_course_entitlements",
return_value=return_value,
):
yield
def create_test_fulfilled_entitlement(self):
@@ -213,7 +223,9 @@ class TestGetEntitlements(SharedModuleStoreTestCase):
available_sessions = {}
for entitlement in fulfilled_test_entitlements + unfulfilled_test_entitlements:
available_sessions[str(entitlement.uuid)] = CatalogCourseRunFactory.create_batch(3)
available_sessions[
str(entitlement.uuid)
] = CatalogCourseRunFactory.create_batch(3)
pseudo_sessions = {}
for entitlement in unfulfilled_test_entitlements:
@@ -222,7 +234,7 @@ class TestGetEntitlements(SharedModuleStoreTestCase):
with self.mock_get_filtered_course_entitlements(
fulfilled_test_entitlements + unfulfilled_test_entitlements,
available_sessions,
pseudo_sessions
pseudo_sessions,
):
(
fulfilled_entitlements_by_course_key,
@@ -231,7 +243,9 @@ class TestGetEntitlements(SharedModuleStoreTestCase):
unfulfilled_entitlement_pseudo_sessions,
) = get_entitlements(self.user, None, None)
assert len(fulfilled_entitlements_by_course_key) == len(fulfilled_test_entitlements)
assert len(fulfilled_entitlements_by_course_key) == len(
fulfilled_test_entitlements
)
assert len(unfulfilled_entitlements) == len(unfulfilled_test_entitlements)
assert set(unfulfilled_entitlements) == set(unfulfilled_test_entitlements)
assert course_entitlement_available_sessions is available_sessions
@@ -257,6 +271,43 @@ class TestGetEntitlements(SharedModuleStoreTestCase):
assert not unfulfilled_entitlement_pseudo_sessions
class TestGetCourseOverviewsForPseudoSessions(SharedModuleStoreTestCase):
"""Tests for get_course_overviews_for_pseudo_sessions"""
def test_basic(self):
# Given several unfulfilled entitlements
unfulfilled_entitlement_uuids = [uuid4() for _ in range(3)]
pseudo_sessions = {}
for uuid in unfulfilled_entitlement_uuids:
pseudo_sessions[str(uuid)] = CatalogCourseRunFactory.create()
# ... that have matching CourseOverviews
expected_course_overviews = {}
for pseudo_session in pseudo_sessions.values():
course_key = CourseKey.from_string(pseudo_session["key"])
mock_course = CourseFactory.create(
org=course_key.org, run=course_key.run, number=course_key.course
)
mock_course_overview = CourseOverviewFactory.create(id=mock_course.id)
expected_course_overviews[course_key] = mock_course_overview
# When I try to get course overviews, keyed by course key
course_overviews = get_course_overviews_for_pseudo_sessions(pseudo_sessions)
# Then they map to the correct courses
self.assertDictEqual(course_overviews, expected_course_overviews)
def test_no_pseudo_sessions(self):
# Given no pseudo sessions
pseudo_sessions = {}
# When I query course overviews
course_overviews = get_course_overviews_for_pseudo_sessions(pseudo_sessions)
# Then I should get an empty dict
self.assertDictEqual(course_overviews, {})
class TestGetEmailSettingsInfo(SharedModuleStoreTestCase):
"""Tests for get_email_settings_info"""

View File

@@ -3,6 +3,7 @@ Views for the learner dashboard.
"""
from django.conf import settings
from edx_django_utils import monitoring as monitoring_utils
from opaque_keys.edx.keys import CourseKey
from rest_framework.response import Response
from rest_framework.generics import RetrieveAPIView
@@ -26,8 +27,11 @@ from lms.djangoapps.courseware.access_utils import (
check_course_open_for_learner,
)
from lms.djangoapps.learner_home.serializers import LearnerDashboardSerializer
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.features.enterprise_support.api import enterprise_customer_from_session_or_learner_data
from openedx.features.enterprise_support.api import (
enterprise_customer_from_session_or_learner_data,
)
def get_platform_settings():
@@ -98,32 +102,48 @@ def get_enrollments(user, org_allow_list, org_block_list, course_limit=None):
def get_entitlements(user, org_allow_list, org_block_list):
"""Get entitlments for the user"""
"""Get entitlements for the user"""
(
filtered_entitlements,
course_entitlement_available_sessions,
unfulfilled_entitlement_pseudo_sessions
) = get_filtered_course_entitlements(
user, org_allow_list, org_block_list
)
unfulfilled_entitlement_pseudo_sessions,
) = get_filtered_course_entitlements(user, org_allow_list, org_block_list)
fulfilled_entitlements_by_course_key = {}
unfulfulled_entitlements = []
unfulfilled_entitlements = []
for course_entitlement in filtered_entitlements:
if course_entitlement.enrollment_course_run:
course_id = str(course_entitlement.enrollment_course_run.course.id)
fulfilled_entitlements_by_course_key[course_id] = course_entitlement
else:
unfulfulled_entitlements.append(course_entitlement)
unfulfilled_entitlements.append(course_entitlement)
return (
fulfilled_entitlements_by_course_key,
unfulfulled_entitlements,
unfulfilled_entitlements,
course_entitlement_available_sessions,
unfulfilled_entitlement_pseudo_sessions,
)
def get_course_overviews_for_pseudo_sessions(unfulfilled_entitlement_pseudo_sessions):
"""
Get course overviews for entitlement pseudo sessions. This is required for
serializing course providers for entitlements.
Returns: dict of course overviews, keyed by CourseKey
"""
course_ids = []
# Get course IDs from unfulfilled entitlement pseudo sessions
for pseudo_session in unfulfilled_entitlement_pseudo_sessions.values():
course_id = pseudo_session.get("key")
if course_id:
course_ids.append(CourseKey.from_string(course_id))
return CourseOverview.get_from_ids(course_ids)
def get_email_settings_info(user, course_enrollments):
"""
Given a user and enrollments, determine which courses allow bulk email (show_email_settings_for)
@@ -231,13 +251,16 @@ class InitializeView(RetrieveAPIView): # pylint: disable=unused-argument
# Get the org whitelist or the org blacklist for the current site
site_org_whitelist, site_org_blacklist = get_org_black_and_whitelist_for_site()
# Get entitlements
# Get entitlements and course overviews for serializing
(
fulfilled_entitlements_by_course_key,
unfulfulled_entitlements,
unfulfilled_entitlements,
course_entitlement_available_sessions,
unfulfilled_entitlement_pseudo_sessions
unfulfilled_entitlement_pseudo_sessions,
) = get_entitlements(user, site_org_whitelist, site_org_blacklist)
pseudo_session_course_overviews = get_course_overviews_for_pseudo_sessions(
unfulfilled_entitlement_pseudo_sessions
)
# Get enrollments
course_enrollments, course_mode_info = get_enrollments(
@@ -268,7 +291,7 @@ class InitializeView(RetrieveAPIView): # pylint: disable=unused-argument
"enterpriseDashboard": enterprise_customer,
"platformSettings": get_platform_settings(),
"enrollments": course_enrollments,
"unfulfilledEntitlements": unfulfulled_entitlements,
"unfulfilledEntitlements": unfulfilled_entitlements,
"suggestedCourses": [],
}
@@ -283,6 +306,7 @@ class InitializeView(RetrieveAPIView): # pylint: disable=unused-argument
"fulfilled_entitlements": fulfilled_entitlements_by_course_key,
"course_entitlement_available_sessions": course_entitlement_available_sessions,
"unfulfilled_entitlement_pseudo_sessions": unfulfilled_entitlement_pseudo_sessions,
"pseudo_session_course_overviews": pseudo_session_course_overviews,
}
response_data = LearnerDashboardSerializer(