Commit d447c075 by cahrens

Support staff locking on the unit page

STUD-1873
parent cf70eb6e
......@@ -231,7 +231,7 @@ class XBlockVisibilityTestCase(TestCase):
vertical.start = self.future
modulestore().update_item(vertical, self.dummy_user)
self.assertTrue(utils.is_xblock_visible_to_students(vertical))
self.assertTrue(utils.is_currently_visible_to_students(vertical))
def _test_visible_to_students(self, expected_visible_without_lock, name, start_date, publish=False):
"""
......@@ -239,13 +239,13 @@ class XBlockVisibilityTestCase(TestCase):
with and without visible_to_staff_only set.
"""
no_staff_lock = self._create_xblock_with_start_date(name, start_date, publish, visible_to_staff_only=False)
self.assertEqual(expected_visible_without_lock, utils.is_xblock_visible_to_students(no_staff_lock))
self.assertEqual(expected_visible_without_lock, utils.is_currently_visible_to_students(no_staff_lock))
# any xblock with visible_to_staff_only set to True should not be visible to students.
staff_lock = self._create_xblock_with_start_date(
name + "_locked", start_date, publish, visible_to_staff_only=True
)
self.assertFalse(utils.is_xblock_visible_to_students(staff_lock))
self.assertFalse(utils.is_currently_visible_to_students(staff_lock))
def _create_xblock_with_start_date(self, name, start_date, publish=False, visible_to_staff_only=False):
"""Helper to create an xblock with a start date, optionally publishing it"""
......
......@@ -164,9 +164,10 @@ def compute_publish_state(xblock):
return modulestore().compute_publish_state(xblock)
def is_xblock_visible_to_students(xblock):
def is_currently_visible_to_students(xblock):
"""
Returns true if there is a published version of the xblock that has been released.
Returns true if there is a published version of the xblock that is currently visible to students.
This means that it has a release date in the past, and the xblock has not been set to staff only.
"""
try:
......
......@@ -21,7 +21,7 @@ from xblock.fields import Scope
from xblock.plugin import PluginMissingError
from xblock.runtime import Mixologist
from contentstore.utils import get_lms_link_for_item, compute_publish_state, is_xblock_visible_to_students
from contentstore.utils import get_lms_link_for_item, compute_publish_state
from contentstore.views.helpers import get_parent_xblock, is_unit, xblock_type_display_name
from contentstore.views.item import create_xblock_info
......@@ -207,7 +207,6 @@ def container_handler(request, usage_key_string):
'xblock_locator': xblock.location,
'unit': unit,
'is_unit_page': is_unit_page,
'is_visible_to_students': is_xblock_visible_to_students(xblock),
'subsection': subsection,
'section': section,
'new_unit_category': 'vertical',
......
......@@ -157,39 +157,3 @@ class ContainerPageTestCase(StudioPageTestCase):
"""
empty_child_container = self._create_item(self.vertical.location, 'split_test', 'Split Test')
self.validate_preview_html(empty_child_container, self.reorderable_child_view, can_add=False)
def test_unreleased_private_container_messages(self):
"""
Verify that an unreleased private container does not display messages.
"""
self.validate_html_for_messages(self.unreleased_private_vertical, False)
def test_unreleased_public_container_messages(self):
"""
Verify that an unreleased public container does not display messages.
"""
self.validate_html_for_messages(self.unreleased_public_vertical, False)
def test_released_private_container_message(self):
"""
Verify that a released private container does not display messages.
"""
self.validate_html_for_messages(self.released_private_vertical, False)
def test_released_public_container_message(self):
"""
Verify that a released public container does display messages.
"""
self.validate_html_for_messages(self.released_public_vertical, True)
def validate_html_for_messages(self, xblock, has_messages):
"""
Validate that the specified HTML has the appropriate messages for the current student visibility state.
"""
# Verify that there are no warning messages for blocks that are not visible to students
html = self.get_page_html(xblock)
messages_html = '<div class="container-message wrapper-message">'
if has_messages:
self.assertIn(messages_html, html)
else:
self.assertNotIn(messages_html, html)
......@@ -567,6 +567,55 @@ class TestEditItem(ItemTest):
published = self.verify_publish_state(self.problem_usage_key, PublishState.public)
self.assertIsNone(published.due)
def test_republish(self):
""" Test republishing an item. """
new_display_name = 'New Display Name'
republish_data = {
'publish': 'republish',
'display_name': new_display_name
}
# When the problem is first created, it is only in draft (because of its category).
self.verify_publish_state(self.problem_usage_key, PublishState.private)
# Republishing when only in draft will update the draft but not cause a public item to be created.
self.client.ajax_post(
self.problem_update_url,
data={
'publish': 'republish',
'metadata': {
'display_name': new_display_name
}
}
)
self.verify_publish_state(self.problem_usage_key, PublishState.private)
draft = self.get_item_from_modulestore(self.problem_usage_key, verify_is_draft=True)
self.assertEqual(draft.display_name, new_display_name)
# Publish the item
self.client.ajax_post(
self.problem_update_url,
data={'publish': 'make_public'}
)
# Now republishing should update the published version
new_display_name_2 = 'New Display Name 2'
self.client.ajax_post(
self.problem_update_url,
data={
'publish': 'republish',
'metadata': {
'display_name': new_display_name_2
}
}
)
self.verify_publish_state(self.problem_usage_key, PublishState.public)
published = modulestore().get_item(
self.problem_usage_key,
revision=ModuleStoreEnum.RevisionOption.published_only
)
self.assertEqual(published.display_name, new_display_name_2)
def _make_draft_content_different_from_published(self):
"""
Helper method to create different draft and published versions of a problem.
......
......@@ -38,7 +38,7 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
* If true, only course staff can see the xblock regardless of publish status or
* release date status.
*/
"locked": null,
"visible_to_staff_only": null,
/**
* Date of the last edit to this xblock or any of its descendants.
*/
......@@ -69,7 +69,12 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
* this will either be the parent subsection or the grandparent section.
* This can be null if the release date is unscheduled.
*/
"release_date_from":null
"release_date_from":null,
/**
* True if this xblock is currently visible to students. This is computed server-side
* so that the logic isn't duplicated on the client.
*/
"currently_visible_to_students": null
},
parse: function(response) {
......
......@@ -29,6 +29,11 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
model: this.model,
view: this.view
});
this.messageView = new ContainerSubviews.MessageView({
el: this.$('.container-message'),
model: this.model
});
this.messageView.render();
this.isUnitPage = this.options.isUnitPage;
if (this.isUnitPage) {
this.xblockPublisher = new ContainerSubviews.Publisher({
......
......@@ -10,7 +10,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
* A view that calls render when "has_changes" or "published" values in XBlockInfo have changed
* after a server sync operation.
*/
var UnitStateListenerView = BaseView.extend({
var ContainerStateListenerView = BaseView.extend({
// takes XBlockInfo as a model
initialize: function() {
......@@ -18,18 +18,43 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
},
onSync: function(model) {
if (ViewUtils.hasChangedAttributes(model, ['has_changes', 'published'])) {
if (this.shouldRefresh(model)) {
this.render();
}
},
shouldRefresh: function(model) {
return false;
},
render: function() {}
});
var MessageView = ContainerStateListenerView.extend({
initialize: function () {
ContainerStateListenerView.prototype.initialize.call(this);
this.template = this.loadTemplate('container-message');
},
shouldRefresh: function(model) {
return ViewUtils.hasChangedAttributes(model, ['currently_visible_to_students']);
},
render: function() {
this.$el.html(this.template({
currentlyVisibleToStudents: this.model.get('currently_visible_to_students')
}));
return this;
}
});
/**
* A controller for updating the "View Live" and "Preview" buttons.
*/
var PreviewActionController = UnitStateListenerView.extend({
var PreviewActionController = ContainerStateListenerView.extend({
shouldRefresh: function(model) {
return ViewUtils.hasChangedAttributes(model, ['has_changes', 'published']);
},
render: function() {
var previewAction = this.$el.find('.button-preview'),
......@@ -59,7 +84,8 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
var Publisher = BaseView.extend({
events: {
'click .action-publish': 'publish',
'click .action-discard': 'discardChanges'
'click .action-discard': 'discardChanges',
'click .action-staff-lock': 'toggleStaffLock'
},
// takes XBlockInfo as a model
......@@ -72,22 +98,25 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
},
onSync: function(model) {
if (ViewUtils.hasChangedAttributes(model, ['has_changes', 'published', 'edited_on', 'edited_by'])) {
if (ViewUtils.hasChangedAttributes(model, [
'has_changes', 'published', 'edited_on', 'edited_by', 'visible_to_staff_only'
])) {
this.render();
}
},
render: function () {
this.$el.html(this.template({
has_changes: this.model.get('has_changes'),
hasChanges: this.model.get('has_changes'),
published: this.model.get('published'),
edited_on: this.model.get('edited_on'),
edited_by: this.model.get('edited_by'),
published_on: this.model.get('published_on'),
published_by: this.model.get('published_by'),
released_to_students: this.model.get('released_to_students'),
release_date: this.model.get('release_date'),
release_date_from: this.model.get('release_date_from')
editedOn: this.model.get('edited_on'),
editedBy: this.model.get('edited_by'),
publishedOn: this.model.get('published_on'),
publishedBy: this.model.get('published_by'),
releasedToStudents: this.model.get('released_to_students'),
releaseDate: this.model.get('release_date'),
releaseDateFrom: this.model.get('release_date_from'),
visibleToStaffOnly: this.model.get('visible_to_staff_only')
}));
return this;
......@@ -127,10 +156,60 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
});
}
);
},
toggleStaffLock: function (e) {
var xblockInfo = this.model, self=this, enableStaffLock,
saveAndPublishStaffLock, revertCheckBox;
if (e && e.preventDefault) {
e.preventDefault();
}
enableStaffLock = !xblockInfo.get('visible_to_staff_only');
revertCheckBox = function() {
self.checkStaffLock(!enableStaffLock);
};
saveAndPublishStaffLock = function() {
return xblockInfo.save({
publish: 'republish',
metadata: {visible_to_staff_only: enableStaffLock}},
{patch: true}
).always(function() {
xblockInfo.set("publish", null);
}).done(function () {
xblockInfo.fetch();
}).fail(function() {
revertCheckBox();
});
};
this.checkStaffLock(enableStaffLock);
if (enableStaffLock) {
ViewUtils.runOperationShowingMessage(gettext('Setting Staff Lock&hellip;'),
_.bind(saveAndPublishStaffLock, self));
} else {
ViewUtils.confirmThenRunOperation(gettext("Remove Staff Lock"),
gettext("Are you sure you want to remove the staff lock? Once you publish this unit, it will be released to students on the release date."),
gettext("Remove Staff Lock"),
function() {
ViewUtils.runOperationShowingMessage(gettext('Removing Staff Lock&hellip;'),
_.bind(saveAndPublishStaffLock, self));
},
function() {
// On cancel, revert the check in the check box
revertCheckBox();
}
);
}
},
checkStaffLock: function(check) {
this.$('.action-staff-lock i').removeClass('icon-check icon-check-empty');
this.$('.action-staff-lock i').addClass(check ? 'icon-check' : 'icon-check-empty');
}
});
/**
* PublishHistory displays when and by whom the xblock was last published, if it ever was.
*/
......@@ -161,6 +240,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
});
return {
'MessageView': MessageView,
'PreviewActionController': PreviewActionController,
'Publisher': Publisher,
'PublishHistory': PublishHistory
......
......@@ -33,7 +33,7 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
/**
* Confirms with the user whether to run an operation or not, and then runs it if desired.
*/
confirmThenRunOperation = function(title, message, actionLabel, operation) {
confirmThenRunOperation = function(title, message, actionLabel, operation, onCancelCallback) {
return new PromptView.Warning({
title: title,
message: message,
......@@ -48,6 +48,9 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
secondary: {
text: gettext('Cancel'),
click: function(prompt) {
if (onCancelCallback) {
onCancelCallback();
}
return prompt.hide();
}
}
......
......@@ -11,8 +11,7 @@
//.wrapper-xblock-header {
.view-outline,
.view-container {
.view-outline {
.add-xblock-component {
text-align: center;
......
......@@ -111,11 +111,15 @@
&.staff-only,
&.is-staff-only {
@extend %bar-module-black;
&.is-scheduled .wrapper-release .copy {
text-decoration: line-through;
}
}
.bar-mod-content {
border: 0;
padding: ($baseline/2) ($baseline*.75) ($baseline*.75) ($baseline*.75);
padding: ($baseline/2) ($baseline*.75) ($baseline/4) ($baseline*.75);
.title {
margin-bottom: ($baseline/10);
......@@ -123,7 +127,6 @@
}
.wrapper-last-draft {
padding: ($baseline*.75) ($baseline*.75) ($baseline/4) ($baseline*.75);
.date,
.user {
......@@ -145,9 +148,9 @@
font-weight: 600;
}
.action-inline [class^="icon-"] {
margin: 0 ($baseline/4);
[class^="icon-"] {
margin-left: ($baseline/4);
color: $gray-d1;
}
}
......@@ -215,107 +218,7 @@
}
.wrapper-unit-tree-location {
.draggable-drop-indicator {
display: none;
}
// need to explicitly set this since the html structure is different than the others
.section-name:hover {
background: $blue-l5;
color: $blue;
}
.subsection,
.courseware-unit {
margin: ($baseline/4) 0 0 ($baseline*.75);
}
.courseware-unit .section-item {
background-color: transparent;
}
.section-item {
@include transition(background $tmg-avg ease-in-out 0);
@include box-sizing(border-box);
@extend %t-copy-sub2;
width: 100%;
display: inline-block;
vertical-align: top;
overflow: hidden;
padding: 6px 8px 8px 16px;
background: $gray-l5;
white-space: nowrap;
text-overflow: ellipsis;
color: $gray;
&:hover {
background: $blue-l5;
color: $blue;
}
&.editing {
background-color: $orange-l3;
}
// TODO: update these once we have different pub states
.public-item {
color: $black;
}
.private-item {
color: $gray-l1;
}
.draft-item {
color: $yellow-d1;
}
.public-item:hover,
.private-item:hover,
.draft-item:hover {
color: $blue;
}
.draft-item:after,
.public-item:after,
.private-item:after {
@include font-size(9);
margin-left: 3px;
font-weight: 600;
text-transform: uppercase;
}
.draft-item:after {
content: "- draft";
}
.private-item:after {
content: "- private";
}
}
.subsection > .section-item:hover {
background-color: $gray-l5;
color: inherit;
}
.new-unit-item {
@extend %ui-btn-flat-outline;
@extend %t-action4;
width: 90%;
margin: 0 0 ($baseline/2) ($baseline/4);
border: 1px solid transparent;
padding: ($baseline/4) ($baseline/2);
font-weight: normal;
color: $gray-l2;
text-align: left;
&:hover {
box-shadow: none;
background-image: none;
}
}
// tree location-specific styles should go here
}
}
}
......
......@@ -25,7 +25,7 @@ templates = ["basic-modal", "modal-button", "edit-xblock-modal",
"editor-mode-button", "upload-dialog", "image-modal",
"add-xblock-component", "add-xblock-component-button", "add-xblock-component-menu",
"add-xblock-component-menu-problem", "xblock-string-field-editor", "publish-xblock", "publish-history",
"unit-outline"]
"unit-outline", "container-message"]
%>
<%block name="header_extras">
% for template_name in templates:
......@@ -116,16 +116,7 @@ templates = ["basic-modal", "modal-button", "edit-xblock-modal",
<section class="content-area">
<article class="content-primary">
% if is_visible_to_students:
<div class="container-message wrapper-message">
<div class="message has-warnings">
<p class="warning">
<i class="icon-warning-sign"></i>
${_("This content is live for students. Edit with caution.")}
</p>
</div>
</div>
% endif
<div class="container-message wrapper-message"></div>
<section class="wrapper-xblock level-page is-hidden studio-xblock-wrapper" data-locator="${xblock_locator}" data-course-key="${xblock_locator.course_key}">
</section>
<div class="ui-loading">
......
<% if (currentlyVisibleToStudents) { %>
<div class="message has-warnings">
<p class="warning">
<i class="icon-warning-sign"></i>
<%= gettext("This content is live for students. Edit with caution.") %>
</p>
</div>
<% } %>
......@@ -14,8 +14,8 @@
<% if (xblockInfo.get('category') === 'vertical') { %>
<a href="<%= xblockInfo.get('studio_url') %>"><%= xblockInfo.get('display_name') %></a>
<% } else { %>
<span class="wrapper-xblock-field is-editable" data-field="display_name" data-field-display-name="<%= gettext("Display Name") %>">
<span class="xblock-field-value"><%= xblockInfo.get('display_name') %></span>
<span class="wrapper-xblock-field incontext-editor is-editable" data-field="display_name" data-field-display-name="<%= gettext("Display Name") %>">
<span class="xblock-field-value incontext-editor-value"><%= xblockInfo.get('display_name') %></span>
</span>
<% } %>
</h3>
......
......@@ -43,6 +43,7 @@
<section class="content-area">
<article class="content-primary window">
<div class="container-message wrapper-message"></div>
<section class="wrapper-xblock level-page studio-xblock-wrapper" data-locator="locator-container">
</section>
<div class="ui-loading is-hidden">
......
<div class="bit-publishing <% if (published && !has_changes) { %>published<% } else { %>draft<%} %>">
<%
var publishClasses = "";
var title = gettext("Draft (Never published)");
if (published) {
if (published && hasChanges) {
publishClasses = publishClasses + " is-draft";
title = gettext("Draft (Unpublished changes)");
} else {
publishClasses = publishClasses + " is-published";
title = gettext("Published");
}
}
if (releaseDate) {
publishClasses = publishClasses + " is-scheduled";
}
if (visibleToStaffOnly) {
publishClasses = publishClasses + " is-staff-only";
title = gettext("Unpublished (Staff only)");
}
%>
<div class="bit-publishing <%= publishClasses %>">
<h3 class="bar-mod-title pub-status"><span class="sr"><%= gettext("Publishing Status") %></span>
<% if (published && !has_changes) { %>
<%= gettext("Published") %>
<% } else { %>
<%= gettext("Draft (Unpublished changes)") %>
<% } %>
<%= title %>
</h3>
<div class="wrapper-last-draft bar-mod-content">
<p class="copy meta">
<% if (has_changes && edited_on && edited_by) {
<% if (hasChanges && editedOn && editedBy) {
var message = gettext("Draft saved on %(last_saved_date)s by %(edit_username)s") %>
<%= interpolate(message, {
last_saved_date: '<span class="date">' + edited_on + '</span>',
edit_username: '<span class="user">' + edited_by + '</span>' }, true) %>
<% } else if (published_on && published_by) {
last_saved_date: '<span class="date">' + editedOn + '</span>',
edit_username: '<span class="user">' + editedBy + '</span>' }, true) %>
<% } else if (publishedOn && publishedBy) {
var message = gettext("Last published %(last_published_date)s by %(publish_username)s"); %>
<%= interpolate(message, {
last_published_date: '<span class="date">' + published_on + '</span>',
publish_username: '<span class="user">' + published_by + '</span>' }, true) %>
last_published_date: '<span class="date">' + publishedOn + '</span>',
publish_username: '<span class="user">' + publishedBy + '</span>' }, true) %>
<% } else { %>
<%= gettext("Previously published") %>
<% } %>
</p>
</div>
<!--TODO this needs strikeout styles once staff lock exists-->
<div class="wrapper-release bar-mod-content">
<h5 class="title">
<% if (published && release_date) {
if (released_to_students) { %>
<% if (published && releaseDate) {
if (releasedToStudents) { %>
<%= gettext("Released:") %>
<% } else { %>
<%= gettext("Scheduled:") %>
......@@ -39,37 +54,45 @@
<% } %>
</h5>
<p class="copy">
<% if (release_date) { %>
<% if (releaseDate) { %>
<% var message = gettext("%(release_date)s with %(section_or_subsection)s") %>
<%= interpolate(message, {
release_date: '<span class="release-date">' + release_date + '</span>',
section_or_subsection: '<span class="release-with">' + release_date_from + '</span>' }, true) %>
release_date: '<span class="release-date">' + releaseDate + '</span>',
section_or_subsection: '<span class="release-with">' + releaseDateFrom + '</span>' }, true) %>
<% } else { %>
<%= gettext("Unscheduled") %>
<% } %>
</p>
</div>
<!--To be added in STUD-1830-->
<!--<div class="wrapper-visibility bar-mod-content">-->
<!--<h5 class="title">Will be Visible to:</h5>-->
<!--<p class="copy">Staff and Students</p>-->
<!--<p class="action-inline">-->
<!--<a href="">-->
<!--<i class="icon-unlock is-disabled"></i> Hide from Students-->
<!--</a>-->
<!--</p>-->
<!--</div>-->
<div class="wrapper-visibility bar-mod-content">
<h5 class="title"><%= gettext("Will Be Visible To:") %></h5>
<% if (visibleToStaffOnly) { %>
<p class="copy"><%= gettext("Staff Only") %></p>
<% } else { %>
<p class="copy"><%= gettext("Staff and Students") %></p>
<% } %>
<p class="action-inline">
<a href="" class="action-staff-lock" role="button" aria-pressed="<%= visibleToStaffOnly %>">
<% if (visibleToStaffOnly) { %>
<i class="icon-check"></i>
<% } else { %>
<i class="icon-check-empty"></i>
<% } %>
<%= gettext('Hide from students') %>
</a>
</p>
</div>
<div class="wrapper-pub-actions bar-mod-actions">
<ul class="action-list">
<li class="action-item">
<a class="action-publish action-primary <% if (published && !has_changes) { %>is-disabled<% } %>"
<a class="action-publish action-primary <% if (published && !hasChanges) { %>is-disabled<% } %>"
href=""><%= gettext("Publish") %>
</a>
</li>
<li class="action-item">
<a class="action-discard action-secondary <% if (!published || !has_changes) { %>is-disabled<% } %>"
<a class="action-discard action-secondary <% if (!published || !hasChanges) { %>is-disabled<% } %>"
href=""><%= gettext("Discard Changes") %>
</a>
</li>
......
......@@ -10,16 +10,24 @@ class StaffPage(PageObject):
"""
url = None
STAFF_STATUS_CSS = '#staffstatus'
def is_browser_on_page(self):
return self.q(css='#staffstatus').present
return self.q(css=self.STAFF_STATUS_CSS).present
@property
def staff_status(self):
"""
Return the current status, either Staff view or Student view
"""
return self.q(css='#staffstatus').text[0]
return self.q(css=self.STAFF_STATUS_CSS).text[0]
def toggle_staff_view(self):
"""
Toggle between staff view and student view.
"""
self.q(css=self.STAFF_STATUS_CSS).first.click()
self.wait_for_ajax()
def open_staff_debug_info(self):
"""
......
......@@ -82,6 +82,33 @@ class ContainerPage(PageObject):
return self.q(css='.pub-status').first.text[0]
@property
def release_title(self):
"""
Returns the title before the release date in the publishing sidebar component.
"""
return self.q(css='.wrapper-release .title').first.text[0]
@property
def release_date(self):
"""
Returns the release date of the unit (with ancestor inherited from), as displayed
in the publishing sidebar component.
"""
return self.q(css='.wrapper-release .copy').first.text[0]
@property
def currently_visible_to_students(self):
"""
Returns True if the unit is marked as currently visible to students
(meaning that a warning is being displayed).
"""
warnings = self.q(css='.container-message .warning')
if not warnings.is_present():
return False
warning_text = warnings.first.text[0]
return warning_text == "This content is live for students. Edit with caution."
@property
def publish_action(self):
"""
Returns the link for publishing a unit.
......@@ -96,6 +123,22 @@ class ContainerPage(PageObject):
self.q(css='a.button.action-primary').first.click()
self.wait_for_ajax()
def toggle_staff_lock(self):
"""
Toggles "hide from students" which enables or disables a staff-only lock.
Returns True if the lock is now enabled, else False.
"""
class_attribute_values = self.q(css='a.action-staff-lock>i').attrs('class')
was_locked_initially = 'icon-check' in class_attribute_values
if not was_locked_initially:
self.q(css='a.action-staff-lock').first.click()
else:
click_css(self, 'a.action-staff-lock', 0, require_notification=False)
self.q(css='a.button.action-primary').first.click()
self.wait_for_ajax()
return not was_locked_initially
def view_published_version(self):
"""
Clicks "View Published Version", which will open the published version of the unit page in the LMS.
......
......@@ -16,6 +16,7 @@ from ..pages.lms.progress import ProgressPage
from ..pages.lms.dashboard import DashboardPage
from ..pages.lms.video.video import VideoPage
from ..pages.xblock.acid import AcidView
from ..pages.lms.courseware import CoursewarePage
from ..fixtures.course import CourseFixture, XBlockFixtureDesc, CourseUpdateDesc
......@@ -421,3 +422,87 @@ class XBlockAcidChildTest(XBlockAcidBase):
@skip('This will fail until we fix support of children in pure XBlocks')
def test_acid_block(self):
super(XBlockAcidChildTest, self).test_acid_block()
class VisibleToStaffOnlyTest(UniqueCourseTest):
"""
Tests that content with visible_to_staff_only set to True cannot be viewed by students.
"""
def setUp(self):
super(VisibleToStaffOnlyTest, self).setUp()
course_fix = CourseFixture(
self.course_info['org'],
self.course_info['number'],
self.course_info['run'],
self.course_info['display_name']
)
course_fix.add_children(
XBlockFixtureDesc('chapter', 'Test Section').add_children(
XBlockFixtureDesc('sequential', 'Subsection With Locked Unit').add_children(
XBlockFixtureDesc('vertical', 'Locked Unit', metadata={'visible_to_staff_only': True}).add_children(
XBlockFixtureDesc('html', 'Html Child in locked unit', data="<html>Visible only to staff</html>"),
),
XBlockFixtureDesc('vertical', 'Unlocked Unit').add_children(
XBlockFixtureDesc('html', 'Html Child in unlocked unit', data="<html>Visible only to all</html>"),
)
),
XBlockFixtureDesc('sequential', 'Unlocked Subsection').add_children(
XBlockFixtureDesc('vertical', 'Test Unit').add_children(
XBlockFixtureDesc('html', 'Html Child in visible unit', data="<html>Visible to all</html>"),
)
),
XBlockFixtureDesc('sequential', 'Locked Subsection', metadata={'visible_to_staff_only': True}).add_children(
XBlockFixtureDesc('vertical', 'Test Unit').add_children(
XBlockFixtureDesc(
'html', 'Html Child in locked subsection', data="<html>Visible only to staff</html>"
)
)
)
)
).install()
self.courseware_page = CoursewarePage(self.browser, self.course_id)
self.course_nav = CourseNavPage(self.browser)
def test_visible_to_staff(self):
"""
Scenario: All content is visible for a user marked is_staff (different from course staff)
Given some of the course content has been marked 'visible_to_staff_only'
And I am logged on with an account marked 'is_staff'
Then I can see all course content
"""
AutoAuthPage(self.browser, username="STAFF_TESTER", email="johndoe_staff@example.com",
course_id=self.course_id, staff=True).visit()
self.courseware_page.visit()
self.assertEqual(3, len(self.course_nav.sections['Test Section']))
self.course_nav.go_to_section("Test Section", "Subsection With Locked Unit")
self.assertEqual(["Html Child in locked unit", "Html Child in unlocked unit"], self.course_nav.sequence_items)
self.course_nav.go_to_section("Test Section", "Unlocked Subsection")
self.assertEqual(["Html Child in visible unit"], self.course_nav.sequence_items)
self.course_nav.go_to_section("Test Section", "Locked Subsection")
self.assertEqual(["Html Child in locked subsection"], self.course_nav.sequence_items)
def test_visible_to_student(self):
"""
Scenario: Content marked 'visible_to_staff_only' is not visible for students in the course
Given some of the course content has been marked 'visible_to_staff_only'
And I am logged on with an authorized student account
Then I can only see content without 'visible_to_staff_only' set to True
"""
AutoAuthPage(self.browser, username="STUDENT_TESTER", email="johndoe_student@example.com",
course_id=self.course_id, staff=False).visit()
self.courseware_page.visit()
self.assertEqual(2, len(self.course_nav.sections['Test Section']))
self.course_nav.go_to_section("Test Section", "Subsection With Locked Unit")
self.assertEqual(["Html Child in unlocked unit"], self.course_nav.sequence_items)
self.course_nav.go_to_section("Test Section", "Unlocked Subsection")
self.assertEqual(["Html Child in visible unit"], self.course_nav.sequence_items)
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