Commit 732bcbde by polesye Committed by Tim Babych

STUD-1911, STUD-1932: Publish sections, subsections and units from course outline.

parent 06842be8
......@@ -1197,7 +1197,7 @@ class ContentStoreTest(ContentStoreTestCase):
resp = self._show_course_overview(course.id)
self.assertContains(
resp,
'<article class="outline outline-course" data-locator="{locator}" data-course-key="{course_key}">'.format(
'<article class="outline outline-complex outline-course" data-locator="{locator}" data-course-key="{course_key}">'.format(
locator='i4x://MITx/999/course/Robot_Super_Course',
course_key='MITx/999/Robot_Super_Course',
),
......
......@@ -181,6 +181,7 @@ def xblock_handler(request, usage_key_string):
content_type="text/plain"
)
# pylint: disable=unused-argument
@require_http_methods(("GET"))
@login_required
......@@ -449,7 +450,7 @@ def _create_item(request):
# if we add one then we need to also add it to the policy information (i.e. metadata)
# we should remove this once we can break this reference from the course to static tabs
if category == 'static_tab':
display_name = display_name or _("Empty") # Prevent name being None
display_name = display_name or _("Empty") # Prevent name being None
course = store.get_course(dest_usage_key.course_key)
course.tabs.append(
StaticTab(
......@@ -635,7 +636,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
return None
is_xblock_unit = is_unit(xblock, parent_xblock)
is_unit_with_changes = is_xblock_unit and modulestore().has_changes(xblock)
has_changes = modulestore().has_changes(xblock)
if graders is None:
graders = CourseGradingModel.fetch(xblock.location.course_key).graders
......@@ -654,7 +655,10 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
# Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
release_date = get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None
visibility_state = _compute_visibility_state(xblock, child_info, is_unit_with_changes) if not xblock.category == 'course' else None
if xblock.category != 'course':
visibility_state = _compute_visibility_state(xblock, child_info, is_xblock_unit and has_changes)
else:
visibility_state = None
published = modulestore().compute_publish_state(xblock) != PublishState.private
xblock_info = {
......@@ -664,7 +668,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None,
"published": published,
"published_on": get_default_time_display(xblock.published_date) if xblock.published_date else None,
'studio_url': xblock_studio_url(xblock, parent_xblock),
"studio_url": xblock_studio_url(xblock, parent_xblock),
"released_to_students": datetime.now(UTC) > xblock.start,
"release_date": release_date,
"visibility_state": visibility_state,
......@@ -675,6 +679,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"due": xblock.fields['due'].to_json(xblock.due),
"format": xblock.format,
"course_graders": json.dumps([grader.get('type') for grader in graders]),
"has_changes": has_changes,
}
if data is not None:
xblock_info["data"] = data
......@@ -689,14 +694,13 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
else:
xblock_info["ancestor_has_staff_lock"] = False
# Currently, 'edited_by', 'published_by', and 'release_date_from', and 'has_changes' are only used by the
# Currently, 'edited_by', 'published_by', and 'release_date_from' are only used by the
# container page when rendering a unit. Since they are expensive to compute, only include them for units
# that are not being rendered on the course outline.
if is_xblock_unit and not course_outline:
xblock_info["edited_by"] = safe_get_username(xblock.subtree_edited_by)
xblock_info["published_by"] = safe_get_username(xblock.published_by)
xblock_info["currently_visible_to_students"] = is_currently_visible_to_students(xblock)
xblock_info['has_changes'] = is_unit_with_changes
if release_date:
xblock_info["release_date_from"] = _get_release_date_from(xblock)
if visibility_state == VisibilityState.staff_only:
......
......@@ -59,7 +59,7 @@ function(Backbone, _, str, ModuleUtils) {
*/
"visibility_state": null,
/**
* True iff the release date of the xblock is in the past.
* True if the release date of the xblock is in the past.
*/
'released_to_students': null,
/**
......@@ -153,6 +153,10 @@ function(Backbone, _, str, ModuleUtils) {
return childInfo && childInfo.children.length > 0;
},
isPublishable: function(){
return !this.get('published') || this.get('has_changes');
},
/**
* Return a list of convenience methods to check affiliation to the category.
* @return {Array}
......
......@@ -8,9 +8,12 @@
* - changes cause a refresh of the entire section rather than just the view for the changed xblock
* - adding units will automatically redirect to the unit page rather than showing them inline
*/
define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_utils",
"js/models/xblock_outline_info", "js/views/modals/edit_outline_item", "js/utils/drag_and_drop"],
function($, _, XBlockOutlineView, ViewUtils, XBlockOutlineInfo, EditSectionXBlockModal, ContentDragger) {
define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_utils", "js/views/utils/xblock_utils",
"js/models/xblock_outline_info", "js/views/modals/course_outline_modals", "js/utils/drag_and_drop"],
function(
$, _, XBlockOutlineView, ViewUtils, XBlockViewUtils,
XBlockOutlineInfo, CourseOutlineModalsFactory, ContentDragger
) {
var CourseOutlineView = XBlockOutlineView.extend({
// takes XBlockOutlineInfo as a model
......@@ -144,13 +147,30 @@ define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_
},
editXBlock: function() {
var modal = new EditSectionXBlockModal({
model: this.model,
var modal = CourseOutlineModalsFactory.getModal('edit', this.model, {
onSave: this.refresh.bind(this),
parentInfo: this.parentInfo
parentInfo: this.parentInfo,
xblockType: XBlockViewUtils.getXBlockType(
this.model.get('category'), this.parentView.model, true
)
});
modal.show();
if (modal) {
modal.show();
}
},
publishXBlock: function() {
var modal = CourseOutlineModalsFactory.getModal('publish', this.model, {
onSave: this.refresh.bind(this),
xblockType: XBlockViewUtils.getXBlockType(
this.model.get('category'), this.parentView.model, true
)
});
if (modal) {
modal.show();
}
},
addButtonActions: function(element) {
......@@ -159,6 +179,10 @@ define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_
event.preventDefault();
this.editXBlock();
}.bind(this));
element.find('.publish-button').click(function(event) {
event.preventDefault();
this.publishXBlock();
}.bind(this));
},
makeContentDraggable: function(element) {
......
......@@ -234,8 +234,11 @@
}
}
// edit outline item settings
.edit-outline-item-modal {
// outline: edit item settings
.wrapper-modal-window-bulkpublish-section,
.wrapper-modal-window-bulkpublish-subsection,
.wrapper-modal-window-bulkpublish-unit,
.course-outline-modal {
.list-fields {
......
......@@ -259,34 +259,10 @@
}
.wrapper-unit-tree-location {
// tree location-specific styles should go here
.outline-section{
box-shadow: none;
border: 0;
padding: 0;
}
.outline-subsection {
border-left: 1px solid $gray-l4;
padding: ($baseline/2);
.subsection-header {
margin-bottom: ($baseline/2);
}
}
.item-title {
@extend %cont-text-wrap;
}
.item-title a {
color: $color-heading-base;
&:hover {
color: $blue;
}
}
}
}
}
......
......@@ -144,7 +144,7 @@ templates = ["basic-modal", "modal-button", "edit-xblock-modal",
</div>
<div class="wrapper-unit-tree-location bar-mod-content">
<h5 class="title">${_("Location in Course Outline")}</h5>
<div class="wrapper-unit-overview">
<div class="wrapper-unit-overview outline outline-simple">
</div>
</div>
</div>
......
......@@ -29,7 +29,7 @@ from contentstore.utils import reverse_usage_url
<%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', 'edit-outline-item-modal']:
% 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']:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
......@@ -91,7 +91,7 @@ from contentstore.utils import reverse_usage_url
course_locator = context_course.location
%>
<h2 class="sr">${_("Course Outline")}</h2>
<article class="outline outline-course" data-locator="${course_locator}" data-course-key="${course_locator.course_key}">
<article class="outline outline-complex outline-course" data-locator="${course_locator}" data-course-key="${course_locator.course_key}">
</article>
</div>
<div class="ui-loading">
......@@ -116,5 +116,4 @@ from contentstore.utils import reverse_usage_url
</aside>
</section>
</div>
<footer></footer>
</%block>
<div class="xblock-editor" data-locator="<%= xblockInfo.get('id') %>" data-course-key="<%= xblockInfo.get('courseKey') %>">
<div class="message modal-introduction">
<p><%= introductionMessage %></p>
</div>
<div class="modal-section"></div>
</div>
<%
var category = xblockInfo.get('category');
var releasedToStudents = xblockInfo.get('released_to_students');
var visibilityState = xblockInfo.get('visibility_state');
var published = xblockInfo.get('published');
......@@ -10,7 +9,7 @@ if (staffOnlyMessage) {
statusType = 'staff-only';
statusMessage = gettext('Contains staff only content');
} else if (visibilityState === 'needs_attention') {
if (category === 'vertical') {
if (xblockInfo.isVertical()) {
statusType = 'warning';
if (published && releasedToStudents) {
statusMessage = gettext('Unpublished changes to live content');
......@@ -63,6 +62,14 @@ if (xblockInfo.get('graded')) {
<div class="<%= xblockType %>-header-actions">
<ul class="actions-list">
<% if (xblockInfo.isPublishable()) { %>
<li class="action-item action-publish">
<a href="#" data-tooltip="<%= gettext('Publish') %>" class="publish-button action-button">
<i class="icon icon-upload-alt"></i>
<span class="sr action-button-text"><%= gettext('Publish') %></span>
</a>
</li>
<% } %>
<% if (xblockInfo.isEditableOnCourseOutline()) { %>
<li class="action-item action-configure">
<a href="#" data-tooltip="<%= gettext('Configure') %>" class="configure-button action-button">
......@@ -141,7 +148,7 @@ if (xblockInfo.get('graded')) {
</a>
</p>
</div>
<% } else if (category !== 'vertical') { %>
<% } else if (!xblockInfo.isVertical()) { %>
<div class="outline-content <%= xblockType %>-content">
<ol class="<%= typeListClass %> is-sortable">
<li class="ui-splint ui-splint-indicator">
......
<ul class="list-fields list-input datepair date-setter">
<li class="field field-text field-due-date">
<label for="due_date"><%= gettext('Due Date:') %></label>
<input type="text" id="due_date" name="due_date" value=""
placeholder="MM/DD/YYYY" class="due-date date input input-text" autocomplete="off"/>
</li>
<li class="field field-text field-due-time">
<label for="due_time"><%= gettext('Due Time in UTC:') %></label>
<input type="text" id="due_time" name="due_time" value=""
placeholder="HH:MM" class="due-time time input input-text" autocomplete="off" />
</li>
</ul>
<ul class="list-actions">
<li class="action-item">
<a href="#" data-tooltip="<%= gettext('Clear Grading Due Date') %>" class="clear-date action-button action-clear">
<i class="icon-undo"></i>
<span class="sr"><%= gettext('Clear Grading Due Date') %></span>
</a>
</li>
</ul>
<div class="xblock-editor" data-locator="<%= xblockInfo.get('id') %>" data-course-key="<%= xblockInfo.get('courseKey') %>">
<div class="message modal-introduction">
<p>
<%= interpolate(gettext("Change the settings for %(display_name)s"), {display_name: xblockInfo.get('display_name')}, true) %>
</p>
</div>
<form class="edit-settings-form" action="#">
<% if (xblockInfo.isChapter() || xblockInfo.isSequential()) { %>
<div class="modal-section edit-settings-release scheduled-date-input">
<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>
<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>
<input type="text" id="start_time" name="start_time"
value=""
placeholder="HH:MM" class="start-time release-time time input input-text" autocomplete="off" />
</li>
</ul>
<% 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">
<i class="icon-undo"></i>
<span class="sr"><%= gettext('Clear Release Date/Time') %></span>
</a>
</li>
</ul>
<% } %>
</div>
</div>
<% } %>
<% if (xblockInfo.isSequential()) { %>
<div class="modal-section edit-settings-grading">
<h3 class="modal-section-title"><%= gettext('Grading') %></h3>
<div class="modal-section-content grading-type">
<ul class="list-fields list-input">
<li class="field field-grading-type field-select">
<label for="grading_type" class="label"><%= gettext('Grade as:') %></label>
<select class="input" id="grading_type">
<option value="notgraded"><%= gettext('Not Graded') %></option>
<% _.each(graderTypes, function(grader) { %>
<option value="<%= grader %>"><%= grader %></option>
<% }); %>
</select>
</li>
</ul>
</div>
<div class="modal-section-content has-actions due-date-input grading-due-date">
<ul class="list-fields list-input datepair date-setter">
<li class="field field-text field-due-date">
<label for="due_date"><%= gettext('Due Date:') %></label>
<input type="text" id="due_date" name="due_date"
value=""
placeholder="MM/DD/YYYY" class="due-date date input input-text" autocomplete="off"/>
</li>
<li class="field field-text field-due-time">
<label for="due_time"><%= gettext('Due Time in UTC:') %></label>
<input type="text" id="due_time" name="due_time"
value=""
placeholder="HH:MM" class="due-time time input input-text" autocomplete="off" />
</li>
</ul>
<ul class="list-actions">
<li class="action-item">
<a href="#" data-tooltip="<%= gettext('Clear Grading Due Date') %>" class="clear-date action-button action-clear">
<i class="icon-undo"></i>
<span class="sr"><%= gettext('Clear Grading Due Date') %></span>
</a>
</li>
</ul>
</div>
</div>
<% } %>
<div class="modal-section edit-staff-lock">
<h3 class="modal-section-title"><%= gettext('Student Visibility') %></h3>
<div class="modal-section-content staff-lock">
<ul class="list-fields list-input">
<li class="field field-checkbox checkbox-cosmetic">
<input type="checkbox" id="staff_lock" name="staff_lock" class="input input-checkbox" />
<label for="staff_lock" class="label">
<i class="icon-check input-checkbox-checked"></i>
<i class="icon-check-empty input-checkbox-unchecked"></i>
<%= gettext('Hide from students') %>
</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.') %>
<% } 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(s).'); %>
<%= 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>
<% } %>
</li>
</ul>
</div>
</div>
</form>
</div>
<h3 class="modal-section-title"><%= gettext('Grading') %></h3>
<div class="modal-section-content grading-type">
<ul class="list-fields list-input">
<li class="field field-grading-type field-select">
<label for="grading_type" class="label"><%= gettext('Grade as:') %></label>
<select class="input" id="grading_type">
<option value="notgraded"><%= gettext('Not Graded') %></option>
<% _.each(graderTypes, function(grader) { %>
<option value="<%= grader %>"><%= grader %></option>
<% }); %>
</select>
</li>
</ul>
</div>
<% if (!xblockInfo.isVertical()) { %>
<div class="modal-section-content">
<div class="outline outline-simple outline-bulkpublish">
<% if (xblockInfo.isChapter()) { %>
<ol class="list-subsections">
<% _.each(xblockInfo.get('child_info').children, function(subsection) { %>
<% if (subsection.isPublishable()) { %>
<li class="outline-item outline-subsection">
<h4 class="subsection-title item-title"><%= subsection.get('display_name') %></h4>
<div class="subsection-content">
<ol class="list-units">
<% _.each(subsection.get('child_info').children, function(unit) { %>
<% if (unit.isPublishable()) { %>
<li class="outline-item outline-unit">
<span class="unit-title item-title"><%= unit.get('display_name') %></span>
</li>
<% } %>
<% }); %>
</ol>
</div>
</li>
<% } %>
<% }); %>
</ol>
<% } else { %>
<ol class="list-units">
<% _.each(xblockInfo.get('child_info').children, function(unit) { %>
<% if (unit.isPublishable()) { %>
<li class="outline-item outline-unit">
<span class="unit-title item-title"><%= unit.get('display_name') %></span>
</li>
<% } %>
<% }); %>
</ol>
<% } %>
</div>
</div>
<% } %>
<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>
<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>
<input type="text" id="start_time" name="start_time"
value=""
placeholder="HH:MM" class="start-time release-time time input input-text" autocomplete="off" />
</li>
</ul>
<% 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">
<i class="icon-undo"></i>
<span class="sr"><%= gettext('Clear Release Date/Time') %></span>
</a>
</li>
</ul>
<% } %>
</div>
<form>
<h3 class="modal-section-title"><%= gettext('Student Visibility') %></h3>
<div class="modal-section-content staff-lock">
<ul class="list-fields list-input">
<li class="field field-checkbox checkbox-cosmetic">
<input type="checkbox" id="staff_lock" name="staff_lock" class="input input-checkbox" />
<label for="staff_lock" class="label">
<i class="icon-check input-checkbox-checked"></i>
<i class="icon-check-empty input-checkbox-unchecked"></i>
<%= gettext('Hide from students') %>
</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.') %>
<% } 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(s).'); %>
<%= 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>
<% } %>
</li>
</ul>
</div>
</form>
\ No newline at end of file
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-section" aria-describedby="modal-window-description" aria-labelledby="modal-window-title" aria-hidden="" role="dialog">
<div class="modal-window-overlay"></div>
<div style="top: 5%; left: 30%;" class="modal-window confirm modal-med modal-type-confirm">
<div class="bulkpublish-section-modal">
<div class="modal-header">
<h2 class="title modal-window-title">${_("Publish [section name]")}</h2>
</div>
<div class="modal-content">
<div class="message modal-introduction">
<p>${_("Publish all unpublished changes for this section?")}</p>
</div>
<div class="modal-section bulkpublish-included">
<h3 class="modal-section-title">${_("The following will be published:")}</h3>
<div class="modal-section-content">
<div class="outline outline-simple outline-bulkpublish">
<ol class="list-subsections">
<li class="outline-item outline-subsection">
<h4 class="subsection-title item-title">Subsection Title</h4>
<div class="subsection-content">
<ol class="list-units">
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title that is really really really long and may span more than just one visual line of text.</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
</ol>
</div>
</li>
<li class="outline-item outline-subsection">
<h4 class="subsection-title item-title">Subsection Title</h4>
<div class="subsection-content">
<ol class="list-units">
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title that is really really really long and may span more than just one visual line of text.</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
</ol>
</div>
</li>
<li class="outline-item outline-subsection">
<h4 class="subsection-title item-title">Subsection Title</h4>
<div class="subsection-content">
<ol class="list-units">
<ol class="list-units">
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title that is really really really long and may span more than just one visual line of text.</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
</ol>
</ol>
</div>
</li>
</ol>
</div>
</div>
</div>
</div>
<div class="modal-actions">
<h3 class="sr">Actions</h3>
<ul>
<li class="action-item">
<a href="#" class="button action-primary action-publish">Publish</a>
</li>
<li class="action-item">
<a href="#" class="button action-cancel">Cancel</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-subsection" aria-describedby="modal-window-description" aria-labelledby="modal-window-title" aria-hidden="" role="dialog">
<div class="modal-window-overlay"></div>
<div style="top: 5%; left: 30%;" class="modal-window confirm modal-med modal-type-confirm">
<div class="bulkpublish-subsection-modal">
<div class="modal-header">
<h2 class="title modal-window-title">${_("Publish [subsection name]")}</h2>
</div>
<div class="modal-content">
<div class="message modal-introduction">
<p>${_("Publish all unpublished changes for this subsection?")}</p>
</div>
<div class="modal-section bulkpublish-included">
<h3 class="modal-section-title">${_("The following will be published:")}</h3>
<div class="modal-section-content">
<div class="outline outline-simple outline-bulkpublish">
<ol class="list-units">
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title that is really really really long and may span more than just one visual line of text.</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
<li class="outline-item outline-unit">
<span class="unit-title item-title">Unit Title</span>
</li>
</ol>
</div>
</div>
</div>
</div>
<div class="modal-actions">
<h3 class="sr">Actions</h3>
<ul>
<li class="action-item">
<a href="#" class="button action-primary action-publish">Publish</a>
</li>
<li class="action-item">
<a href="#" class="button action-cancel">Cancel</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper wrapper-modal-window wrapper-modal-window-bulkpublish-unit" aria-describedby="modal-window-description" aria-labelledby="modal-window-title" aria-hidden="" role="dialog">
<div class="modal-window-overlay"></div>
<div style="top: 5%; left: 30%;" class="modal-window confirm modal-med modal-type-confirm">
<div class="bulkpublish-unit-modal">
<div class="modal-header">
<h2 class="title modal-window-title">${_("Publish [unit name]")}</h2>
</div>
<div class="modal-content">
<div class="message modal-introduction">
<p>${_("Publish all unpublished changes for this unit?")}</p>
</div>
</div>
<div class="modal-actions">
<h3 class="sr">Actions</h3>
<ul>
<li class="action-item">
<a href="#" class="button action-primary action-publish">Publish</a>
</li>
<li class="action-item">
<a href="#" class="button action-cancel">Cancel</a>
</li>
</ul>
</div>
</div>
</div>
</div>
......@@ -77,7 +77,7 @@ class CourseOutlineItem(object):
@property
def has_staff_lock_warning(self):
""" Returns True iff the 'Contains staff only content' message is visible """
""" Returns True if the 'Contains staff only content' message is visible """
return self.status_message == 'Contains staff only content' if self.has_status_message else False
@property
......@@ -149,6 +149,22 @@ class CourseOutlineItem(object):
element = self.q(css=self._bounded_selector(".status-grading-value"))
return element.first.text[0] if element.present else None
def publish(self):
"""
Publish the unit.
"""
click_css(self, self._bounded_selector('.action-publish'), require_notification=False)
modal = CourseOutlineModal(self)
EmptyPromise(lambda: modal.is_shown(), 'Modal is shown.')
modal.publish()
@property
def publish_action(self):
"""
Returns the link for publishing a unit.
"""
return self.q(css=self._bounded_selector('.action-publish')).first
class CourseOutlineContainer(CourseOutlineItem):
"""
......@@ -483,7 +499,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
class CourseOutlineModal(object):
MODAL_SELECTOR = ".edit-outline-item-modal"
MODAL_SELECTOR = ".wrapper-modal-window"
def __init__(self, page):
self.page = page
......@@ -507,6 +523,10 @@ class CourseOutlineModal(object):
self.click(".action-save")
self.page.wait_for_ajax()
def publish(self):
self.click(".action-publish")
self.page.wait_for_ajax()
def cancel(self):
self.click(".action-cancel")
......
......@@ -11,6 +11,7 @@ from bok_choy.promise import EmptyPromise
from ..pages.studio.overview import CourseOutlinePage, ContainerPage, ExpandCollapseLinkState
from ..pages.studio.utils import add_discussion
from ..pages.lms.courseware import CoursewarePage
from ..pages.lms.course_nav import CourseNavPage
from ..pages.lms.staff_view import StaffPage
from ..fixtures.course import XBlockFixtureDesc
......@@ -1369,3 +1370,129 @@ class UnitNavigationTest(CourseOutlineTest):
self.course_outline_page.section_at(0).subsection_at(0).toggle_expand()
unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).go_to()
self.assertTrue(unit.is_browser_on_page)
@attr('shard_1')
class PublishSectionTest(CourseOutlineTest):
"""
Feature: Publish sections.
"""
__test__ = True
def populate_course_fixture(self, course_fixture):
"""
Sets up a course structure with 2 subsections inside a single section.
The first subsection has 2 units, and the second subsection has one unit.
"""
self.courseware = CoursewarePage(self.browser, self.course_id)
self.course_nav = CourseNavPage(self.browser)
course_fixture.add_children(
XBlockFixtureDesc('chapter', SECTION_NAME).add_children(
XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children(
XBlockFixtureDesc('vertical', UNIT_NAME),
XBlockFixtureDesc('vertical', 'Test Unit 2'),
),
XBlockFixtureDesc('sequential', 'Test Subsection 2').add_children(
XBlockFixtureDesc('vertical', 'Test Unit 3'),
),
),
)
def test_unit_publishing(self):
"""
Scenario: Can publish a unit and see published content in LMS
Given I have a section with 2 subsections and 3 unpublished units
When I go to the course outline
Then I see publish button for the first unit, subsection, section
When I publish the first unit
Then I see that publish button for the first unit disappears
And I see publish buttons for subsection, section
And I see the changed content in LMS
"""
self._add_unpublished_content()
self.course_outline_page.visit()
section, subsection, unit = self._get_items()
self.assertTrue(unit.publish_action)
self.assertTrue(subsection.publish_action)
self.assertTrue(section.publish_action)
unit.publish()
self.assertFalse(unit.publish_action)
self.assertTrue(subsection.publish_action)
self.assertTrue(section.publish_action)
self.courseware.visit()
self.assertEqual(1, self.courseware.num_xblock_components)
def test_subsection_publishing(self):
"""
Scenario: Can publish a subsection and see published content in LMS
Given I have a section with 2 subsections and 3 unpublished units
When I go to the course outline
Then I see publish button for the unit, subsection, section
When I publish the first subsection
Then I see that publish button for the first subsection disappears
And I see that publish buttons disappear for the child units of the subsection
And I see publish button for section
And I see the changed content in LMS
"""
self._add_unpublished_content()
self.course_outline_page.visit()
section, subsection, unit = self._get_items()
self.assertTrue(unit.publish_action)
self.assertTrue(subsection.publish_action)
self.assertTrue(section.publish_action)
self.course_outline_page.section(SECTION_NAME).subsection(SUBSECTION_NAME).publish()
self.assertFalse(unit.publish_action)
self.assertFalse(subsection.publish_action)
self.assertTrue(section.publish_action)
self.courseware.visit()
self.assertEqual(1, self.courseware.num_xblock_components)
self.course_nav.go_to_sequential_position(2)
self.assertEqual(1, self.courseware.num_xblock_components)
def test_section_publishing(self):
"""
Scenario: Can publish a section and see published content in LMS
Given I have a section with 2 subsections and 3 unpublished units
When I go to the course outline
Then I see publish button for the unit, subsection, section
When I publish the section
Then I see that publish buttons disappears
And I see the changed content in LMS
"""
self._add_unpublished_content()
self.course_outline_page.visit()
section, subsection, unit = self._get_items()
self.assertTrue(subsection.publish_action)
self.assertTrue(section.publish_action)
self.assertTrue(unit.publish_action)
self.course_outline_page.section(SECTION_NAME).publish()
self.assertFalse(subsection.publish_action)
self.assertFalse(section.publish_action)
self.assertFalse(unit.publish_action)
self.courseware.visit()
self.assertEqual(1, self.courseware.num_xblock_components)
self.course_nav.go_to_sequential_position(2)
self.assertEqual(1, self.courseware.num_xblock_components)
self.course_nav.go_to_section(SECTION_NAME, 'Test Subsection 2')
self.assertEqual(1, self.courseware.num_xblock_components)
def _add_unpublished_content(self):
"""
Adds unpublished HTML content to first three units in the course.
"""
for index in xrange(3):
self.course_fixture.create_xblock(
self.course_fixture.get_nested_xblocks(category="vertical")[index].locator,
XBlockFixtureDesc('html', 'Unpublished HTML Component ' + str(index)),
)
def _get_items(self):
"""
Returns first section, subsection, and unit on the page.
"""
section = self.course_outline_page.section(SECTION_NAME)
subsection = section.subsection(SUBSECTION_NAME)
unit = subsection.toggle_expand().unit(UNIT_NAME)
return (section, subsection, unit)
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