From 6e416310b952e8a85271111b3c790257b8cc26f8 Mon Sep 17 00:00:00 2001
From: cahrens
Date: Tue, 11 Apr 2017 17:43:26 -0400
Subject: [PATCH] Show messages about component visibility.
TNL-6746
---
.../contentstore/tests/test_utils.py | 22 +++---
cms/djangoapps/contentstore/utils.py | 10 +--
cms/djangoapps/contentstore/views/item.py | 9 ++-
cms/djangoapps/contentstore/views/preview.py | 6 ++
cms/static/js/models/xblock_info.js | 72 +++++++++---------
.../views/pages/container_subviews_spec.js | 8 +-
.../spec/views/pages/course_outline_spec.js | 34 +++++++++
.../js/views/pages/container_subviews.js | 72 +++++++++++-------
cms/static/sass/elements/_modules.scss | 4 +-
cms/static/sass/elements/_xblocks.scss | 18 +++--
cms/templates/js/course-outline.underscore | 73 ++++++++++++-------
cms/templates/js/publish-xblock.underscore | 36 ++++++---
cms/templates/studio_xblock_wrapper.html | 24 +++---
.../test/acceptance/pages/studio/container.py | 9 +++
.../tests/studio/test_studio_container.py | 14 ++++
15 files changed, 272 insertions(+), 139 deletions(-)
diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py
index f9d58d5047..3c8bb283db 100644
--- a/cms/djangoapps/contentstore/tests/test_utils.py
+++ b/cms/djangoapps/contentstore/tests/test_utils.py
@@ -391,8 +391,8 @@ class GroupVisibilityTest(CourseTestCase):
def verify_all_components_visible_to_all(): # pylint: disable=invalid-name
""" Verifies when group_access has not been set on anything. """
for item in (self.sequential, self.vertical, self.html, self.problem):
- self.assertFalse(utils.has_children_visible_to_specific_content_groups(item))
- self.assertFalse(utils.is_visible_to_specific_content_groups(item))
+ self.assertFalse(utils.has_children_visible_to_specific_partition_groups(item))
+ self.assertFalse(utils.is_visible_to_specific_partition_groups(item))
verify_all_components_visible_to_all()
@@ -409,16 +409,16 @@ class GroupVisibilityTest(CourseTestCase):
self.set_group_access(self.vertical, {1: []})
self.set_group_access(self.problem, {2: [3, 4]})
- # Note that "has_children_visible_to_specific_content_groups" only checks immediate children.
- self.assertFalse(utils.has_children_visible_to_specific_content_groups(self.sequential))
- self.assertTrue(utils.has_children_visible_to_specific_content_groups(self.vertical))
- self.assertFalse(utils.has_children_visible_to_specific_content_groups(self.html))
- self.assertFalse(utils.has_children_visible_to_specific_content_groups(self.problem))
+ # Note that "has_children_visible_to_specific_partition_groups" only checks immediate children.
+ self.assertFalse(utils.has_children_visible_to_specific_partition_groups(self.sequential))
+ self.assertTrue(utils.has_children_visible_to_specific_partition_groups(self.vertical))
+ self.assertFalse(utils.has_children_visible_to_specific_partition_groups(self.html))
+ self.assertFalse(utils.has_children_visible_to_specific_partition_groups(self.problem))
- self.assertTrue(utils.is_visible_to_specific_content_groups(self.sequential))
- self.assertFalse(utils.is_visible_to_specific_content_groups(self.vertical))
- self.assertFalse(utils.is_visible_to_specific_content_groups(self.html))
- self.assertTrue(utils.is_visible_to_specific_content_groups(self.problem))
+ self.assertTrue(utils.is_visible_to_specific_partition_groups(self.sequential))
+ self.assertFalse(utils.is_visible_to_specific_partition_groups(self.vertical))
+ self.assertFalse(utils.is_visible_to_specific_partition_groups(self.html))
+ self.assertTrue(utils.is_visible_to_specific_partition_groups(self.problem))
class GetUserPartitionInfoTest(ModuleStoreTestCase):
diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py
index 4a97d056d4..a414f5815c 100644
--- a/cms/djangoapps/contentstore/utils.py
+++ b/cms/djangoapps/contentstore/utils.py
@@ -163,24 +163,24 @@ def is_currently_visible_to_students(xblock):
return True
-def has_children_visible_to_specific_content_groups(xblock):
+def has_children_visible_to_specific_partition_groups(xblock):
"""
- Returns True if this xblock has children that are limited to specific content groups.
+ Returns True if this xblock has children that are limited to specific user partition groups.
Note that this method is not recursive (it does not check grandchildren).
"""
if not xblock.has_children:
return False
for child in xblock.get_children():
- if is_visible_to_specific_content_groups(child):
+ if is_visible_to_specific_partition_groups(child):
return True
return False
-def is_visible_to_specific_content_groups(xblock):
+def is_visible_to_specific_partition_groups(xblock):
"""
- Returns True if this xblock has visibility limited to specific content groups.
+ Returns True if this xblock has visibility limited to specific user partition groups.
"""
if not xblock.group_access:
return False
diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py
index 0da2dd07a5..0abd4970cf 100644
--- a/cms/djangoapps/contentstore/views/item.py
+++ b/cms/djangoapps/contentstore/views/item.py
@@ -28,7 +28,7 @@ from xblock_django.user_service import DjangoXBlockUserService
from cms.lib.xblock.authoring_mixin import VISIBILITY_VIEW
from contentstore.utils import (
find_release_date_source, find_staff_lock_source, is_currently_visible_to_students,
- ancestor_has_staff_lock, has_children_visible_to_specific_content_groups,
+ ancestor_has_staff_lock, has_children_visible_to_specific_partition_groups,
get_user_partition_info, get_split_group_display_name,
)
from contentstore.views.helpers import is_unit, xblock_studio_url, xblock_primary_child_category, \
@@ -1005,6 +1005,7 @@ def _get_module_info(xblock, rewrite_static_links=True, include_ancestor_info=Fa
)
if include_publishing_info:
add_container_page_publishing_info(xblock, xblock_info)
+
return xblock_info
@@ -1217,6 +1218,10 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
)
else:
xblock_info['staff_only_message'] = False
+
+ xblock_info["has_partition_group_components"] = has_children_visible_to_specific_partition_groups(
+ xblock
+ )
return xblock_info
@@ -1245,7 +1250,7 @@ def add_container_page_publishing_info(xblock, xblock_info): # pylint: disable=
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_content_group_components"] = has_children_visible_to_specific_content_groups(xblock)
+ xblock_info["has_partition_group_components"] = has_children_visible_to_specific_partition_groups(xblock)
if xblock_info["release_date"]:
xblock_info["release_date_from"] = _get_release_date_from(xblock)
if xblock_info["visibility_state"] == VisibilityState.staff_only:
diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py
index 361b6817a5..17699427a9 100644
--- a/cms/djangoapps/contentstore/views/preview.py
+++ b/cms/djangoapps/contentstore/views/preview.py
@@ -7,6 +7,7 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponseBadRequest
from django.contrib.auth.decorators import login_required
+from django.utils.translation import ugettext as _
from edxmako.shortcuts import render_to_string
from openedx.core.lib.xblock_utils import (
@@ -38,6 +39,7 @@ import static_replace
from .session_kv_store import SessionKeyValueStore
from .helpers import render_from_lms
+from contentstore.utils import get_visibility_partition_info
from contentstore.views.access import get_user_role
from xblock_config.models import StudioConfig
@@ -279,6 +281,9 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
root_xblock = context.get('root_xblock')
is_root = root_xblock and xblock.location == root_xblock.location
is_reorderable = _is_xblock_reorderable(xblock, context)
+ selected_groups_label = get_visibility_partition_info(xblock)['selected_groups_label']
+ if selected_groups_label:
+ selected_groups_label = _('Visible to: {list_of_groups}').format(list_of_groups=selected_groups_label)
template_context = {
'xblock_context': context,
'xblock': xblock,
@@ -288,6 +293,7 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
'is_reorderable': is_reorderable,
'can_edit': context.get('can_edit', True),
'can_edit_visibility': context.get('can_edit_visibility', True),
+ 'selected_groups_label': selected_groups_label,
'can_add': context.get('can_add', True),
'can_move': context.get('can_move', True)
}
diff --git a/cms/static/js/models/xblock_info.js b/cms/static/js/models/xblock_info.js
index 7c44a07216..e55bd2c4d9 100644
--- a/cms/static/js/models/xblock_info.js
+++ b/cms/static/js/models/xblock_info.js
@@ -9,46 +9,46 @@ function(Backbone, _, str, ModuleUtils) {
// NOTE: 'publish' is not an attribute on XBlockInfo, but it is used to signal the publish
// and discard changes actions. Therefore 'publish' cannot be introduced as an attribute.
defaults: {
- 'id': null,
- 'display_name': null,
- 'category': null,
- 'data': null,
- 'metadata': null,
+ id: null,
+ display_name: null,
+ category: null,
+ data: null,
+ metadata: null,
/**
* The Studio URL for this xblock, or null if it doesn't have one.
*/
- 'studio_url': null,
+ studio_url: null,
/**
* An optional object with information about the children as well as about
* the primary xblock type that is supported as a child.
*/
- 'child_info': null,
+ child_info: null,
/**
* An optional object with information about each of the ancestors.
*/
- 'ancestor_info': null,
+ ancestor_info: null,
/**
* Date of the last edit to this xblock or any of its descendants.
*/
- 'edited_on': null,
+ edited_on: null,
/**
* User who last edited the xblock or any of its descendants. Will only be present if
* publishing info was explicitly requested.
*/
- 'edited_by': null,
+ edited_by: null,
/**
* True iff a published version of the xblock exists.
*/
- 'published': null,
+ published: null,
/**
* Date of the last publish of this xblock, or null if never published.
*/
- 'published_on': null,
+ published_on: null,
/**
* User who last published the xblock, or null if never published. Will only be present if
* publishing info was explicitly requested.
*/
- 'published_by': null,
+ published_by: null,
/**
* True if the xblock is a parentable xblock.
*/
@@ -58,108 +58,108 @@ function(Backbone, _, str, ModuleUtils) {
* Note: this is not always provided as a performance optimization. It is only provided for
* verticals functioning as units.
*/
- 'has_changes': null,
+ has_changes: null,
/**
* Represents the possible publish states for an xblock. See the documentation
* for XBlockVisibility to see a comprehensive enumeration of the states.
*/
- 'visibility_state': null,
+ visibility_state: null,
/**
* True if the release date of the xblock is in the past.
*/
- 'released_to_students': null,
+ released_to_students: null,
/**
* If the xblock is published, the date on which it will be released to students.
* This can be null if the release date is unscheduled.
*/
- 'release_date': null,
+ release_date: null,
/**
* The xblock which is determining the release date. For instance, for a unit,
* this will either be the parent subsection or the grandparent section.
* This can be null if the release date is unscheduled. Will only be present if
* publishing info was explicitly requested.
*/
- '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. Will only be present if
* publishing info was explicitly requested.
*/
- 'currently_visible_to_students': null,
+ currently_visible_to_students: null,
/**
* If xblock is graded, the date after which student assessment will be evaluated.
* It has same format as release date, for example: 'Jan 02, 2015 at 00:00 UTC'.
*/
- 'due_date': null,
+ due_date: null,
/**
* Grading policy for xblock.
*/
- 'format': null,
+ format: null,
/**
* List of course graders names.
*/
- 'course_graders': null,
+ course_graders: null,
/**
* True if this xblock contributes to the final course grade.
*/
- 'graded': null,
+ graded: null,
/**
* The same as `release_date` but as an ISO-formatted date string.
*/
- 'start': null,
+ start: null,
/**
* The same as `due_date` but as an ISO-formatted date string.
*/
- 'due': null,
+ due: null,
/**
* True iff this xblock is explicitly staff locked.
*/
- 'has_explicit_staff_lock': null,
+ has_explicit_staff_lock: null,
/**
* True iff this any of this xblock's ancestors are staff locked.
*/
- 'ancestor_has_staff_lock': null,
+ ancestor_has_staff_lock: null,
/**
* The xblock which is determining the staff lock value. For instance, for a unit,
* this will either be the parent subsection or the grandparent section.
* This can be null if the xblock has no inherited staff lock. Will only be present if
* publishing info was explicitly requested.
*/
- 'staff_lock_from': null,
+ staff_lock_from: null,
/**
* True iff this xblock should display a "Contains staff only content" message.
*/
- 'staff_only_message': null,
+ staff_only_message: null,
/**
* True iff this xblock is a unit, and it has children that are only visible to certain
- * content groups. Note that this is not a recursive property. Will only be present if
+ * user partition groups. Note that this is not a recursive property. Will only be present if
* publishing info was explicitly requested.
*/
- 'has_content_group_components': null,
+ has_partition_group_components: null,
/**
* actions defines the state of delete, drag and child add functionality for a xblock.
* currently, each xblock has default value of 'True' for keys: deletable, draggable and childAddable.
*/
- 'actions': null,
+ actions: null,
/**
* Header visible to UI.
*/
- 'is_header_visible': null,
+ is_header_visible: null,
/**
* Optional explanatory message about the xblock.
*/
- 'explanatory_message': null,
+ explanatory_message: null,
/**
* The XBlock's group access rules. This is a dictionary keyed to user partition IDs,
* where the values are lists of group IDs.
*/
- 'group_access': null,
+ group_access: null,
/**
* User partition dictionary. This is pre-processed by Studio, so it contains
* some additional fields that are not stored in the course descriptor
* (for example, which groups are selected for this particular XBlock).
*/
- 'user_partitions': null
+ user_partitions: null
},
initialize: function() {
diff --git a/cms/static/js/spec/views/pages/container_subviews_spec.js b/cms/static/js/spec/views/pages/container_subviews_spec.js
index 13bfc8d3e2..cc1d6cf8c5 100644
--- a/cms/static/js/spec/views/pages/container_subviews_spec.js
+++ b/cms/static/js/spec/views/pages/container_subviews_spec.js
@@ -109,15 +109,15 @@ define(['jquery', 'underscore', 'underscore.string', 'edx-ui-toolkit/js/utils/sp
expect(containerPage.$(viewPublishedCss)).toHaveClass(disabledCss);
});
- it('updates when has_content_group_components attribute changes', function() {
+ it('updates when has_partition_group_components attribute changes', function() {
renderContainerPage(this, mockContainerXBlockHtml);
- fetch({has_content_group_components: false});
+ fetch({has_partition_group_components: false});
expect(containerPage.$(visibilityNoteCss).length).toBe(0);
- fetch({has_content_group_components: true});
+ fetch({has_partition_group_components: true});
expect(containerPage.$(visibilityNoteCss).length).toBe(1);
- fetch({has_content_group_components: false});
+ fetch({has_partition_group_components: false});
expect(containerPage.$(visibilityNoteCss).length).toBe(0);
});
});
diff --git a/cms/static/js/spec/views/pages/course_outline_spec.js b/cms/static/js/spec/views/pages/course_outline_spec.js
index ff49fd0060..f811fc4bd3 100644
--- a/cms/static/js/spec/views/pages/course_outline_spec.js
+++ b/cms/static/js/spec/views/pages/course_outline_spec.js
@@ -1452,6 +1452,19 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
// Note: most tests for units can be found in Bok Choy
describe('Unit', function() {
+ var getUnitStatus = function(options) {
+ mockCourseJSON = createMockCourseJSON({}, [
+ createMockSectionJSON({}, [
+ createMockSubsectionJSON({}, [
+ createMockVerticalJSON(options)
+ ])
+ ])
+ ]);
+ createCourseOutlinePage(this, mockCourseJSON);
+ expandItemsAndVerifyState('subsection');
+ return getItemsOfType('unit').find('.unit-status .status-message');
+ };
+
it('can be deleted', function() {
var promptSpy = EditHelpers.createPromptSpy();
createCourseOutlinePage(this, mockCourseJSON);
@@ -1473,6 +1486,27 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
expect(unitAnchor.attr('href')).toBe('/container/mock-unit');
});
+ it('shows partition group information', function() {
+ var messages = getUnitStatus({has_partition_group_components: true});
+ expect(messages.length).toBe(1);
+ expect(messages).toContainText(
+ 'Some content in this unit is visible only to specific groups of learners'
+ );
+ });
+
+ it('does not show partition group information if visible to all', function() {
+ var messages = getUnitStatus({});
+ expect(messages.length).toBe(0);
+ });
+
+ it('does not show partition group information if staff locked', function() {
+ var messages = getUnitStatus(
+ {has_partition_group_components: true, staff_only_message: true}
+ );
+ expect(messages.length).toBe(1);
+ expect(messages).toContainText('Contains staff only content');
+ });
+
verifyTypePublishable('unit', function(options) {
return createMockCourseJSON({}, [
createMockSectionJSON({}, [
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index 0a0e028b23..78a9670c59 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -2,8 +2,8 @@
* Subviews (usually small side panels) for XBlockContainerPage.
*/
define(['jquery', 'underscore', 'gettext', 'js/views/baseview', 'common/js/components/utils/view_utils',
- 'js/views/utils/xblock_utils', 'js/views/utils/move_xblock_utils'],
- function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils) {
+ 'js/views/utils/xblock_utils', 'js/views/utils/move_xblock_utils', 'edx-ui-toolkit/js/utils/html-utils'],
+ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, HtmlUtils) {
'use strict';
var disabledCss = 'is-disabled';
@@ -43,9 +43,12 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview', 'common/js/compo
},
render: function() {
- this.$el.html(this.template({
- currentlyVisibleToStudents: this.model.get('currently_visible_to_students')
- }));
+ HtmlUtils.setHtml(
+ this.$el,
+ HtmlUtils.HTML(
+ this.template({currentlyVisibleToStudents: this.model.get('currently_visible_to_students')})
+ )
+ );
return this;
}
});
@@ -95,30 +98,38 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview', 'common/js/compo
onSync: function(model) {
if (ViewUtils.hasChangedAttributes(model, [
'has_changes', 'published', 'edited_on', 'edited_by', 'visibility_state',
- 'has_explicit_staff_lock', 'has_content_group_components'
+ 'has_explicit_staff_lock', 'has_partition_group_components'
])) {
this.render();
}
},
render: function() {
- this.$el.html(this.template({
- visibilityState: this.model.get('visibility_state'),
- visibilityClass: XBlockViewUtils.getXBlockVisibilityClass(this.model.get('visibility_state')),
- hasChanges: this.model.get('has_changes'),
- editedOn: this.model.get('edited_on'),
- editedBy: this.model.get('edited_by'),
- published: this.model.get('published'),
- publishedOn: this.model.get('published_on'),
- publishedBy: this.model.get('published_by'),
- released: this.model.get('released_to_students'),
- releaseDate: this.model.get('release_date'),
- releaseDateFrom: this.model.get('release_date_from'),
- hasExplicitStaffLock: this.model.get('has_explicit_staff_lock'),
- staffLockFrom: this.model.get('staff_lock_from'),
- hasContentGroupComponents: this.model.get('has_content_group_components'),
- course: window.course
- }));
+ HtmlUtils.setHtml(
+ this.$el,
+ HtmlUtils.HTML(
+ this.template({
+ visibilityState: this.model.get('visibility_state'),
+ visibilityClass: XBlockViewUtils.getXBlockVisibilityClass(
+ this.model.get('visibility_state')
+ ),
+ hasChanges: this.model.get('has_changes'),
+ editedOn: this.model.get('edited_on'),
+ editedBy: this.model.get('edited_by'),
+ published: this.model.get('published'),
+ publishedOn: this.model.get('published_on'),
+ publishedBy: this.model.get('published_by'),
+ released: this.model.get('released_to_students'),
+ releaseDate: this.model.get('release_date'),
+ releaseDateFrom: this.model.get('release_date_from'),
+ hasExplicitStaffLock: this.model.get('has_explicit_staff_lock'),
+ staffLockFrom: this.model.get('staff_lock_from'),
+ hasPartitionGroupComponents: this.model.get('has_partition_group_components'),
+ course: window.course,
+ HtmlUtils: HtmlUtils
+ })
+ )
+ );
return this;
},
@@ -243,11 +254,16 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview', 'common/js/compo
},
render: function() {
- this.$el.html(this.template({
- published: this.model.get('published'),
- published_on: this.model.get('published_on'),
- published_by: this.model.get('published_by')
- }));
+ HtmlUtils.setHtml(
+ this.$el,
+ HtmlUtils.HTML(
+ this.template({
+ published: this.model.get('published'),
+ published_on: this.model.get('published_on'),
+ published_by: this.model.get('published_by')
+ })
+ )
+ );
return this;
}
diff --git a/cms/static/sass/elements/_modules.scss b/cms/static/sass/elements/_modules.scss
index af82c1c87f..6c5abbf767 100644
--- a/cms/static/sass/elements/_modules.scss
+++ b/cms/static/sass/elements/_modules.scss
@@ -499,13 +499,13 @@ $outline-indent-width: $baseline;
}
// status - message
- .status-message {
+ .status-messages {
margin-top: ($baseline/2);
border-top: 1px solid $gray-l4;
padding-top: ($baseline/4);
.icon {
- margin-right: ($baseline/4);
+ @include margin-right($baseline/4);
}
}
diff --git a/cms/static/sass/elements/_xblocks.scss b/cms/static/sass/elements/_xblocks.scss
index fe789bfe2f..b162ce567e 100644
--- a/cms/static/sass/elements/_xblocks.scss
+++ b/cms/static/sass/elements/_xblocks.scss
@@ -13,6 +13,8 @@
// * +Editing - Xblocks
// * +Case - Special Xblock Type Overrides
+@import 'edx-pattern-library-shims/base/variables';
+
// +Layout - Xblocks
// ====================
@@ -37,23 +39,29 @@
min-height: ($baseline*2.5);
background-color: $gray-l6;
padding: ($baseline/2) ($baseline/2) ($baseline/2) ($baseline);
+ display: flex;
+ align-items: center;
.header-details {
@extend %cont-truncated;
- display: inline-block;
width: 50%;
vertical-align: middle;
.xblock-display-name {
- display: inline-block;
- vertical-align: middle;
+ @extend %t-copy-lead1;
+ font-weight: font-weight(semi-bold);
+ }
+
+ .xblock-group-visibility-label {
+ @extend %t-copy-sub1;
+ white-space: normal;
+ font-weight: font-weight(semi-bold);
+ color: $gray;
}
}
.header-actions {
- display: inline-block;
width: 49%;
- vertical-align: middle;
@include text-align(right);
}
}
diff --git a/cms/templates/js/course-outline.underscore b/cms/templates/js/course-outline.underscore
index 4e1418a658..4bc6ab6fcc 100644
--- a/cms/templates/js/course-outline.underscore
+++ b/cms/templates/js/course-outline.underscore
@@ -3,9 +3,27 @@ var releasedToStudents = xblockInfo.get('released_to_students');
var visibilityState = xblockInfo.get('visibility_state');
var published = xblockInfo.get('published');
var prereq = xblockInfo.get('prereq');
+var hasPartitionGroups = xblockInfo.get('has_partition_group_components');
-var statusMessage = null;
+var statusMessages = [];
+var messageType;
+var messageText;
var statusType = null;
+var addStatusMessage = function (statusType, message) {
+ var statusIconClass = '';
+ if (statusType === 'warning') {
+ statusIconClass = 'fa-file-o';
+ } else if (statusType === 'error') {
+ statusIconClass = 'fa-warning';
+ } else if (statusType === 'staff-only' || statusType === 'gated') {
+ statusIconClass = 'fa-lock';
+ } else if (statusType === 'partition-groups') {
+ statusIconClass = 'fa-eye';
+ }
+
+ statusMessages.push({iconClass: statusIconClass, text: message});
+};
+
if (prereq) {
var prereqDisplayName = '';
_.each(xblockInfo.get('prereqs'), function (p) {
@@ -14,38 +32,37 @@ if (prereq) {
return false;
}
});
- statusType = 'gated';
- statusMessage = interpolate(
+ messageType = 'gated';
+ messageText = interpolate(
gettext('Prerequisite: %(prereq_display_name)s'),
{prereq_display_name: prereqDisplayName},
true
);
+ addStatusMessage(messageType, messageText);
}
if (staffOnlyMessage) {
- statusType = 'staff-only';
- statusMessage = gettext('Contains staff only content');
-} else if (visibilityState === 'needs_attention') {
- if (xblockInfo.isVertical()) {
- statusType = 'warning';
+ messageType = 'staff-only';
+ messageText = gettext('Contains staff only content');
+ addStatusMessage(messageType, messageText);
+} else {
+ if (visibilityState === 'needs_attention' && xblockInfo.isVertical()) {
+ messageType = 'warning';
if (published && releasedToStudents) {
- statusMessage = gettext('Unpublished changes to live content');
+ messageText = gettext('Unpublished changes to live content');
} else if (!published) {
- statusMessage = gettext('Unpublished units will not be released');
+ messageText = gettext('Unpublished units will not be released');
} else {
- statusMessage = gettext('Unpublished changes to content that will release in the future');
+ messageText = gettext('Unpublished changes to content that will release in the future');
}
+ addStatusMessage(messageType, messageText);
}
-}
-var statusIconClass = '';
-if (statusType === 'warning') {
- statusIconClass = 'fa-file-o';
-} else if (statusType === 'error') {
- statusIconClass = 'fa-warning';
-} else if (statusType === 'staff-only') {
- statusIconClass = 'fa-lock';
-} else if (statusType === 'gated') {
- statusIconClass = 'fa-lock';
+ if (hasPartitionGroups) {
+ addStatusMessage(
+ 'partition-groups',
+ gettext('Some content in this unit is visible only to specific groups of learners')
+ );
+ }
}
var gradingType = gettext('Ungraded');
@@ -211,11 +228,15 @@ if (is_proctored_exam) {
<% } %>
- <% if (statusMessage) { %>
-
-
-
<%- statusMessage %>
-
+ <% if (statusMessages.length > 0) { %>
+
+ <% for (var i=0; i
+
+
+
<%- statusMessages[i].text %>
+
+ <% } %>
+
<% } %>
<% } %>
diff --git a/cms/templates/js/publish-xblock.underscore b/cms/templates/js/publish-xblock.underscore
index cab0f43246..203c9c3825 100644
--- a/cms/templates/js/publish-xblock.underscore
+++ b/cms/templates/js/publish-xblock.underscore
@@ -26,16 +26,30 @@ var visibleToStaffOnly = visibilityState === 'staff_only';
- <% if (hasChanges && editedOn && editedBy) {
- var message = gettext("Draft saved on %(last_saved_date)s by %(edit_username)s") %>
- <%= interpolate(_.escape(message), {
- last_saved_date: '' + _.escape(editedOn) + '',
- edit_username: '' + _.escape(editedBy) + '' }, true) %>
- <% } else if (publishedOn && publishedBy) {
- var message = gettext("Last published %(last_published_date)s by %(publish_username)s"); %>
- <%= interpolate(_.escape(message), {
- last_published_date: '' + _.escape(publishedOn) + '',
- publish_username: '' + _.escape(publishedBy) + '' }, true) %>
+ <% if (hasChanges && editedOn && editedBy) { %>
+ <%= HtmlUtils.interpolateHtml(
+ gettext("Draft saved on {lastSavedStart}{editedOn}{lastSavedEnd} by {editedByStart}{editedBy}{editedByEnd}"),
+ {
+ lastSavedStart: HtmlUtils.HTML(''),
+ editedOn: editedOn,
+ lastSavedEnd: HtmlUtils.HTML(''),
+ editedByStart: HtmlUtils.HTML(''),
+ editedBy: editedBy,
+ editedByEnd: HtmlUtils.HTML('')
+ }
+ ) %>
+ <% } else if (publishedOn && publishedBy) { %>
+ <%= HtmlUtils.interpolateHtml(
+ gettext("Last published {lastPublishedStart}{publishedOn}{lastPublishedEnd} by {publishedByStart}{publishedBy}{publishedByEnd}"),
+ {
+ lastPublishedStart: HtmlUtils.HTML(''),
+ publishedOn: publishedOn,
+ lastPublishedEnd: HtmlUtils.HTML(''),
+ publishedByStart: HtmlUtils.HTML(''),
+ publishedBy: publishedBy,
+ publishedByEnd: HtmlUtils.HTML('')
+ }
+ ) %>
<% } else { %>
<%- gettext("Previously published") %>
<% } %>
@@ -83,7 +97,7 @@ var visibleToStaffOnly = visibilityState === 'staff_only';
<% } else { %>
<%- gettext("Staff and Learners") %>
<% } %>
- <% if (hasContentGroupComponents) { %>
+ <% if (hasPartitionGroupComponents) { %>
<%- gettext("Some content in this unit is visible only to specific groups of learners.") %>
diff --git a/cms/templates/studio_xblock_wrapper.html b/cms/templates/studio_xblock_wrapper.html
index 969e4e7af7..6cba8735dc 100644
--- a/cms/templates/studio_xblock_wrapper.html
+++ b/cms/templates/studio_xblock_wrapper.html
@@ -1,7 +1,8 @@
+<%page expression_filter="h"/>
<%!
from django.utils.translation import ugettext as _
from contentstore.views.helpers import xblock_studio_url
-from contentstore.utils import is_visible_to_specific_content_groups
+from contentstore.utils import is_visible_to_specific_partition_groups
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
@@ -36,13 +37,13 @@ messages = xblock.validate().to_json()
% if not is_root:
% if is_reorderable:
-
+
% else:
-
+
% endif
@@ -61,7 +62,12 @@ messages = xblock.validate().to_json()
${_('Expand or Collapse')}
% endif
- ${label | h}
+
+
${label}
+ % if selected_groups_label:
+
${selected_groups_label}
+ % endif
+
% if not is_root:
-
+
% if xblock_url: