diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py index 5e614dda77a8..3756ca20f0a2 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py +++ b/cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py @@ -67,10 +67,15 @@ def get_use_new_custom_pages(self, obj): def get_use_new_schedule_details_page(self, obj): """ - Method to get the use_new_schedule_details_page switch + Method to indicate whether we should use the new schedule details page. + + This used to be based on a waffle flag but the flag is being removed so we + default it to true for now until we can remove the need for it from the consumers + of this serializer and the related APIs. + + See https://github.com/openedx/edx-platform/issues/36275 """ - course_key = self.get_course_key() - return toggles.use_new_schedule_details_page(course_key) + return True def get_use_new_advanced_settings_page(self, obj): """ diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index 39a5f90935a9..dbef28fe0b2e 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -1493,8 +1493,9 @@ def test_get_json(handler): test_get_html('export_handler') with override_waffle_flag(toggles.LEGACY_STUDIO_COURSE_TEAM, True): test_get_html('course_team_handler') - with override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True): - test_get_html('settings_handler') + with override_settings(COURSE_AUTHORING_MICROFRONTEND_URL='https://mfe.example'): + resp = self.client.get_html(get_url('settings_handler', course_key, 'course_key_string')) + self.assertEqual(resp.status_code, 302) # noqa: PT009 with override_settings(COURSE_AUTHORING_MICROFRONTEND_URL='https://mfe.example'): resp = self.client.get_html(get_url('grading_handler', course_key, 'course_key_string')) self.assertEqual(resp.status_code, 302) # noqa: PT009 diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index a9e5b9792049..9bb6d44bcf8d 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -39,8 +39,7 @@ from cms.djangoapps.models.settings.course_metadata import CourseMetadata from cms.djangoapps.models.settings.encoder import CourseSettingsEncoder from cms.djangoapps.models.settings.waffle import MATERIAL_RECOMPUTE_ONLY_FLAG -from common.djangoapps.course_modes.models import CourseMode -from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole +from common.djangoapps.student.roles import CourseStaffRole from common.djangoapps.student.tests.factories import UserFactory from common.djangoapps.util import milestones_helpers from common.djangoapps.xblock_django.models import XBlockStudioConfigurationFlag @@ -162,63 +161,25 @@ def test_discussion_fields_available(self, is_pages_and_resources_enabled, self.assertEqual('discussion_blackouts' in response, fields_visible) # noqa: PT009 self.assertEqual('discussion_topics' in response, fields_visible) # noqa: PT009 - @ddt.data(False, True) - @override_waffle_flag(toggles.LEGACY_STUDIO_ADVANCED_SETTINGS, True) - @override_waffle_flag(toggles.LEGACY_STUDIO_IMPORT, True) - @override_waffle_flag(toggles.LEGACY_STUDIO_EXPORT, True) - @override_waffle_flag(toggles.LEGACY_STUDIO_COURSE_TEAM, True) - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_disable_advanced_settings_feature(self, disable_advanced_settings): - """ - If this feature is enabled, only Django Staff/Superuser should be able to access the "Advanced Settings" page. - For non-staff users the "Advanced Settings" tab link should not be visible. - """ - advanced_settings_link_html = f"Advanced Settings".encode('utf-8') # noqa: UP012 # pylint: disable=line-too-long - - with override_settings(FEATURES={ - 'DISABLE_ADVANCED_SETTINGS': disable_advanced_settings, - }, COURSE_AUTHORING_MICROFRONTEND_URL='https://mfe.example'): - for handler in ( - 'import_handler', - 'export_handler', - 'course_team_handler', - 'settings_handler', - ): - # Test that non-staff users don't see the "Advanced Settings" tab link. - response = self.non_staff_client.get_html( - get_url(self.course.id, handler) - ) - self.assertEqual(response.status_code, 200) # noqa: PT009 - if disable_advanced_settings: - self.assertNotIn(advanced_settings_link_html, response.content) # noqa: PT009 - else: - self.assertIn(advanced_settings_link_html, response.content) # noqa: PT009 - - # Test that staff users see the "Advanced Settings" tab link. - response = self.client.get_html( - get_url(self.course.id, handler) - ) - self.assertEqual(response.status_code, 200) # noqa: PT009 - self.assertIn(advanced_settings_link_html, response.content) # noqa: PT009 + def test_grading_handler_redirects_to_mfe(self): + """grading_handler redirects to the authoring MFE.""" + response = self.client.get_html(get_url(self.course.id, 'grading_handler')) + self.assertEqual(response.status_code, 302) # noqa: PT009 - # Test that non-staff users can't access the "Advanced Settings" page. - response = self.non_staff_client.get_html(self.course_setting_url) - self.assertEqual(response.status_code, 403 if disable_advanced_settings else 200) # noqa: PT009 + def test_settings_handler_redirects_to_mfe(self): + """settings_handler (schedule & details) redirects to the authoring MFE.""" + response = self.client.get_html(get_url(self.course.id, 'settings_handler')) + self.assertEqual(response.status_code, 302) # noqa: PT009 - # Test that staff users can access the "Advanced Settings" page. - response = self.client.get_html(self.course_setting_url) - self.assertEqual(response.status_code, 200) # noqa: PT009 + def test_import_handler_redirects_to_mfe(self): + """import_handler redirects to the authoring MFE.""" + response = self.client.get_html(get_url(self.course.id, 'import_handler')) + self.assertEqual(response.status_code, 302) # noqa: PT009 - @override_waffle_flag(toggles.LEGACY_STUDIO_ADVANCED_SETTINGS, True) - @override_waffle_flag(toggles.LEGACY_STUDIO_IMPORT, True) - @override_waffle_flag(toggles.LEGACY_STUDIO_EXPORT, True) - @override_waffle_flag(toggles.LEGACY_STUDIO_COURSE_TEAM, True) - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_grading_handler_redirects_to_mfe(self): - """grading_handler redirects to the authoring MFE.""" - with override_settings(COURSE_AUTHORING_MICROFRONTEND_URL='https://mfe.example'): - response = self.client.get_html(get_url(self.course.id, 'grading_handler')) - self.assertEqual(response.status_code, 302) # noqa: PT009 + def test_export_handler_redirects_to_mfe(self): + """export_handler redirects to the authoring MFE.""" + response = self.client.get_html(get_url(self.course.id, 'export_handler')) + self.assertEqual(response.status_code, 302) # noqa: PT009 @ddt.ddt @@ -310,37 +271,6 @@ def compare_date_fields(self, details, encoded, context, field): elif field in encoded and encoded[field] is not None: self.fail(field + " included in encoding but missing from details at " + context) - @ddt.data( - (False, False), - (True, False), - (True, True), - ) - @ddt.unpack - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_upgrade_deadline(self, has_verified_mode, has_expiration_date): - if has_verified_mode: - deadline = None - if has_expiration_date: - deadline = self.course.start + datetime.timedelta(days=2) - CourseMode.objects.get_or_create( - course_id=self.course.id, - mode_display_name="Verified", - mode_slug="verified", - min_price=1, - _expiration_datetime=deadline, - ) - - settings_details_url = get_url(self.course.id) - response = self.client.get_html(settings_details_url) - self.assertEqual(b"Upgrade Deadline Date" in response.content, has_expiration_date and has_verified_mode) # noqa: PT009 # pylint: disable=line-too-long - - @mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True}) - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_pre_requisite_course_list_present(self): - settings_details_url = get_url(self.course.id) - response = self.client.get_html(settings_details_url) - self.assertContains(response, "Prerequisite Course") - @mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True}) def test_pre_requisite_course_update_and_fetch(self): self.assertFalse(milestones_helpers.any_unfulfilled_milestones(self.course.id, self.user.id), # noqa: PT009 @@ -390,62 +320,6 @@ def test_invalid_pre_requisite_course(self): response = self.client.ajax_post(url, course_detail_json) self.assertEqual(400, response.status_code) # noqa: PT009 - @ddt.data( - (False, False, False), - (True, False, True), - (False, True, False), - (True, True, True), - ) - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - @override_settings(MILESTONES_APP=False) - def test_visibility_of_entrance_exam_section(self, feature_flags): - """ - Tests entrance exam section is available if ENTRANCE_EXAMS feature is enabled no matter any other - feature is enabled or disabled i.e ENABLE_PUBLISHER. - """ - with patch.dict("django.conf.settings.FEATURES", { - 'ENABLE_PUBLISHER': feature_flags[1] - }), override_settings(ENTRANCE_EXAMS=feature_flags[0]): - course_details_url = get_url(self.course.id) - resp = self.client.get_html(course_details_url) - self.assertEqual( # noqa: PT009 - feature_flags[2], - b'

' in resp.content - ) - - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - @override_settings(MILESTONES_APP=False) - @override_settings(ENTRANCE_EXAMS=False) - def test_marketing_site_fetch(self): - settings_details_url = get_url(self.course.id) - - with mock.patch.dict('django.conf.settings.FEATURES', { - 'ENABLE_PUBLISHER': True, - 'ENABLE_MKTG_SITE': True, - 'ENABLE_PREREQUISITE_COURSES': False, - }): - response = self.client.get_html(settings_details_url) - self.assertNotContains(response, "Course Summary Page") - self.assertNotContains(response, "Send a note to students via email") - self.assertContains(response, "course summary page will not be viewable") - - self.assertContains(response, "Course Start Date") - self.assertContains(response, "Course End Date") - self.assertContains(response, "Enrollment Start Date") - self.assertContains(response, "Enrollment End Date") - - self.assertContains(response, "Course Short Description") - self.assertNotContains(response, "Course About Sidebar HTML") - self.assertNotContains(response, "Course Title") - self.assertNotContains(response, "Course Subtitle") - self.assertNotContains(response, "Course Duration") - self.assertNotContains(response, "Course Description") - self.assertNotContains(response, "Course Overview") - self.assertNotContains(response, "Course Introduction Video") - self.assertNotContains(response, "Requirements") - self.assertNotContains(response, "Course Banner Image") - self.assertNotContains(response, "Course Video Thumbnail Image") - @unittest.skipUnless(settings.FEATURES.get('ENTRANCE_EXAMS', False), True) def test_entrance_exam_created_updated_and_deleted_successfully(self): """ @@ -460,13 +334,6 @@ def test_entrance_exam_created_updated_and_deleted_successfully(self): data = { 'entrance_exam_enabled': 'true', 'entrance_exam_minimum_score_pct': '60', - 'syllabus': 'none', - 'short_description': 'empty', - 'overview': '', - 'effort': '', - 'intro_video': '', - 'start_date': '2012-01-01', - 'end_date': '2012-12-31', } response = self.client.post(settings_details_url, data=json.dumps(data), content_type='application/json', HTTP_ACCEPT='application/json') @@ -521,13 +388,6 @@ def test_entrance_exam_store_default_min_score(self): settings_details_url = get_url(self.course.id) test_data_1 = { 'entrance_exam_enabled': 'true', - 'syllabus': 'none', - 'short_description': 'empty', - 'overview': '', - 'effort': '', - 'intro_video': '', - 'start_date': '2012-01-01', - 'end_date': '2012-12-31', } response = self.client.post( settings_details_url, @@ -542,19 +402,11 @@ def test_entrance_exam_store_default_min_score(self): # entrance_exam_minimum_score_pct is not present in the request so default value should be saved. self.assertEqual(course.entrance_exam_minimum_score_pct, .5) # noqa: PT009 - #add entrance_exam_minimum_score_pct with empty value in json request. + # add entrance_exam_minimum_score_pct with empty value in json request. test_data_2 = { 'entrance_exam_enabled': 'true', 'entrance_exam_minimum_score_pct': '', - 'syllabus': 'none', - 'short_description': 'empty', - 'overview': '', - 'effort': '', - 'intro_video': '', - 'start_date': '2012-01-01', - 'end_date': '2012-12-31', } - response = self.client.post( settings_details_url, data=json.dumps(test_data_2), @@ -626,14 +478,6 @@ def test_entrance_after_changing_other_setting(self): assert milestones_helpers.any_unfulfilled_milestones(self.course.id, self.user.id), \ 'The entrance exam should be required.' - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_editable_short_description_fetch(self): - settings_details_url = get_url(self.course.id) - - with mock.patch.dict('django.conf.settings.FEATURES', {'EDITABLE_SHORT_DESCRIPTION': False}): - response = self.client.get_html(settings_details_url) - self.assertNotContains(response, "Course Short Description") - def test_empty_course_overview_keep_default_value(self): """ Test saving the course with an empty course overview. @@ -665,35 +509,6 @@ def test_empty_course_overview_keep_default_value(self): self.assertEqual(response.status_code, 200) # noqa: PT009 self.assertEqual(course_details.overview, '

 

') # noqa: PT009 - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_regular_site_fetch(self): - settings_details_url = get_url(self.course.id) - - with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_PUBLISHER': False, - 'ENABLE_EXTENDED_COURSE_DETAILS': True}): - response = self.client.get_html(settings_details_url) - self.assertContains(response, "Course Summary Page") - self.assertContains(response, "Send a note to students via email") - self.assertNotContains(response, "course summary page will not be viewable") - - self.assertContains(response, "Course Start Date") - self.assertContains(response, "Course End Date") - self.assertContains(response, "Enrollment Start Date") - self.assertContains(response, "Enrollment End Date") - - self.assertContains(response, "Introducing Your Course") - self.assertContains(response, "Course Card Image") - self.assertContains(response, "Course Title") - self.assertContains(response, "Course Subtitle") - self.assertContains(response, "Course Duration") - self.assertContains(response, "Course Description") - self.assertContains(response, "Course Short Description") - self.assertNotContains(response, "Course About Sidebar HTML") - self.assertContains(response, "Course Overview") - self.assertContains(response, "Course Introduction Video") - self.assertContains(response, "Requirements") - self.assertContains(response, "Course Banner Image") - self.assertContains(response, "Course Video Thumbnail Image") @ddt.ddt @@ -1933,132 +1748,3 @@ def test_add(self): self.assertEqual(obj, grader) # noqa: PT009 current_graders = CourseGradingModel.fetch(self.course.id).graders self.assertEqual(len(self.starting_graders) + 1, len(current_graders)) # noqa: PT009 - - -class CourseEnrollmentEndFieldTest(CourseTestCase): - """ - Base class to test the enrollment end fields in the course settings details view in Studio - when using marketing site flag and global vs non-global staff to access the page. - """ - - NOT_EDITABLE_HELPER_MESSAGE = "Contact your edX partner manager to update these settings." - NOT_EDITABLE_DATE_WRAPPER = "
" - NOT_EDITABLE_TIME_WRAPPER = "
" - NOT_EDITABLE_DATE_FIELD = "" - NOT_EDITABLE_TIME_FIELD = "" - - EDITABLE_DATE_WRAPPER = "
" - EDITABLE_TIME_WRAPPER = "
" - EDITABLE_DATE_FIELD = "" - EDITABLE_TIME_FIELD = "" - - EDITABLE_ELEMENTS = [ - EDITABLE_DATE_WRAPPER, - EDITABLE_TIME_WRAPPER, - EDITABLE_DATE_FIELD, - EDITABLE_TIME_FIELD, - ] - - NOT_EDITABLE_ELEMENTS = [ - NOT_EDITABLE_HELPER_MESSAGE, - NOT_EDITABLE_DATE_WRAPPER, - NOT_EDITABLE_TIME_WRAPPER, - NOT_EDITABLE_DATE_FIELD, - NOT_EDITABLE_TIME_FIELD, - ] - - def setUp(self): - """ - Initialize course used to test enrollment fields. - """ - super().setUp() - self.course = CourseFactory.create(org='edX', number='dummy', display_name='Marketing Site Course') - self.course_details_url = reverse_course_url('settings_handler', str(self.course.id)) - - def _get_course_details_response(self, global_staff): - """ - Return the course details page as either global or non-global staff - """ - user = UserFactory(is_staff=global_staff, password=self.TEST_PASSWORD) - CourseInstructorRole(self.course.id).add_users(user) - - self.client.login(username=user.username, password=self.TEST_PASSWORD) - - return self.client.get_html(self.course_details_url) - - def _verify_editable(self, response): - """ - Verify that the response has expected editable fields. - - Assert that all editable field content exists and no - uneditable field content exists for enrollment end fields. - """ - self.assertEqual(response.status_code, 200) # noqa: PT009 - for element in self.NOT_EDITABLE_ELEMENTS: - self.assertNotContains(response, element) - - for element in self.EDITABLE_ELEMENTS: - self.assertContains(response, element) - - def _verify_not_editable(self, response): - """ - Verify that the response has expected non-editable fields. - - Assert that all uneditable field content exists and no - editable field content exists for enrollment end fields. - """ - self.assertEqual(response.status_code, 200) # noqa: PT009 - for element in self.NOT_EDITABLE_ELEMENTS: - self.assertContains(response, element) - - for element in self.EDITABLE_ELEMENTS: - self.assertNotContains(response, element) - - @mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_PUBLISHER': False}) - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_course_details_with_disabled_setting_global_staff(self): - """ - Test that user enrollment end date is editable in response. - - Feature flag 'ENABLE_PUBLISHER' is not enabled. - User is global staff. - """ - self._verify_editable(self._get_course_details_response(True)) - - @mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_PUBLISHER': False}) - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_course_details_with_disabled_setting_non_global_staff(self): - """ - Test that user enrollment end date is editable in response. - - Feature flag 'ENABLE_PUBLISHER' is not enabled. - User is non-global staff. - """ - self._verify_editable(self._get_course_details_response(False)) - - @mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_PUBLISHER': True}) - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_course_details_with_enabled_setting_global_staff(self): - """ - Test that user enrollment end date is editable in response. - - Feature flag 'ENABLE_PUBLISHER' is enabled. - User is global staff. - """ - self._verify_editable(self._get_course_details_response(True)) - - @mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_PUBLISHER': True}) - @override_settings(PLATFORM_NAME='edX') - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_course_details_with_enabled_setting_non_global_staff(self): - """ - Test that user enrollment end date is not editable in response. - - Feature flag 'ENABLE_PUBLISHER' is enabled. - User is non-global staff. - """ - self._verify_not_editable(self._get_course_details_response(False)) diff --git a/cms/djangoapps/contentstore/toggles.py b/cms/djangoapps/contentstore/toggles.py index f4e513ce6709..32640abe2ca8 100644 --- a/cms/djangoapps/contentstore/toggles.py +++ b/cms/djangoapps/contentstore/toggles.py @@ -159,25 +159,6 @@ def use_react_markdown_editor(course_key): return ENABLE_REACT_MARKDOWN_EDITOR.is_enabled(course_key) -# .. toggle_name: legacy_studio.schedule_details -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Temporarily fall back to the old Studio Schedule & Details page. -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2025-03-14 -# .. toggle_target_removal_date: 2025-09-14 -# .. toggle_tickets: https://github.com/openedx/edx-platform/issues/36275 -# .. toggle_warning: In Ulmo, this toggle will be removed. Only the new (React-based) experience will be available. -LEGACY_STUDIO_SCHEDULE_DETAILS = CourseWaffleFlag('legacy_studio.schedule_details', __name__) - - -def use_new_schedule_details_page(course_key): - """ - Returns a boolean if new studio schedule and details mfe is enabled - """ - return not LEGACY_STUDIO_SCHEDULE_DETAILS.is_enabled(course_key) - - # .. toggle_name: legacy_studio.advanced_settings # .. toggle_implementation: WaffleFlag # .. toggle_default: False diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 63c97a855c32..e5142f8da263 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -47,7 +47,6 @@ use_new_export_page, use_new_group_configurations_page, use_new_import_page, - use_new_schedule_details_page, use_new_unit_page, ) from cms.djangoapps.models.settings.course_grading import CourseGradingModel @@ -303,13 +302,10 @@ def get_schedule_details_url(course_locator) -> str: """ Gets course authoring microfrontend URL for schedule and details pages view. """ - schedule_details_url = None - if use_new_schedule_details_page(course_locator): - mfe_base_url = get_course_authoring_url(course_locator) - course_mfe_url = f'{mfe_base_url}/course/{course_locator}/settings/details' - if mfe_base_url: - schedule_details_url = course_mfe_url - return schedule_details_url + mfe_base_url = get_course_authoring_url(course_locator) + if mfe_base_url: + return f'{mfe_base_url}/course/{course_locator}/settings/details' + return None def get_advanced_settings_url(course_locator) -> str: diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index ee9ba6cddb4f..0762d093d53c 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -101,14 +101,12 @@ default_enable_flexible_peer_openassessments, use_new_advanced_settings_page, use_new_group_configurations_page, - use_new_schedule_details_page, ) from ..utils import ( add_instructor, get_advanced_settings_url, get_course_outline_url, get_course_rerun_context, - get_course_settings, get_grading_url, get_group_configurations_context, get_group_configurations_url, @@ -1380,10 +1378,7 @@ def settings_handler(request, course_key_string): # pylint: disable=too-many-st with modulestore().bulk_operations(course_key): course_block = get_course_and_check_access(course_key, request.user) if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET': - if use_new_schedule_details_page(course_key): - return redirect(get_schedule_details_url(course_key)) - settings_context = get_course_settings(request, course_key, course_block) - return render_to_response('settings.html', settings_context) + return redirect(get_schedule_details_url(course_key)) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): # pylint: disable=too-many-nested-blocks if request.method == 'GET': course_details = CourseDetails.fetch(course_key) diff --git a/cms/djangoapps/contentstore/views/tests/test_credit_eligibility.py b/cms/djangoapps/contentstore/views/tests/test_credit_eligibility.py index f924b26384e7..686a3ab55267 100644 --- a/cms/djangoapps/contentstore/views/tests/test_credit_eligibility.py +++ b/cms/djangoapps/contentstore/views/tests/test_credit_eligibility.py @@ -3,17 +3,9 @@ """ -from unittest import mock - -from edx_toggles.toggles.testutils import override_waffle_flag - -from cms.djangoapps.contentstore import toggles from cms.djangoapps.contentstore.tests.utils import CourseTestCase from cms.djangoapps.contentstore.utils import reverse_course_url -from openedx.core.djangoapps.credit.api import get_credit_requirements -from openedx.core.djangoapps.credit.models import CreditCourse -from openedx.core.djangoapps.credit.signals.handlers import on_course_publish -from xmodule.modulestore.tests.factories import CourseFactory # pylint: disable=wrong-import-order +from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order class CreditEligibilityTest(CourseTestCase): @@ -25,43 +17,3 @@ def setUp(self): super().setUp() self.course = CourseFactory.create(org='edX', number='dummy', display_name='Credit Course') self.course_details_url = reverse_course_url('settings_handler', str(self.course.id)) - - @mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_CREDIT_ELIGIBILITY': False}) - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_course_details_with_disabled_setting(self): - """ - Test that user don't see credit eligibility requirements in response - if the feature flag 'ENABLE_CREDIT_ELIGIBILITY' is not enabled. - """ - response = self.client.get_html(self.course_details_url) - self.assertEqual(response.status_code, 200) # noqa: PT009 - self.assertNotContains(response, "Course Credit Requirements") - self.assertNotContains(response, "Steps required to earn course credit") - - @mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_CREDIT_ELIGIBILITY': True}) - @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - def test_course_details_with_enabled_setting(self): - """ - Test that credit eligibility requirements are present in - response if the feature flag 'ENABLE_CREDIT_ELIGIBILITY' is enabled. - """ - # verify that credit eligibility requirements block don't show if the - # course is not set as credit course - response = self.client.get_html(self.course_details_url) - self.assertEqual(response.status_code, 200) # noqa: PT009 - self.assertNotContains(response, "Course Credit Requirements") - self.assertNotContains(response, "Steps required to earn course credit") - - # verify that credit eligibility requirements block shows if the - # course is set as credit course and it has eligibility requirements - credit_course = CreditCourse(course_key=str(self.course.id), enabled=True) - credit_course.save() - self.assertEqual(len(get_credit_requirements(self.course.id)), 0) # noqa: PT009 - # test that after publishing course, minimum grade requirement is added - on_course_publish(self.course.id) - self.assertEqual(len(get_credit_requirements(self.course.id)), 1) # noqa: PT009 - - response = self.client.get_html(self.course_details_url) - self.assertEqual(response.status_code, 200) # noqa: PT009 - self.assertContains(response, "Course Credit Requirements") - self.assertContains(response, "Steps required to earn course credit") diff --git a/cms/djangoapps/contentstore/views/tests/test_exam_settings_view.py b/cms/djangoapps/contentstore/views/tests/test_exam_settings_view.py index f7edd959a2af..e59946d60d61 100644 --- a/cms/djangoapps/contentstore/views/tests/test_exam_settings_view.py +++ b/cms/djangoapps/contentstore/views/tests/test_exam_settings_view.py @@ -25,7 +25,6 @@ }, ) @override_waffle_flag(toggles.LEGACY_STUDIO_CERTIFICATES, True) -@override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) @override_waffle_flag(toggles.LEGACY_STUDIO_CONFIGURATIONS, True) @override_waffle_flag(toggles.LEGACY_STUDIO_ADVANCED_SETTINGS, True) @override_settings(COURSE_AUTHORING_MICROFRONTEND_URL='https://mfe.example') @@ -52,7 +51,6 @@ def _get_exam_settings_alert_text(raw_html_content): @override_waffle_flag(toggles.LEGACY_STUDIO_EXAM_SETTINGS, True) @ddt.data( "certificates_list_handler", - "settings_handler", "group_configurations_list_handler", "advanced_settings_handler" ) @@ -68,7 +66,6 @@ def test_view_without_exam_settings_enabled(self, handler): @ddt.data( "certificates_list_handler", - "settings_handler", "group_configurations_list_handler", "advanced_settings_handler" ) @@ -88,6 +85,13 @@ def test_grading_handler_redirects_to_mfe(self): resp = self.client.get(url, HTTP_ACCEPT='text/html') self.assertEqual(resp.status_code, 302) # noqa: PT009 + def test_settings_handler_redirects_to_mfe(self): + """settings_handler (schedule & details) redirects to the authoring MFE.""" + url = reverse_course_url('settings_handler', self.course.id) + resp = self.client.get(url, HTTP_ACCEPT='text/html') + self.assertEqual(resp.status_code, 302) # noqa: PT009 + + @override_settings( PROCTORING_BACKENDS={ 'DEFAULT': 'test_proctoring_provider', diff --git a/cms/static/cms/js/build.js b/cms/static/cms/js/build.js index f9f9bccea342..f544a38fec57 100644 --- a/cms/static/cms/js/build.js +++ b/cms/static/cms/js/build.js @@ -26,7 +26,6 @@ 'js/factories/index', 'js/factories/manage_users', 'js/factories/outline', - 'js/factories/settings', 'js/factories/settings_advanced' ]), /** diff --git a/cms/static/js/factories/settings.js b/cms/static/js/factories/settings.js deleted file mode 100644 index 472f3c874e9e..000000000000 --- a/cms/static/js/factories/settings.js +++ /dev/null @@ -1,41 +0,0 @@ -define([ - 'jquery', 'js/models/settings/course_details', 'js/views/settings/main' -], function($, CourseDetailsModel, MainView) { - 'use strict'; - - return function(detailsUrl, showMinGradeWarning, showCertificateAvailableDate, upgradeDeadline) { - var model; - // highlighting labels when fields are focused in - $('form :input') - .focus(function() { - $('label[for="' + this.id + '"]').addClass('is-focused'); - }) - .blur(function() { - $('label').removeClass('is-focused'); - }); - - // Toggle collapsibles when trigger is clicked - $('.collapsible .collapsible-trigger').click(function() { - const contentId = this.id.replace('-trigger', '-content'); - $(`#${contentId}`).toggleClass('collapsed'); - }); - - model = new CourseDetailsModel(); - model.urlRoot = detailsUrl; - model.showCertificateAvailableDate = showCertificateAvailableDate; - model.set('upgrade_deadline', upgradeDeadline); - model.fetch({ - // eslint-disable-next-line no-shadow - success: function(model) { - var editor = new MainView({ - el: $('.settings-details'), - model: model, - showMinGradeWarning: showMinGradeWarning - }); - editor.render(); - }, - reset: true, - cache: false - }); - }; -}); diff --git a/cms/templates/settings.html b/cms/templates/settings.html deleted file mode 100644 index df64bcc39361..000000000000 --- a/cms/templates/settings.html +++ /dev/null @@ -1,723 +0,0 @@ -<%page expression_filter="h"/> -<%inherit file="base.html" /> -<%def name="online_help_token()"><% return "schedule" %> -<%block name="title">${_("Schedule & Details Settings")} -<%block name="bodyclass">is-signedin course schedule view-settings feature-upload - -<%namespace name='static' file='static_content.html'/> -<%! - from django.utils.translation import gettext as _ - from common.djangoapps.student.auth import has_studio_advanced_settings_access - from cms.djangoapps.contentstore import utils - from lms.djangoapps.certificates.api import can_show_certificate_available_date_field - from openedx.core.djangolib.js_utils import ( - dump_js_escaped_json, js_escaped_string - ) - from openedx.core.djangolib.markup import HTML, Text - from six.moves.urllib.parse import quote - from six.moves.urllib import parse as urllib -%> - -<%block name="header_extras"> -% for template_name in ["basic-modal", "modal-button", "upload-dialog", "license-selector", "course-settings-learning-fields", "course-instructor-details"]: - -% endfor - - -<%block name="jsextra"> - - - - - -<%block name="requirejs"> - require(["js/factories/settings"], function(SettingsFactory) { - SettingsFactory( - "${details_url | n, js_escaped_string}", - ${show_min_grade_warning | n, dump_js_escaped_json}, - ${can_show_certificate_available_date_field(context_course) | n, dump_js_escaped_json}, - "${upgrade_deadline | n, js_escaped_string}", - ); - }); - - -<%block name="content"> -
-
-

