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 a64b48c3c9..1a94a2e892 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 @@ -2,12 +2,13 @@ Tests for Outline Tab API in the Course Home API """ +import itertools from datetime import datetime import ddt from django.conf import settings from django.urls import reverse -from mock import patch +from mock import Mock, patch from course_modes.models import CourseMode from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests @@ -17,7 +18,7 @@ from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactor from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, ENABLE_COURSE_GOALS from student.models import CourseEnrollment from student.tests.factories import UserFactory -from xmodule.course_module import COURSE_VISIBILITY_PUBLIC +from xmodule.course_module import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -108,29 +109,11 @@ class OutlineTabTestViews(BaseCourseHomeTests): @COURSE_HOME_MICROFRONTEND.override(active=True) @COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override(active=True) - @ddt.data( - (True, True, True, True), # happy path - (True, False, False, True), # is enrolled - (False, True, False, True), # is staff - (False, False, True, True), # public visibility - (False, False, False, False), # no access - ) - @ddt.unpack @COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.override() - def test_handouts(self, is_enrolled, is_staff, is_public, handouts_visible): - if is_enrolled: - CourseEnrollment.enroll(self.user, self.course.id) - if is_staff: - self.user.is_staff = True - self.user.save() - if is_public: - self.course.course_visibility = COURSE_VISIBILITY_PUBLIC - self.course = self.update_course(self.course, self.user.id) - + def test_handouts(self): + CourseEnrollment.enroll(self.user, self.course.id) self.store.create_item(self.user.id, self.course.id, 'course_info', 'handouts', fields={'data': '

Hi

'}) - - handouts_html = self.client.get(self.url).data['handouts_html'] - self.assertEqual(handouts_html, '

Hi

' if handouts_visible else '') + self.assertEqual(self.client.get(self.url).data['handouts_html'], '

Hi

') @COURSE_HOME_MICROFRONTEND.override(active=True) @COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override(active=True) @@ -151,6 +134,7 @@ class OutlineTabTestViews(BaseCourseHomeTests): @COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override(active=True) @ddt.data(True, False) def test_welcome_message(self, welcome_message_is_dismissed): + CourseEnrollment.enroll(self.user, self.course.id) self.store.create_item( self.user.id, self.course.id, 'course_info', @@ -175,19 +159,17 @@ class OutlineTabTestViews(BaseCourseHomeTests): @COURSE_HOME_MICROFRONTEND.override(active=True) @COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override(active=True) + @patch('lms.djangoapps.course_home_api.outline.v1.views.generate_offer_html', new=Mock(return_value='

Offer

')) def test_offer_html(self): - with patch('lms.djangoapps.course_home_api.outline.v1.views.generate_offer_html') as gen_html: - html = '
Offer HTML
' - gen_html.return_value = html - self.assertEqual(self.client.get(self.url).data['offer_html'], html) + CourseEnrollment.enroll(self.user, self.course.id) + self.assertEqual(self.client.get(self.url).data['offer_html'], '

Offer

') @COURSE_HOME_MICROFRONTEND.override(active=True) @COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override(active=True) + @patch('lms.djangoapps.course_home_api.outline.v1.views.generate_course_expired_message', new=Mock(return_value='

Expired

')) def test_course_expired_html(self): - with patch('lms.djangoapps.course_home_api.outline.v1.views.generate_course_expired_message') as gen_html: - html = '
Course expired HTML
' - gen_html.return_value = html - self.assertEqual(self.client.get(self.url).data['course_expired_html'], html) + CourseEnrollment.enroll(self.user, self.course.id) + self.assertEqual(self.client.get(self.url).data['course_expired_html'], '

Expired

') @ENABLE_COURSE_GOALS.override(active=True) @COURSE_HOME_MICROFRONTEND.override(active=True) @@ -284,3 +266,42 @@ class OutlineTabTestViews(BaseCourseHomeTests): ungraded_data = response.data['course_blocks']['blocks'][str(sequential2.location)] self.assertEqual(ungraded_data['display_name'], 'Ungraded') self.assertIsNone(ungraded_data['icon']) + + @COURSE_HOME_MICROFRONTEND.override() + @COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override() + @COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.override() + @patch('lms.djangoapps.course_home_api.outline.v1.views.generate_offer_html', new=Mock(return_value='

Offer

')) + @patch('lms.djangoapps.course_home_api.outline.v1.views.generate_course_expired_message', new=Mock(return_value='

Expired

')) + @ddt.data(*itertools.product([True, False], [True, False], + [None, COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE])) + @ddt.unpack + def test_visibility(self, is_enrolled, is_staff, course_visibility): + if is_enrolled: + CourseEnrollment.enroll(self.user, self.course.id) + if is_staff: + self.user.is_staff = True + self.user.save() + if course_visibility: + self.course.course_visibility = course_visibility + self.course = self.update_course(self.course, self.user.id) + + self.store.create_item(self.user.id, self.course.id, 'course_info', 'handouts', fields={'data': '

Handouts

'}) + self.store.create_item(self.user.id, self.course.id, 'course_info', 'updates', fields={ + 'items': [{ + 'content': '

Welcome

', + 'status': 'visible', + 'date': 'July 23, 2020', + 'id': 1, + }] + }) + + show_enrolled = is_enrolled or is_staff + is_public = course_visibility == COURSE_VISIBILITY_PUBLIC + is_public_outline = course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE + + data = self.client.get(self.url).data + self.assertEqual(data['course_blocks'] is not None, show_enrolled or is_public or is_public_outline) + self.assertEqual(data['handouts_html'] is not None, show_enrolled or is_public) + 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) diff --git a/lms/djangoapps/course_home_api/outline/v1/views.py b/lms/djangoapps/course_home_api/outline/v1/views.py index eef2b3d98c..9912e7d551 100644 --- a/lms/djangoapps/course_home_api/outline/v1/views.py +++ b/lms/djangoapps/course_home_api/outline/v1/views.py @@ -39,7 +39,7 @@ from openedx.features.course_experience.views.latest_update import LatestUpdateF from openedx.features.course_experience.views.welcome_message import PREFERENCE_KEY, WelcomeMessageFragmentView from openedx.features.discounts.utils import generate_offer_html from student.models import CourseEnrollment -from xmodule.course_module import COURSE_VISIBILITY_PUBLIC +from xmodule.course_module import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE from xmodule.modulestore.django import modulestore @@ -150,22 +150,23 @@ class OutlineTabView(RetrieveAPIView): enrollment = CourseEnrollment.get_enrollment(request.user, course_key) allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key) allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC + allow_public_outline = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE is_enrolled = enrollment and enrollment.is_active - is_staff = has_access(request.user, 'staff', course_key) + is_staff = bool(has_access(request.user, 'staff', course_key)) show_enrolled = is_enrolled or is_staff show_handouts = show_enrolled or allow_public handouts_html = get_course_info_section(request, request.user, course, 'handouts') if show_handouts else '' # TODO: TNL-7185 Legacy: Refactor to return the offer & expired data and format the message in the MFE - offer_html = generate_offer_html(request.user, course_overview) - course_expired_html = generate_course_expired_message(request.user, course_overview) + offer_html = show_enrolled and generate_offer_html(request.user, course_overview) + course_expired_html = show_enrolled and generate_course_expired_message(request.user, course_overview) welcome_message_html = None - if get_course_tag(request.user, course_key, PREFERENCE_KEY) != 'False': + if show_enrolled: if LATEST_UPDATE_FLAG.is_enabled(course_key): welcome_message_html = LatestUpdateFragmentView().latest_update_html(request, course) - else: + elif get_course_tag(request.user, course_key, PREFERENCE_KEY) != 'False': welcome_message_html = WelcomeMessageFragmentView().welcome_message_html(request, course) enroll_alert = { @@ -191,25 +192,26 @@ class OutlineTabView(RetrieveAPIView): if course_home_mfe_dates_tab_is_active(course.id): dates_tab_link = get_microfrontend_url(course_key=course.id, view_name='dates') - course_blocks = get_course_outline_block_tree(request, course_key_string, request.user if is_enrolled else None) - - has_visited_course = False - try: - resume_block = get_key_to_last_completed_block(request.user, course.id) - has_visited_course = True - except UnavailableCompletionData: - resume_block = course_usage_key - - resume_path = reverse('jump_to', kwargs={ - 'course_id': course_key_string, - 'location': str(resume_block) - }) - resume_course_url = request.build_absolute_uri(resume_path) + course_blocks = None + if show_enrolled or allow_public or allow_public_outline: + outline_user = request.user if show_enrolled else None + course_blocks = get_course_outline_block_tree(request, course_key_string, outline_user) resume_course = { - 'has_visited_course': has_visited_course, - 'url': resume_course_url, + 'has_visited_course': False, + 'url': None, } + if show_enrolled: + try: + resume_block = get_key_to_last_completed_block(request.user, course.id) + resume_course['has_visited_course'] = True + except UnavailableCompletionData: + resume_block = course_usage_key + resume_path = reverse('jump_to', kwargs={ + 'course_id': course_key_string, + 'location': str(resume_block) + }) + resume_course['url'] = request.build_absolute_uri(resume_path) dates_widget = { 'course_date_blocks': [block for block in date_blocks if not isinstance(block, TodaysDate)], @@ -241,15 +243,15 @@ class OutlineTabView(RetrieveAPIView): data = { 'course_blocks': course_blocks, - 'course_expired_html': course_expired_html, + 'course_expired_html': course_expired_html or None, 'course_goals': course_goals, 'course_tools': course_tools, 'dates_widget': dates_widget, 'enroll_alert': enroll_alert, - 'handouts_html': handouts_html, - 'offer_html': offer_html, + 'handouts_html': handouts_html or None, + 'offer_html': offer_html or None, 'resume_course': resume_course, - 'welcome_message_html': welcome_message_html, + 'welcome_message_html': welcome_message_html or None, } context = self.get_serializer_context() context['course_key'] = course_key diff --git a/lms/djangoapps/courseware/utils.py b/lms/djangoapps/courseware/utils.py index 983b321e4a..439c2e9887 100644 --- a/lms/djangoapps/courseware/utils.py +++ b/lms/djangoapps/courseware/utils.py @@ -42,16 +42,15 @@ def can_show_verified_upgrade(user, enrollment, course=None): Arguments: user (:class:`.AuthUser`): The user from the request.user property enrollment (:class:`.CourseEnrollment`): The enrollment under consideration. - If None, then the enrollment is considered to be upgradeable. + If None, then the enrollment is not considered to be upgradeable. course (:class:`.ModulestoreCourse`): Optional passed in modulestore course. If provided, it is expected to correspond to `enrollment.course.id`. If not provided, the course will be loaded from the modulestore. We use the course to retrieve user partitions when calculating whether the upgrade link will be shown. """ - # Return `true` if user is not enrolled in course if enrollment is None: - return False + return False # this got accidentally flipped in 2017 (commit 8468357), but leaving alone to not switch again partition_service = PartitionService(enrollment.course.id, course=course) enrollment_track_partition = partition_service.get_user_partition(ENROLLMENT_TRACK_PARTITION_ID) group = partition_service.get_group(user, enrollment_track_partition) diff --git a/openedx/core/djangoapps/courseware_api/views.py b/openedx/core/djangoapps/courseware_api/views.py index fd453f18c6..beecbd97ff 100644 --- a/openedx/core/djangoapps/courseware_api/views.py +++ b/openedx/core/djangoapps/courseware_api/views.py @@ -34,6 +34,7 @@ from lms.djangoapps.courseware.toggles import REDIRECT_TO_COURSEWARE_MICROFRONTE from lms.djangoapps.courseware.utils import can_show_verified_upgrade from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin +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 generate_course_expired_message from openedx.features.discounts.utils import generate_offer_html @@ -132,8 +133,8 @@ class CoursewareMeta: @property def can_show_upgrade_sock(self): - can_show = can_show_verified_upgrade(self.effective_user, self.enrollment_object) - return can_show + return (DISPLAY_COURSE_SOCK_FLAG.is_enabled(self.course_key) and + can_show_verified_upgrade(self.effective_user, self.enrollment_object)) @property def license(self):