diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index e26bb36cd0..1e61ac540e 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -92,6 +92,7 @@ from openedx.features.course_experience import ( from openedx.features.course_experience.tests.views.helpers import add_course_mode from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired from student.models import CourseEnrollment +from student.roles import CourseStaffRole from student.tests.factories import TEST_PASSWORD, AdminFactory, CourseEnrollmentFactory, UserFactory from util.tests.test_date_utils import fake_pgettext, fake_ugettext from util.url import reload_django_url_config @@ -100,8 +101,10 @@ from xmodule.course_module import COURSE_VISIBILITY_PRIVATE, COURSE_VISIBILITY_P from xmodule.graders import ShowCorrectness from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore + from xmodule.modulestore.tests.django_utils import ( TEST_DATA_MIXED_MODULESTORE, + TEST_DATA_SPLIT_MODULESTORE, CourseUserType, ModuleStoreTestCase, SharedModuleStoreTestCase @@ -304,19 +307,9 @@ class IndexQueryTestCase(ModuleStoreTestCase): self.assertEqual(response.status_code, 200) -@ddt.ddt -class ViewsTestCase(ModuleStoreTestCase): - """ - Tests for views.py methods. - """ - YESTERDAY = 'yesterday' - DATES = { - YESTERDAY: datetime.now(UTC) - timedelta(days=1), - None: None, - } - +class BaseViewsTestCase(ModuleStoreTestCase): def setUp(self): - super(ViewsTestCase, self).setUp() + super(BaseViewsTestCase, self).setUp() self.course = CourseFactory.create(display_name=u'teꜱᴛ course', run="Testing_course") with self.store.bulk_operations(self.course.id): self.chapter = ItemFactory.create( @@ -376,6 +369,25 @@ class ViewsTestCase(ModuleStoreTestCase): # refresh the course from the modulestore so that it has children self.course = modulestore().get_course(self.course.id) + def _create_global_staff_user(self): + """ + Create global staff user and log them in + """ + self.global_staff = GlobalStaffFactory.create() # pylint: disable=attribute-defined-outside-init + self.assertTrue(self.client.login(username=self.global_staff.username, password=TEST_PASSWORD)) + + +@ddt.ddt +class ViewsTestCase(BaseViewsTestCase): + """ + Tests for views.py methods. + """ + YESTERDAY = 'yesterday' + DATES = { + YESTERDAY: datetime.now(UTC) - timedelta(days=1), + None: None, + } + def test_index_success(self): response = self._verify_index_response() self.assertContains(response, self.problem2.location) @@ -443,13 +455,6 @@ class ViewsTestCase(ModuleStoreTestCase): self.assertNotContains(response, 'Problem 1') self.assertNotContains(response, 'Problem 2') - def _create_global_staff_user(self): - """ - Create global staff user and log them in - """ - self.global_staff = GlobalStaffFactory.create() # pylint: disable=attribute-defined-outside-init - self.assertTrue(self.client.login(username=self.global_staff.username, password=TEST_PASSWORD)) - def _create_url_for_enroll_staff(self): """ creates the courseware url and enroll staff url @@ -3348,3 +3353,61 @@ class TestShowCoursewareMFE(TestCase): '/block-v1:OpenEdX+MFE+2020+type@sequential+block@Introduction' '/block-v1:OpenEdX+MFE+2020+type@vertical+block@Getting_To_Know_You' ) + + +@patch.dict('django.conf.settings.FEATURES', {'ENABLE_COURSEWARE_MICROFRONTEND': True}) +@ddt.ddt +class MFERedirectTests(BaseViewsTestCase): + MODULESTORE = TEST_DATA_SPLIT_MODULESTORE + + def _get_urls(self): + lms_url = reverse( + 'courseware_section', + kwargs={ + 'course_id': str(self.course_key), + 'chapter': str(self.chapter.location.block_id), + 'section': str(self.section2.location.block_id), + } + ) + mfe_url = '{}/course/{}/{}'.format( + settings.LEARNING_MICROFRONTEND_URL, + self.course_key, + self.section2.location + ) + return lms_url, mfe_url + + def test_learner_redirect(self): + # learners will be redirected when the waffle flag is set + lms_url, mfe_url = self._get_urls() + + with override_waffle_flag(REDIRECT_TO_COURSEWARE_MICROFRONTEND, True): + assert self.client.get(lms_url).url == mfe_url + + def test_staff_no_redirect(self): + lms_url, mfe_url = self._get_urls() + + # course staff will not redirect + course_staff = UserFactory.create(is_staff=False) + CourseStaffRole(self.course_key).add_users(course_staff) + self.client.login(username=course_staff.username, password='test') + + assert self.client.get(lms_url).status_code == 200 + with override_waffle_flag(REDIRECT_TO_COURSEWARE_MICROFRONTEND, True): + assert self.client.get(lms_url).status_code == 200 + + # global staff will never be redirected + self._create_global_staff_user() + assert self.client.get(lms_url).status_code == 200 + + with override_waffle_flag(REDIRECT_TO_COURSEWARE_MICROFRONTEND, True): + assert self.client.get(lms_url).status_code == 200 + + def test_exam_no_redirect(self): + # exams will not redirect to the mfe, for the time being + self.section2.is_time_limited = True + self.store.update_item(self.section2, self.user.id) + + lms_url, mfe_url = self._get_urls() + + with override_waffle_flag(REDIRECT_TO_COURSEWARE_MICROFRONTEND, True): + assert self.client.get(lms_url).status_code == 200 diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py index f1131f6d5b..e1f26bfd9c 100644 --- a/lms/djangoapps/courseware/views/index.py +++ b/lms/djangoapps/courseware/views/index.py @@ -30,13 +30,7 @@ from opaque_keys.edx.keys import CourseKey, UsageKey from web_fragments.fragment import Fragment from edxmako.shortcuts import render_to_response, render_to_string -from lms.djangoapps.courseware.courses import allow_public_access -from lms.djangoapps.courseware.exceptions import CourseAccessRedirect -from lms.djangoapps.courseware.toggles import ( - COURSEWARE_MICROFRONTEND_COURSE_TEAM_PREVIEW, - should_redirect_to_courseware_microfrontend, -) -from lms.djangoapps.courseware.url_helpers import get_microfrontend_url +from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context from lms.djangoapps.gating.api import get_entrance_exam_score_ratio, get_entrance_exam_usage_key from lms.djangoapps.grades.api import CourseGradeFactory @@ -62,7 +56,13 @@ from xmodule.modulestore.django import modulestore from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW from ..access import has_access -from ..courses import check_course_access, get_course_with_access, get_current_child, get_studio_url +from ..courses import ( + allow_public_access, + check_course_access, + get_course_with_access, + get_current_child, + get_studio_url +) from ..entrance_exams import ( course_has_entrance_exam, get_entrance_exam_content, @@ -73,6 +73,8 @@ from ..masquerade import check_content_start_date_for_masquerade_user, setup_mas from ..model_data import FieldDataCache from ..module_render import get_module_for_descriptor, toc_for_course from ..permissions import MASQUERADE_AS_STUDENT +from ..toggles import COURSEWARE_MICROFRONTEND_COURSE_TEAM_PREVIEW, should_redirect_to_courseware_microfrontend +from ..url_helpers import get_microfrontend_url from .views import CourseTabView @@ -185,6 +187,27 @@ class CoursewareIndex(View): # Set the user in the request to the effective user. self.request.user = self.effective_user + def _redirect_to_learning_mfe(self, request): + """ + Redirect to the new courseware micro frontend, + unless this is a time limited exam. + """ + # learners should redirect, if the waffle flag is set + if should_redirect_to_courseware_microfrontend(self.course_key): + # but exams should not redirect to the mfe until they're supported + if getattr(self.section, 'is_time_limited', False): + return + + # and staff will not redirect, either + if self.is_staff: + return + + url = get_microfrontend_url( + self.course_key, + self.section.location + ) + raise Redirect(url) + def render(self, request): """ Render the index page. @@ -201,6 +224,7 @@ class CoursewareIndex(View): self._redirect_if_not_requested_section() self._save_positions() self._prefetch_and_bind_section() + self._redirect_to_learning_mfe(request) check_content_start_date_for_masquerade_user(self.course_key, self.effective_user, request, self.course.start, self.chapter.start, self.section.start) diff --git a/openedx/core/djangoapps/content/course_overviews/models.py b/openedx/core/djangoapps/content/course_overviews/models.py index 43812555b4..9fdab1d91a 100644 --- a/openedx/core/djangoapps/content/course_overviews/models.py +++ b/openedx/core/djangoapps/content/course_overviews/models.py @@ -779,6 +779,20 @@ class CourseOverview(TimeStampedModel): """ return self._original_course.textbooks + @property + def pdf_textbooks(self): + """ + TODO: move this to the model. + """ + return self._original_course.pdf_textbooks + + @property + def html_textbooks(self): + """ + TODO: move this to the model. + """ + return self._original_course.html_textbooks + @property def hide_progress_tab(self): """ diff --git a/openedx/core/djangoapps/courseware_api/serializers.py b/openedx/core/djangoapps/courseware_api/serializers.py index 9a546a8ee9..d47d6c276f 100644 --- a/openedx/core/djangoapps/courseware_api/serializers.py +++ b/openedx/core/djangoapps/courseware_api/serializers.py @@ -107,7 +107,7 @@ class CourseInfoSerializer(serializers.Serializer): # pylint: disable=abstract- tabs = [] for priority, tab in enumerate(get_course_tab_list(course_overview.effective_user, course_overview)): tabs.append({ - 'title': tab.title, + 'title': tab.title or tab.get('name', ''), 'slug': tab.tab_id, 'priority': priority, 'type': tab.type,