- ${_("Settings")} - > ${_("Schedule & Details")} -

-
-
- -
-
-
-
-
-
-

${_("Basic Information")}

- ${_("The nuts and bolts of your course")} -
- -
    -
  1. - - -
  2. - -
  3. - - -
  4. - -
  5. - - -
  6. -
- - % if not marketing_enabled: -
-

${_("Course Summary Page")} ${_("(for student enrollment and access)")}

-
- <% - link_for_about_page = lms_link_for_about_page - %> -

${link_for_about_page}

-
- -
    -
  • - <% - email_subject = urllib.quote(_("Enroll in {course_display_name}").format( - course_display_name = context_course.display_name_with_default - ).encode("utf-8")) - email_body = urllib.quote(_('The course "{course_display_name}", provided by {platform_name}, is open for enrollment. Please navigate to this course at {link_for_about_page} to enroll.').format( - course_display_name = context_course.display_name_with_default, - platform_name = settings.PLATFORM_NAME, - link_for_about_page = link_for_about_page - ).encode("utf-8")) - %> - - ${_("Invite your students")} -
  • -
-
- % endif - - % if marketing_enabled: -
-

${_("Promoting Your Course with {platform_name}").format(platform_name=settings.PLATFORM_NAME)}

-
-

${_( - 'Your course summary page will not be viewable until your course ' - 'has been announced. To provide content for the page and preview ' - 'it, follow the instructions provided by your Program Manager.')} - ${_( - 'Please note that changes here may take up to a business day to ' - 'appear on your course summary page.')} -

