Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
6e416310
Commit
6e416310
authored
Apr 11, 2017
by
cahrens
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Show messages about component visibility.
TNL-6746
parent
be5c4fad
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
272 additions
and
139 deletions
+272
-139
cms/djangoapps/contentstore/tests/test_utils.py
+11
-11
cms/djangoapps/contentstore/utils.py
+5
-5
cms/djangoapps/contentstore/views/item.py
+7
-2
cms/djangoapps/contentstore/views/preview.py
+6
-0
cms/static/js/models/xblock_info.js
+36
-36
cms/static/js/spec/views/pages/container_subviews_spec.js
+4
-4
cms/static/js/spec/views/pages/course_outline_spec.js
+34
-0
cms/static/js/views/pages/container_subviews.js
+44
-28
cms/static/sass/elements/_modules.scss
+2
-2
cms/static/sass/elements/_xblocks.scss
+13
-5
cms/templates/js/course-outline.underscore
+47
-26
cms/templates/js/publish-xblock.underscore
+25
-11
cms/templates/studio_xblock_wrapper.html
+15
-9
common/test/acceptance/pages/studio/container.py
+9
-0
common/test/acceptance/tests/studio/test_studio_container.py
+14
-0
No files found.
cms/djangoapps/contentstore/tests/test_utils.py
View file @
6e416310
...
...
@@ -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
):
...
...
cms/djangoapps/contentstore/utils.py
View file @
6e416310
...
...
@@ -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
...
...
cms/djangoapps/contentstore/views/item.py
View file @
6e416310
...
...
@@ -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
:
...
...
cms/djangoapps/contentstore/views/preview.py
View file @
6e416310
...
...
@@ -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
)
}
...
...
cms/static/js/models/xblock_info.js
View file @
6e416310
...
...
@@ -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
()
{
...
...
cms/static/js/spec/views/pages/container_subviews_spec.js
View file @
6e416310
...
...
@@ -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
);
});
});
...
...
cms/static/js/spec/views/pages/course_outline_spec.js
View file @
6e416310
...
...
@@ -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
({},
[
...
...
cms/static/js/views/pages/container_subviews.js
View file @
6e416310
...
...
@@ -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
;
}
...
...
cms/static/sass/elements/_modules.scss
View file @
6e416310
...
...
@@ -499,13 +499,13 @@ $outline-indent-width: $baseline;
}
// status - message
.status-message
{
.status-message
s
{
margin-top
:
(
$baseline
/
2
);
border-top
:
1px
solid
$gray-l4
;
padding-top
:
(
$baseline
/
4
);
.icon
{
margin-right
:
(
$baseline
/
4
);
@include
margin-right
(
$baseline
/
4
);
}
}
...
...
cms/static/sass/elements/_xblocks.scss
View file @
6e416310
...
...
@@ -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
);
}
}
...
...
cms/templates/js/course-outline.underscore
View file @
6e416310
...
...
@@ -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;
}
});
status
Type = 'gated';
statusMessage
= interpolate(
message
Type = '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) {
</p>
</div>
<% } %>
<% if (statusMessage) { %>
<div class="status-message">
<span class="icon fa <%- statusIconClass %>" aria-hidden="true"></span>
<p class="status-message-copy"><%- statusMessage %></p>
</div>
<% if (statusMessages.length > 0) { %>
<div class="status-messages">
<% for (var i=0; i<statusMessages.length; i++) { %>
<div class="status-message">
<span class="icon fa <%- statusMessages[i].iconClass %>" aria-hidden="true"></span>
<p class="status-message-copy"><%- statusMessages[i].text %></p>
</div>
<% } %>
</div>
<% } %>
</div>
<% } %>
...
...
cms/templates/js/publish-xblock.underscore
View file @
6e416310
...
...
@@ -26,16 +26,30 @@ var visibleToStaffOnly = visibilityState === 'staff_only';
<div class="wrapper-last-draft bar-mod-content">
<p class="copy meta">
<% if (hasChanges && editedOn && editedBy) {
var message = gettext("Draft saved on %(last_saved_date)s by %(edit_username)s") %>
<%= interpolate(_.escape(message), {
last_saved_date: '<span class="date">' + _.escape(editedOn) + '</span>',
edit_username: '<span class="user">' + _.escape(editedBy) + '</span>' }, true) %>
<% } else if (publishedOn && publishedBy) {
var message = gettext("Last published %(last_published_date)s by %(publish_username)s"); %>
<%= interpolate(_.escape(message), {
last_published_date: '<span class="date">' + _.escape(publishedOn) + '</span>',
publish_username: '<span class="user">' + _.escape(publishedBy) + '</span>' }, true) %>
<% if (hasChanges && editedOn && editedBy) { %>
<%= HtmlUtils.interpolateHtml(
gettext("Draft saved on {lastSavedStart}{editedOn}{lastSavedEnd} by {editedByStart}{editedBy}{editedByEnd}"),
{
lastSavedStart: HtmlUtils.HTML('<span class="date">'),
editedOn: editedOn,
lastSavedEnd: HtmlUtils.HTML('</span>'),
editedByStart: HtmlUtils.HTML('<span class="user">'),
editedBy: editedBy,
editedByEnd: HtmlUtils.HTML('</span>')
}
) %>
<% } else if (publishedOn && publishedBy) { %>
<%= HtmlUtils.interpolateHtml(
gettext("Last published {lastPublishedStart}{publishedOn}{lastPublishedEnd} by {publishedByStart}{publishedBy}{publishedByEnd}"),
{
lastPublishedStart: HtmlUtils.HTML('<span class="date">'),
publishedOn: publishedOn,
lastPublishedEnd: HtmlUtils.HTML('</span>'),
publishedByStart: HtmlUtils.HTML('<span class="user">'),
publishedBy: publishedBy,
publishedByEnd: HtmlUtils.HTML('</span>')
}
) %>
<% } else { %>
<%- gettext("Previously published") %>
<% } %>
...
...
@@ -83,7 +97,7 @@ var visibleToStaffOnly = visibilityState === 'staff_only';
<% } else { %>
<p class="visbility-copy copy"><%- gettext("Staff and Learners") %></p>
<% } %>
<% if (has
Content
GroupComponents) { %>
<% if (has
Partition
GroupComponents) { %>
<p class="note-visibility">
<span class="icon fa fa-eye" aria-hidden="true"></span>
<span class="note-copy"><%- gettext("Some content in this unit is visible only to specific groups of learners.") %></span>
...
...
cms/templates/studio_xblock_wrapper.html
View file @
6e416310
<
%
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:
<li
class=
"studio-xblock-wrapper is-draggable"
data-locator=
"${xblock.location
| h}"
data-course-key=
"${xblock.location.course_key | h
}"
>
<li
class=
"studio-xblock-wrapper is-draggable"
data-locator=
"${xblock.location
}"
data-course-key=
"${xblock.location.course_key
}"
>
% else:
<div
class=
"studio-xblock-wrapper"
data-locator=
"${xblock.location
| h}"
data-course-key=
"${xblock.location.course_key | h
}"
>
<div
class=
"studio-xblock-wrapper"
data-locator=
"${xblock.location
}"
data-course-key=
"${xblock.location.course_key
}"
>
% endif
<section
class=
"wrapper-xblock ${section_class} ${collapsible_class}
% if is_visible_to_specific_
content
_groups(xblock):
% if is_visible_to_specific_
partition
_groups(xblock):
has-group-visibility-set
% endif
"
>
...
...
@@ -61,7 +62,12 @@ messages = xblock.validate().to_json()
<span
class=
"sr"
>
${_('Expand or Collapse')}
</span>
</a>
% endif
<span
class=
"xblock-display-name"
>
${label | h}
</span>
<div
class=
"xblock-display-title"
>
<span
class=
"xblock-display-name"
>
${label}
</span>
% if selected_groups_label:
<p
class=
"xblock-group-visibility-label"
>
${selected_groups_label}
</p>
% endif
</div>
</div>
<div
class=
"header-actions"
>
<ul
class=
"actions-list"
>
...
...
@@ -128,7 +134,7 @@ messages = xblock.validate().to_json()
</div>
</div>
% if not is_root:
<div
class=
"wrapper-xblock-message xblock-validation-messages"
data-locator=
"${xblock.location
| h
}"
/>
<div
class=
"wrapper-xblock-message xblock-validation-messages"
data-locator=
"${xblock.location}"
/>
% if xblock_url:
<div
class=
"xblock-header-secondary"
>
<div
class=
"meta-info"
>
${_('This block contains multiple components.')}
</div>
...
...
@@ -147,17 +153,17 @@ messages = xblock.validate().to_json()
</header>
% if is_root:
<div
class=
"wrapper-xblock-message xblock-validation-messages"
data-locator=
"${xblock.location
| h
}"
/>
<div
class=
"wrapper-xblock-message xblock-validation-messages"
data-locator=
"${xblock.location}"
/>
% endif
% if show_preview:
% if is_root or not xblock_url:
<article
class=
"xblock-render"
>
${content}
${content
| n, decode.utf8
}
</article>
% else:
<div
class=
"xblock-message-area"
>
${content}
${content
| n, decode.utf8
}
</div>
% endif
% endif
...
...
common/test/acceptance/pages/studio/container.py
View file @
6e416310
...
...
@@ -524,6 +524,15 @@ class XBlockWrapper(PageObject):
"""
return
self
.
q
(
css
=
self
.
_bounded_selector
(
'.move-button'
))
.
is_present
()
@property
def
get_partition_group_message
(
self
):
"""
Returns the message about user partition group visibility, shown under the display name
(if not present, returns None).
"""
message
=
self
.
q
(
css
=
self
.
_bounded_selector
(
'.xblock-group-visibility-label'
))
return
None
if
len
(
message
)
==
0
else
message
.
first
.
text
[
0
]
def
go_to_container
(
self
):
"""
Open the container page linked to by this xblock, and return
...
...
common/test/acceptance/tests/studio/test_studio_container.py
View file @
6e416310
...
...
@@ -644,10 +644,22 @@ class EnrollmentTrackVisibilityModalTest(BaseGroupConfigurationsTest):
{
'group_access'
:
{
ENROLLMENT_TRACK_PARTITION_ID
:
[
2
]}}
# "2" is Verified
)
def
verify_component_group_visibility_messsage
(
self
,
component
,
expected_groups
):
"""
Verifies that the group visibility message below the component display name is correct.
"""
if
not
expected_groups
:
self
.
assertIsNone
(
component
.
get_partition_group_message
)
else
:
self
.
assertEqual
(
"Visible to: "
+
expected_groups
,
component
.
get_partition_group_message
)
def
test_setting_enrollment_tracks
(
self
):
"""
Test that enrollment track groups can be selected.
"""
# Verify that the "Verified" Group is shown on the unit page (under the unit display name).
self
.
verify_component_group_visibility_messsage
(
self
.
html_component
,
"Verified Track"
)
# Open dialog with "Verified" already selected.
visibility_editor
=
self
.
edit_component_visibility
(
self
.
html_component
)
self
.
verify_current_groups_message
(
visibility_editor
,
self
.
VERIFIED_TRACK
)
...
...
@@ -661,10 +673,12 @@ class EnrollmentTrackVisibilityModalTest(BaseGroupConfigurationsTest):
# Select "All Learners and Staff". The helper method saves the change,
# then reopens the dialog to verify that it was persisted.
self
.
select_and_verify_saved
(
self
.
html_component
,
self
.
ALL_LEARNERS_AND_STAFF
)
self
.
verify_component_group_visibility_messsage
(
self
.
html_component
,
None
)
# Select "Audit" enrollment track. The helper method saves the change,
# then reopens the dialog to verify that it was persisted.
self
.
select_and_verify_saved
(
self
.
html_component
,
self
.
ENROLLMENT_TRACK_PARTITION
,
[
self
.
AUDIT_TRACK
])
self
.
verify_component_group_visibility_messsage
(
self
.
html_component
,
"Audit Track"
)
@attr
(
shard
=
1
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment