From 35ae67b530b53c675c8ad0baa038f01f3f74fe1f Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Wed, 20 Jul 2016 19:58:51 -0400 Subject: [PATCH] New CMS visibility settings (#12940) TNL-4906 Subsections now use radio buttons, to allow for "hide after due" as a visibility option. Also, all tabs have been consolidated to "Basic" and "Advanced", and visibility options have moved there. Documentation links are updated to assist course authors with the new visibility options. Tests have also been updated, and the changes suggested in TNL-4951 are included. --- cms/djangoapps/contentstore/views/item.py | 6 +- .../views/pages/container_subviews_spec.js | 2 +- .../spec/views/pages/course_outline_spec.js | 122 ++++----- cms/static/js/utils/date_utils.js | 5 +- .../js/views/modals/course_outline_modals.js | 241 ++++++++++++------ cms/static/sass/elements/_modal-window.scss | 7 +- cms/templates/course_outline.html | 29 ++- .../js/content-visibility-editor.underscore | 34 +++ cms/templates/js/course-outline.underscore | 19 +- cms/templates/js/publish-xblock.underscore | 7 +- .../js/release-date-editor.underscore | 10 +- cms/templates/js/staff-lock-editor.underscore | 25 +- ...d-examination-preference-editor.underscore | 8 - .../test/acceptance/pages/studio/overview.py | 101 ++++++-- .../tests/lms/test_lms_courseware.py | 14 +- .../acceptance/tests/lms/test_lms_gating.py | 8 +- docs/cms_config.ini | 2 + 17 files changed, 413 insertions(+), 227 deletions(-) create mode 100644 cms/templates/js/content-visibility-editor.underscore diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index eb40ebed51..0004a13111 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -982,6 +982,11 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F "user_partitions": get_user_partition_info(xblock, course=course), } + if xblock.category == 'sequential': + xblock_info.update({ + "hide_after_due": xblock.hide_after_due, + }) + # update xblock_info with special exam information if the feature flag is enabled if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): if xblock.category == 'course': @@ -997,7 +1002,6 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F "is_time_limited": xblock.is_time_limited, "exam_review_rules": xblock.exam_review_rules, "default_time_limit_minutes": xblock.default_time_limit_minutes, - "hide_after_due": xblock.hide_after_due, }) # Update with gating info diff --git a/cms/static/js/spec/views/pages/container_subviews_spec.js b/cms/static/js/spec/views/pages/container_subviews_spec.js index 22af2fb667..66c6997e0a 100644 --- a/cms/static/js/spec/views/pages/container_subviews_spec.js +++ b/cms/static/js/spec/views/pages/container_subviews_spec.js @@ -434,7 +434,7 @@ define(["jquery", "underscore", "underscore.string", "edx-ui-toolkit/js/utils/sp expect(visibilityCopy).toContain('Staff Only'); expect(containerPage.$(bitPublishingCss)).toHaveClass(staffOnlyClass); } else { - expect(visibilityCopy).toBe('Staff and Students'); + expect(visibilityCopy).toBe('Staff and Learners'); expect(containerPage.$(bitPublishingCss)).not.toHaveClass(staffOnlyClass); verifyExplicitStaffOnly(false); verifyImplicitStaffOnly(false); 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 efbe55607a..7d59717898 100644 --- a/cms/static/js/spec/views/pages/course_outline_spec.js +++ b/cms/static/js/spec/views/pages/course_outline_spec.js @@ -7,10 +7,10 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j describe("CourseOutlinePage", function() { var createCourseOutlinePage, displayNameInput, model, outlinePage, requests, getItemsOfType, getItemHeaders, verifyItemsExpanded, expandItemsAndVerifyState, - collapseItemsAndVerifyState, createMockCourseJSON, createMockSectionJSON, createMockSubsectionJSON, - verifyTypePublishable, mockCourseJSON, mockEmptyCourseJSON, mockSingleSectionCourseJSON, - createMockVerticalJSON, createMockIndexJSON, mockCourseEntranceExamJSON, - mockOutlinePage = readFixtures('mock/mock-course-outline-page.underscore'), + collapseItemsAndVerifyState, selectBasicSettings, selectAdvancedSettings, createMockCourseJSON, + createMockSectionJSON, createMockSubsectionJSON, verifyTypePublishable, mockCourseJSON, + mockEmptyCourseJSON, mockSingleSectionCourseJSON, createMockVerticalJSON, createMockIndexJSON, + mockCourseEntranceExamJSON, mockOutlinePage = readFixtures('mock/mock-course-outline-page.underscore'), mockRerunNotification = readFixtures('mock/mock-course-rerun-notification.underscore'); createMockCourseJSON = function(options, children) { @@ -137,6 +137,14 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j verifyItemsExpanded(type, false); }; + selectBasicSettings = function() { + this.$(".modal-section .settings-tab-button[data-tab='basic']").click(); + }; + + selectAdvancedSettings = function() { + this.$(".modal-section .settings-tab-button[data-tab='advanced']").click(); + }; + createCourseOutlinePage = function(test, courseJSON, createOnly) { requests = AjaxHelpers.requests(test); model = new XBlockOutlineInfo(courseJSON, { parse: true }); @@ -230,8 +238,8 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j 'course-outline', 'xblock-string-field-editor', 'modal-button', 'basic-modal', 'course-outline-modal', 'release-date-editor', 'due-date-editor', 'grading-editor', 'publish-editor', - 'staff-lock-editor', 'settings-modal-tabs', 'timed-examination-preference-editor', - 'access-editor' + 'staff-lock-editor','content-visibility-editor', 'settings-modal-tabs', + 'timed-examination-preference-editor', 'access-editor' ]); appendSetFixtures(mockOutlinePage); mockCourseJSON = createMockCourseJSON({}, [ @@ -535,8 +543,10 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j expect($("due_date")).not.toExist(); expect($("grading_format")).not.toExist(); - // Staff lock controls are always visible + // Staff lock controls are always visible on the advanced tab + selectAdvancedSettings(); expect($("#staff_lock")).toExist(); + selectBasicSettings(); $(".wrapper-modal-window .action-save").click(); AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/mock-section', { "metadata":{ @@ -605,43 +615,33 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j }); describe("Subsection", function() { - var getDisplayNameWrapper, setEditModalValues, mockServerValuesJson, - selectDisableSpecialExams, selectBasicSettings, selectAdvancedSettings, - selectAccessSettings, selectTimedExam, selectProctoredExam, selectPracticeExam, + var getDisplayNameWrapper, setEditModalValues, setContentVisibility, mockServerValuesJson, + selectDisableSpecialExams, selectTimedExam, selectProctoredExam, selectPracticeExam, selectPrerequisite, selectLastPrerequisiteSubsection, checkOptionFieldVisibility; getDisplayNameWrapper = function() { return getItemHeaders('subsection').find('.wrapper-xblock-field'); }; - setEditModalValues = function (start_date, due_date, grading_type, is_locked) { + setEditModalValues = function (start_date, due_date, grading_type) { $("#start_date").val(start_date); $("#due_date").val(due_date); $("#grading_type").val(grading_type); - $("#staff_lock").prop('checked', is_locked); + }; + + setContentVisibility = function (visibility) { + $('input[name=content-visibility][value='+visibility+']').prop('checked', true); }; selectDisableSpecialExams = function() { this.$("input.no_special_exam").prop('checked', true).trigger('change'); }; - selectBasicSettings = function() { - this.$(".modal-section .settings-tab-button[data-tab='basic']").click(); - }; - - selectAdvancedSettings = function() { - this.$(".modal-section .settings-tab-button[data-tab='advanced']").click(); - }; - - selectAccessSettings = function() { - this.$(".modal-section .settings-tab-button[data-tab='access']").click(); - }; - - selectTimedExam = function(time_limit, hide_after_due) { + selectTimedExam = function(time_limit) { 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'); + setContentVisibility("hide_after_due"); }; selectProctoredExam = function(time_limit) { @@ -666,10 +666,9 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j }; // Helper to validate oft-checked additional option fields' visibility - checkOptionFieldVisibility = function(time_limit, review_rules, hide_after_due) { + checkOptionFieldVisibility = function(time_limit, review_rules) { 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. @@ -767,7 +766,6 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j selectBasicSettings(); expect($('.modal-section .settings-tab-button[data-tab="basic"]')).toHaveClass('active'); expect($('.modal-section .settings-tab-button[data-tab="advanced"]')).not.toHaveClass('active'); - expect($('.modal-section .settings-tab-button[data-tab="access"]')).not.toHaveClass('active'); }); it('can show advanced settings', function() { @@ -776,20 +774,11 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j selectAdvancedSettings(); expect($('.modal-section .settings-tab-button[data-tab="basic"]')).not.toHaveClass('active'); expect($('.modal-section .settings-tab-button[data-tab="advanced"]')).toHaveClass('active'); - expect($('.modal-section .settings-tab-button[data-tab="access"]')).not.toHaveClass('active'); - }); - - it('can show access settings', function() { - createCourseOutlinePage(this, mockCourseJSON, false); - outlinePage.$('.outline-subsection .configure-button').click(); - selectAccessSettings(); - expect($('.modal-section .settings-tab-button[data-tab="basic"]')).not.toHaveClass('active'); - expect($('.modal-section .settings-tab-button[data-tab="advanced"]')).not.toHaveClass('active'); - expect($('.modal-section .settings-tab-button[data-tab="access"]')).toHaveClass('active'); }); it('does not show settings tab headers if there is only one tab to show', function() { - var mockSubsectionJSON = createMockSubsectionJSON({}, []); + var mockVerticalJSON = createMockVerticalJSON({}, []); + var mockSubsectionJSON = createMockSubsectionJSON({}, [mockVerticalJSON]); delete mockSubsectionJSON.is_prereq; delete mockSubsectionJSON.prereqs; delete mockSubsectionJSON.prereq; @@ -801,7 +790,7 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j createMockSectionJSON({}, [mockSubsectionJSON]) ]); createCourseOutlinePage(this, mockCourseJSON, false); - outlinePage.$('.outline-subsection .configure-button').click(); + outlinePage.$('.outline-unit .configure-button').click(); expect($(".settings-tabs-header").length).toBe(0); }); @@ -818,7 +807,7 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j expect($(".edit-settings-release").length).toBe(0); expect($(".grading-due-date").length).toBe(0); expect($(".edit-settings-grading").length).toBe(1); - expect($(".edit-staff-lock").length).toBe(1); + expect($(".edit-content-visibility").length).toBe(1); }); it('can select valid time', function() { @@ -847,16 +836,16 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j it('can be edited', function() { createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); - setEditModalValues("7/9/2014", "7/10/2014", "Lab", true); + setEditModalValues("7/9/2014", "7/10/2014", "Lab"); selectAdvancedSettings(); - selectTimedExam("02:30", true); + selectTimedExam("02:30"); $(".wrapper-modal-window .action-save").click(); AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/mock-subsection', { "graderType":"Lab", "publish": "republish", "isPrereq": false, "metadata":{ - "visible_to_staff_only": true, + "visible_to_staff_only": null, "start":"2014-07-09T00:00:00.000Z", "due":"2014-07-10T00:00:00.000Z", "exam_review_rules": "", @@ -892,21 +881,21 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j expect($("#start_date").val()).toBe('7/9/2014'); expect($("#due_date").val()).toBe('7/10/2014'); expect($("#grading_type").val()).toBe('Lab'); - expect($("#staff_lock").is(":checked")).toBe(true); + expect($("input[name=content-visibility][value=staff_only]").is(":checked")).toBe(true); 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 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); + setEditModalValues("7/9/2014", "7/10/2014", "Lab"); selectAdvancedSettings(); selectDisableSpecialExams(); + setContentVisibility("staff_only"); // all additional options should be hidden expect($('.exam-options').is(':hidden')).toBe(true); @@ -915,12 +904,13 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j 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); + setEditModalValues("7/9/2014", "7/10/2014", "Lab"); selectAdvancedSettings(); selectPracticeExam("00:30"); + setContentVisibility("staff_only"); - // time limit should be visible, review rules and hide after due should be hidden - checkOptionFieldVisibility(true, false, false); + // time limit should be visible, review rules should be hidden + checkOptionFieldVisibility(true, false); $(".wrapper-modal-window .action-save").click(); }); @@ -928,12 +918,12 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j it('can select the timed exam', function() { createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); - setEditModalValues("7/9/2014", "7/10/2014", "Lab", true); + setEditModalValues("7/9/2014", "7/10/2014", "Lab"); selectAdvancedSettings(); selectTimedExam("00:30"); - // time limit and hide after due should be visible, review rules should be hidden - checkOptionFieldVisibility(true, false, true); + // time limit should be visible, review rules should be hidden + checkOptionFieldVisibility(true, false); $(".wrapper-modal-window .action-save").click(); }); @@ -941,12 +931,13 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j it('can select the Proctored exam option', function() { createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); - setEditModalValues("7/9/2014", "7/10/2014", "Lab", true); + setEditModalValues("7/9/2014", "7/10/2014", "Lab"); selectAdvancedSettings(); selectProctoredExam("00:30"); + setContentVisibility("staff_only"); - // time limit and review rules should be visible, hide after due should be hidden - checkOptionFieldVisibility(true, true, false); + // time limit and review rules should be visible + checkOptionFieldVisibility(true, true); $(".wrapper-modal-window .action-save").click(); @@ -955,9 +946,10 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j it('entering invalid time format uses default value of 30 minutes.', function() { createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); - setEditModalValues("7/9/2014", "7/10/2014", "Lab", true); + setEditModalValues("7/9/2014", "7/10/2014", "Lab"); selectAdvancedSettings(); selectProctoredExam("abcd"); + setContentVisibility("staff_only"); // time limit field should be visible and have the correct value expect($('.field-time-limit').is(':visible')).toBe(true); @@ -992,7 +984,6 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j 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 when hide_after_due is true', function() { @@ -1022,7 +1013,6 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j 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() { @@ -1081,7 +1071,6 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j 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() { @@ -1110,7 +1099,6 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j 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() { @@ -1138,7 +1126,6 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j 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() { @@ -1300,7 +1287,7 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j ]); createCourseOutlinePage(this, mockCourseWithPreqsJSON, false); outlinePage.$('.outline-subsection .configure-button').click(); - selectAccessSettings(); + selectAdvancedSettings(); selectLastPrerequisiteSubsection(''); expect($('#prereq_min_score_error').css('display')).toBe('none'); selectLastPrerequisiteSubsection('80'); @@ -1314,7 +1301,8 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j it('release date, due date, grading type, and staff lock can be cleared.', function() { createCourseOutlinePage(this, mockCourseJSON, false); outlinePage.$('.outline-item .outline-subsection .configure-button').click(); - setEditModalValues("7/9/2014", "7/10/2014", "Lab", true); + setEditModalValues("7/9/2014", "7/10/2014", "Lab"); + setContentVisibility("staff_only"); $(".wrapper-modal-window .action-save").click(); // This is the response for the change operation. @@ -1339,7 +1327,7 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j expect($("#start_date").val()).toBe('7/9/2014'); expect($("#due_date").val()).toBe('7/10/2014'); expect($("#grading_type").val()).toBe('Lab'); - expect($("#staff_lock").is(":checked")).toBe(true); + expect($("input[name=content-visibility][value=staff_only]").is(":checked")).toBe(true); $(".wrapper-modal-window .scheduled-date-input .action-clear").click(); $(".wrapper-modal-window .due-date-input .action-clear").click(); @@ -1347,7 +1335,7 @@ define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "common/j expect($("#due_date").val()).toBe(''); $("#grading_type").val('notgraded'); - $("#staff_lock").prop('checked', false); + setContentVisibility("visible"); $(".wrapper-modal-window .action-save").click(); diff --git a/cms/static/js/utils/date_utils.js b/cms/static/js/utils/date_utils.js index db2b9ff726..ee96162063 100644 --- a/cms/static/js/utils/date_utils.js +++ b/cms/static/js/utils/date_utils.js @@ -52,7 +52,10 @@ function($, date, TriggerChangeEventOnEnter) { // given a pair of inputs (datepicker and timepicker), return a JS Date // object that corresponds to the datetime.js that they represent. Assume // UTC timezone, NOT the timezone of the user's browser. - var date = $(datepickerInput).datepicker("getDate"), time = null; + var date = null, time = null; + if (datepickerInput.length > 0) { + date = $(datepickerInput).datepicker("getDate"); + } if (timepickerInput.length > 0) { time = $(timepickerInput).timepicker("getTime"); } diff --git a/cms/static/js/views/modals/course_outline_modals.js b/cms/static/js/views/modals/course_outline_modals.js index ee4a202d88..340f52e605 100644 --- a/cms/static/js/views/modals/course_outline_modals.js +++ b/cms/static/js/views/modals/course_outline_modals.js @@ -7,14 +7,15 @@ */ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', 'js/views/modals/base_modal', 'date', 'js/views/utils/xblock_utils', - 'js/utils/date_utils' + 'js/utils/date_utils', 'edx-ui-toolkit/js/utils/html-utils', + 'edx-ui-toolkit/js/utils/string-utils' ], function( - $, Backbone, _, gettext, BaseView, BaseModal, date, XBlockViewUtils, DateUtils + $, Backbone, _, gettext, BaseView, BaseModal, date, XBlockViewUtils, DateUtils, HtmlUtils, StringUtils ) { 'use strict'; var CourseOutlineXBlockModal, SettingsXBlockModal, PublishXBlockModal, AbstractEditor, BaseDateEditor, - ReleaseDateEditor, DueDateEditor, GradingEditor, PublishEditor, StaffLockEditor, - VerificationAccessEditor, TimedExaminationPreferenceEditor, AccessEditor; + ReleaseDateEditor, DueDateEditor, GradingEditor, PublishEditor, AbstractVisibilityEditor, StaffLockEditor, + ContentVisibilityEditor, VerificationAccessEditor, TimedExaminationPreferenceEditor, AccessEditor; CourseOutlineXBlockModal = BaseModal.extend({ events : _.extend({}, BaseModal.prototype.events, { @@ -105,9 +106,9 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', SettingsXBlockModal = CourseOutlineXBlockModal.extend({ getTitle: function () { - return interpolate( - gettext('%(display_name)s Settings'), - { display_name: this.model.get('display_name') }, true + return StringUtils.interpolate( + gettext('{display_name} Settings'), + { display_name: this.model.get('display_name') } ); }, @@ -115,9 +116,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', var message = ''; var tabs = this.options.tabs; if (!tabs || tabs.length < 2) { - message = interpolate( - gettext('Change the settings for %(display_name)s'), - { display_name: this.model.get('display_name') }, true); + message = StringUtils.interpolate( + gettext('Change the settings for {display_name}'), + { display_name: this.model.get('display_name') } + ); } return message; }, @@ -127,7 +129,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', if (tabs && tabs.length > 0) { if (tabs.length > 1) { var tabsTemplate = this.loadTemplate('settings-modal-tabs'); - this.$('.modal-section').html(tabsTemplate({tabs: tabs})); + HtmlUtils.setHtml(this.$('.modal-section'), HtmlUtils.HTML(tabsTemplate({tabs: tabs}))); _.each(this.options.tabs, function(tab) { this.options.editors.push.apply( this.options.editors, @@ -196,16 +198,16 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', }, getTitle: function () { - return interpolate( - gettext('Publish %(display_name)s'), - { display_name: this.model.get('display_name') }, true + return StringUtils.interpolate( + gettext('Publish {display_name}'), + { display_name: this.model.get('display_name') } ); }, getIntroductionMessage: function () { - return interpolate( - gettext('Publish all unpublished changes for this %(item)s?'), - { item: this.options.xblockType }, true + return StringUtils.interpolate( + gettext('Publish all unpublished changes for this {item}?'), + { item: this.options.xblockType } ); }, @@ -233,7 +235,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', enable_timed_exam: this.options.enable_timed_exams }, this.getContext())); - this.$el.html(html); + HtmlUtils.setHtml(this.$el, HtmlUtils.HTML(html)); this.parentElement.append(this.$el); }, @@ -343,7 +345,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', this.$('.exam-options').hide(); this.$('.field-time-limit input').val('00:00'); }, - selectSpecialExam: function (showRulesField, showHideAfterDueField) { + selectSpecialExam: function (showRulesField) { this.$('.exam-options').show(); this.$('.field-time-limit').show(); if (!this.isValidTimeLimit(this.$('.field-time-limit input').val())) { @@ -355,24 +357,18 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', else { 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, true); + this.selectSpecialExam(false); }, setPracticeExam: function (event) { event.preventDefault(); - this.selectSpecialExam(false, false); + this.selectSpecialExam(false); }, setProctoredExam: function (event) { event.preventDefault(); - this.selectSpecialExam(true, false); + this.selectSpecialExam(true); }, timeLimitFocusout: function(event) { event.preventDefault(); @@ -395,12 +391,10 @@ 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.$('input.no_special_exam').prop('checked', true); @@ -421,7 +415,6 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', // if the subsection is not time limited, then // here we rightfully assume that it just a timed exam this.$('input.timed_exam').prop('checked', true); - this.$('.field-hide-after-due').show(); } }, setExamTime: function(value) { @@ -431,9 +424,6 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', setReviewRules: function (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"; @@ -459,7 +449,6 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', var is_proctored_exam; var time_limit = this.getExamTimeLimit(); 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.$('input.no_special_exam').is(':checked')){ is_time_limited = false; @@ -484,7 +473,6 @@ 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 @@ -590,9 +578,11 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', } }); - StaffLockEditor = AbstractEditor.extend({ - templateName: 'staff-lock-editor', - className: 'edit-staff-lock', + AbstractVisibilityEditor = AbstractEditor.extend({ + afterRender: function () { + AbstractEditor.prototype.afterRender.call(this); + }, + isModelLocked: function() { return this.model.get('has_explicit_staff_lock'); }, @@ -601,8 +591,19 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', return this.model.get('ancestor_has_staff_lock'); }, + getContext: function () { + return { + hasExplicitStaffLock: this.isModelLocked(), + ancestorLocked: this.isAncestorLocked() + }; + } + }); + + StaffLockEditor = AbstractVisibilityEditor.extend({ + templateName: 'staff-lock-editor', + className: 'edit-staff-lock', afterRender: function () { - AbstractEditor.prototype.afterRender.call(this); + AbstractVisibilityEditor.prototype.afterRender.call(this); this.setLock(this.isModelLocked()); }, @@ -619,19 +620,100 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', }, getRequestData: function() { - return this.hasChanges() ? { - publish: 'republish', - metadata: { - visible_to_staff_only: this.isLocked() ? true : null + if (this.hasChanges()) { + return { + publish: 'republish', + metadata: { + visible_to_staff_only: this.isLocked() ? true : null } - } : {}; + }; + } else { + return {}; + } + }, + }); + + ContentVisibilityEditor = AbstractVisibilityEditor.extend({ + templateName: 'content-visibility-editor', + className: 'edit-content-visibility', + events: { + 'change input[name=content-visibility]': 'toggleUnlockWarning' + }, + + modelVisibility: function() { + if (this.model.get('has_explicit_staff_lock')) { + return 'staff_only'; + } else if (this.model.get('hide_after_due')) { + return 'hide_after_due'; + } else { + return 'visible'; + } + }, + + afterRender: function () { + AbstractVisibilityEditor.prototype.afterRender.call(this); + this.setVisibility(this.modelVisibility()); + this.$('input[name=content-visibility]:checked').change(); + }, + + setVisibility: function(value) { + this.$('input[name=content-visibility][value='+value+']').prop('checked', true); + }, + + currentVisibility: function() { + return this.$('input[name=content-visibility]:checked').val(); + }, + + hasChanges: function() { + return this.modelVisibility() !== this.currentVisibility(); + }, + + toggleUnlockWarning: function() { + var warning = this.$('.staff-lock .tip-warning'); + if (warning) { + var display; + if (this.currentVisibility() !== 'staff_only') { + display = 'block'; + } else { + display = 'none'; + } + $.each(warning, function(_, element) { + element.style.display = display; + }); + } + }, + + getRequestData: function() { + if (this.hasChanges()) { + var metadata = {}; + if (this.currentVisibility() === 'staff_only') { + metadata.visible_to_staff_only = true; + metadata.hide_after_due = null; + } + else if (this.currentVisibility() === 'hide_after_due') { + metadata.visible_to_staff_only = null; + metadata.hide_after_due = true; + } else { + metadata.visible_to_staff_only = null; + metadata.hide_after_due = null; + } + + return { + publish: 'republish', + metadata: metadata + }; + } + else { + return {}; + } }, getContext: function () { - return { - hasExplicitStaffLock: this.isModelLocked(), - ancestorLocked: this.isAncestorLocked() - }; + return $.extend( + {}, + AbstractVisibilityEditor.prototype.getContext.call(this), + { hide_after_due: this.modelVisibility() === 'hide_after_due'} + ); } }); @@ -748,41 +830,45 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', }, getEditModal: function (xblockInfo, options) { - var editors = []; + var tabs = []; - - if (xblockInfo.isChapter()) { - editors = [ReleaseDateEditor, StaffLockEditor]; - } else if (xblockInfo.isSequential()) { - tabs.push({ - name: 'basic', - displayName: gettext('Basic'), - editors: [ReleaseDateEditor, GradingEditor, DueDateEditor, StaffLockEditor] - }); - - if (options.enable_proctored_exams || options.enable_timed_exams) { - tabs.push({ - name: 'advanced', - displayName: gettext('Advanced'), - editors: [TimedExaminationPreferenceEditor] - }); - } - - if (typeof(xblockInfo.get('is_prereq')) !== 'undefined') { - tabs.push({ - name: 'access', - // Translators: This label refers to access to course content. - displayName: gettext('Access'), - editors: [AccessEditor] - }); - } - } else if (xblockInfo.isVertical()) { + var editors = []; + if (xblockInfo.isVertical()) { editors = [StaffLockEditor]; if (xblockInfo.hasVerifiedCheckpoints()) { editors.push(VerificationAccessEditor); } + } else { + tabs = [ + { + name: 'basic', + displayName: gettext('Basic'), + editors: [] + }, + { + name: 'advanced', + displayName: gettext('Advanced'), + editors: [] + } + ]; + if (xblockInfo.isChapter()) { + tabs[0].editors = [ReleaseDateEditor]; + tabs[1].editors = [StaffLockEditor]; + } else if (xblockInfo.isSequential()) { + tabs[0].editors = [ReleaseDateEditor, GradingEditor, DueDateEditor]; + tabs[1].editors = [ContentVisibilityEditor]; + + if (options.enable_proctored_exams || options.enable_timed_exams) { + tabs[1].editors.push(TimedExaminationPreferenceEditor); + } + + if (typeof(xblockInfo.get('is_prereq')) !== 'undefined') { + tabs[1].editors.push(AccessEditor); + } + } } + /* globals course */ if (course.get('self_paced')) { editors = _.without(editors, ReleaseDateEditor, DueDateEditor); @@ -790,6 +876,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', tab.editors = _.without(tab.editors, ReleaseDateEditor, DueDateEditor); }); } + return new SettingsXBlockModal($.extend({ tabs: tabs, editors: editors, diff --git a/cms/static/sass/elements/_modal-window.scss b/cms/static/sass/elements/_modal-window.scss index 12636be084..4a5ec294c9 100644 --- a/cms/static/sass/elements/_modal-window.scss +++ b/cms/static/sass/elements/_modal-window.scss @@ -669,8 +669,13 @@ } } - .edit-staff-lock { + .edit-staff-lock, .edit-content-visibility { margin-bottom: $baseline; + + .tip { + font-weight: bold; + font-size: 12px; + } } // UI: staff lock section diff --git a/cms/templates/course_outline.html b/cms/templates/course_outline.html index 85539600f5..c3753c31d6 100644 --- a/cms/templates/course_outline.html +++ b/cms/templates/course_outline.html @@ -1,6 +1,6 @@ <%page expression_filter="h"/> <%inherit file="base.html" /> -<%def name="online_help_token()"><% return "outline" %> +<%def name="online_help_token()"><% return "develop_course" %> <%! import logging from util.date_utils import get_default_time_display @@ -26,7 +26,7 @@ from openedx.core.djangolib.markup import HTML, Text <%block name="header_extras"> -% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs']: +% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs']: @@ -42,7 +42,7 @@ from openedx.core.djangolib.markup import HTML, Text

${_("This course was created as a re-run. Some manual configuration is needed.")}

-

${_("No course content is currently visible, and no students are enrolled. Be sure to review and reset all dates, including the Course Start Date; set up the course team; review course updates and other assets for dated material; and seed the discussions and wiki.")}

+

${_("No course content is currently visible, and no learners are enrolled. Be sure to review and reset all dates, including the Course Start Date; set up the course team; review course updates and other assets for dated material; and seed the discussions and wiki.")}

+

+ <%- gettext("Note: Do not hide graded assignments after they have been released.") %> +

diff --git a/cms/templates/js/release-date-editor.underscore b/cms/templates/js/release-date-editor.underscore index 6d558d34b0..cc62f36bf5 100644 --- a/cms/templates/js/release-date-editor.underscore +++ b/cms/templates/js/release-date-editor.underscore @@ -1,14 +1,14 @@ - +