-
-
- % endif -
-
- - % if credit_eligibility_enabled and is_credit_course: -
-
-

${_("Course Credit Requirements")}

- ${_("Steps required to earn course credit")} -
- A requirement appears in this list when you publish the unit that contains the requirement. - - % if credit_requirements: -
    - % if 'grade' in credit_requirements: -
  1. - - % for requirement in credit_requirements['grade']: - - - % endfor -
  2. - % endif - - % if 'proctored_exam' in credit_requirements: -
  3. - - % for requirement in credit_requirements['proctored_exam']: - - - % endfor -
  4. - % endif - - % if 'reverification' in credit_requirements: -
  5. - - % for requirement in credit_requirements['reverification']: - - - % endfor -
  6. - % endif -
- % else: -

No credit requirements found.

- % endif -
-
- % endif - -
-
- -
-

${_("Course Pacing")}

- ${_("Set the pacing for this course")} -
-
- - -
    -
  1. - - - ${_("Instructor-paced courses progress at the pace that the course author sets. You can configure release dates for course content and due dates for assignments.")} -
  2. -
  3. - - - ${_("Self-paced courses offer suggested due dates for assignments or exams based on the learner’s enrollment date and the expected course duration. These courses offer learners flexibility to modify the assignment dates as needed.")} -
  4. -
-
-
- -
- -
-
-

${_('Course Schedule')}

- ${_('Dates that control when your course can be viewed')} -
- -
    -
  1. -
    - - - - ${_("First day the course begins")} -
    - -
    - - - ${_("(UTC)")} -
    -
  2. - -
  3. -
    - - - - ${_("Last day your course is active")} -
    - -
    - - - ${_("(UTC)")} -
    -
  4. -
- - % if can_show_certificate_available_date_field(context_course): -
    -
  1. -
    - - - ${_("Certificates are awarded at the end of a course run")} - - -
    - - -
    -
    - - -
  2. -
- % endif - -
    -
  1. -
    - - - - ${_("First day students can enroll")} -
    - -
    - - - ${_("(UTC)")} -
    -
  2. - <% - enrollment_end_readonly = HTML("readonly aria-readonly=\"true\"") if not enrollment_end_editable else "" - enrollment_end_editable_class = "is-not-editable" if not enrollment_end_editable else "" - %> -
  3. -
    - - - - - ${_("Last day students can enroll.")} - % if not enrollment_end_editable: - ${_("Contact your {platform_name} partner manager to update these settings.").format(platform_name=settings.PLATFORM_NAME)} - % endif - -
    - -
    - - - ${_("(UTC)")} -
    -
  4. -
