Commit 35ae67b5 by Eric Fischer Committed by GitHub

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.
parent f3a46bfb
......@@ -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
......
......@@ -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);
......
......@@ -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);
};
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();
setContentVisibility = function (visibility) {
$('input[name=content-visibility][value='+visibility+']').prop('checked', true);
};
selectAccessSettings = function() {
this.$(".modal-section .settings-tab-button[data-tab='access']").click();
selectDisableSpecialExams = function() {
this.$("input.no_special_exam").prop('checked', true).trigger('change');
};
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();
......
......@@ -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");
}
......
......@@ -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 tabs = [];
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,
......
......@@ -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
......
<%page expression_filter="h"/>
<%inherit file="base.html" />
<%def name="online_help_token()"><% return "outline" %></%def>
<%def name="online_help_token()"><% return "develop_course" %></%def>
<%!
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">
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
% 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']:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
......@@ -42,7 +42,7 @@ from openedx.core.djangolib.markup import HTML, Text
<div class="copy">
<h2 class="title title-3">${_("This course was created as a re-run. Some manual configuration is needed.")}</h2>
<p>${_("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.")}</p>
<p>${_("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.")}</p>
</div>
<ul class="nav-actions">
......@@ -193,16 +193,29 @@ from openedx.core.djangolib.markup import HTML, Text
<h3 class="title-3">${_("Creating your course organization")}</h3>
<p>${_("You add sections, subsections, and units directly in the outline.")}</p>
<p>${_("Create a section, then add subsections and units. Open a unit to add course components.")}</p>
</div>
<div class="bit">
<h3 class="title-3">${_("Reorganizing your course")}</h3>
<p>${_("Drag sections, subsections, and units to new locations in the outline.")}</p>
<div class="external-help">
<a href="${get_online_help_info('outline')['doc_url']}" target="_blank" class="button external-help-button">${_("Learn more about the course outline")}</a>
</div>
</div>
<div class="bit">
<h3 class="title-3">${_("Setting release dates and grading policies")}</h3>
<p>${_("Select the Configure icon for a section or subsection to set its release date. When you configure a subsection, you can also set the grading policy and due date.")}</p>
<h3 class="title-3">${_("Changing the content students see")}</h3>
<p>${_("To publish draft content, select the Publish icon for a section, subsection, or unit.")}</p>
<p>${Text(_("To hide content from students, select the Configure icon for a section, subsection, or unit, then select {em_start}Hide from students{em_end}.")).format(em_start=HTML("<strong>"), em_end=HTML("</strong>"))}</p>
<div class="external-help">
<a href="${get_online_help_info('grading')['doc_url']}" target="_blank" class="button external-help-button">${_("Learn more about grading policy settings")}</a>
</div>
</div>
<div class="bit external-help">
<a href="${get_online_help_info(online_help_token())['doc_url']}" target="_blank" class="button external-help-button">${_("Learn more about the course outline")}</a>
<div class="bit">
<h3 class="title-3">${_("Changing the content learners see")}</h3>
<p>${_("To publish draft content, select the Publish icon for a section, subsection, or unit.")}</p>
<p>${Text(_("To make a section, subsection, or unit unavailable to learners, select the Configure icon for that level, then select the appropriate {em_start}Hide{em_end} option. Grades for hidden sections, subsections, and units are not included in grade calculations.")).format(em_start=HTML("<strong>"), em_end=HTML("</strong>"))}</p>
<p>${Text(_("To hide the content of a subsection from learners after the subsection due date has passed, select the Configure icon for a subsection, then select {em_start}Hide content after due date{em_end}. Grades for the subsection remain included in grade calculations.")).format(em_start=HTML("<strong>"), em_end=HTML("</strong>"))}</p>
<div class="external-help">
<a href="${get_online_help_info('visibility')['doc_url']}" target="_blank" class="button external-help-button">${_("Learn more about content visibility settings")}</a>
</div>
</div>
</aside>
......
<form>
<h3 class="modal-section-title" id="content_visibility_label"><%- gettext('Subsection Visibility') %></h3>
<div class="modal-section-content staff-lock">
<ul class="list-fields list-input content-visibility" role="group" aria-labelledby="content_visibility_label">
<li class="field-radio">
<label class="label">
<input class="input input-radio" name="content-visibility" type="radio" value="visible" aria-described-by"visible_description">
<%- gettext('Show entire subsection') %>
</label>
<p class='field-message' id='visible_description'> <%- gettext('Learners see the published subsection and can access its content.') %> </p>
</li>
<li class="field-radio">
<label class="label">
<input class="input input-radio" name="content-visibility" type="radio" value="hide_after_due" aria-described-by="hide_after_due_description">
<%- gettext('Hide content after due date') %>
</label>
<p class='field-message' id='hide_after_due_description'> <%- gettext('After the subsection\'s due date has passed, learners can no longer access its content. The subsection remains included in grade calculations.') %> </p>
</li>
<li class="field-radio">
<label class="label">
<input class="input input-radio" name="content-visibility" type="radio" value="staff_only" aria-described-by="staff_only_description">
<%- gettext('Hide entire subsection') %>
</label>
<p class='field-message' id='staff_only_description'> <%- gettext('Learners do not see the subsection in the course outline. The subsection is not included in grade calculations.') %> </p>
</li>
</ul>
<% if (hasExplicitStaffLock && !ancestorLocked) { %>
<p class="tip tip-warning">
<%- gettext('Units inherit the visibility setting of the subsection they are in. If you make this subsection visible to learners, published units that were previously hidden also become visible. Only units that were explicitly hidden remain hidden regardless of the option you select for this subsection.') %>
</p>
<% } %>
</div>
</form>
......@@ -177,17 +177,6 @@ if (is_proctored_exam) {
<% } %>
</p>
</div>
<div class="status-hide-after-due">
<p>
<% if (!is_proctored_exam && xblockInfo.get('hide_after_due')) { %>
<span class="icon fa fa-eye-slash" aria-hidden="true"></span>
<span class="status-hide-after-due-value"> <%- gettext("Exam will remain hidden after due date") %> </span>
<% } else { %>
<span class="icon fa fa-eye" aria-hidden="true"></span>
<span class="status-hide-after-due-value"> <%- gettext("Exam will be visible after due date") %> </span>
<% } %>
</p>
</div>
<% } else if (xblockInfo.get('due_date') || xblockInfo.get('graded')) { %>
<div class="status-grading">
<p>
......@@ -200,6 +189,14 @@ if (is_proctored_exam) {
</p>
</div>
<% } %>
<div class="status-hide-after-due">
<p>
<% if (xblockInfo.get('hide_after_due')) { %>
<span class="icon fa fa-eye-slash" aria-hidden="true"></span>
<span class="status-hide-after-due-value"> <%- gettext("Subsection is hidden after due date") %> </span>
<% } %>
</p>
</div>
<% } %>
<% if (statusMessage) { %>
<div class="status-message">
......
......@@ -81,7 +81,7 @@ var visibleToStaffOnly = visibilityState === 'staff_only';
<% } %>
</p>
<% } else { %>
<p class="visbility-copy copy"><%- gettext("Staff and Students") %></p>
<p class="visbility-copy copy"><%- gettext("Staff and Learners") %></p>
<% } %>
<% if (hasContentGroupComponents) { %>
<p class="note-visibility">
......@@ -97,10 +97,13 @@ var visibleToStaffOnly = visibilityState === 'staff_only';
<% } else { %>
<span class="icon fa fa-square-o" aria-hidden="true"></span>
<% } %>
<%- gettext('Hide from students') %>
<%- gettext('Hide from learners') %>
</a>
</li>
</ul>
<p>
<%- gettext("Note: Do not hide graded assignments after they have been released.") %>
</p>
</div>
<div class="wrapper-pub-actions bar-mod-actions">
......
<h3 class="modal-section-title"><%= gettext('Release Date and Time') %></h3>
<h3 class="modal-section-title"><%- gettext('Release Date and Time') %></h3>
<div class="modal-section-content has-actions">
<ul class="list-fields list-input datepair">
<li class="field field-text field-start-date field-release-date">
<label for="start_date" class="label"><%= gettext('Release Date:') %></label>
<label for="start_date" class="label"><%- gettext('Release Date:') %></label>
<input type="text" id="start_date" name="start_date"
value=""
placeholder="MM/DD/YYYY" class="start-date release-date date input input-text" autocomplete="off" />
</li>
<li class="field field-text field-start-time field-release-time">
<label for="start_time" class="label"><%= gettext('Release Time in UTC:') %></label>
<label for="start_time" class="label"><%- gettext('Release Time in UTC:') %></label>
<input type="text" id="start_time" name="start_time"
value=""
placeholder="HH:MM" class="start-time release-time time input input-text" autocomplete="off" />
......@@ -18,9 +18,9 @@
<% if (xblockInfo.isSequential()) { %>
<ul class="list-actions">
<li class="action-item">
<a href="#" data-tooltip="<%= gettext('Clear Release Date/Time') %>" class="clear-date action-button action-clear">
<a href="#" data-tooltip="<%- gettext('Clear Release Date/Time') %>" class="clear-date action-button action-clear">
<span class="icon fa fa-undo" aria-hidden="true"></span>
<span class="sr"><%= gettext('Clear Release Date/Time') %></span>
<span class="sr"><%- gettext('Clear Release Date/Time') %></span>
</a>
</li>
</ul>
......
<form>
<h3 class="modal-section-title"><%= gettext('Student Visibility') %></h3>
<h3 class="modal-section-title">
<% if (xblockInfo.isVertical()) { %>
<%- gettext('Unit Visibility') %>
<% } else { %>
<%- gettext('Section Visibility') %>
<% } %>
</h3>
<div class="modal-section-content staff-lock">
<ul class="list-fields list-input">
<li class="field field-checkbox checkbox-cosmetic">
......@@ -7,25 +13,16 @@
<label for="staff_lock" class="label">
<span class="icon fa fa-check-square-o input-checkbox-checked" aria-hidden="true"></span>
<span class="icon fa fa-square-o input-checkbox-unchecked" aria-hidden="true"></span>
<%= gettext('Hide from students') %>
<%- gettext('Hide from learners') %>
</label>
<% if (hasExplicitStaffLock && !ancestorLocked) { %>
<p class="tip tip-warning">
<% if (xblockInfo.isVertical()) { %>
<%= gettext('If the unit was previously published and released to students, any changes you made to the unit when it was hidden will now be visible to students.') %>
<%- gettext('If the unit was previously published and released to learners, any changes you made to the unit when it was hidden will now be visible to learners.') %>
<% } else { %>
<% var message = gettext('If you make this %(xblockType)s visible to students, students will be able to see its content after the release date has passed and you have published the unit.'); %>
<%= interpolate(message, { xblockType: xblockType }, true) %>
<% } %>
</p>
<p class="tip tip-warning">
<% if (xblockInfo.isChapter()) { %>
<%= gettext('Any subsections or units that are explicitly hidden from students will remain hidden after you clear this option for the section.') %>
<% } %>
<% if (xblockInfo.isSequential()) { %>
<%= gettext('Any units that are explicitly hidden from students will remain hidden after you clear this option for the subsection.') %>
<% var message = gettext('If you make this %(xblockType)s visible to learners, learners will be able to see its content after the release date has passed and you have published the unit. Only units that are explicitly hidden from learners will remain hidden after you clear this option for the %(xblockType)s.') %>
<%- interpolate(message, { xblockType: xblockType }, true) %>
<% } %>
</p>
<% } %>
......
......@@ -52,14 +52,6 @@
</label>
<p class='field-message' id='review-rules-description'><%- gettext('Specify any additional rules or rule exceptions that the proctoring review team should enforce when reviewing the videos. For example, you could specify that calculators are allowed.') %></p>
</li>
<li class="field-checkbox field-hide-after-due">
<label class="label">
<input type="checkbox" class="input input-checkbox"
aria-describedby="hide-after-due-description"/>
<%- gettext('Hide Exam After Due Date') %>
</label>
<p class='field-message' id='hide-after-due-description'><%- gettext('By default, submitted exams are available for review after the due date has passed. Select this option to keep exams hidden after that date.') %></p>
</li>
</ul>
</div>
</form>
......@@ -142,7 +142,10 @@ class CourseOutlineItem(object):
Puts the item into editable form.
"""
self.q(css=self._bounded_selector(self.CONFIGURATION_BUTTON_SELECTOR)).first.click() # pylint: disable=no-member
modal = CourseOutlineModal(self)
if 'subsection' in self.BODY_SELECTOR:
modal = SubsectionOutlineModal(self)
else:
modal = CourseOutlineModal(self)
EmptyPromise(lambda: modal.is_shown(), 'Modal is shown.') # pylint: disable=unnecessary-lambda
return modal
......@@ -569,12 +572,15 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
self.q(css=".action-save").first.click()
self.wait_for_ajax()
def select_advanced_tab(self):
def select_advanced_tab(self, desired_item='special_exam'):
"""
Select the advanced settings tab
"""
self.q(css=".settings-tab-button[data-tab='advanced']").first.click()
self.wait_for_element_presence('input.no_special_exam', 'Special exam settings fields not present.')
if desired_item == 'special_exam':
self.wait_for_element_presence('input.no_special_exam', 'Special exam settings fields not present.')
if desired_item == 'gated_content':
self.wait_for_element_visibility('#is_prereq', 'Gating settings fields are present.')
def make_exam_proctored(self):
"""
......@@ -590,7 +596,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
"""
self.q(css="input.timed_exam").first.click()
if hide_after_due:
self.q(css='.field-hide-after-due input').first.click()
self.q(css='input[name=content-visibility][value=hide_after_due]').first.click()
self.q(css=".action-save").first.click()
self.wait_for_ajax()
......@@ -630,12 +636,6 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
"""
return self.q(css=".field-exam-review-rules").visible
def hide_after_due_field_visible(self):
"""
Returns whether the hide after due field is visible
"""
return self.q(css=".field-hide-after-due").visible
def proctoring_items_are_displayed(self):
"""
Returns True if all the items are found.
......@@ -659,13 +659,6 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
return True
def select_access_tab(self):
"""
Select the access settings tab.
"""
self.q(css=".settings-tab-button[data-tab='access']").first.click()
self.wait_for_element_visibility('#is_prereq', 'Gating settings fields are present.')
def make_gating_prerequisite(self):
"""
Makes a subsection a gating prerequisite.
......@@ -848,6 +841,8 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
class CourseOutlineModal(object):
"""
Page object specifically for a modal window on the course outline page.
Subsections are handled slightly differently in some regards, and should use SubsectionOutlineModal.
"""
MODAL_SELECTOR = ".wrapper-modal-window"
......@@ -1037,17 +1032,38 @@ class CourseOutlineModal(object):
).fulfill()
@property
def is_staff_lock_visible(self):
"""
Returns True if the staff lock option is visible.
"""
return self.find_css('#staff_lock').visible
def ensure_staff_lock_visible(self):
"""
Ensures the staff lock option is visible, clicking on the advanced tab
if needed.
"""
if not self.is_staff_lock_visible:
self.find_css(".settings-tab-button[data-tab=advanced]").click()
EmptyPromise(
lambda: self.is_staff_lock_visible,
"Staff lock option is visible",
).fulfill()
@property
def is_explicitly_locked(self):
"""
Returns true if the explict staff lock checkbox is checked, false otherwise.
"""
self.ensure_staff_lock_visible()
return self.find_css('#staff_lock')[0].is_selected()
@is_explicitly_locked.setter
def is_explicitly_locked(self, value):
"""
Checks the explicit staff lock box if value is true, otherwise unchecks the box.
Checks the explicit staff lock box if value is true, otherwise selects "visible".
"""
self.ensure_staff_lock_visible()
if value != self.is_explicitly_locked:
self.find_css('label[for="staff_lock"]').click()
EmptyPromise(lambda: value == self.is_explicitly_locked, "Explicit staff lock is updated").fulfill()
......@@ -1067,3 +1083,52 @@ class CourseOutlineModal(object):
return select.first_selected_option.text
else:
return None
class SubsectionOutlineModal(CourseOutlineModal):
"""
Subclass to handle a few special cases with subsection modals.
"""
def __init__(self, page):
super(SubsectionOutlineModal, self).__init__(page)
@property
def is_explicitly_locked(self):
"""
Override - returns True if staff_only is set.
"""
return self.subsection_visibility == 'staff_only'
@property
def subsection_visibility(self):
"""
Returns the current visibility setting for a subsection
"""
self.ensure_staff_lock_visible()
return self.find_css('input[name=content-visibility]:checked').first.attrs('value')[0]
@is_explicitly_locked.setter
def is_explicitly_locked(self, value): # pylint: disable=arguments-differ
"""
Override - sets visibility to staff_only if True, else 'visible'.
For hide_after_due, use the set_subsection_visibility method directly.
"""
self.subsection_visibility = 'staff_only' if value else 'visible'
@subsection_visibility.setter
def subsection_visibility(self, value):
"""
Sets the subsection visibility to the given value.
"""
self.ensure_staff_lock_visible()
self.find_css('input[name=content-visibility][value=' + value + ']').click()
EmptyPromise(lambda: value == self.subsection_visibility, "Subsection visibility is updated").fulfill()
@property
def is_staff_lock_visible(self):
"""
Override - Returns true if the staff lock option is visible.
"""
return self.find_css('input[name=content-visibility]').visible
......@@ -331,11 +331,11 @@ class ProctoredExamTest(UniqueCourseTest):
And the subsection edit dialog is open
select advanced settings tab
For each of None, Timed, Proctored, and Practice exam types
The time allotted, review rules, and hide after due fields have proper visibility
None: False, False, False
Timed: True, False, True
Proctored: True, True, False
Practice: True, False, False
The time allotted and review rules fields have proper visibility
None: False, False
Timed: True, False
Proctored: True, True
Practice: True, False
"""
LogoutPage(self.browser).visit()
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
......@@ -347,22 +347,18 @@ class ProctoredExamTest(UniqueCourseTest):
self.course_outline.select_none_exam()
self.assertFalse(self.course_outline.time_allotted_field_visible())
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
self.assertFalse(self.course_outline.hide_after_due_field_visible())
self.course_outline.select_timed_exam()
self.assertTrue(self.course_outline.time_allotted_field_visible())
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
self.assertTrue(self.course_outline.hide_after_due_field_visible())
self.course_outline.select_proctored_exam()
self.assertTrue(self.course_outline.time_allotted_field_visible())
self.assertTrue(self.course_outline.exam_review_rules_field_visible())
self.assertFalse(self.course_outline.hide_after_due_field_visible())
self.course_outline.select_practice_exam()
self.assertTrue(self.course_outline.time_allotted_field_visible())
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
self.assertFalse(self.course_outline.hide_after_due_field_visible())
class CoursewareMultipleVerticalsTest(UniqueCourseTest, EventsTestMixin):
......
......@@ -87,7 +87,7 @@ class GatingTest(UniqueCourseTest):
# Make the first subsection a prerequisite
self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog(0)
self.course_outline.select_access_tab()
self.course_outline.select_advanced_tab(desired_item='gated_content')
self.course_outline.make_gating_prerequisite()
def _setup_gated_subsection(self):
......@@ -100,7 +100,7 @@ class GatingTest(UniqueCourseTest):
# Gate the second subsection based on the score achieved in the first subsection
self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog(1)
self.course_outline.select_access_tab()
self.course_outline.select_advanced_tab(desired_item='gated_content')
self.course_outline.add_prerequisite_to_subsection("80")
def test_subsection_gating_in_studio(self):
......@@ -116,7 +116,7 @@ class GatingTest(UniqueCourseTest):
# Assert settings are displayed correctly for a prerequisite subsection
self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog(0)
self.course_outline.select_access_tab()
self.course_outline.select_advanced_tab(desired_item='gated_content')
self.assertTrue(self.course_outline.gating_prerequisite_checkbox_is_visible())
self.assertTrue(self.course_outline.gating_prerequisite_checkbox_is_checked())
self.assertFalse(self.course_outline.gating_prerequisites_dropdown_is_visible())
......@@ -127,7 +127,7 @@ class GatingTest(UniqueCourseTest):
# Assert settings are displayed correctly for a gated subsection
self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog(1)
self.course_outline.select_access_tab()
self.course_outline.select_advanced_tab(desired_item='gated_content')
self.assertTrue(self.course_outline.gating_prerequisite_checkbox_is_visible())
self.assertTrue(self.course_outline.gating_prerequisites_dropdown_is_visible())
self.assertTrue(self.course_outline.gating_prerequisite_min_score_is_visible())
......
......@@ -16,8 +16,10 @@ pdf_file = edx-partner-course-staff.pdf
[pages]
default = index.html
home = getting_started/get_started.html
develop_course = developing_course/index.html
outline = developing_course/course_outline.html
unit = developing_course/course_units.html
visibility = developing_course/controlling_content_visibility.html
updates = course_assets/handouts_updates.html
pages = course_assets/pages.html
files = course_assets/course_files.html
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment