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 ...@@ -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), "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 # update xblock_info with special exam information if the feature flag is enabled
if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'):
if xblock.category == 'course': if xblock.category == 'course':
...@@ -997,7 +1002,6 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F ...@@ -997,7 +1002,6 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"is_time_limited": xblock.is_time_limited, "is_time_limited": xblock.is_time_limited,
"exam_review_rules": xblock.exam_review_rules, "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 # Update with gating info
......
...@@ -434,7 +434,7 @@ define(["jquery", "underscore", "underscore.string", "edx-ui-toolkit/js/utils/sp ...@@ -434,7 +434,7 @@ define(["jquery", "underscore", "underscore.string", "edx-ui-toolkit/js/utils/sp
expect(visibilityCopy).toContain('Staff Only'); expect(visibilityCopy).toContain('Staff Only');
expect(containerPage.$(bitPublishingCss)).toHaveClass(staffOnlyClass); expect(containerPage.$(bitPublishingCss)).toHaveClass(staffOnlyClass);
} else { } else {
expect(visibilityCopy).toBe('Staff and Students'); expect(visibilityCopy).toBe('Staff and Learners');
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(staffOnlyClass); expect(containerPage.$(bitPublishingCss)).not.toHaveClass(staffOnlyClass);
verifyExplicitStaffOnly(false); verifyExplicitStaffOnly(false);
verifyImplicitStaffOnly(false); verifyImplicitStaffOnly(false);
......
...@@ -52,7 +52,10 @@ function($, date, TriggerChangeEventOnEnter) { ...@@ -52,7 +52,10 @@ function($, date, TriggerChangeEventOnEnter) {
// given a pair of inputs (datepicker and timepicker), return a JS Date // given a pair of inputs (datepicker and timepicker), return a JS Date
// object that corresponds to the datetime.js that they represent. Assume // object that corresponds to the datetime.js that they represent. Assume
// UTC timezone, NOT the timezone of the user's browser. // 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) { if (timepickerInput.length > 0) {
time = $(timepickerInput).timepicker("getTime"); time = $(timepickerInput).timepicker("getTime");
} }
......
...@@ -669,8 +669,13 @@ ...@@ -669,8 +669,13 @@
} }
} }
.edit-staff-lock { .edit-staff-lock, .edit-content-visibility {
margin-bottom: $baseline; margin-bottom: $baseline;
.tip {
font-weight: bold;
font-size: 12px;
}
} }
// UI: staff lock section // UI: staff lock section
......
<%page expression_filter="h"/> <%page expression_filter="h"/>
<%inherit file="base.html" /> <%inherit file="base.html" />
<%def name="online_help_token()"><% return "outline" %></%def> <%def name="online_help_token()"><% return "develop_course" %></%def>
<%! <%!
import logging import logging
from util.date_utils import get_default_time_display from util.date_utils import get_default_time_display
...@@ -26,7 +26,7 @@ from openedx.core.djangolib.markup import HTML, Text ...@@ -26,7 +26,7 @@ from openedx.core.djangolib.markup import HTML, Text
<%block name="header_extras"> <%block name="header_extras">
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" /> <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"> <script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" /> <%static:include path="js/${template_name}.underscore" />
</script> </script>
...@@ -42,7 +42,7 @@ from openedx.core.djangolib.markup import HTML, Text ...@@ -42,7 +42,7 @@ from openedx.core.djangolib.markup import HTML, Text
<div class="copy"> <div class="copy">
<h2 class="title title-3">${_("This course was created as a re-run. Some manual configuration is needed.")}</h2> <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> </div>
<ul class="nav-actions"> <ul class="nav-actions">
...@@ -193,16 +193,29 @@ from openedx.core.djangolib.markup import HTML, Text ...@@ -193,16 +193,29 @@ from openedx.core.djangolib.markup import HTML, Text
<h3 class="title-3">${_("Creating your course organization")}</h3> <h3 class="title-3">${_("Creating your course organization")}</h3>
<p>${_("You add sections, subsections, and units directly in the outline.")}</p> <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> <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> <h3 class="title-3">${_("Reorganizing your course")}</h3>
<p>${_("Drag sections, subsections, and units to new locations in the outline.")}</p> <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> <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> <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> <div class="external-help">
<p>${_("To publish draft content, select the Publish icon for a section, subsection, or unit.")}</p> <a href="${get_online_help_info('grading')['doc_url']}" target="_blank" class="button external-help-button">${_("Learn more about grading policy settings")}</a>
<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>
</div> </div>
<div class="bit external-help"> <div class="bit">
<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> <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> </div>
</aside> </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) { ...@@ -177,17 +177,6 @@ if (is_proctored_exam) {
<% } %> <% } %>
</p> </p>
</div> </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')) { %> <% } else if (xblockInfo.get('due_date') || xblockInfo.get('graded')) { %>
<div class="status-grading"> <div class="status-grading">
<p> <p>
...@@ -200,6 +189,14 @@ if (is_proctored_exam) { ...@@ -200,6 +189,14 @@ if (is_proctored_exam) {
</p> </p>
</div> </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) { %> <% if (statusMessage) { %>
<div class="status-message"> <div class="status-message">
......
...@@ -81,7 +81,7 @@ var visibleToStaffOnly = visibilityState === 'staff_only'; ...@@ -81,7 +81,7 @@ var visibleToStaffOnly = visibilityState === 'staff_only';
<% } %> <% } %>
</p> </p>
<% } else { %> <% } else { %>
<p class="visbility-copy copy"><%- gettext("Staff and Students") %></p> <p class="visbility-copy copy"><%- gettext("Staff and Learners") %></p>
<% } %> <% } %>
<% if (hasContentGroupComponents) { %> <% if (hasContentGroupComponents) { %>
<p class="note-visibility"> <p class="note-visibility">
...@@ -97,10 +97,13 @@ var visibleToStaffOnly = visibilityState === 'staff_only'; ...@@ -97,10 +97,13 @@ var visibleToStaffOnly = visibilityState === 'staff_only';
<% } else { %> <% } else { %>
<span class="icon fa fa-square-o" aria-hidden="true"></span> <span class="icon fa fa-square-o" aria-hidden="true"></span>
<% } %> <% } %>
<%- gettext('Hide from students') %> <%- gettext('Hide from learners') %>
</a> </a>
</li> </li>
</ul> </ul>
<p>
<%- gettext("Note: Do not hide graded assignments after they have been released.") %>
</p>
</div> </div>
<div class="wrapper-pub-actions bar-mod-actions"> <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"> <div class="modal-section-content has-actions">
<ul class="list-fields list-input datepair"> <ul class="list-fields list-input datepair">
<li class="field field-text field-start-date field-release-date"> <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" <input type="text" id="start_date" name="start_date"
value="" value=""
placeholder="MM/DD/YYYY" class="start-date release-date date input input-text" autocomplete="off" /> placeholder="MM/DD/YYYY" class="start-date release-date date input input-text" autocomplete="off" />
</li> </li>
<li class="field field-text field-start-time field-release-time"> <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" <input type="text" id="start_time" name="start_time"
value="" value=""
placeholder="HH:MM" class="start-time release-time time input input-text" autocomplete="off" /> placeholder="HH:MM" class="start-time release-time time input input-text" autocomplete="off" />
...@@ -18,9 +18,9 @@ ...@@ -18,9 +18,9 @@
<% if (xblockInfo.isSequential()) { %> <% if (xblockInfo.isSequential()) { %>
<ul class="list-actions"> <ul class="list-actions">
<li class="action-item"> <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="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> </a>
</li> </li>
</ul> </ul>
......
<form> <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"> <div class="modal-section-content staff-lock">
<ul class="list-fields list-input"> <ul class="list-fields list-input">
<li class="field field-checkbox checkbox-cosmetic"> <li class="field field-checkbox checkbox-cosmetic">
...@@ -7,25 +13,16 @@ ...@@ -7,25 +13,16 @@
<label for="staff_lock" class="label"> <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-check-square-o input-checkbox-checked" aria-hidden="true"></span>
<span class="icon fa fa-square-o input-checkbox-unchecked" 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> </label>
<% if (hasExplicitStaffLock && !ancestorLocked) { %> <% if (hasExplicitStaffLock && !ancestorLocked) { %>
<p class="tip tip-warning"> <p class="tip tip-warning">
<% if (xblockInfo.isVertical()) { %> <% 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 { %> <% } 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.'); %> <% 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) %> <%- 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.') %>
<% } %> <% } %>
</p> </p>
<% } %> <% } %>
......
...@@ -52,14 +52,6 @@ ...@@ -52,14 +52,6 @@
</label> </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> <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>
<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> </ul>
</div> </div>
</form> </form>
...@@ -142,7 +142,10 @@ class CourseOutlineItem(object): ...@@ -142,7 +142,10 @@ class CourseOutlineItem(object):
Puts the item into editable form. Puts the item into editable form.
""" """
self.q(css=self._bounded_selector(self.CONFIGURATION_BUTTON_SELECTOR)).first.click() # pylint: disable=no-member 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 EmptyPromise(lambda: modal.is_shown(), 'Modal is shown.') # pylint: disable=unnecessary-lambda
return modal return modal
...@@ -569,12 +572,15 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer): ...@@ -569,12 +572,15 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
self.q(css=".action-save").first.click() self.q(css=".action-save").first.click()
self.wait_for_ajax() self.wait_for_ajax()
def select_advanced_tab(self): def select_advanced_tab(self, desired_item='special_exam'):
""" """
Select the advanced settings tab Select the advanced settings tab
""" """
self.q(css=".settings-tab-button[data-tab='advanced']").first.click() 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): def make_exam_proctored(self):
""" """
...@@ -590,7 +596,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer): ...@@ -590,7 +596,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
""" """
self.q(css="input.timed_exam").first.click() self.q(css="input.timed_exam").first.click()
if hide_after_due: 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.q(css=".action-save").first.click()
self.wait_for_ajax() self.wait_for_ajax()
...@@ -630,12 +636,6 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer): ...@@ -630,12 +636,6 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
""" """
return self.q(css=".field-exam-review-rules").visible 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): def proctoring_items_are_displayed(self):
""" """
Returns True if all the items are found. Returns True if all the items are found.
...@@ -659,13 +659,6 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer): ...@@ -659,13 +659,6 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
return True 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): def make_gating_prerequisite(self):
""" """
Makes a subsection a gating prerequisite. Makes a subsection a gating prerequisite.
...@@ -848,6 +841,8 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer): ...@@ -848,6 +841,8 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
class CourseOutlineModal(object): class CourseOutlineModal(object):
""" """
Page object specifically for a modal window on the course outline page. 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" MODAL_SELECTOR = ".wrapper-modal-window"
...@@ -1037,17 +1032,38 @@ class CourseOutlineModal(object): ...@@ -1037,17 +1032,38 @@ class CourseOutlineModal(object):
).fulfill() ).fulfill()
@property @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): def is_explicitly_locked(self):
""" """
Returns true if the explict staff lock checkbox is checked, false otherwise. 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() return self.find_css('#staff_lock')[0].is_selected()
@is_explicitly_locked.setter @is_explicitly_locked.setter
def is_explicitly_locked(self, value): 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: if value != self.is_explicitly_locked:
self.find_css('label[for="staff_lock"]').click() self.find_css('label[for="staff_lock"]').click()
EmptyPromise(lambda: value == self.is_explicitly_locked, "Explicit staff lock is updated").fulfill() EmptyPromise(lambda: value == self.is_explicitly_locked, "Explicit staff lock is updated").fulfill()
...@@ -1067,3 +1083,52 @@ class CourseOutlineModal(object): ...@@ -1067,3 +1083,52 @@ class CourseOutlineModal(object):
return select.first_selected_option.text return select.first_selected_option.text
else: else:
return None 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): ...@@ -331,11 +331,11 @@ class ProctoredExamTest(UniqueCourseTest):
And the subsection edit dialog is open And the subsection edit dialog is open
select advanced settings tab select advanced settings tab
For each of None, Timed, Proctored, and Practice exam types For each of None, Timed, Proctored, and Practice exam types
The time allotted, review rules, and hide after due fields have proper visibility The time allotted and review rules fields have proper visibility
None: False, False, False None: False, False
Timed: True, False, True Timed: True, False
Proctored: True, True, False Proctored: True, True
Practice: True, False, False Practice: True, False
""" """
LogoutPage(self.browser).visit() LogoutPage(self.browser).visit()
self._auto_auth("STAFF_TESTER", "staff101@example.com", True) self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
...@@ -347,22 +347,18 @@ class ProctoredExamTest(UniqueCourseTest): ...@@ -347,22 +347,18 @@ class ProctoredExamTest(UniqueCourseTest):
self.course_outline.select_none_exam() self.course_outline.select_none_exam()
self.assertFalse(self.course_outline.time_allotted_field_visible()) self.assertFalse(self.course_outline.time_allotted_field_visible())
self.assertFalse(self.course_outline.exam_review_rules_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.course_outline.select_timed_exam()
self.assertTrue(self.course_outline.time_allotted_field_visible()) self.assertTrue(self.course_outline.time_allotted_field_visible())
self.assertFalse(self.course_outline.exam_review_rules_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.course_outline.select_proctored_exam()
self.assertTrue(self.course_outline.time_allotted_field_visible()) self.assertTrue(self.course_outline.time_allotted_field_visible())
self.assertTrue(self.course_outline.exam_review_rules_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.course_outline.select_practice_exam()
self.assertTrue(self.course_outline.time_allotted_field_visible()) self.assertTrue(self.course_outline.time_allotted_field_visible())
self.assertFalse(self.course_outline.exam_review_rules_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): class CoursewareMultipleVerticalsTest(UniqueCourseTest, EventsTestMixin):
......
...@@ -87,7 +87,7 @@ class GatingTest(UniqueCourseTest): ...@@ -87,7 +87,7 @@ class GatingTest(UniqueCourseTest):
# Make the first subsection a prerequisite # Make the first subsection a prerequisite
self.course_outline.visit() self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog(0) 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() self.course_outline.make_gating_prerequisite()
def _setup_gated_subsection(self): def _setup_gated_subsection(self):
...@@ -100,7 +100,7 @@ class GatingTest(UniqueCourseTest): ...@@ -100,7 +100,7 @@ class GatingTest(UniqueCourseTest):
# Gate the second subsection based on the score achieved in the first subsection # Gate the second subsection based on the score achieved in the first subsection
self.course_outline.visit() self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog(1) 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") self.course_outline.add_prerequisite_to_subsection("80")
def test_subsection_gating_in_studio(self): def test_subsection_gating_in_studio(self):
...@@ -116,7 +116,7 @@ class GatingTest(UniqueCourseTest): ...@@ -116,7 +116,7 @@ class GatingTest(UniqueCourseTest):
# Assert settings are displayed correctly for a prerequisite subsection # Assert settings are displayed correctly for a prerequisite subsection
self.course_outline.visit() self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog(0) 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_visible())
self.assertTrue(self.course_outline.gating_prerequisite_checkbox_is_checked()) self.assertTrue(self.course_outline.gating_prerequisite_checkbox_is_checked())
self.assertFalse(self.course_outline.gating_prerequisites_dropdown_is_visible()) self.assertFalse(self.course_outline.gating_prerequisites_dropdown_is_visible())
...@@ -127,7 +127,7 @@ class GatingTest(UniqueCourseTest): ...@@ -127,7 +127,7 @@ class GatingTest(UniqueCourseTest):
# Assert settings are displayed correctly for a gated subsection # Assert settings are displayed correctly for a gated subsection
self.course_outline.visit() self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog(1) 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_prerequisite_checkbox_is_visible())
self.assertTrue(self.course_outline.gating_prerequisites_dropdown_is_visible()) self.assertTrue(self.course_outline.gating_prerequisites_dropdown_is_visible())
self.assertTrue(self.course_outline.gating_prerequisite_min_score_is_visible()) self.assertTrue(self.course_outline.gating_prerequisite_min_score_is_visible())
......
...@@ -16,8 +16,10 @@ pdf_file = edx-partner-course-staff.pdf ...@@ -16,8 +16,10 @@ pdf_file = edx-partner-course-staff.pdf
[pages] [pages]
default = index.html default = index.html
home = getting_started/get_started.html home = getting_started/get_started.html
develop_course = developing_course/index.html
outline = developing_course/course_outline.html outline = developing_course/course_outline.html
unit = developing_course/course_units.html unit = developing_course/course_units.html
visibility = developing_course/controlling_content_visibility.html
updates = course_assets/handouts_updates.html updates = course_assets/handouts_updates.html
pages = course_assets/pages.html pages = course_assets/pages.html
files = course_assets/course_files.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