- - % if upgrade_deadline: -
    -
  1. -
    - - - - ${_("Last day students can upgrade to a verified enrollment.")} - ${_("Contact your {platform_name} partner manager to update these settings.").format(platform_name=settings.PLATFORM_NAME)} - -
    - -
    - - - ${_("(UTC)")} -
    -
  2. -
- % endif -
- - % if about_page_editable: -
-
-

${_('Course Details')}

- ${_('Provide useful information about your course')} -
-
    -
  1. - - - ${_("Identify the course language here. This is used to assist users find courses that are taught in a specific language. It is also used to localize the 'From:' field in bulk emails.")} -
  2. -
-
- % endif - -
-
- - % if about_page_editable: -
-

${_("Introducing Your Course")}

- ${_("Information for prospective students")} -
- % endif - -
    - - % if enable_extended_course_details: -
  1. - - - ${_("Displayed as title on the course details page. Limit to 50 characters.")} -
  2. -
  3. - - - ${_("Displayed as subtitle on the course details page. Limit to 150 characters.")} -
  4. -
  5. - - - ${_("Displayed on the course details page. Limit to 50 characters.")} -
  6. -
  7. - - - ${_("Displayed on the course details page. Limit to 1000 characters.")} -
  8. - % endif - - % if short_description_editable: -
  9. - - - ${_("Appears on the course catalog page when students roll over the course name. Limit to ~150 characters")} -
  10. - % endif - - % if about_page_editable: -
  11. - - - - ${ - Text(_("Introductions, prerequisites, FAQs that are used on {a_link_start}your course summary page{a_link_end} (formatted in HTML)")).format( - a_link_start=HTML("").format(lms_link_for_about_page=lms_link_for_about_page), - a_link_end=HTML("") - )} -
  12. - % if sidebar_html_enabled: -
  13. - - ${ - Text(_("Custom sidebar content for {a_link_start}your course summary page{a_link_end} (formatted in HTML)")).format( - a_link_start=HTML("").format(lms_link_for_about_page=lms_link_for_about_page), - a_link_end=HTML("") - )} -
  14. - % endif - % endif - - % if about_page_editable: -
  15. - -
    - % if context_course.course_image: - - ${_('Course Card Image')} - - - - ${Text(_("You can manage this image along with all of your other {a_link_start}files and uploads{a_link_end}")).format( - a_link_start=HTML("").format(upload_asset_url=upload_asset_url), - a_link_end=HTML("") - )} - - - % else: - - ${_('Course Card Image')} - - ${_("Your course currently does not have an image. Please upload one (JPEG or PNG format, and minimum suggested dimensions are 375px wide by 200px tall)")} - % endif -
    - -
    -
    - ## Translators: This is the placeholder text for a field that requests the URL for a course image - - ${_("Please provide a valid path and name to your course image (Note: only JPEG or PNG format supported)")} -
    - -
    -
  16. - % endif - - % if enable_extended_course_details: -
  17. - -
    - % if context_course.banner_image: - - - - - - ${Text(_("You can manage this image along with all of your other {a_link_start}files and uploads{a_link_end}")).format( - a_link_start=HTML("").format(upload_asset_url=upload_asset_url), - a_link_end=HTML("") - )} - - - % else: - - - - ${_("Your course currently does not have an image. Please upload one (JPEG or PNG format, and minimum suggested dimensions are 1440px wide by 400px tall)")} - % endif -
    - -
    -
    - ## Translators: This is the placeholder text for a field that requests the URL for a course banner image - - ${_("Please provide a valid path and name to your banner image (Note: only JPEG or PNG format supported)")} -
    - -
    -
  18. - -
  19. - -
    - % if context_course.video_thumbnail_image: - - ${_('Video Thumbnail Image')} - - - - ${Text(_("You can manage this image along with all of your other {a_link_start}files and uploads{a_link_end}")).format( - a_link_start=HTML("").format(upload_asset_url=upload_asset_url), - a_link_end=HTML("") - )} - - - % else: - - ${_('Video Thumbnail Image')} - - ${_("Your course currently does not have a video thumbnail image. Please upload one (JPEG or PNG format, and minimum suggested dimensions are 375px wide by 200px tall)")} - % endif -
    - -
    -
    - ## Translators: This is the placeholder text for a field that requests the URL for a course video thumbnail image - - ${_("Please provide a valid path and name to your video thumbnail image (Note: only JPEG or PNG format supported)")} -
    - -
    -
  20. - % endif - - % if about_page_editable: -
  21. - - - -
    - ## Translators: This is the placeholder text for a field that requests a YouTube video ID for a course video - - ${_("Enter your YouTube video's ID (along with any restriction parameters)")} -
    -
  22. - % endif -
-
- - % if enable_extended_course_details: -
-
-
-

${_("Learning Outcomes")}

- ${_("Add the learning outcomes for this course")} -
-
    -
  1. -
-
- -
-
- -
-
-
-

${_("Instructors")}

- ${_("Add details about the instructors for this course")} -
-
    -
  1. -
-
- -
-
- % endif - - % if about_page_editable or is_prerequisite_courses_enabled or is_entrance_exams_enabled: -
- -
-
-

${_("Requirements")}

- ${_("Expectations of the students taking this course")} -
- -
    - % if about_page_editable: -
  1. - - - ${_("Time spent on all course work")} -
  2. - % endif - % if is_prerequisite_courses_enabled: -
  3. - - - ${_("Course that students must complete before beginning this course")} - -
  4. - % endif - % if is_entrance_exams_enabled: -
  5. -

    ${_("Entrance Exam")}

    -
    -
    - - -
    - -
    -
  6. - % endif -
-
- % endif - - % if settings.FEATURES.get("LICENSING", False): -
- -
-
-

${_("Course Content License")}

- ## Translators: At the course settings, the editor is able to select the default course content license. - ## The course content will have this license set, some assets can override the license with their own. - ## In the form, the license selector for course content is described using the following string: - ${_("Select the default license for course content")} -
- -
    -
  1. -
    -
  2. -
