diff --git a/cms/djangoapps/contentstore/views/tests/test_exam_settings_view.py b/cms/djangoapps/contentstore/views/tests/test_exam_settings_view.py
new file mode 100644
index 0000000000..adca08d9ab
--- /dev/null
+++ b/cms/djangoapps/contentstore/views/tests/test_exam_settings_view.py
@@ -0,0 +1,72 @@
+#-*- coding: utf-8 -*-
+
+"""
+Exam Settings View Tests
+"""
+
+import ddt
+
+from django.conf import settings
+from django.test.utils import override_settings
+
+from contentstore.tests.utils import CourseTestCase
+from contentstore.utils import reverse_course_url
+from util.testing import UrlResetMixin
+
+FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
+FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
+
+FEATURES_WITH_EXAM_SETTINGS_ENABLED = FEATURES_WITH_CERTS_ENABLED.copy()
+FEATURES_WITH_EXAM_SETTINGS_ENABLED['ENABLE_EXAM_SETTINGS_HTML_VIEW'] = True
+
+FEATURES_WITH_EXAM_SETTINGS_DISABLED = FEATURES_WITH_CERTS_ENABLED.copy()
+FEATURES_WITH_EXAM_SETTINGS_DISABLED['ENABLE_EXAM_SETTINGS_HTML_VIEW'] = False
+
+
+@ddt.ddt
+class TestExamSettingsView(CourseTestCase, UrlResetMixin):
+ """
+ Unit tests for the exam settings view.
+ """
+ def setUp(self):
+ """
+ Set up the for the exam settings view tests.
+ """
+ super(TestExamSettingsView, self).setUp()
+ self.reset_urls()
+
+ @override_settings(FEATURES=FEATURES_WITH_EXAM_SETTINGS_DISABLED)
+ @ddt.data(
+ "certificates_list_handler",
+ "settings_handler",
+ "group_configurations_list_handler",
+ "grading_handler",
+ "advanced_settings_handler"
+ )
+ def test_view_without_exam_settings_enabled(self, handler):
+ """
+ Tests pages should not have `Exam Settings` item
+ if course does not have the Exam Settings view enabled.
+ """
+ outline_url = reverse_course_url(handler, self.course.id)
+ resp = self.client.get(outline_url, HTTP_ACCEPT='text/html')
+ self.assertEqual(resp.status_code, 200)
+ self.assertNotContains(resp, 'Proctored Exam Settings')
+
+ @override_settings(FEATURES=FEATURES_WITH_EXAM_SETTINGS_ENABLED)
+ @ddt.data(
+ "certificates_list_handler",
+ "settings_handler",
+ "group_configurations_list_handler",
+ "grading_handler",
+ "advanced_settings_handler"
+ )
+ def test_view_with_exam_settings_enabled(self, handler):
+ """
+ Tests pages should have `Exam Settings` item
+ if course does have Exam Settings view enabled.
+ """
+ outline_url = reverse_course_url(handler, self.course.id)
+ resp = self.client.get(outline_url, HTTP_ACCEPT='text/html')
+ self.assertEqual(resp.status_code, 200)
+ self.assertContains(resp, 'Proctored Exam Settings')
diff --git a/cms/djangoapps/contentstore/views/tests/test_header_menu.py b/cms/djangoapps/contentstore/views/tests/test_header_menu.py
index 1109327910..c2b00f3063 100644
--- a/cms/djangoapps/contentstore/views/tests/test_header_menu.py
+++ b/cms/djangoapps/contentstore/views/tests/test_header_menu.py
@@ -15,6 +15,12 @@ from util.testing import UrlResetMixin
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
+FEATURES_WITH_EXAM_SETTINGS_ENABLED = settings.FEATURES.copy()
+FEATURES_WITH_EXAM_SETTINGS_ENABLED['ENABLE_EXAM_SETTINGS_HTML_VIEW'] = True
+
+FEATURES_WITH_EXAM_SETTINGS_DISABLED = settings.FEATURES.copy()
+FEATURES_WITH_EXAM_SETTINGS_DISABLED['ENABLE_EXAM_SETTINGS_HTML_VIEW'] = False
+
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
class TestHeaderMenu(CourseTestCase, UrlResetMixin):
@@ -49,3 +55,25 @@ class TestHeaderMenu(CourseTestCase, UrlResetMixin):
resp = self.client.get(outline_url, HTTP_ACCEPT='text/html')
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, '
')
+
+ @override_settings(FEATURES=FEATURES_WITH_EXAM_SETTINGS_DISABLED)
+ def test_header_menu_without_exam_settings_enabled(self):
+ """
+ Tests course header menu should not have `Exam Settings` menu item
+ if course does not have the Exam Settings view enabled.
+ """
+ outline_url = reverse_course_url('course_handler', self.course.id)
+ resp = self.client.get(outline_url, HTTP_ACCEPT='text/html')
+ self.assertEqual(resp.status_code, 200)
+ self.assertNotContains(resp, ' ')
+
+ @override_settings(FEATURES=FEATURES_WITH_EXAM_SETTINGS_ENABLED)
+ def test_header_menu_with_exam_settings_enabled(self):
+ """
+ Tests course header menu should have `Exam Settings` menu item
+ if course does have Exam Settings view enabled.
+ """
+ outline_url = reverse_course_url('course_handler', self.course.id)
+ resp = self.client.get(outline_url, HTTP_ACCEPT='text/html')
+ self.assertEqual(resp.status_code, 200)
+ self.assertContains(resp, ' ')
diff --git a/cms/envs/common.py b/cms/envs/common.py
index 11dc5dd9cc..c495596554 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -412,6 +412,9 @@ ENABLE_JASMINE = False
# IDA for which the social auth flow uses DOT (Django OAuth Toolkit).
IDA_LOGOUT_URI_LIST = []
+############################# MICROFRONTENDS ###################################
+COURSE_AUTHORING_MICROFRONTEND_URL = None
+
############################# SOCIAL MEDIA SHARING #############################
SOCIAL_SHARING_SETTINGS = {
# Note: Ensure 'CUSTOM_COURSE_URLS' has a matching value in lms/envs/common.py
diff --git a/cms/envs/test.py b/cms/envs/test.py
index ff22d4134a..fcfd77b6d6 100644
--- a/cms/envs/test.py
+++ b/cms/envs/test.py
@@ -135,6 +135,8 @@ LMS_BASE = "localhost:8000"
LMS_ROOT_URL = "http://{}".format(LMS_BASE)
FEATURES['PREVIEW_LMS_BASE'] = "preview.localhost"
+COURSE_AUTHORING_MICROFRONTEND_URL = "http://course-authoring-mfe/"
+
CACHES = {
# This is the cache used for most things. Askbot will not work without a
# functioning cache -- it relies on caching to load its settings in places.
diff --git a/cms/templates/certificates.html b/cms/templates/certificates.html
index 13792d216b..43dbb5bdde 100644
--- a/cms/templates/certificates.html
+++ b/cms/templates/certificates.html
@@ -106,6 +106,9 @@ CMS.User.isGlobalStaff = '${is_global_staff | n, js_escaped_string}'=='True' ? t
grading_url = utils.reverse_course_url('grading_handler', context_course.id)
course_team_url = utils.reverse_course_url('course_team_handler', context_course.id)
advanced_settings_url = utils.reverse_course_url('advanced_settings_handler', context_course.id)
+ exams_url = ''
+ if settings.FEATURES.get('ENABLE_EXAM_SETTINGS_HTML_VIEW'):
+ exams_url = settings.COURSE_AUTHORING_MICROFRONTEND_URL
%>
${_("Other Course Settings")}
@@ -115,6 +118,9 @@ CMS.User.isGlobalStaff = '${is_global_staff | n, js_escaped_string}'=='True' ? t
${_("Course Team")}
${_("Advanced Settings")}
${_("Group Configurations")}
+ % if exams_url:
+ ${_("Proctored Exam Settings")}
+ % endif
% endif
diff --git a/cms/templates/group_configurations.html b/cms/templates/group_configurations.html
index 0e9708954c..e002fa6bb4 100644
--- a/cms/templates/group_configurations.html
+++ b/cms/templates/group_configurations.html
@@ -117,6 +117,9 @@ from openedx.core.djangolib.markup import HTML, Text
grading_url = utils.reverse_course_url('grading_handler', context_course.id)
course_team_url = utils.reverse_course_url('course_team_handler', context_course.id)
advanced_settings_url = utils.reverse_course_url('advanced_settings_handler', context_course.id)
+ exams_url = ''
+ if settings.FEATURES.get('ENABLE_EXAM_SETTINGS_HTML_VIEW'):
+ exams_url = settings.COURSE_AUTHORING_MICROFRONTEND_URL
%>
${_("Other Course Settings")}
@@ -125,6 +128,9 @@ from openedx.core.djangolib.markup import HTML, Text
${_("Grading")}
${_("Course Team")}
${_("Advanced Settings")}
+ % if exams_url:
+ ${_("Proctored Exam Settings")}
+ % endif
% endif
diff --git a/cms/templates/settings.html b/cms/templates/settings.html
index 19a736466c..a2f1c4eb22 100644
--- a/cms/templates/settings.html
+++ b/cms/templates/settings.html
@@ -658,6 +658,9 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url | n, js_escaped_string}'
course_team_url = utils.reverse_course_url('course_team_handler', context_course.id)
grading_config_url = utils.reverse_course_url('grading_handler', context_course.id)
advanced_config_url = utils.reverse_course_url('advanced_settings_handler', context_course.id)
+ exams_url = ''
+ if settings.FEATURES.get('ENABLE_EXAM_SETTINGS_HTML_VIEW'):
+ exams_url = settings.COURSE_AUTHORING_MICROFRONTEND_URL
%>
${_("Other Course Settings")}
@@ -666,6 +669,9 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url | n, js_escaped_string}'
${_("Course Team")}
${_("Group Configurations")}
${_("Advanced Settings")}
+ % if exams_url:
+ ${_("Proctored Exam Settings")}
+ % endif
% endif
diff --git a/cms/templates/settings_advanced.html b/cms/templates/settings_advanced.html
index c3571a3b8e..b63f555227 100644
--- a/cms/templates/settings_advanced.html
+++ b/cms/templates/settings_advanced.html
@@ -98,6 +98,9 @@
details_url = utils.reverse_course_url('settings_handler', context_course.id)
grading_url = utils.reverse_course_url('grading_handler', context_course.id)
course_team_url = utils.reverse_course_url('course_team_handler', context_course.id)
+ exams_url = ''
+ if settings.FEATURES.get('ENABLE_EXAM_SETTINGS_HTML_VIEW'):
+ exams_url = settings.COURSE_AUTHORING_MICROFRONTEND_URL
%>
${_("Other Course Settings")}
@@ -106,6 +109,9 @@
${_("Grading")}
${_("Course Team")}
${_("Group Configurations")}
+ % if exams_url:
+ ${_("Proctored Exam Settings")}
+ % endif
% endif
diff --git a/cms/templates/settings_graders.html b/cms/templates/settings_graders.html
index 55c475bb9b..fd83943626 100644
--- a/cms/templates/settings_graders.html
+++ b/cms/templates/settings_graders.html
@@ -1,3 +1,4 @@
+<%page expression_filter="h"/>
<%inherit file="base.html" />
<%def name="online_help_token()"><% return "grading" %>%def>
<%block name="title">${_("Grading Settings")}%block>
@@ -28,7 +29,7 @@
<%block name="requirejs">
require(["js/factories/settings_graders"], function(SettingsGradersFactory) {
SettingsGradersFactory(
- _.extend(${dump_js_escaped_json(course_details, cls=CourseSettingsEncoder) | n},
+ _.extend(${dump_js_escaped_json(course_details, cls=CourseSettingsEncoder) | n, decode.utf8},
{is_credit_course: ${is_credit_course | n, dump_js_escaped_json}}),
"${grading_url | n, js_escaped_string}"
);
@@ -105,7 +106,7 @@
- ${_("Grading Rules & Policies")}
+ ${_("Grading Rules & Policies")}
${_("Deadlines, requirements, and logistics around grading student work")}
@@ -154,14 +155,20 @@
detailed_settings_url = utils.reverse_course_url('settings_handler', context_course.id)
course_team_url = utils.reverse_course_url('course_team_handler', context_course.id)
advanced_settings_url = utils.reverse_course_url('advanced_settings_handler', context_course.id)
+ exams_url = ''
+ if settings.FEATURES.get('ENABLE_EXAM_SETTINGS_HTML_VIEW'):
+ exams_url = settings.COURSE_AUTHORING_MICROFRONTEND_URL
%>
${_("Other Course Settings")}
% endif
diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html
index f33497c053..1df4f81e19 100644
--- a/cms/templates/widgets/header.html
+++ b/cms/templates/widgets/header.html
@@ -37,6 +37,9 @@
certificates_url = ''
if settings.FEATURES.get("CERTIFICATES_HTML_VIEW") and context_course.cert_html_view_enabled:
certificates_url = reverse('certificates_list_handler', kwargs={'course_key_string': six.text_type(course_key)})
+ exams_url = ''
+ if settings.FEATURES.get("ENABLE_EXAM_SETTINGS_HTML_VIEW"):
+ exams_url = settings.COURSE_AUTHORING_MICROFRONTEND_URL
checklists_url = reverse('checklists_handler', kwargs={'course_key_string': six.text_type(course_key)})
%>
@@ -100,6 +103,11 @@
${_("Group Configurations")}
+ % if exams_url:
+
+ ${_("Proctored Exam Settings")}
+
+ % endif
${_("Advanced Settings")}