Merge pull request #19684 from edx/mroytman/EDUCATOR-3952-practice-proctored-to-onboarding
Change "practice exam" to "onboarding" for courses that support onboarding exams.
This commit is contained in:
@@ -76,7 +76,8 @@ def register_special_exams(course_key):
|
||||
'time_limit_mins': timed_exam.default_time_limit_minutes,
|
||||
'due_date': timed_exam.due,
|
||||
'is_proctored': timed_exam.is_proctored_exam,
|
||||
'is_practice_exam': timed_exam.is_practice_exam,
|
||||
# backends that support onboarding exams will treat onboarding exams as practice
|
||||
'is_practice_exam': timed_exam.is_practice_exam or timed_exam.is_onboarding_exam,
|
||||
'is_active': True,
|
||||
'hide_after_due': timed_exam.hide_after_due,
|
||||
'backend': course.proctoring_provider,
|
||||
@@ -106,7 +107,7 @@ def register_special_exams(course_key):
|
||||
}
|
||||
|
||||
# only create/update exam policy for the proctored exams
|
||||
if timed_exam.is_proctored_exam and not timed_exam.is_practice_exam:
|
||||
if timed_exam.is_proctored_exam and not timed_exam.is_practice_exam and not timed_exam.is_onboarding_exam:
|
||||
try:
|
||||
update_review_policy(**exam_review_policy_metadata)
|
||||
except ProctoredExamReviewPolicyNotFoundException:
|
||||
|
||||
@@ -61,10 +61,43 @@ class TestProctoredExams(ModuleStoreTestCase):
|
||||
self.assertEqual(exam['exam_name'], sequence.display_name)
|
||||
self.assertEqual(exam['time_limit_mins'], sequence.default_time_limit_minutes)
|
||||
self.assertEqual(exam['is_proctored'], sequence.is_proctored_exam)
|
||||
self.assertEqual(exam['is_practice_exam'], sequence.is_practice_exam)
|
||||
self.assertEqual(exam['is_practice_exam'], sequence.is_practice_exam or sequence.is_onboarding_exam)
|
||||
self.assertEqual(exam['is_active'], expected_active)
|
||||
self.assertEqual(exam['backend'], self.course.proctoring_provider)
|
||||
|
||||
@ddt.data(
|
||||
(False, True),
|
||||
(True, False),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_onboarding_exam_is_practice_exam(self, is_practice_exam, is_onboarding_exam):
|
||||
"""
|
||||
Check that an onboarding exam is treated as a practice exam when
|
||||
communicating with the edx-proctoring subsystem.
|
||||
"""
|
||||
default_time_limit_minutes = 10
|
||||
is_proctored_exam = True
|
||||
|
||||
chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
|
||||
sequence = ItemFactory.create(
|
||||
parent=chapter,
|
||||
category='sequential',
|
||||
display_name='Test Proctored Exam',
|
||||
graded=True,
|
||||
is_time_limited=True,
|
||||
default_time_limit_minutes=default_time_limit_minutes,
|
||||
is_proctored_exam=is_proctored_exam,
|
||||
is_practice_exam=is_practice_exam,
|
||||
due=datetime.now(UTC) + timedelta(minutes=default_time_limit_minutes + 1),
|
||||
exam_review_rules="allow_use_of_paper",
|
||||
hide_after_due=True,
|
||||
is_onboarding_exam=is_onboarding_exam,
|
||||
)
|
||||
|
||||
listen_for_course_publish(self, self.course.id)
|
||||
|
||||
self._verify_exam_data(sequence, True)
|
||||
|
||||
@ddt.data(
|
||||
(True, False, True, False, False),
|
||||
(False, False, True, False, False),
|
||||
@@ -93,6 +126,7 @@ class TestProctoredExams(ModuleStoreTestCase):
|
||||
due=datetime.now(UTC) + timedelta(minutes=default_time_limit_minutes + 1),
|
||||
exam_review_rules="allow_use_of_paper",
|
||||
hide_after_due=hide_after_due,
|
||||
is_onboarding_exam=False,
|
||||
)
|
||||
|
||||
listen_for_course_publish(self, self.course.id)
|
||||
@@ -125,6 +159,7 @@ class TestProctoredExams(ModuleStoreTestCase):
|
||||
default_time_limit_minutes=10,
|
||||
is_proctored_exam=True,
|
||||
hide_after_due=False,
|
||||
is_onboarding_exam=False,
|
||||
)
|
||||
|
||||
listen_for_course_publish(self, self.course.id)
|
||||
|
||||
@@ -68,7 +68,7 @@ from xmodule.modulestore.inheritance import own_metadata
|
||||
from xmodule.services import ConfigurationService, SettingsService
|
||||
from xmodule.tabs import CourseTabList
|
||||
from xmodule.x_module import DEPRECATION_VSCOMPAT_EVENT, PREVIEW_VIEWS, STUDENT_VIEW, STUDIO_VIEW
|
||||
from edx_proctoring.api import get_exam_configuration_dashboard_url
|
||||
from edx_proctoring.api import get_exam_configuration_dashboard_url, does_backend_support_onboarding
|
||||
|
||||
__all__ = [
|
||||
'orphan_handler', 'xblock_handler', 'xblock_view_handler', 'xblock_outline_handler', 'xblock_container_handler'
|
||||
@@ -1222,6 +1222,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
})
|
||||
elif xblock.category == 'sequential':
|
||||
rules_url = settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('online_proctoring_rules', "")
|
||||
supports_onboarding = does_backend_support_onboarding(course.proctoring_provider)
|
||||
|
||||
proctoring_exam_configuration_link = None
|
||||
if xblock.is_proctored_exam:
|
||||
@@ -1232,10 +1233,12 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
'is_proctored_exam': xblock.is_proctored_exam,
|
||||
'online_proctoring_rules': rules_url,
|
||||
'is_practice_exam': xblock.is_practice_exam,
|
||||
'is_onboarding_exam': xblock.is_onboarding_exam,
|
||||
'is_time_limited': xblock.is_time_limited,
|
||||
'exam_review_rules': xblock.exam_review_rules,
|
||||
'default_time_limit_minutes': xblock.default_time_limit_minutes,
|
||||
'proctoring_exam_configuration_link': proctoring_exam_configuration_link,
|
||||
'supports_onboarding': supports_onboarding,
|
||||
})
|
||||
|
||||
# Update with gating info
|
||||
|
||||
@@ -2786,8 +2786,10 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertIsNone(xblock_info.get('child_info', None))
|
||||
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True})
|
||||
@patch('contentstore.views.item.does_backend_support_onboarding')
|
||||
@patch('contentstore.views.item.get_exam_configuration_dashboard_url')
|
||||
def test_proctored_exam_xblock_info(self, get_exam_configuration_dashboard_url_patch):
|
||||
def test_proctored_exam_xblock_info(self, get_exam_configuration_dashboard_url_patch,
|
||||
does_backend_support_onboarding_patch):
|
||||
self.course.enable_proctored_exams = True
|
||||
self.course.save()
|
||||
self.store.update_item(self.course, self.user.id)
|
||||
@@ -2805,11 +2807,12 @@ class TestXBlockInfo(ItemTest):
|
||||
parent_location=self.chapter.location, category='sequential',
|
||||
display_name="Test Lesson 1", user_id=self.user.id,
|
||||
is_proctored_exam=True, is_time_limited=True,
|
||||
default_time_limit_minutes=100
|
||||
default_time_limit_minutes=100, is_onboarding_exam=False
|
||||
)
|
||||
sequential = modulestore().get_item(sequential.location)
|
||||
|
||||
get_exam_configuration_dashboard_url_patch.return_value = 'test_url'
|
||||
does_backend_support_onboarding_patch.return_value = True
|
||||
xblock_info = create_xblock_info(
|
||||
sequential,
|
||||
include_child_info=True,
|
||||
@@ -2820,6 +2823,8 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertEqual(xblock_info['is_time_limited'], True)
|
||||
self.assertEqual(xblock_info['default_time_limit_minutes'], 100)
|
||||
self.assertEqual(xblock_info['proctoring_exam_configuration_link'], 'test_url')
|
||||
self.assertEqual(xblock_info['supports_onboarding'], True)
|
||||
self.assertEqual(xblock_info['is_onboarding_exam'], False)
|
||||
get_exam_configuration_dashboard_url_patch.assert_called_with(self.course.id, xblock_info['id'])
|
||||
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ class CourseMetadata(object):
|
||||
'chrome',
|
||||
'default_tab',
|
||||
'highlights_enabled_for_messaging',
|
||||
'is_onboarding_exam',
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('CourseOutlinePage', function() {
|
||||
selectVisibilitySettings, selectAdvancedSettings, createMockCourseJSON, createMockSectionJSON,
|
||||
createMockSubsectionJSON, verifyTypePublishable, mockCourseJSON, mockEmptyCourseJSON, setSelfPaced,
|
||||
mockSingleSectionCourseJSON, createMockVerticalJSON, createMockIndexJSON, mockCourseEntranceExamJSON,
|
||||
selectOnboardingExam,
|
||||
mockOutlinePage = readFixtures('templates/mock/mock-course-outline-page.underscore'),
|
||||
mockRerunNotification = readFixtures('templates/mock/mock-course-rerun-notification.underscore');
|
||||
|
||||
@@ -1025,6 +1026,12 @@ describe('CourseOutlinePage', function() {
|
||||
$('.field-time-limit input').trigger('focusout');
|
||||
};
|
||||
|
||||
selectOnboardingExam = function(time_limit) {
|
||||
$('input.onboarding_exam').prop('checked', true).trigger('change');
|
||||
$('.field-time-limit input').val(time_limit);
|
||||
$('.field-time-limit input').trigger('focusout');
|
||||
};
|
||||
|
||||
selectPrerequisite = function() {
|
||||
$('#is_prereq').prop('checked', true).trigger('change');
|
||||
};
|
||||
@@ -1070,7 +1077,8 @@ describe('CourseOutlinePage', function() {
|
||||
is_time_limited: false,
|
||||
exam_review_rules: '',
|
||||
is_proctored_enabled: false,
|
||||
default_time_limit_minutes: null
|
||||
default_time_limit_minutes: null,
|
||||
is_onboarding_exam: false
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1274,7 +1282,8 @@ describe('CourseOutlinePage', function() {
|
||||
is_practice_exam: false,
|
||||
is_proctored_enabled: false,
|
||||
default_time_limit_minutes: 150,
|
||||
hide_after_due: true
|
||||
hide_after_due: true,
|
||||
is_onboarding_exam: false,
|
||||
}
|
||||
});
|
||||
expect(requests[0].requestHeaders['X-HTTP-Method-Override']).toBe('PATCH');
|
||||
@@ -1339,6 +1348,96 @@ describe('CourseOutlinePage', function() {
|
||||
$('.wrapper-modal-window .action-save').click();
|
||||
});
|
||||
|
||||
it('can select the onboarding exam when a course supports onboarding', function () {
|
||||
var mockCourseWithSpecialExamJSON = createMockCourseJSON({}, [
|
||||
createMockSectionJSON({
|
||||
has_changes: true,
|
||||
enable_proctored_exams: true,
|
||||
enable_timed_exams: true
|
||||
|
||||
}, [
|
||||
createMockSubsectionJSON({
|
||||
has_changes: true,
|
||||
is_time_limited: true,
|
||||
is_practice_exam: true,
|
||||
is_proctored_exam: true,
|
||||
default_time_limit_minutes: 150,
|
||||
supports_onboarding: true,
|
||||
}, [
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false);
|
||||
outlinePage.$('.outline-subsection .configure-button').click();
|
||||
setEditModalValues('7/9/2014', '7/10/2014', 'Lab');
|
||||
selectVisibilitySettings();
|
||||
setContentVisibility('staff_only');
|
||||
selectAdvancedSettings();
|
||||
selectOnboardingExam('00:30');
|
||||
|
||||
// time limit should be visible, review rules should be hidden
|
||||
checkOptionFieldVisibility(true, false);
|
||||
|
||||
$('.wrapper-modal-window .action-save').click();
|
||||
});
|
||||
|
||||
it('does not show practice exam option when course supports onboarding', function() {
|
||||
var mockCourseWithSpecialExamJSON = createMockCourseJSON({}, [
|
||||
createMockSectionJSON({
|
||||
has_changes: true,
|
||||
enable_proctored_exams: true,
|
||||
enable_timed_exams: true
|
||||
|
||||
}, [
|
||||
createMockSubsectionJSON({
|
||||
has_changes: true,
|
||||
is_time_limited: true,
|
||||
is_practice_exam: true,
|
||||
is_proctored_exam: true,
|
||||
default_time_limit_minutes: 150,
|
||||
supports_onboarding: true,
|
||||
}, [
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false);
|
||||
outlinePage.$('.outline-subsection .configure-button').click();
|
||||
selectAdvancedSettings();
|
||||
expect($('input.practice_exam')).not.toExist();
|
||||
expect($('input.onboarding_exam')).toExist();
|
||||
expect($('.field-time-limit input').val()).toBe('02:30');
|
||||
});
|
||||
|
||||
it('does not show onboarding exam option when course does not support onboarding', function() {
|
||||
var mockCourseWithSpecialExamJSON = createMockCourseJSON({}, [
|
||||
createMockSectionJSON({
|
||||
has_changes: true,
|
||||
enable_proctored_exams: true,
|
||||
enable_timed_exams: true
|
||||
|
||||
}, [
|
||||
createMockSubsectionJSON({
|
||||
has_changes: true,
|
||||
is_time_limited: true,
|
||||
is_practice_exam: true,
|
||||
is_proctored_exam: true,
|
||||
default_time_limit_minutes: 150,
|
||||
supports_onboarding: false,
|
||||
}, [
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false);
|
||||
outlinePage.$('.outline-subsection .configure-button').click();
|
||||
selectAdvancedSettings();
|
||||
expect($('input.practice_exam')).toExist();
|
||||
expect($('input.onboarding_exam')).not.toExist();
|
||||
expect($('.field-time-limit input').val()).toBe('02:30');
|
||||
});
|
||||
|
||||
it('can select the timed exam', function() {
|
||||
createCourseOutlinePage(this, mockCourseJSON, false);
|
||||
outlinePage.$('.outline-subsection .configure-button').click();
|
||||
@@ -1525,6 +1624,36 @@ describe('CourseOutlinePage', function() {
|
||||
expect($('.field-time-limit input').val()).toBe('02:30');
|
||||
});
|
||||
|
||||
it('can show a saved onboarding exam correctly', function() {
|
||||
var mockCourseWithSpecialExamJSON = createMockCourseJSON({}, [
|
||||
createMockSectionJSON({
|
||||
has_changes: true,
|
||||
enable_proctored_exams: true,
|
||||
enable_timed_exams: true
|
||||
|
||||
}, [
|
||||
createMockSubsectionJSON({
|
||||
has_changes: true,
|
||||
is_time_limited: true,
|
||||
is_practice_exam: false,
|
||||
is_proctored_exam: true,
|
||||
default_time_limit_minutes: 150,
|
||||
supports_onboarding: true,
|
||||
is_onboarding_exam: true
|
||||
}, [
|
||||
])
|
||||
])
|
||||
]);
|
||||
createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false);
|
||||
outlinePage.$('.outline-subsection .configure-button').click();
|
||||
selectAdvancedSettings();
|
||||
expect($('input.timed_exam').is(':checked')).toBe(false);
|
||||
expect($('input.proctored_exam').is(':checked')).toBe(false);
|
||||
expect($('input.no_special_exam').is(':checked')).toBe(false);
|
||||
expect($('input.onboarding_exam').is(':checked')).toBe(true);
|
||||
expect($('.field-time-limit input').val()).toBe('02:30');
|
||||
});
|
||||
|
||||
it('does not show proctored settings if proctored exams not enabled', function() {
|
||||
var mockCourseWithSpecialExamJSON = createMockCourseJSON({}, [
|
||||
createMockSectionJSON({
|
||||
|
||||
@@ -70,8 +70,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
},
|
||||
|
||||
save: function(event) {
|
||||
var requestData;
|
||||
|
||||
event.preventDefault();
|
||||
var requestData = this.getRequestData();
|
||||
requestData = this.getRequestData();
|
||||
if (!_.isEqual(requestData, {metadata: {}})) {
|
||||
XBlockViewUtils.updateXBlockFields(this.model, requestData, {
|
||||
success: this.options.onSave
|
||||
@@ -122,10 +124,11 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
},
|
||||
|
||||
initializeEditors: function() {
|
||||
var tabsTemplate;
|
||||
var tabs = this.options.tabs;
|
||||
if (tabs && tabs.length > 0) {
|
||||
if (tabs.length > 1) {
|
||||
var tabsTemplate = this.loadTemplate('settings-modal-tabs');
|
||||
tabsTemplate = this.loadTemplate('settings-modal-tabs');
|
||||
HtmlUtils.setHtml(this.$('.modal-section'), HtmlUtils.HTML(tabsTemplate({tabs: tabs})));
|
||||
_.each(this.options.tabs, function(tab) {
|
||||
this.options.editors.push.apply(
|
||||
@@ -411,9 +414,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
className: 'edit-settings-timed-examination',
|
||||
events: {
|
||||
'change input.no_special_exam': 'notTimedExam',
|
||||
'change input.timed_exam': 'setTimedExam',
|
||||
'change input.practice_exam': 'setPracticeExam',
|
||||
'change input.timed_exam': 'setSpecialExamWithoutRules',
|
||||
'change input.practice_exam': 'setSpecialExamWithoutRules',
|
||||
'change input.proctored_exam': 'setProctoredExam',
|
||||
'change input.onboarding_exam': 'setSpecialExamWithoutRules',
|
||||
'focusout .field-time-limit input': 'timeLimitFocusout'
|
||||
},
|
||||
notTimedExam: function(event) {
|
||||
@@ -433,11 +437,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
this.$('.field-exam-review-rules').hide();
|
||||
}
|
||||
},
|
||||
setTimedExam: function(event) {
|
||||
event.preventDefault();
|
||||
this.selectSpecialExam(false);
|
||||
},
|
||||
setPracticeExam: function(event) {
|
||||
setSpecialExamWithoutRules: function(event) {
|
||||
event.preventDefault();
|
||||
this.selectSpecialExam(false);
|
||||
},
|
||||
@@ -446,8 +446,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
this.selectSpecialExam(true);
|
||||
},
|
||||
timeLimitFocusout: function(event) {
|
||||
var selectedTimeLimit;
|
||||
|
||||
event.preventDefault();
|
||||
var selectedTimeLimit = $(event.currentTarget).val();
|
||||
selectedTimeLimit = $(event.currentTarget).val();
|
||||
if (!this.isValidTimeLimit(selectedTimeLimit)) {
|
||||
$(event.currentTarget).val('00:30');
|
||||
}
|
||||
@@ -462,24 +464,26 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
});
|
||||
|
||||
this.setExamType(this.model.get('is_time_limited'), this.model.get('is_proctored_exam'),
|
||||
this.model.get('is_practice_exam'));
|
||||
this.model.get('is_practice_exam'), this.model.get('is_onboarding_exam'));
|
||||
this.setExamTime(this.model.get('default_time_limit_minutes'));
|
||||
|
||||
this.setReviewRules(this.model.get('exam_review_rules'));
|
||||
},
|
||||
setExamType: function(is_time_limited, is_proctored_exam, is_practice_exam) {
|
||||
setExamType: function(isTimeLimited, isProctoredExam, isPracticeExam, isOnboardingExam) {
|
||||
this.$('.field-time-limit').hide();
|
||||
this.$('.field-exam-review-rules').hide();
|
||||
|
||||
if (!is_time_limited) {
|
||||
if (!isTimeLimited) {
|
||||
this.$('input.no_special_exam').prop('checked', true);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$('.field-time-limit').show();
|
||||
|
||||
if (this.options.enable_proctored_exams && is_proctored_exam) {
|
||||
if (is_practice_exam) {
|
||||
if (this.options.enable_proctored_exams && isProctoredExam) {
|
||||
if (isOnboardingExam) {
|
||||
this.$('input.onboarding_exam').prop('checked', true);
|
||||
} else if (isPracticeExam) {
|
||||
this.$('input.practice_exam').prop('checked', true);
|
||||
} else {
|
||||
this.$('input.proctored_exam').prop('checked', true);
|
||||
@@ -499,9 +503,9 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
setReviewRules: function(value) {
|
||||
this.$('.field-exam-review-rules textarea').val(value);
|
||||
},
|
||||
isValidTimeLimit: function(time_limit) {
|
||||
isValidTimeLimit: function(timeLimit) {
|
||||
var pattern = new RegExp('^\\d{1,2}:[0-5][0-9]$');
|
||||
return pattern.test(time_limit) && time_limit !== '00:00';
|
||||
return pattern.test(timeLimit) && timeLimit !== '00:00';
|
||||
},
|
||||
getExamTimeLimit: function() {
|
||||
return this.$('.field-time-limit input').val();
|
||||
@@ -513,48 +517,32 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
actualMinutesStr = '00'.substring(0, 2 - actualMinutesStr.length) + actualMinutesStr;
|
||||
return hoursStr + ':' + actualMinutesStr;
|
||||
},
|
||||
convertTimeLimitToMinutes: function(time_limit) {
|
||||
var time = time_limit.split(':');
|
||||
var total_time = (parseInt(time[0]) * 60) + parseInt(time[1]);
|
||||
return total_time;
|
||||
convertTimeLimitToMinutes: function(timeLimit) {
|
||||
var time = timeLimit.split(':');
|
||||
var totalTime = (parseInt(time[0], 10) * 60) + parseInt(time[1], 10);
|
||||
return totalTime;
|
||||
},
|
||||
getRequestData: function() {
|
||||
var is_time_limited;
|
||||
var is_practice_exam;
|
||||
var is_proctored_exam;
|
||||
var time_limit = this.getExamTimeLimit();
|
||||
var exam_review_rules = this.$('.field-exam-review-rules textarea').val();
|
||||
|
||||
if (this.$('input.no_special_exam').is(':checked')) {
|
||||
is_time_limited = false;
|
||||
is_practice_exam = false;
|
||||
is_proctored_exam = false;
|
||||
} else if (this.$('input.timed_exam').is(':checked')) {
|
||||
is_time_limited = true;
|
||||
is_practice_exam = false;
|
||||
is_proctored_exam = false;
|
||||
} else if (this.$('input.proctored_exam').is(':checked')) {
|
||||
is_time_limited = true;
|
||||
is_practice_exam = false;
|
||||
is_proctored_exam = true;
|
||||
} else if (this.$('input.practice_exam').is(':checked')) {
|
||||
is_time_limited = true;
|
||||
is_practice_exam = true;
|
||||
is_proctored_exam = true;
|
||||
}
|
||||
var isNoSpecialExamChecked = this.$('input.no_special_exam').is(':checked');
|
||||
var isProctoredExamChecked = this.$('input.proctored_exam').is(':checked');
|
||||
var isPracticeExamChecked = this.$('input.practice_exam').is(':checked');
|
||||
var isOnboardingExamChecked = this.$('input.onboarding_exam').is(':checked');
|
||||
var timeLimit = this.getExamTimeLimit();
|
||||
var examReviewRules = this.$('.field-exam-review-rules textarea').val();
|
||||
|
||||
return {
|
||||
metadata: {
|
||||
is_practice_exam: is_practice_exam,
|
||||
is_time_limited: is_time_limited,
|
||||
exam_review_rules: exam_review_rules,
|
||||
is_practice_exam: isPracticeExamChecked,
|
||||
is_time_limited: !isNoSpecialExamChecked,
|
||||
exam_review_rules: examReviewRules,
|
||||
// We have to use the legacy field name
|
||||
// as the Ajax handler directly populates
|
||||
// the xBlocks fields. We will have to
|
||||
// update this call site when we migrate
|
||||
// seq_module.py to use 'is_proctored_exam'
|
||||
is_proctored_enabled: is_proctored_exam,
|
||||
default_time_limit_minutes: this.convertTimeLimitToMinutes(time_limit)
|
||||
is_proctored_enabled: isProctoredExamChecked || isPracticeExamChecked || isOnboardingExamChecked,
|
||||
default_time_limit_minutes: this.convertTimeLimitToMinutes(timeLimit),
|
||||
is_onboarding_exam: isOnboardingExamChecked
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -569,10 +557,12 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
'keyup #prereq_min_score': 'validateScoreAndCompletion'
|
||||
},
|
||||
afterRender: function() {
|
||||
var prereq, prereqMinScore, prereqMinCompletion;
|
||||
|
||||
AbstractEditor.prototype.afterRender.call(this);
|
||||
var prereq = this.model.get('prereq') || '';
|
||||
var prereqMinScore = this.model.get('prereq_min_score') || '100';
|
||||
var prereqMinCompletion = this.model.get('prereq_min_completion') || '100';
|
||||
prereq = this.model.get('prereq') || '';
|
||||
prereqMinScore = this.model.get('prereq_min_score') || '100';
|
||||
prereqMinCompletion = this.model.get('prereq_min_completion') || '100';
|
||||
this.$('#is_prereq').prop('checked', this.model.get('is_prereq'));
|
||||
this.$('#prereq option[value="' + prereq + '"]').prop('selected', true);
|
||||
this.$('#prereq_min_score').val(prereqMinScore);
|
||||
@@ -853,9 +843,9 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
},
|
||||
|
||||
toggleUnlockWarning: function() {
|
||||
var display;
|
||||
var warning = this.$('.staff-lock .tip-warning');
|
||||
if (warning) {
|
||||
var display;
|
||||
if (this.currentVisibility() !== 'staff_only') {
|
||||
display = 'block';
|
||||
} else {
|
||||
@@ -868,8 +858,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
},
|
||||
|
||||
getRequestData: function() {
|
||||
var metadata;
|
||||
|
||||
if (this.hasChanges()) {
|
||||
var metadata = {};
|
||||
metadata = {};
|
||||
if (this.currentVisibility() === 'staff_only') {
|
||||
metadata.visible_to_staff_only = true;
|
||||
metadata.hide_after_due = null;
|
||||
|
||||
@@ -84,15 +84,19 @@ if (xblockInfo.get('graded')) {
|
||||
|
||||
var is_proctored_exam = xblockInfo.get('is_proctored_exam');
|
||||
var is_practice_exam = xblockInfo.get('is_practice_exam');
|
||||
var is_onboarding_exam = xblockInfo.get('is_onboarding_exam');
|
||||
var exam_value;
|
||||
if (is_proctored_exam) {
|
||||
if (is_practice_exam) {
|
||||
var exam_value = gettext('Practice proctored Exam');
|
||||
if (is_onboarding_exam) {
|
||||
exam_value = gettext('Onboarding Exam');
|
||||
} else if (is_practice_exam) {
|
||||
exam_value = gettext('Practice proctored Exam');
|
||||
} else {
|
||||
var exam_value = gettext('Proctored Exam');
|
||||
exam_value = gettext('Proctored Exam');
|
||||
}
|
||||
|
||||
} else {
|
||||
var exam_value = gettext('Timed Exam');
|
||||
exam_value = gettext('Timed Exam');
|
||||
}
|
||||
%>
|
||||
<% if (parentInfo) { %>
|
||||
|
||||
@@ -19,12 +19,23 @@
|
||||
<%- gettext('Proctored') %>
|
||||
</label>
|
||||
<p class='field-message' id='proctored-exam-description'> <%- gettext('Proctored exams are timed and they record video of each learner taking the exam. The videos are then reviewed to ensure that learners follow all examination rules.') %> </p>
|
||||
<label class="label">
|
||||
<input type="radio" name="exam_type" class="input input-radio practice_exam"
|
||||
aria-describedby="practice-exam-description"/>
|
||||
<%- gettext('Practice Proctored') %>
|
||||
</label>
|
||||
<p class='field-message' id='practice-exam-description'> <%- gettext("Use a practice proctored exam to introduce learners to the proctoring tools and processes. Results of a practice exam do not affect a learner's grade.") %> </p>
|
||||
|
||||
<% var supports_onboarding = xblockInfo.get('supports_onboarding'); %>
|
||||
<% if (supports_onboarding) { %>
|
||||
<label class="label">
|
||||
<input type="radio" name="exam_type" class="input input-radio onboarding_exam"
|
||||
aria-describedby="onboarding-exam-description"/>
|
||||
<%- gettext('Onboarding') %>
|
||||
</label>
|
||||
<p class='field-message' id='onboarding-exam-description'> <%- gettext("Use Onboarding to introduce learners to proctoring, verify their identity, and create an onboarding profile. Learners must complete the onboarding profile step prior to taking a proctored exam. Profile reviews take 2+ business days.") %> </p>
|
||||
<% } else { %>
|
||||
<label class="label">
|
||||
<input type="radio" name="exam_type" class="input input-radio practice_exam"
|
||||
aria-describedby="practice-exam-description"/>
|
||||
<%- gettext('Practice Proctored') %>
|
||||
</label>
|
||||
<p class='field-message' id='practice-exam-description'> <%- gettext("Use a practice proctored exam to introduce learners to the proctoring tools and processes. Results of a practice exam do not affect a learner's grade.") %> </p>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="list-fields list-input exam-options">
|
||||
@@ -45,6 +56,7 @@
|
||||
<% var online_proctoring_rules = xblockInfo.get('online_proctoring_rules'); %>
|
||||
<p class='field-message' id='review-rules-description'>
|
||||
<% if (online_proctoring_rules) { %>
|
||||
% // xss-lint: disable=underscore-not-escaped %>
|
||||
<%= edx.HtmlUtils.interpolateHtml(
|
||||
gettext('Specify any rules or rule exceptions that the proctoring review team should enforce when reviewing the videos. For example, you could specify that calculators are allowed. These specified rules are visible to learners before the learners start the exam, along with the {linkStart}general proctored exam rules{linkEnd}.'),
|
||||
{
|
||||
|
||||
@@ -125,6 +125,15 @@ class ProctoringFields(object):
|
||||
scope=Scope.settings,
|
||||
)
|
||||
|
||||
is_onboarding_exam = Boolean(
|
||||
display_name=_("Is Onboarding Exam"),
|
||||
help=_(
|
||||
"This setting indicates whether this exam is an onboarding exam."
|
||||
),
|
||||
default=False,
|
||||
scope=Scope.settings,
|
||||
)
|
||||
|
||||
def _get_course(self):
|
||||
"""
|
||||
Return course by course id.
|
||||
|
||||
Reference in New Issue
Block a user