From d48e88ba8a21b9b48ab3e1437c8ea206207e6de9 Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Thu, 21 Apr 2016 09:26:22 -0400 Subject: [PATCH] Hide After Due setting for Timed Exams TNL-4366 Changes in studio to allow the hide_after_due setting to be utilized. Includes: -python changes to contentstore, where the data is stored. -refactoring of timed-examination-preference-editor.underscore, to add this setting and make the editor more accessible. -javascript changes to link the new setting to the correct data model. -sass updates to fix a11y issues on the editor modal. -addition of post-due visibility information to course outline in studio. -new tests: python, js, acceptance, and a11y --- cms/djangoapps/contentstore/proctoring.py | 6 +- .../contentstore/tests/test_proctoring.py | 30 ++- cms/djangoapps/contentstore/views/item.py | 3 +- .../models/settings/course_metadata.py | 1 + .../spec/views/pages/course_outline_spec.js | 195 ++++++++++++------ .../js/views/modals/course_outline_modals.js | 80 ++++--- cms/static/sass/elements/_modal-window.scss | 2 +- cms/static/sass/elements/_modules.scss | 10 +- cms/templates/js/course-outline.underscore | 103 ++++----- ...d-examination-preference-editor.underscore | 114 +++++----- common/lib/xmodule/xmodule/seq_module.py | 10 + .../test/acceptance/pages/lms/courseware.py | 9 +- .../test/acceptance/pages/studio/overview.py | 49 +++-- .../tests/lms/test_lms_courseware.py | 142 ++++--------- .../tests/studio/test_studio_settings.py | 63 ++++++ 15 files changed, 480 insertions(+), 337 deletions(-) diff --git a/cms/djangoapps/contentstore/proctoring.py b/cms/djangoapps/contentstore/proctoring.py index f456ceb0ee..55b31d95c3 100644 --- a/cms/djangoapps/contentstore/proctoring.py +++ b/cms/djangoapps/contentstore/proctoring.py @@ -83,7 +83,8 @@ def register_special_exams(course_key): due_date=timed_exam.due, is_proctored=timed_exam.is_proctored_exam, is_practice_exam=timed_exam.is_practice_exam, - is_active=True + is_active=True, + hide_after_due=timed_exam.hide_after_due, ) msg = 'Updated timed exam {exam_id}'.format(exam_id=exam['id']) log.info(msg) @@ -97,7 +98,8 @@ def register_special_exams(course_key): due_date=timed_exam.due, is_proctored=timed_exam.is_proctored_exam, is_practice_exam=timed_exam.is_practice_exam, - is_active=True + is_active=True, + hide_after_due=timed_exam.hide_after_due, ) msg = 'Created new timed exam {exam_id}'.format(exam_id=exam_id) log.info(msg) diff --git a/cms/djangoapps/contentstore/tests/test_proctoring.py b/cms/djangoapps/contentstore/tests/test_proctoring.py index d7e08f85b2..5be8af4928 100644 --- a/cms/djangoapps/contentstore/tests/test_proctoring.py +++ b/cms/djangoapps/contentstore/tests/test_proctoring.py @@ -53,6 +53,10 @@ class TestProctoredExams(ModuleStoreTestCase): exam_review_policy = get_review_policy_by_exam_id(exam['id']) self.assertEqual(exam_review_policy['review_policy'], sequence.exam_review_rules) + if not exam['is_proctored'] and not exam['is_practice_exam']: + # the hide after due value only applies to timed exams + self.assertEqual(exam['hide_after_due'], sequence.hide_after_due) + self.assertEqual(exam['course_id'], unicode(self.course.id)) self.assertEqual(exam['content_id'], unicode(sequence.location)) self.assertEqual(exam['exam_name'], sequence.display_name) @@ -62,13 +66,14 @@ class TestProctoredExams(ModuleStoreTestCase): self.assertEqual(exam['is_active'], expected_active) @ddt.data( - (True, 10, True, False, True, False), - (True, 10, False, False, True, False), - (True, 10, True, True, True, True), + (True, 10, True, False, True, False, False), + (True, 10, False, False, True, False, False), + (True, 10, False, False, True, False, True), + (True, 10, True, True, True, True, False), ) @ddt.unpack - def test_publishing_exam(self, is_time_limited, default_time_limit_minutes, - is_proctored_exam, is_practice_exam, expected_active, republish): + def test_publishing_exam(self, is_time_limited, default_time_limit_minutes, is_proctored_exam, + is_practice_exam, expected_active, republish, hide_after_due): """ Happy path testing to see that when a course is published which contains a proctored exam, it will also put an entry into the exam tables @@ -85,7 +90,8 @@ class TestProctoredExams(ModuleStoreTestCase): 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" + exam_review_rules="allow_use_of_paper", + hide_after_due=hide_after_due, ) listen_for_course_publish(self, self.course.id) @@ -117,7 +123,8 @@ class TestProctoredExams(ModuleStoreTestCase): graded=True, is_time_limited=True, default_time_limit_minutes=10, - is_proctored_exam=True + is_proctored_exam=True, + hide_after_due=False, ) listen_for_course_publish(self, self.course.id) @@ -147,7 +154,8 @@ class TestProctoredExams(ModuleStoreTestCase): graded=True, is_time_limited=True, default_time_limit_minutes=10, - is_proctored_exam=True + is_proctored_exam=True, + hide_after_due=False, ) listen_for_course_publish(self, self.course.id) @@ -182,7 +190,8 @@ class TestProctoredExams(ModuleStoreTestCase): graded=True, is_time_limited=True, default_time_limit_minutes=10, - is_proctored_exam=True + is_proctored_exam=True, + hide_after_due=False, ) listen_for_course_publish(self, self.course.id) @@ -218,7 +227,8 @@ class TestProctoredExams(ModuleStoreTestCase): is_time_limited=True, default_time_limit_minutes=10, is_proctored_exam=True, - exam_review_rules="allow_use_of_paper" + exam_review_rules="allow_use_of_paper", + hide_after_due=False, ) listen_for_course_publish(self, self.course.id) diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index b75a0be37f..1109600af0 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -935,7 +935,8 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F "is_practice_exam": xblock.is_practice_exam, "is_time_limited": xblock.is_time_limited, "exam_review_rules": xblock.exam_review_rules, - "default_time_limit_minutes": xblock.default_time_limit_minutes + "default_time_limit_minutes": xblock.default_time_limit_minutes, + "hide_after_due": xblock.hide_after_due, }) # Update with gating info diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index 5859205317..2095dfd18f 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -50,6 +50,7 @@ class CourseMetadata(object): 'is_time_limited', 'is_practice_exam', 'exam_review_rules', + 'hide_after_due', 'self_paced', 'chrome', 'default_tab', 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 e8db1e1b67..9277a20282 100644 --- a/cms/static/js/spec/views/pages/course_outline_spec.js +++ b/cms/static/js/spec/views/pages/course_outline_spec.js @@ -598,7 +598,7 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u var getDisplayNameWrapper, setEditModalValues, mockServerValuesJson, selectDisableSpecialExams, selectBasicSettings, selectAdvancedSettings, selectAccessSettings, selectTimedExam, selectProctoredExam, selectPracticeExam, - selectPrerequisite, selectLastPrerequisiteSubsection; + selectPrerequisite, selectLastPrerequisiteSubsection, checkOptionFieldVisibility; getDisplayNameWrapper = function() { return getItemHeaders('subsection').find('.wrapper-xblock-field'); @@ -612,7 +612,7 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u }; selectDisableSpecialExams = function() { - this.$("#id_not_timed").prop('checked', true).trigger('change'); + this.$("input.no_special_exam").prop('checked', true).trigger('change'); }; selectBasicSettings = function() { @@ -627,22 +627,23 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u this.$(".modal-section .settings-tab-button[data-tab='access']").click(); }; - selectTimedExam = function(time_limit) { - this.$("#id_timed_exam").prop('checked', true).trigger('change'); - this.$("#id_time_limit").val(time_limit); - this.$("#id_time_limit").trigger('focusout'); + selectTimedExam = function(time_limit, hide_after_due) { + this.$("input.timed_exam").prop('checked', true).trigger('change'); + this.$(".field-time-limit input").val(time_limit); + this.$(".field-time-limit input").trigger('focusout'); + this.$('.field-hide-after-due input').prop('checked', hide_after_due).trigger('change'); }; selectProctoredExam = function(time_limit) { - this.$("#id_proctored_exam").prop('checked', true).trigger('change'); - this.$("#id_time_limit").val(time_limit); - this.$("#id_time_limit").trigger('focusout'); + this.$("input.proctored_exam").prop('checked', true).trigger('change'); + this.$(".field-time-limit input").val(time_limit); + this.$(".field-time-limit input").trigger('focusout'); }; selectPracticeExam = function(time_limit) { - this.$("#id_practice_exam").prop('checked', true).trigger('change'); - this.$("#id_time_limit").val(time_limit); - this.$("#id_time_limit").trigger('focusout'); + this.$("input.practice_exam").prop('checked', true).trigger('change'); + this.$(".field-time-limit input").val(time_limit); + this.$(".field-time-limit input").trigger('focusout'); }; selectPrerequisite = function() { @@ -654,6 +655,13 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u this.$("#prereq_min_score").val(minScore).trigger('keyup'); }; + // Helper to validate oft-checked additional option fields' visibility + checkOptionFieldVisibility = function(time_limit, review_rules, hide_after_due) { + expect($('.field-time-limit').is(':visible')).toBe(time_limit); + expect($('.field-exam-review-rules').is(':visible')).toBe(review_rules); + expect($('.field-hide-after-due').is(':visible')).toBe(hide_after_due); + }; + // Contains hard-coded dates because dates are presented in different formats. mockServerValuesJson = createMockSectionJSON({ release_date: 'Jan 01, 2970 at 05:00 UTC' @@ -670,8 +678,9 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u is_prereq: false, "is_time_limited": true, "is_practice_exam": false, - "is_proctored_exam": true, - "default_time_limit_minutes": 150 + "is_proctored_exam": false, + "default_time_limit_minutes": 150, + "hide_after_due": true, }, [ createMockVerticalJSON({ has_changes: true, @@ -815,13 +824,13 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u for (i = 0; i < valid_times.length; i++){ time_limit = valid_times[i]; selectTimedExam(time_limit); - expect($("#id_time_limit").val()).toEqual(time_limit); + expect($(".field-time-limit input").val()).toEqual(time_limit); } for (i = 0; i < invalid_times.length; i++){ time_limit = invalid_times[i]; selectTimedExam(time_limit); - expect($("#id_time_limit").val()).not.toEqual(time_limit); - expect($("#id_time_limit").val()).toEqual(default_time); + expect($(".field-time-limit input").val()).not.toEqual(time_limit); + expect($(".field-time-limit input").val()).toEqual(default_time); } }); @@ -829,7 +838,8 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); setEditModalValues("7/9/2014", "7/10/2014", "Lab", true); - selectProctoredExam("02:30"); + selectAdvancedSettings(); + selectTimedExam("02:30", true); $(".wrapper-modal-window .action-save").click(); AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/mock-subsection', { "graderType":"Lab", @@ -842,8 +852,9 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u "exam_review_rules": "", "is_time_limited": true, "is_practice_exam": false, - "is_proctored_enabled": true, - "default_time_limit_minutes": 150 + "is_proctored_enabled": false, + "default_time_limit_minutes": 150, + "hide_after_due": true, } }); expect(requests[0].requestHeaders['X-HTTP-Method-Override']).toBe('PATCH'); @@ -872,30 +883,35 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u expect($("#due_date").val()).toBe('7/10/2014'); expect($("#grading_type").val()).toBe('Lab'); expect($("#staff_lock").is(":checked")).toBe(true); - expect($("#id_timed_exam").is(":checked")).toBe(false); - expect($("#id_proctored_exam").is(":checked")).toBe(true); - expect($("#id_not_timed").is(":checked")).toBe(false); - expect($("#id_practice_exam").is(":checked")).toBe(false); - expect($("#id_time_limit").val()).toBe("02:30"); + expect($("input.timed_exam").is(":checked")).toBe(true); + expect($("input.proctored_exam").is(":checked")).toBe(false); + expect($("input.no_special_exam").is(":checked")).toBe(false); + expect($("input.practice_exam").is(":checked")).toBe(false); + expect($(".field-time-limit input").val()).toBe("02:30"); + expect($(".field-hide-after-due input").is(":checked")).toBe(true); }); - it('can hide the time limit field when the None radio box is selected', function() { + it('can hide time limit and hide after due fields when the None radio box is selected', function() { createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); setEditModalValues("7/9/2014", "7/10/2014", "Lab", true); + selectAdvancedSettings(); selectDisableSpecialExams(); - // id_time_limit_div should be hidden when None is specified - expect($('#id_time_limit_div')).toHaveClass('is-hidden'); + // all additional options should be hidden + expect($('.exam-options').is(':hidden')).toBe(true); }); it('can select the practice exam', function() { createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); setEditModalValues("7/9/2014", "7/10/2014", "Lab", true); + selectAdvancedSettings(); selectPracticeExam("00:30"); - // id_time_limit_div should not be hidden when practice exam is specified - expect($('#id_time_limit_div')).not.toHaveClass('is-hidden"'); + + // time limit should be visible, review rules and hide after due should be hidden + checkOptionFieldVisibility(true, false, false); + $(".wrapper-modal-window .action-save").click(); }); @@ -903,9 +919,12 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); setEditModalValues("7/9/2014", "7/10/2014", "Lab", true); + selectAdvancedSettings(); selectTimedExam("00:30"); - // id_time_limit_div should not be hidden when timed exam is specified - expect($('#id_time_limit_div')).not.toHaveClass('is-hidden"'); + + // time limit and hide after due should be visible, review rules should be hidden + checkOptionFieldVisibility(true, false, true); + $(".wrapper-modal-window .action-save").click(); }); @@ -913,9 +932,12 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); setEditModalValues("7/9/2014", "7/10/2014", "Lab", true); + selectAdvancedSettings(); selectProctoredExam("00:30"); - // id_time_limit_div should not be hidden when timed exam is specified - expect($('#id_time_limit_div')).not.toHaveClass('is-hidden"'); + + // time limit and review rules should be visible, hide after due should be hidden + checkOptionFieldVisibility(true, true, false); + $(".wrapper-modal-window .action-save").click(); }); @@ -924,10 +946,12 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); setEditModalValues("7/9/2014", "7/10/2014", "Lab", true); + selectAdvancedSettings(); selectProctoredExam("abcd"); - // id_time_limit_div should not be hidden when timed exam is specified - expect($('#id_time_limit_div')).not.toHaveClass('is-hidden"'); - expect($('#id_time_limit')).toHaveValue('00:30'); + + // time limit field should be visible and have the correct value + expect($('.field-time-limit').is(':visible')).toBe(true); + expect($('.field-time-limit input').val()).toEqual("00:30"); }); @@ -944,21 +968,24 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u "is_time_limited": false, "is_practice_exam": false, "is_proctored_exam": false, - "default_time_limit_minutes": 150 + "default_time_limit_minutes": 150, + "hide_after_due": false, }, [ ]), ]) ]); createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); - expect($("#id_timed_exam").is(":checked")).toBe(false); - expect($("#id_proctored_exam").is(":checked")).toBe(false); - expect($("#id_not_timed").is(":checked")).toBe(true); - expect($("#id_practice_exam").is(":checked")).toBe(false); - expect($("#id_time_limit").val()).toBe("02:30"); + 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(true); + expect($("input.practice_exam").is(":checked")).toBe(false); + expect($(".field-time-limit input").val()).toBe("02:30"); + expect($('.field-hide-after-due').is(':hidden')).toBe(true); }); - it('can show a saved timed exam correctly', function() { + it('can show a saved timed exam correctly when hide_after_due is true', function() { var mockCourseWithSpecialExamJSON = createMockCourseJSON({}, [ createMockSectionJSON({ has_changes: true, @@ -971,18 +998,51 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u "is_time_limited": true, "is_practice_exam": false, "is_proctored_exam": false, - "default_time_limit_minutes": 10 + "default_time_limit_minutes": 10, + "hide_after_due": true, }, [ ]), ]) ]); createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); - expect($("#id_timed_exam").is(":checked")).toBe(true); - expect($("#id_proctored_exam").is(":checked")).toBe(false); - expect($("#id_not_timed").is(":checked")).toBe(false); - expect($("#id_practice_exam").is(":checked")).toBe(false); - expect($("#id_time_limit").val()).toBe("00:10"); + selectAdvancedSettings(); + expect($("input.timed_exam").is(":checked")).toBe(true); + expect($("input.proctored_exam").is(":checked")).toBe(false); + expect($("input.no_special_exam").is(":checked")).toBe(false); + expect($("input.practice_exam").is(":checked")).toBe(false); + expect($(".field-time-limit input").val()).toBe("00:10"); + expect($('.field-hide-after-due input').is(":checked")).toBe(true); + }); + + it('can show a saved timed exam correctly when hide_after_due is true', 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": false, + "default_time_limit_minutes": 10, + "hide_after_due": false, + }, [ + ]), + ]) + ]); + createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false); + outlinePage.$('.outline-subsection .configure-button').click(); + selectAdvancedSettings(); + expect($("input.timed_exam").is(":checked")).toBe(true); + expect($("input.proctored_exam").is(":checked")).toBe(false); + expect($("input.no_special_exam").is(":checked")).toBe(false); + expect($("input.practice_exam").is(":checked")).toBe(false); + expect($(".field-time-limit input").val()).toBe("00:10"); + expect($('.field-hide-after-due input').is(":checked")).toBe(false); }); it('can show a saved practice exam correctly', function() { @@ -1005,11 +1065,13 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u ]); createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); - expect($("#id_timed_exam").is(":checked")).toBe(false); - expect($("#id_proctored_exam").is(":checked")).toBe(false); - expect($("#id_not_timed").is(":checked")).toBe(false); - expect($("#id_practice_exam").is(":checked")).toBe(true); - expect($("#id_time_limit").val()).toBe("02:30"); + 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.practice_exam").is(":checked")).toBe(true); + expect($(".field-time-limit input").val()).toBe("02:30"); + expect($('.field-hide-after-due').is(':hidden')).toBe(true); }); it('can show a saved proctored exam correctly', function() { @@ -1032,11 +1094,13 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u ]); createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); - expect($("#id_timed_exam").is(":checked")).toBe(false); - expect($("#id_proctored_exam").is(":checked")).toBe(true); - expect($("#id_not_timed").is(":checked")).toBe(false); - expect($("#id_practice_exam").is(":checked")).toBe(false); - expect($("#id_time_limit").val()).toBe("02:30"); + selectAdvancedSettings(); + expect($("input.timed_exam").is(":checked")).toBe(false); + expect($("input.proctored_exam").is(":checked")).toBe(true); + expect($("input.no_special_exam").is(":checked")).toBe(false); + expect($("input.practice_exam").is(":checked")).toBe(false); + expect($(".field-time-limit input").val()).toBe("02:30"); + expect($('.field-hide-after-due').is(':hidden')).toBe(true); }); it('does not show proctored settings if proctored exams not enabled', function() { @@ -1052,16 +1116,19 @@ define(["jquery", "common/js/spec_helpers/ajax_helpers", "common/js/components/u "is_time_limited": true, "is_practice_exam": false, "is_proctored_exam": false, - "default_time_limit_minutes": 150 + "default_time_limit_minutes": 150, + "hide_after_due": true, }, [ ]), ]) ]); createCourseOutlinePage(this, mockCourseWithSpecialExamJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); - expect($("#id_timed_exam").is(":checked")).toBe(true); - expect($("#id_not_timed").is(":checked")).toBe(false); - expect($("#id_time_limit").val()).toBe("02:30"); + selectAdvancedSettings(); + expect($("input.timed_exam").is(":checked")).toBe(true); + expect($("input.no_special_exam").is(":checked")).toBe(false); + expect($(".field-time-limit input").val()).toBe("02:30"); + expect($('.field-hide-after-due input').is(":checked")).toBe(true); }); it('can select prerequisite', function() { diff --git a/cms/static/js/views/modals/course_outline_modals.js b/cms/static/js/views/modals/course_outline_modals.js index a08a7097df..2aee819b4a 100644 --- a/cms/static/js/views/modals/course_outline_modals.js +++ b/cms/static/js/views/modals/course_outline_modals.js @@ -332,41 +332,47 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', templateName: 'timed-examination-preference-editor', className: 'edit-settings-timed-examination', events : { - 'change #id_not_timed': 'notTimedExam', - 'change #id_timed_exam': 'setTimedExam', - 'change #id_practice_exam': 'setPracticeExam', - 'change #id_proctored_exam': 'setProctoredExam', - 'focusout #id_time_limit': 'timeLimitFocusout' + 'change input.no_special_exam': 'notTimedExam', + 'change input.timed_exam': 'setTimedExam', + 'change input.practice_exam': 'setPracticeExam', + 'change input.proctored_exam': 'setProctoredExam', + 'focusout .field-time-limit input': 'timeLimitFocusout' }, notTimedExam: function (event) { event.preventDefault(); - this.$('#id_time_limit_div').hide(); - this.$('.exam-review-rules-list-fields').hide(); - this.$('#id_time_limit').val('00:00'); + this.$('.exam-options').hide(); + this.$('.field-time-limit input').val('00:00'); }, - selectSpecialExam: function (showRulesField) { - this.$('#id_time_limit_div').show(); - if (!this.isValidTimeLimit(this.$('#id_time_limit').val())) { - this.$('#id_time_limit').val('00:30'); + selectSpecialExam: function (showRulesField, showHideAfterDueField) { + this.$('.exam-options').show(); + this.$('.field-time-limit').show(); + if (!this.isValidTimeLimit(this.$('.field-time-limit input').val())) { + this.$('.field-time-limit input').val('00:30'); } if (showRulesField) { - this.$('.exam-review-rules-list-fields').show(); + this.$('.field-exam-review-rules').show(); } else { - this.$('.exam-review-rules-list-fields').hide(); + this.$('.field-exam-review-rules').hide(); + } + if (showHideAfterDueField) { + this.$('.field-hide-after-due').show(); + } + else { + this.$('.field-hide-after-due').hide(); } }, setTimedExam: function (event) { event.preventDefault(); - this.selectSpecialExam(false); + this.selectSpecialExam(false, true); }, setPracticeExam: function (event) { event.preventDefault(); - this.selectSpecialExam(false); + this.selectSpecialExam(false, false); }, setProctoredExam: function (event) { event.preventDefault(); - this.selectSpecialExam(true); + this.selectSpecialExam(true, false); }, timeLimitFocusout: function(event) { event.preventDefault(); @@ -389,43 +395,51 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', this.setExamTime(this.model.get('default_time_limit_minutes')); this.setReviewRules(this.model.get('exam_review_rules')); + this.setHideAfterDue(this.model.get('hide_after_due')); }, setExamType: function(is_time_limited, is_proctored_exam, is_practice_exam) { + this.$('.field-time-limit').hide(); + this.$('.field-exam-review-rules').hide(); + this.$('.field-hide-after-due').hide(); + if (!is_time_limited) { - this.$("#id_not_timed").prop('checked', true); + this.$('input.no_special_exam').prop('checked', true); return; } - this.$('#id_time_limit_div').show(); - this.$('.exam-review-rules-list-fields').hide(); + this.$('.field-time-limit').show(); if (this.options.enable_proctored_exams && is_proctored_exam) { if (is_practice_exam) { - this.$('#id_practice_exam').prop('checked', true); + this.$('input.practice_exam').prop('checked', true); } else { - this.$('#id_proctored_exam').prop('checked', true); - this.$('.exam-review-rules-list-fields').show(); + this.$('input.proctored_exam').prop('checked', true); + this.$('.field-exam-review-rules').show(); } } else { // Since we have an early exit at the top of the method // if the subsection is not time limited, then // here we rightfully assume that it just a timed exam - this.$("#id_timed_exam").prop('checked', true); + this.$('input.timed_exam').prop('checked', true); + this.$('.field-hide-after-due').show(); } }, setExamTime: function(value) { var time = this.convertTimeLimitMinutesToString(value); - this.$('#id_time_limit').val(time); + this.$('.field-time-limit input').val(time); }, setReviewRules: function (value) { - this.$('#id_exam_review_rules').val(value); + this.$('.field-exam-review-rules textarea').val(value); + }, + setHideAfterDue: function(value) { + this.$('.field-hide-after-due input').prop('checked', value); }, isValidTimeLimit: function(time_limit) { var pattern = new RegExp('^\\d{1,2}:[0-5][0-9]$'); return pattern.test(time_limit) && time_limit !== "00:00"; }, getExamTimeLimit: function () { - return this.$('#id_time_limit').val(); + return this.$('.field-time-limit input').val(); }, convertTimeLimitMinutesToString: function (timeLimitMinutes) { var hoursStr = "" + Math.floor(timeLimitMinutes / 60); @@ -444,21 +458,22 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', var is_practice_exam; var is_proctored_exam; var time_limit = this.getExamTimeLimit(); - var exam_review_rules = this.$('#id_exam_review_rules').val(); + var exam_review_rules = this.$('.field-exam-review-rules textarea').val(); + var hide_after_due = this.$('.field-hide-after-due input').is(':checked'); - if (this.$("#id_not_timed").is(':checked')){ + if (this.$('input.no_special_exam').is(':checked')){ is_time_limited = false; is_practice_exam = false; is_proctored_exam = false; - } else if (this.$("#id_timed_exam").is(':checked')){ + } else if (this.$('input.timed_exam').is(':checked')){ is_time_limited = true; is_practice_exam = false; is_proctored_exam = false; - } else if (this.$("#id_proctored_exam").is(':checked')){ + } else if (this.$('input.proctored_exam').is(':checked')){ is_time_limited = true; is_practice_exam = false; is_proctored_exam = true; - } else if (this.$("#id_practice_exam").is(':checked')){ + } else if (this.$('input.practice_exam').is(':checked')){ is_time_limited = true; is_practice_exam = true; is_proctored_exam = true; @@ -469,6 +484,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', 'is_practice_exam': is_practice_exam, 'is_time_limited': is_time_limited, 'exam_review_rules': exam_review_rules, + 'hide_after_due': hide_after_due, // We have to use the legacy field name // as the Ajax handler directly populates // the xBlocks fields. We will have to diff --git a/cms/static/sass/elements/_modal-window.scss b/cms/static/sass/elements/_modal-window.scss index 7e2f222d19..12636be084 100644 --- a/cms/static/sass/elements/_modal-window.scss +++ b/cms/static/sass/elements/_modal-window.scss @@ -564,7 +564,7 @@ } .list-fields { .field-message { - color: $gray; + color: $gray-d1; font-size: ($baseline/2); } .field { diff --git a/cms/static/sass/elements/_modules.scss b/cms/static/sass/elements/_modules.scss index 7540c8700d..06bb1fe45d 100644 --- a/cms/static/sass/elements/_modules.scss +++ b/cms/static/sass/elements/_modules.scss @@ -303,7 +303,7 @@ $outline-indent-width: $baseline; %outline-item-status { @extend %t-copy-sub2; @extend %t-strong; - color: $color-copy-base; + color: $gray-d1; .icon { @extend %t-icon5; @@ -576,12 +576,16 @@ $outline-indent-width: $baseline; > .subsection-status .status-timed-proctored-exam { opacity: 1.0; } + + > .subsection-status .status-hide-after-due { + opacity: 1.0; + } } // status - grading - .status-grading, .status-timed-proctored-exam { + .status-grading, .status-timed-proctored-exam, .status-hide-after-due { @include transition(opacity $tmg-f2 ease-in-out 0s); - opacity: 0.65; + opacity: 0.75; } .status-grading-value, .status-proctored-exam-value { diff --git a/cms/templates/js/course-outline.underscore b/cms/templates/js/course-outline.underscore index 235761c37d..78f25272d0 100644 --- a/cms/templates/js/course-outline.underscore +++ b/cms/templates/js/course-outline.underscore @@ -67,96 +67,96 @@ if (is_proctored_exam) { } %> <% if (parentInfo) { %> -
  • +
  • <% if (xblockInfo.isHeaderVisible()) { %> -
    +
    <% if (includesChildren) { %> -

    -header-details expand-collapse <%- isCollapsed ? 'expand' : 'collapse' %> ui-toggle-expansion" + title="<%- interpolate( gettext('Collapse/Expand this %(xblock_type)s'), { xblock_type: xblockTypeDisplayName }, true ) %>" > <% } else { %> -

    +

    <% } %> <% if (xblockInfo.isVertical()) { %> - <%- xblockInfo.get('display_name') %> + <%- xblockInfo.get('display_name') %> <% } else { %> - "> - <%- xblockInfo.get('display_name') %> + "> + <%- xblockInfo.get('display_name') %> <% } %>

    -
    +
    -
    +
    <% if (!xblockInfo.isVertical()) { %> <% if (xblockInfo.get('explanatory_message') !=null) { %>
    - <%= xblockInfo.get('explanatory_message') %> + <%- xblockInfo.get('explanatory_message') %>
    <% } else { %>

    - <%= gettext('Release Status:') %> + <%- gettext('Release Status:') %> <% if (!course.get('self_paced')) { %> <% if (xblockInfo.get('released_to_students')) { %> - - <%= gettext('Released:') %> + + <%- gettext('Released:') %> <% } else if (xblockInfo.get('release_date')) { %> - <%= gettext('Scheduled:') %> + <%- gettext('Scheduled:') %> <% } else { %> - <%= gettext('Unscheduled') %> + <%- gettext('Unscheduled') %> <% } %> <% if (xblockInfo.get('release_date')) { %> - <%= xblockInfo.get('release_date') %> + <%- xblockInfo.get('release_date') %> <% } %> <% } %> @@ -166,25 +166,36 @@ if (is_proctored_exam) { <% if (xblockInfo.get('is_time_limited')) { %>

    - <%= gettext('Graded as:') %> + <%- gettext('Graded as:') %> - <%= gradingType %> + <%- gradingType %> - <%- exam_value %> <%- exam_value %> <% if (xblockInfo.get('due_date')) { %> - <%= gettext('Due:') %> <%= xblockInfo.get('due_date') %> + <%- gettext('Due:') %> <%- xblockInfo.get('due_date') %> + <% } %> +

    +
    +
    +

    + <% if (!is_proctored_exam && xblockInfo.get('hide_after_due')) { %> + + <%- gettext("Exam will remain hidden after due date") %> + <% } else { %> + + <%- gettext("Exam will be visible after due date") %> <% } %>

    <% } else if (xblockInfo.get('due_date') || xblockInfo.get('graded')) { %>

    - <%= gettext('Graded as:') %> + <%- gettext('Graded as:') %> - <%= gradingType %> + <%- gradingType %> <% if (xblockInfo.get('due_date') && !course.get('self_paced')) { %> - <%= gettext('Due:') %> <%= xblockInfo.get('due_date') %> + <%- gettext('Due:') %> <%- xblockInfo.get('due_date') %> <% } %>

    @@ -192,7 +203,7 @@ if (is_proctored_exam) { <% } %> <% if (statusMessage) { %>
    - +

    <%- statusMessage %>

    <% } %> @@ -202,20 +213,20 @@ if (is_proctored_exam) { <% if (!parentInfo && xblockInfo.get('child_info') && xblockInfo.get('child_info').children.length === 0) { %>
    -

    <%= gettext("You haven't added any content to this course yet.") %> - + - <%= addChildLabel %> + <%- addChildLabel %>

    <% } else if (!xblockInfo.isVertical()) { %> -
    -
      +
      +
      1. @@ -223,14 +234,14 @@ if (is_proctored_exam) { <% if (childType) { %> <% if (xblockInfo.isChildAddable()) { %> - <% } %> diff --git a/cms/templates/js/timed-examination-preference-editor.underscore b/cms/templates/js/timed-examination-preference-editor.underscore index 061672ab0b..9debf2c463 100644 --- a/cms/templates/js/timed-examination-preference-editor.underscore +++ b/cms/templates/js/timed-examination-preference-editor.underscore @@ -1,73 +1,65 @@