-
- % endif -
-
- -
-
- diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 9ad8492919b4..0dcdce32ff5a 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -37,7 +37,6 @@

import_url = reverse('import_handler', kwargs={'course_key_string': str(course_key)}) course_info_url = reverse('course_info_handler', kwargs={'course_key_string': str(course_key)}) export_url = reverse('export_handler', kwargs={'course_key_string': str(course_key)}) - settings_url = reverse('settings_handler', kwargs={'course_key_string': str(course_key)}) advanced_settings_url = reverse('advanced_settings_handler', kwargs={'course_key_string': str(course_key)}) tabs_url = reverse('tabs_handler', kwargs={'course_key_string': str(course_key)}) certificates_url = '' @@ -45,7 +44,6 @@

certificates_url = reverse('certificates_list_handler', kwargs={'course_key_string': str(course_key)}) checklists_url = reverse('checklists_handler', kwargs={'course_key_string': str(course_key)}) pages_and_resources_mfe_enabled = ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND.is_enabled(context_course.id) - schedule_details_mfe_enabled = toggles.use_new_schedule_details_page(context_course.id) course_team_mfe_enabled = toggles.use_new_course_team_page(context_course.id) advanced_settings_mfe_enabled = toggles.use_new_advanced_settings_page(context_course.id) import_mfe_enabled = toggles.use_new_import_page(context_course.id) @@ -116,16 +114,9 @@

${_("Course"