diff --git a/cms/djangoapps/contentstore/toggles.py b/cms/djangoapps/contentstore/toggles.py index f16a5e0848..39fd1482c3 100644 --- a/cms/djangoapps/contentstore/toggles.py +++ b/cms/djangoapps/contentstore/toggles.py @@ -62,3 +62,20 @@ def bypass_olx_failure_enabled(): Check if bypass is enabled for course olx validation errors. """ return BYPASS_OLX_FAILURE.is_enabled() + + +# .. toggle_name: FEATURES['ENABLE_EXAM_SETTINGS_HTML_VIEW'] +# .. toggle_implementation: SettingDictToggle +# .. toggle_default: False +# .. toggle_description: When enabled, users can access the new course authoring view for proctoring exams +# .. toggle_warnings: None +# .. toggle_creation_date: 2020-07-23 +ENABLE_EXAM_SETTINGS_HTML_VIEW = SettingDictToggle( + "FEATURES", "ENABLE_EXAM_SETTINGS_HTML_VIEW", default=False, module_name=__name__ +) + +def exam_setting_view_enabled(): + """ + Returns a boolean if proctoring exam setting mfe view is enabled. + """ + return ENABLE_EXAM_SETTINGS_HTML_VIEW.is_enabled() diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index db310a6100..703f7dc653 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -2,7 +2,6 @@ Common utility functions useful throughout the contentstore """ - import logging from contextlib import contextmanager from datetime import datetime @@ -16,6 +15,7 @@ from opaque_keys.edx.locator import LibraryLocator from pytz import UTC from cms.djangoapps.contentstore.config.waffle import ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND +from cms.djangoapps.contentstore.toggles import exam_setting_view_enabled from common.djangoapps.student import auth from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole @@ -157,37 +157,39 @@ def get_lms_link_for_certificate_web_view(course_key, mode): ) -def get_course_authoring_url(course_module): +def get_course_authoring_url(course_locator): """ Gets course authoring microfrontend URL """ return configuration_helpers.get_value_for_org( - course_module.location.org, + course_locator.org, 'COURSE_AUTHORING_MICROFRONTEND_URL', settings.COURSE_AUTHORING_MICROFRONTEND_URL ) -def get_pages_and_resources_url(course_module): +def get_pages_and_resources_url(course_locator): """ Gets course authoring microfrontend URL for Pages and Resources view. """ pages_and_resources_url = None - if ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND.is_enabled(course_module.id): - mfe_base_url = get_course_authoring_url(course_module) + if ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND.is_enabled(course_locator): + mfe_base_url = get_course_authoring_url(course_locator) if mfe_base_url: - pages_and_resources_url = f'{mfe_base_url}/course/{course_module.id}/pages-and-resources' + pages_and_resources_url = f'{mfe_base_url}/course/{course_locator}/pages-and-resources' return pages_and_resources_url -def get_proctored_exam_settings_url(course_module): +def get_proctored_exam_settings_url(course_locator) -> str: """ Gets course authoring microfrontend URL for links to proctored exam settings page """ - course_authoring_microfrontend_url = '' - if settings.FEATURES.get('ENABLE_EXAM_SETTINGS_HTML_VIEW'): - course_authoring_microfrontend_url = get_course_authoring_url(course_module) - return course_authoring_microfrontend_url + proctored_exam_settings_url = '' + if exam_setting_view_enabled(): + mfe_base_url = get_course_authoring_url(course_locator) + if mfe_base_url: + proctored_exam_settings_url = f'{mfe_base_url}/course/{course_locator}/proctored-exam-settings' + return proctored_exam_settings_url def course_import_olx_validation_is_enabled(): diff --git a/cms/djangoapps/contentstore/views/certificates.py b/cms/djangoapps/contentstore/views/certificates.py index a12d4755de..95aedb85e2 100644 --- a/cms/djangoapps/contentstore/views/certificates.py +++ b/cms/djangoapps/contentstore/views/certificates.py @@ -411,11 +411,7 @@ def certificates_list_handler(request, course_key_string): ) else: certificate_web_view_url = None - is_active, certificates = CertificateManager.is_activated(course) - - course_authoring_microfrontend_url = get_proctored_exam_settings_url(course) - return render_to_response('certificates.html', { 'context_course': course, 'certificate_url': certificate_url, @@ -428,7 +424,7 @@ def certificates_list_handler(request, course_key_string): 'is_active': is_active, 'is_global_staff': GlobalStaff().has_user(request.user), 'certificate_activation_handler_url': activation_handler_url, - 'course_authoring_microfrontend_url': course_authoring_microfrontend_url, + 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course.id), }) elif "application/json" in request.META.get('HTTP_ACCEPT'): # Retrieve the list of certificates for the specified course diff --git a/cms/djangoapps/contentstore/views/checklists.py b/cms/djangoapps/contentstore/views/checklists.py index c73e52194c..d9a440200a 100644 --- a/cms/djangoapps/contentstore/views/checklists.py +++ b/cms/djangoapps/contentstore/views/checklists.py @@ -28,17 +28,8 @@ def checklists_handler(request, course_key_string=None): raise PermissionDenied() course_module = modulestore().get_course(course_key) - - course_authoring_microfrontend_url = get_proctored_exam_settings_url(course_module) - proctored_exam_settings_url = ( - '{course_authoring_microfrontend_url}/proctored-exam-settings/{course_key_string}'.format( - course_authoring_microfrontend_url=course_authoring_microfrontend_url, - course_key_string=course_key_string, - ) - ) - return render_to_response('checklists.html', { 'language_code': request.LANGUAGE_CODE, 'context_course': course_module, - 'proctored_exam_settings_url': proctored_exam_settings_url, + 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_module.id), }) diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index bca41f6748..44ea5c58bb 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -700,9 +700,6 @@ def course_index(request, course_key): 'FRONTEND_APP_PUBLISHER_URL', settings.FEATURES.get('FRONTEND_APP_PUBLISHER_URL', False) ) - - course_authoring_microfrontend_url = get_proctored_exam_settings_url(course_module) - # gather any errors in the currently stored proctoring settings. advanced_dict = CourseMetadata.fetch(course_module) proctoring_errors = CourseMetadata.validate_proctoring_settings(course_module, advanced_dict, request.user) @@ -727,7 +724,7 @@ def course_index(request, course_key): }, ) if current_action else None, 'frontend_app_publisher_url': frontend_app_publisher_url, - 'course_authoring_microfrontend_url': course_authoring_microfrontend_url, + 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_module.id), 'advance_settings_url': reverse_course_url('advanced_settings_handler', course_module.id), 'proctoring_errors': proctoring_errors, }) @@ -1142,9 +1139,6 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab verified_mode = CourseMode.verified_mode_for_course(course_key, include_expired=True) upgrade_deadline = (verified_mode and verified_mode.expiration_datetime and verified_mode.expiration_datetime.isoformat()) - - course_authoring_microfrontend_url = get_proctored_exam_settings_url(course_module) - settings_context = { 'context_course': course_module, 'course_locator': course_key, @@ -1168,7 +1162,7 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab 'is_entrance_exams_enabled': core_toggles.ENTRANCE_EXAMS.is_enabled(), 'enable_extended_course_details': enable_extended_course_details, 'upgrade_deadline': upgrade_deadline, - 'course_authoring_microfrontend_url': course_authoring_microfrontend_url, + 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_module.id), } if is_prerequisite_courses_enabled(): courses, in_process_course_actions = get_courses_accessible_to_user(request) @@ -1282,16 +1276,13 @@ def grading_handler(request, course_key_string, grader_index=None): if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET': course_details = CourseGradingModel.fetch(course_key) - - course_authoring_microfrontend_url = get_proctored_exam_settings_url(course_module) - return render_to_response('settings_graders.html', { 'context_course': course_module, 'course_locator': course_key, 'course_details': course_details, 'grading_url': reverse_course_url('grading_handler', course_key), 'is_credit_course': is_credit_course(course_key), - 'course_authoring_microfrontend_url': course_authoring_microfrontend_url, + 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_module.id), }) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): if request.method == 'GET': @@ -1388,9 +1379,6 @@ def advanced_settings_handler(request, course_key_string): 'ENABLE_PUBLISHER', settings.FEATURES.get('ENABLE_PUBLISHER', False) ) - - course_authoring_microfrontend_url = get_proctored_exam_settings_url(course_module) - # gather any errors in the currently stored proctoring settings. proctoring_errors = CourseMetadata.validate_proctoring_settings(course_module, advanced_dict, request.user) @@ -1399,7 +1387,7 @@ def advanced_settings_handler(request, course_key_string): 'advanced_dict': advanced_dict, 'advanced_settings_url': reverse_course_url('advanced_settings_handler', course_key), 'publisher_enabled': publisher_enabled, - 'course_authoring_microfrontend_url': course_authoring_microfrontend_url, + 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_module.id), 'proctoring_errors': proctoring_errors, }) elif 'application/json' in request.META.get('HTTP_ACCEPT', ''): @@ -1743,9 +1731,6 @@ def group_configurations_list_handler(request, course_key_string): # This will add ability to add new groups in the view. if not has_content_groups: displayable_partitions.append(GroupConfiguration.get_or_create_content_group(store, course)) - - course_authoring_microfrontend_url = get_proctored_exam_settings_url(course) - return render_to_response('group_configurations.html', { 'context_course': course, 'group_configuration_url': group_configuration_url, @@ -1754,7 +1739,7 @@ def group_configurations_list_handler(request, course_key_string): 'should_show_experiment_groups': should_show_experiment_groups, 'all_group_configurations': displayable_partitions, 'should_show_enrollment_track': should_show_enrollment_track, - 'course_authoring_microfrontend_url': course_authoring_microfrontend_url, + 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course.id), }) elif "application/json" in request.META.get('HTTP_ACCEPT'): if request.method == 'POST': diff --git a/cms/djangoapps/contentstore/views/tabs.py b/cms/djangoapps/contentstore/views/tabs.py index c6db72d797..1e9df725c4 100644 --- a/cms/djangoapps/contentstore/views/tabs.py +++ b/cms/djangoapps/contentstore/views/tabs.py @@ -71,7 +71,7 @@ def tabs_handler(request, course_key_string): 'context_course': course_item, 'tabs_to_render': tabs_to_render, 'lms_link': get_lms_link_for_item(course_item.location), - 'pages_and_resources_mfe_link': get_pages_and_resources_url(course_item), + 'pages_and_resources_mfe_link': get_pages_and_resources_url(course_item.id), }) else: return HttpResponseNotFound() diff --git a/cms/djangoapps/contentstore/views/tests/test_course_index.py b/cms/djangoapps/contentstore/views/tests/test_course_index.py index 92e428c585..b2182b9ec9 100644 --- a/cms/djangoapps/contentstore/views/tests/test_course_index.py +++ b/cms/djangoapps/contentstore/views/tests/test_course_index.py @@ -6,6 +6,7 @@ Unit tests for getting the list of courses and the course outline. import datetime import json from unittest import mock +from unittest.mock import patch import ddt import lxml @@ -19,7 +20,12 @@ from search.api import perform_search from cms.djangoapps.contentstore.courseware_index import CoursewareSearchIndexer, SearchIndexingError from cms.djangoapps.contentstore.tests.utils import CourseTestCase -from cms.djangoapps.contentstore.utils import add_instructor, reverse_course_url, reverse_usage_url +from cms.djangoapps.contentstore.utils import ( + add_instructor, + get_proctored_exam_settings_url, + reverse_course_url, + reverse_usage_url +) from common.djangoapps.course_action_state.managers import CourseRerunUIStateManager from common.djangoapps.course_action_state.models import CourseRerunState from common.djangoapps.student.auth import has_course_author_access @@ -589,6 +595,28 @@ class TestCourseOutline(CourseTestCase): expected_block_types ) + @override_settings(FEATURES={'ENABLE_EXAM_SETTINGS_HTML_VIEW': True}) + @patch('cms.djangoapps.models.settings.course_metadata.CourseMetadata.validate_proctoring_settings') + def test_proctoring_link_is_visible(self, mock_validate_proctoring_settings): + """ + Test to check proctored exam settings mfe url is rendering properly + """ + mock_validate_proctoring_settings.return_value = [ + { + 'key': 'proctoring_provider', + 'message': 'error message', + 'model': {'display_name': 'proctoring_provider'} + }, + { + 'key': 'proctoring_provider', + 'message': 'error message', + 'model': {'display_name': 'proctoring_provider'} + } + ] + response = self.client.get_html(reverse_course_url('course_handler', self.course.id)) + proctored_exam_settings_url = get_proctored_exam_settings_url(self.course.id) + self.assertContains(response, proctored_exam_settings_url, 2) + class TestCourseReIndex(CourseTestCase): """ 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 6525d15573..7cd95c74b9 100644 --- a/cms/djangoapps/contentstore/views/tests/test_exam_settings_view.py +++ b/cms/djangoapps/contentstore/views/tests/test_exam_settings_view.py @@ -2,13 +2,15 @@ Exam Settings View Tests """ +from unittest.mock import patch + import ddt import lxml from django.conf import settings from django.test.utils import override_settings from cms.djangoapps.contentstore.tests.utils import CourseTestCase -from cms.djangoapps.contentstore.utils import reverse_course_url +from cms.djangoapps.contentstore.utils import get_proctored_exam_settings_url, reverse_course_url from common.djangoapps.util.testing import UrlResetMixin FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy() @@ -164,3 +166,26 @@ class TestExamSettingsView(CourseTestCase, UrlResetMixin): parsed_html = lxml.html.fromstring(resp.content) alert_nodes = parsed_html.find_class('exam-settings-alert') assert len(alert_nodes) == 0 + + @override_settings(FEATURES={'ENABLE_EXAM_SETTINGS_HTML_VIEW': True}) + @patch('cms.djangoapps.models.settings.course_metadata.CourseMetadata.validate_proctoring_settings') + def test_proctoring_link_is_visible(self, mock_validate_proctoring_settings): + + """ + Test to check proctored exam settings mfe url is rendering properly + """ + mock_validate_proctoring_settings.return_value = [ + { + 'key': 'proctoring_provider', + 'message': 'error message', + 'model': {'display_name': 'proctoring_provider'} + }, + { + 'key': 'proctoring_provider', + 'message': 'error message', + 'model': {'display_name': 'proctoring_provider'} + } + ] + response = self.client.get_html(reverse_course_url('advanced_settings_handler', self.course.id)) + proctored_exam_settings_url = get_proctored_exam_settings_url(self.course.id) + self.assertContains(response, proctored_exam_settings_url, 3) diff --git a/cms/templates/certificates.html b/cms/templates/certificates.html index c662af796e..8c7853d89d 100644 --- a/cms/templates/certificates.html +++ b/cms/templates/certificates.html @@ -118,8 +118,8 @@ CMS.User.isGlobalStaff = '${is_global_staff | n, js_escaped_string}'=='True' ? t - % if course_authoring_microfrontend_url: - + % if mfe_proctored_exam_settings_url: + % endif diff --git a/cms/templates/checklists.html b/cms/templates/checklists.html index 11f50a60ab..bfe7fa2908 100644 --- a/cms/templates/checklists.html +++ b/cms/templates/checklists.html @@ -67,7 +67,7 @@ "course_updates": ${utils.reverse_course_url('course_info_handler', course_key) | n, dump_js_escaped_json}, "grading_policy": ${utils.reverse_course_url('grading_handler', course_key) | n, dump_js_escaped_json}, "settings": ${utils.reverse_course_url('settings_handler', course_key) | n, dump_js_escaped_json}, - "proctored_exam_settings": ${proctored_exam_settings_url | n, dump_js_escaped_json} + "proctored_exam_settings": ${mfe_proctored_exam_settings_url | n, dump_js_escaped_json} } } diff --git a/cms/templates/course_outline.html b/cms/templates/course_outline.html index 1be2997a43..0bf9ce60d4 100644 --- a/cms/templates/course_outline.html +++ b/cms/templates/course_outline.html @@ -115,7 +115,7 @@ from django.urls import reverse %endif - + %if proctoring_errors:
@@ -124,12 +124,11 @@ from django.urls import reverse

${_("This course has proctored exam settings that are incomplete or invalid.")}

- % if course_authoring_microfrontend_url: + % if mfe_proctored_exam_settings_url: <% url_encoded_course_id = quote(six.text_type(context_course.id).encode('utf-8'), safe='') %> ${Text(_("To update these settings go to the {link_start}Proctored Exam Settings page{link_end}.")).format( - link_start=HTML('').format( - course_authoring_microfrontend_url=course_authoring_microfrontend_url, - url_encoded_course_id=url_encoded_course_id, + link_start=HTML('').format( + mfe_proctored_exam_settings_url=mfe_proctored_exam_settings_url ), link_end=HTML("") )} diff --git a/cms/templates/group_configurations.html b/cms/templates/group_configurations.html index 08ab1f752d..27a0c3f252 100644 --- a/cms/templates/group_configurations.html +++ b/cms/templates/group_configurations.html @@ -128,8 +128,8 @@ from six.moves.urllib.parse import quote

- % if course_authoring_microfrontend_url: - + % if mfe_proctored_exam_settings_url: + % endif diff --git a/cms/templates/settings.html b/cms/templates/settings.html index e5202573ea..22832c4adc 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -669,8 +669,8 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url | n, js_escaped_string}' - % if course_authoring_microfrontend_url: - + % if mfe_proctored_exam_settings_url: + % endif diff --git a/cms/templates/settings_advanced.html b/cms/templates/settings_advanced.html index 4f88af2366..91362eaa5a 100644 --- a/cms/templates/settings_advanced.html +++ b/cms/templates/settings_advanced.html @@ -42,12 +42,11 @@

