diff --git a/lms/djangoapps/course_home_api/dates/v1/views.py b/lms/djangoapps/course_home_api/dates/v1/views.py index 1726490122..0cc0595773 100644 --- a/lms/djangoapps/course_home_api/dates/v1/views.py +++ b/lms/djangoapps/course_home_api/dates/v1/views.py @@ -20,7 +20,6 @@ from lms.djangoapps.courseware.masquerade import setup_masquerade from lms.djangoapps.course_home_api.dates.v1.serializers import DatesTabSerializer from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser -from openedx.features.course_experience.utils import dates_banner_should_display from openedx.features.content_type_gating.models import ContentTypeGatingConfig diff --git a/lms/djangoapps/course_home_api/mixins.py b/lms/djangoapps/course_home_api/mixins.py index 2483a68b44..eb540c9930 100644 --- a/lms/djangoapps/course_home_api/mixins.py +++ b/lms/djangoapps/course_home_api/mixins.py @@ -3,13 +3,13 @@ Course Home Mixins. """ - +from opaque_keys.edx.keys import CourseKey from rest_framework import serializers -from opaque_keys.edx.keys import CourseKey - -from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link +from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link +from openedx.core.djangoapps.courseware_api.utils import serialize_upgrade_info from openedx.features.content_type_gating.models import ContentTypeGatingConfig +from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG from openedx.features.course_experience.utils import dates_banner_should_display @@ -42,3 +42,24 @@ class DatesBannerSerializerMixin(serializers.Serializer): ) info['verified_upgrade_link'] = verified_upgrade_deadline_link(request.user, course_id=course_key) return info + + +class VerifiedModeSerializerMixin(serializers.Serializer): + """ + Serializer Mixin for displaying verified mode upgrade information. + + Requires 'course_overview', 'enrollment', and 'request' from self.context. + """ + can_show_upgrade_sock = serializers.SerializerMethodField() + verified_mode = serializers.SerializerMethodField() + + def get_can_show_upgrade_sock(self, _): + course_overview = self.context['course_overview'] + return DISPLAY_COURSE_SOCK_FLAG.is_enabled(course_overview.id) + + def get_verified_mode(self, _): + """Return verified mode information, or None.""" + course_overview = self.context['course_overview'] + enrollment = self.context['enrollment'] + request = self.context['request'] + return serialize_upgrade_info(request.user, course_overview, enrollment) diff --git a/lms/djangoapps/course_home_api/outline/v1/serializers.py b/lms/djangoapps/course_home_api/outline/v1/serializers.py index 01c3e03e6b..a51e0bbad9 100644 --- a/lms/djangoapps/course_home_api/outline/v1/serializers.py +++ b/lms/djangoapps/course_home_api/outline/v1/serializers.py @@ -6,7 +6,7 @@ from django.utils.translation import ngettext from rest_framework import serializers from lms.djangoapps.course_home_api.dates.v1.serializers import DateSummarySerializer -from lms.djangoapps.course_home_api.mixins import DatesBannerSerializerMixin +from lms.djangoapps.course_home_api.mixins import DatesBannerSerializerMixin, VerifiedModeSerializerMixin class CourseBlockSerializer(serializers.Serializer): @@ -74,8 +74,8 @@ class CourseToolSerializer(serializers.Serializer): url = serializers.SerializerMethodField() def get_url(self, tool): - course_key = self.context.get('course_key') - url = tool.url(course_key) + course_overview = self.context.get('course_overview') + url = tool.url(course_overview.id) request = self.context.get('request') return request.build_absolute_uri(url) @@ -105,7 +105,7 @@ class ResumeCourseSerializer(serializers.Serializer): url = serializers.URLField() -class OutlineTabSerializer(DatesBannerSerializerMixin, serializers.Serializer): +class OutlineTabSerializer(DatesBannerSerializerMixin, VerifiedModeSerializerMixin, serializers.Serializer): """ Serializer for the Outline Tab """ diff --git a/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py b/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py index 4d3a2cfc09..d0022db7d9 100644 --- a/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py +++ b/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py @@ -6,18 +6,22 @@ import itertools from datetime import datetime import ddt +from edx_toggles.toggles.testutils import override_waffle_flag from django.conf import settings from django.urls import reverse from mock import Mock, patch from common.djangoapps.course_modes.models import CourseMode -from edx_toggles.toggles.testutils import override_waffle_flag from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests from lms.djangoapps.course_home_api.toggles import COURSE_HOME_MICROFRONTEND, COURSE_HOME_MICROFRONTEND_OUTLINE_TAB from lms.djangoapps.experiments.testutils import override_experiment_waffle_flag +from openedx.core.djangoapps.course_date_signals.utils import MIN_DURATION from openedx.core.djangoapps.user_api.preferences.api import set_user_preference from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactory -from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, ENABLE_COURSE_GOALS +from openedx.features.course_duration_limits.models import CourseDurationLimitConfig +from openedx.features.course_experience import ( + COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, DISPLAY_COURSE_SOCK_FLAG, ENABLE_COURSE_GOALS, +) from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import UserFactory from xmodule.course_module import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE @@ -307,3 +311,27 @@ class OutlineTabTestViews(BaseCourseHomeTests): self.assertEqual(data['offer_html'] is not None, show_enrolled) self.assertEqual(data['course_expired_html'] is not None, show_enrolled) self.assertEqual(data['resume_course']['url'] is not None, show_enrolled) + + @override_experiment_waffle_flag(COURSE_HOME_MICROFRONTEND, active=True) + @override_waffle_flag(COURSE_HOME_MICROFRONTEND_OUTLINE_TAB, active=True) + @ddt.data(True, False) + def test_can_show_upgrade_sock(self, sock_enabled): + with override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=sock_enabled): + response = self.client.get(self.url) + self.assertEqual(response.data['can_show_upgrade_sock'], sock_enabled) + + @override_experiment_waffle_flag(COURSE_HOME_MICROFRONTEND, active=True) + @override_waffle_flag(COURSE_HOME_MICROFRONTEND_OUTLINE_TAB, active=True) + def test_verified_mode(self): + enrollment = CourseEnrollment.enroll(self.user, self.course.id) + CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1)) + + response = self.client.get(self.url) + self.assertEqual(response.data['verified_mode'], { + 'access_expiration_date': enrollment.created + MIN_DURATION, + 'currency': 'USD', + 'currency_symbol': '$', + 'price': 149, + 'sku': 'ABCD1234', + 'upgrade_url': '/dashboard', + }) diff --git a/lms/djangoapps/course_home_api/outline/v1/views.py b/lms/djangoapps/course_home_api/outline/v1/views.py index b2a64703c1..c120f8e778 100644 --- a/lms/djangoapps/course_home_api/outline/v1/views.py +++ b/lms/djangoapps/course_home_api/outline/v1/views.py @@ -256,8 +256,9 @@ class OutlineTabView(RetrieveAPIView): 'welcome_message_html': welcome_message_html or None, } context = self.get_serializer_context() - context['course_key'] = course_key + context['course_overview'] = course_overview context['enable_links'] = show_enrolled or allow_public + context['enrollment'] = enrollment serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data) diff --git a/lms/djangoapps/course_home_api/tests/utils.py b/lms/djangoapps/course_home_api/tests/utils.py index 0144f7c96b..a23c7fc035 100644 --- a/lms/djangoapps/course_home_api/tests/utils.py +++ b/lms/djangoapps/course_home_api/tests/utils.py @@ -41,7 +41,9 @@ class BaseCourseHomeTests(ModuleStoreTestCase, MasqueradeMixin): CourseModeFactory( course_id=self.course.id, mode_slug=CourseMode.VERIFIED, - expiration_datetime=datetime(2028, 1, 1) + expiration_datetime=datetime(2028, 1, 1), + min_price=149, + sku='ABCD1234', ) VerificationDeadline.objects.create(course_key=self.course.id, deadline=datetime(2028, 1, 1)) diff --git a/openedx/core/djangoapps/courseware_api/utils.py b/openedx/core/djangoapps/courseware_api/utils.py new file mode 100644 index 0000000000..7c916b61a5 --- /dev/null +++ b/openedx/core/djangoapps/courseware_api/utils.py @@ -0,0 +1,29 @@ +""" +Courseware API Mixins. +""" + +from babel.numbers import get_currency_symbol + +from common.djangoapps.course_modes.models import CourseMode +from lms.djangoapps.courseware.utils import can_show_verified_upgrade, verified_upgrade_deadline_link +from openedx.features.course_duration_limits.access import get_user_course_expiration_date + + +def serialize_upgrade_info(user, course_overview, enrollment): + """ + Return verified mode upgrade information, or None. + + This is used in a few API views to provide consistent upgrade info to frontends. + """ + if not can_show_verified_upgrade(user, enrollment): + return None + + mode = CourseMode.verified_mode_for_course(course=course_overview) + return { + 'access_expiration_date': get_user_course_expiration_date(user, course_overview), + 'currency': mode.currency.upper(), + 'currency_symbol': get_currency_symbol(mode.currency.upper()), + 'price': mode.min_price, + 'sku': mode.sku, + 'upgrade_url': verified_upgrade_deadline_link(user, course_overview), + } diff --git a/openedx/core/djangoapps/courseware_api/views.py b/openedx/core/djangoapps/courseware_api/views.py index e13a8a0ae4..00b5c9769c 100644 --- a/openedx/core/djangoapps/courseware_api/views.py +++ b/openedx/core/djangoapps/courseware_api/views.py @@ -4,7 +4,6 @@ Course API Views import json -from babel.numbers import get_currency_symbol from completion.exceptions import UnavailableCompletionData from completion.utilities import get_key_to_last_completed_block from django.conf import settings @@ -33,8 +32,6 @@ from lms.djangoapps.courseware.masquerade import setup_masquerade from lms.djangoapps.courseware.module_render import get_module_by_usage_id from lms.djangoapps.courseware.tabs import get_course_tab_list from lms.djangoapps.courseware.toggles import REDIRECT_TO_COURSEWARE_MICROFRONTEND, course_exit_page_is_active -from lms.djangoapps.courseware.utils import can_show_verified_upgrade -from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link from lms.djangoapps.courseware.views.views import get_cert_data from lms.djangoapps.grades.api import CourseGradeFactory from lms.djangoapps.verify_student.services import IDVerificationService @@ -42,9 +39,7 @@ from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin from openedx.core.djangoapps.programs.utils import ProgramProgressMeter from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG from openedx.features.content_type_gating.models import ContentTypeGatingConfig -from openedx.features.course_duration_limits.access import ( - get_user_course_expiration_date, generate_course_expired_message -) +from openedx.features.course_duration_limits.access import generate_course_expired_message from openedx.features.discounts.utils import generate_offer_html from common.djangoapps.student.models import ( CourseEnrollment, CourseEnrollmentCelebration, LinkedInAddToProfileConfiguration @@ -53,6 +48,7 @@ from xmodule.modulestore.django import modulestore from xmodule.modulestore.search import path_to_location from .serializers import CourseInfoSerializer +from .utils import serialize_upgrade_info class CoursewareMeta: @@ -186,18 +182,7 @@ class CoursewareMeta: """ Return verified mode information, or None. """ - if not can_show_verified_upgrade(self.effective_user, self.enrollment_object): - return None - - mode = CourseMode.verified_mode_for_course(self.course_key) - return { - 'access_expiration_date': get_user_course_expiration_date(self.effective_user, self.overview), - 'price': mode.min_price, - 'currency': mode.currency.upper(), - 'currency_symbol': get_currency_symbol(mode.currency.upper()), - 'sku': mode.sku, - 'upgrade_url': verified_upgrade_deadline_link(self.effective_user, self.overview), - } + return serialize_upgrade_info(self.effective_user, self.overview, self.enrollment_object) @property def notes(self):