diff --git a/cms/djangoapps/contentstore/proctoring.py b/cms/djangoapps/contentstore/proctoring.py index 06be401a28..2db79e3b2f 100644 --- a/cms/djangoapps/contentstore/proctoring.py +++ b/cms/djangoapps/contentstore/proctoring.py @@ -28,7 +28,7 @@ def register_proctored_exams(course_key): This is typically called on a course published signal. The course is examined for sequences that are marked as timed exams. Then these are registered with the edx-proctoring subsystem. Likewise, if formerly registered exams are unmarked, then those - registred exams are marked as inactive + registered exams are marked as inactive """ if not settings.FEATURES.get('ENABLE_PROCTORED_EXAMS'): @@ -76,6 +76,7 @@ def register_proctored_exams(course_key): exam_name=timed_exam.display_name, time_limit_mins=timed_exam.default_time_limit_minutes, is_proctored=timed_exam.is_proctored_enabled, + is_practice_exam=timed_exam.is_practice_exam, is_active=True ) msg = 'Updated timed exam {exam_id}'.format(exam_id=exam['id']) @@ -87,6 +88,7 @@ def register_proctored_exams(course_key): exam_name=timed_exam.display_name, time_limit_mins=timed_exam.default_time_limit_minutes, is_proctored=timed_exam.is_proctored_enabled, + is_practice_exam=timed_exam.is_practice_exam, is_active=True ) msg = 'Created new timed exam {exam_id}'.format(exam_id=exam_id) diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 4029951784..04bf559e65 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -862,7 +862,8 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F xblock_info.update({ "is_proctored_enabled": xblock.is_proctored_enabled, "is_time_limited": xblock.is_time_limited, - "default_time_limit_minutes": xblock.default_time_limit_minutes + "default_time_limit_minutes": xblock.default_time_limit_minutes, + "is_practice_exam": xblock.is_practice_exam }) # Entrance exam subsection should be hidden. in_entrance_exam is inherited metadata, all children will have it. diff --git a/cms/envs/aws.py b/cms/envs/aws.py index 4c239d521f..fdf9681363 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -352,7 +352,6 @@ XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {}) XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False) XBLOCK_SETTINGS.setdefault("VideoModule", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY) - ################# PROCTORING CONFIGURATION ################## PROCTORING_BACKEND_PROVIDER = AUTH_TOKENS.get("PROCTORING_BACKEND_PROVIDER", PROCTORING_BACKEND_PROVIDER) diff --git a/cms/envs/bok_choy.env.json b/cms/envs/bok_choy.env.json index d4a648394e..981d4deca9 100644 --- a/cms/envs/bok_choy.env.json +++ b/cms/envs/bok_choy.env.json @@ -77,7 +77,8 @@ "SUBDOMAIN_BRANDING": false, "SUBDOMAIN_COURSE_LISTINGS": false, "ALLOW_ALL_ADVANCED_COMPONENTS": true, - "ENABLE_CONTENT_LIBRARIES": true + "ENABLE_CONTENT_LIBRARIES": true, + "ENABLE_PROCTORED_EXAMS": true }, "FEEDBACK_SUBMISSION_EMAIL": "", "GITHUB_REPO_ROOT": "** OVERRIDDEN **", diff --git a/cms/envs/bok_choy.py b/cms/envs/bok_choy.py index 90187a7a44..83a8fdc570 100644 --- a/cms/envs/bok_choy.py +++ b/cms/envs/bok_choy.py @@ -101,6 +101,8 @@ FEATURES['ENABLE_VIDEO_BUMPER'] = True # Enable video bumper in Studio settings ########################### Entrance Exams ################################# FEATURES['ENTRANCE_EXAMS'] = True +FEATURES['ENABLE_PROCTORED_EXAMS'] = True + # Point the URL used to test YouTube availability to our stub YouTube server YOUTUBE_PORT = 9080 YOUTUBE['API'] = "http://127.0.0.1:{0}/get_youtube_api/".format(YOUTUBE_PORT) diff --git a/cms/envs/common.py b/cms/envs/common.py index 3214e7231a..44be9626ad 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -173,6 +173,9 @@ FEATURES = { # Show video bumper in Studio 'ENABLE_VIDEO_BUMPER': False, + # Timed Proctored Exams + 'ENABLE_PROCTORED_EXAMS': False, + # How many seconds to show the bumper again, default is 7 days: 'SHOW_BUMPER_PERIODICITY': 7 * 24 * 3600, @@ -775,6 +778,9 @@ INSTALLED_APPS = ( 'openedx.core.djangoapps.credit', 'xblock_django', + + # edX Proctoring + 'edx_proctoring', ) @@ -1040,11 +1046,10 @@ CREDIT_PROVIDER_TIMESTAMP_EXPIRATION = 15 * 60 DEPRECATED_BLOCK_TYPES = ['peergrading', 'combinedopenended'] - #### PROCTORING CONFIGURATION DEFAULTS PROCTORING_BACKEND_PROVIDER = { - 'class': 'edx_proctoring.backends.NullBackendProvider', + 'class': 'edx_proctoring.backends.null.NullBackendProvider', 'options': {}, } PROCTORING_SETTINGS = {} diff --git a/cms/static/js/spec/views/pages/course_outline_spec.js b/cms/static/js/spec/views/pages/course_outline_spec.js index 0e9104d9d2..1bbaaf228b 100644 --- a/cms/static/js/spec/views/pages/course_outline_spec.js +++ b/cms/static/js/spec/views/pages/course_outline_spec.js @@ -620,6 +620,7 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "js/views/utils/view_ut has_explicit_staff_lock: true, staff_only_message: true, "is_time_limited": true, + "is_practice_exam": false, "is_proctored_enabled": true, "default_time_limit_minutes": 150 }, [ @@ -706,6 +707,7 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "js/views/utils/view_ut "start":"2014-07-09T00:00:00.000Z", "due":"2014-07-10T00:00:00.000Z", "is_time_limited": true, + "is_practice_exam": false, "is_proctored_enabled": true, "default_time_limit_minutes": 150 } @@ -740,6 +742,7 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "js/views/utils/view_ut expect($("#staff_lock").is(":checked")).toBe(true); expect($("#id_timed_examination").is(":checked")).toBe(true); expect($("#id_exam_proctoring").is(":checked")).toBe(true); + expect($("#is_practice_exam").is(":checked")).toBe(false); expect($("#id_time_limit").val()).toBe("02:30"); }); diff --git a/cms/static/js/views/modals/course_outline_modals.js b/cms/static/js/views/modals/course_outline_modals.js index 9758fd7f0d..2fa152dd7a 100644 --- a/cms/static/js/views/modals/course_outline_modals.js +++ b/cms/static/js/views/modals/course_outline_modals.js @@ -275,11 +275,17 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', event.preventDefault(); if (!$(event.currentTarget).is(':checked')) { this.$('#id_exam_proctoring').attr('checked', false); - this.$('#id_time_limit').val('00:30'); + this.$('#id_time_limit').val('00:00'); this.$('#id_exam_proctoring').attr('disabled','disabled'); this.$('#id_time_limit').attr('disabled', 'disabled'); + this.$('#id_practice_exam').attr('checked', false); + this.$('#id_practice_exam').attr('disabled','disabled'); } else { + if (!this.isValidTimeLimit(this.$('#id_time_limit').val())) { + this.$('#id_time_limit').val('00:30'); + } + this.$('#id_practice_exam').removeAttr('disabled'); this.$('#id_exam_proctoring').removeAttr('disabled'); this.$('#id_time_limit').removeAttr('disabled'); } @@ -289,11 +295,17 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', AbstractEditor.prototype.afterRender.call(this); this.$('input.time').timepicker({ 'timeFormat' : 'H:i', + 'minTime': '00:30', + 'maxTime': '05:00', 'forceRoundTime': false }); this.setExamTime(this.model.get('default_time_limit_minutes')); this.setExamTmePreference(this.model.get('is_time_limited')); this.setExamProctoring(this.model.get('is_proctored_enabled')); + this.setPracticeExam(this.model.get('is_practice_exam')); + }, + setPracticeExam: function(value) { + this.$('#id_practice_exam').prop('checked', value); }, setExamProctoring: function(value) { this.$('#id_exam_proctoring').prop('checked', value); @@ -307,14 +319,18 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', if (!this.$('#id_timed_examination').is(':checked')) { this.$('#id_exam_proctoring').attr('disabled','disabled'); this.$('#id_time_limit').attr('disabled', 'disabled'); + this.$('#id_practice_exam').attr('disabled', 'disabled'); } }, isExamTimeEnabled: function () { return this.$('#id_timed_examination').is(':checked'); }, + isPracticeExam: function () { + return this.$('#id_practice_exam').is(':checked'); + }, isValidTimeLimit: function(time_limit) { var pattern = new RegExp('^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$'); - return pattern.test(time_limit); + return pattern.test(time_limit) && time_limit !== "00:00"; }, getExamTimeLimit: function () { return this.$('#id_time_limit').val(); @@ -338,6 +354,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', var time_limit = this.getExamTimeLimit(); return { metadata: { + 'is_practice_exam': this.isPracticeExam(), 'is_time_limited': this.isExamTimeEnabled(), 'is_proctored_enabled': this.isExamProctoringEnabled(), 'default_time_limit_minutes': this.convertTimeLimitToMinutes(time_limit) diff --git a/cms/templates/js/timed-examination-preference-editor.underscore b/cms/templates/js/timed-examination-preference-editor.underscore index 027156cb0f..cba0399b8c 100644 --- a/cms/templates/js/timed-examination-preference-editor.underscore +++ b/cms/templates/js/timed-examination-preference-editor.underscore @@ -24,6 +24,17 @@

<%- gettext('Students see warnings when 20% and 5% of the allotted time remains. In certain cases, students can be granted allowances that give them extra time to complete the exam.') %>

+