${_("This course has proctored exam settings that are incomplete or invalid.")}

- % if course_authoring_microfrontend_url: + % if mfe_proctored_exam_settings_url: <% url_encoded_course_id = quote(six.text_type(context_course.id).encode('utf-8'), safe='') %> ${Text(_("You will be unable to make changes until the errors are resolved. To update these settings go to the {link_start}Proctored Exam Settings page{link_end}.")).format( - link_start=HTML('').format( - course_authoring_microfrontend_url=course_authoring_microfrontend_url, - url_encoded_course_id=url_encoded_course_id, + link_start=HTML('').format( + mfe_proctored_exam_settings_url=mfe_proctored_exam_settings_url ), link_end=HTML("") )} @@ -149,8 +148,8 @@

- % if course_authoring_microfrontend_url: - + % if mfe_proctored_exam_settings_url: + % endif diff --git a/cms/templates/settings_graders.html b/cms/templates/settings_graders.html index 15d95f8f9d..fc55dc7d97 100644 --- a/cms/templates/settings_graders.html +++ b/cms/templates/settings_graders.html @@ -166,8 +166,8 @@ - % if course_authoring_microfrontend_url: - + % if mfe_proctored_exam_settings_url: + % endif diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 4594f113b1..137b33e261 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -104,9 +104,9 @@ - % if course_authoring_microfrontend_url: + % if mfe_proctored_exam_settings_url: % endif