Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions cms/djangoapps/contentstore/features/course-outline.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,3 @@ def all_sections_are_collapsed_or_expanded(step, text):
def change_grading_status(step):
world.css_find('a.menu-toggle').click()
world.css_find('.menu li').first.click()


@step(u'I reorder subsections')
def reorder_subsections(_step):
draggable_css = '.subsection-drag-handle'
ele = world.css_find(draggable_css).first
ele.action_chains.drag_and_drop_by_offset(
ele._element,
0,
25
).perform()
4 changes: 2 additions & 2 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def find_xblock_info(xblock_info, locator):
"""
if xblock_info['id'] == locator:
return xblock_info
children = xblock_info['child_info']['children'] if xblock_info['child_info'] else None
children = xblock_info['child_info']['children'] if xblock_info.get('child_info', None) else None
if children:
for child_xblock_info in children:
result = find_xblock_info(child_xblock_info, locator)
Expand All @@ -295,7 +295,7 @@ def collect_all_locators(locators, xblock_info):
Collect all the locators for an xblock and its children.
"""
locators.append(xblock_info['id'])
children = xblock_info['child_info']['children'] if xblock_info['child_info'] else None
children = xblock_info['child_info']['children'] if xblock_info.get('child_info', None) else None
if children:
for child_xblock_info in children:
collect_all_locators(locators, child_xblock_info)
Expand Down
76 changes: 66 additions & 10 deletions cms/djangoapps/contentstore/views/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,10 +606,8 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
In addition, an optional include_children_predicate argument can be provided to define whether or
not a particular xblock should have its children included.
"""
published = modulestore().has_item(xblock.location, revision=ModuleStoreEnum.RevisionOption.published_only)

# Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
release_date = get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None

def safe_get_username(user_id):
"""
Expand All @@ -628,12 +626,27 @@ def safe_get_username(user_id):

return None

# Compute the child info first so it can be included in aggregate information for the parent
if include_child_info and xblock.has_children:
child_info = _create_xblock_child_info(
xblock, include_children_predicate=include_children_predicate
)
else:
child_info = None

visible_to_staff_only = _is_visible_to_staff_only(xblock, child_info)
release_date = get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None
currently_visible_to_students = is_currently_visible_to_students(xblock)
fully_visible_to_students = currently_visible_to_students
if fully_visible_to_students and not visible_to_staff_only and child_info:
fully_visible_to_students = all(
(info['fully_visible_to_students'] or info['visible_to_staff_only']) for info in child_info['children']
)

xblock_info = {
"id": unicode(xblock.location),
"display_name": xblock.display_name_with_default,
"category": xblock.category,
"has_changes": modulestore().has_changes(xblock.location),
"published": published,
"edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None,
"edited_by": safe_get_username(xblock.subtree_edited_by),
"published_on": get_default_time_display(xblock.published_date) if xblock.published_date else None,
Expand All @@ -642,22 +655,65 @@ def safe_get_username(user_id):
"released_to_students": datetime.now(UTC) > xblock.start,
"release_date": release_date,
"release_date_from": _get_release_date_from(xblock) if release_date else None,
"visible_to_staff_only": xblock.visible_to_staff_only,
"currently_visible_to_students": is_currently_visible_to_students(xblock),
"visible_to_staff_only": visible_to_staff_only,
"currently_visible_to_students": currently_visible_to_students,
"fully_published": _is_fully_published(xblock, child_info),
"fully_visible_to_students": fully_visible_to_students,
"has_unpublished_content": not visible_to_staff_only and _has_unpublished_content(xblock, child_info),
}
if data is not None:
xblock_info["data"] = data
if metadata is not None:
xblock_info["metadata"] = metadata
if include_ancestor_info:
xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock)
if include_child_info and xblock.has_children:
xblock_info['child_info'] = _create_xblock_child_info(
xblock, include_children_predicate=include_children_predicate
)
if child_info:
xblock_info['child_info'] = child_info
return xblock_info


def _is_visible_to_staff_only(xblock, child_info):
"""
Returns true if the specified xblock and all of its children are shown to staff only.
"""
if xblock.visible_to_staff_only:
return True
elif child_info and len(child_info['children']) > 0:
return all(info['visible_to_staff_only'] for info in child_info['children'])
return False


def _has_unpublished_content(xblock, child_info):
"""
Returns true if the xblock or its children have unpublished content (that is not staff only)
"""
if is_unit(xblock):
return modulestore().has_changes(xblock.location)
elif child_info:
return any(info['has_unpublished_content'] for info in child_info['children'])
else:
return False


def _is_fully_published(xblock, child_info):
"""
Returns true if the specified xblock and all of its children are published (or marked as staff only).
"""
if is_unit(xblock):
return (
modulestore().has_item(xblock.location, revision=ModuleStoreEnum.RevisionOption.published_only)
if is_unit(xblock)
else False
)
elif child_info and len(child_info['children']) > 0:
return all(
(info['fully_published'] or info['visible_to_staff_only']) for info in child_info['children']
)
else:
return False



def _create_xblock_ancestor_info(xblock):
"""
Returns information about the ancestors of an xblock. Note that the direct parent will also return
Expand Down
81 changes: 80 additions & 1 deletion cms/djangoapps/contentstore/views/tests/test_item.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Tests for items views."""

import json
from datetime import datetime
from datetime import datetime, timedelta
import ddt

from mock import patch
Expand Down Expand Up @@ -1248,3 +1248,82 @@ def validate_xblock_info_consistency(self, xblock_info, has_ancestor_info=False,
)
else:
self.assertIsNone(xblock_info.get('child_info', None))


class TestXBlockPublishingInfo(ItemTest):
"""
Unit tests for XBlock's outline handling.
"""
def _create_child(self, parent, category, display_name, publish_item=False):
return ItemFactory.create(
parent_location=parent.location, category=category, display_name=display_name,
user_id=self.user.id, publish_item=publish_item
)

def get_child(self, xblock_info, index):
"""
Returns the child at the specified index.
"""
children = xblock_info['child_info']['children']
self.assertTrue(len(children) > index)
return children[index]

def test_empty_chapter_publishing_info(self):
empty_chapter = self._create_child(self.course, 'chapter', "Empty Chapter")
xblock_info = create_xblock_info(
modulestore().get_item(empty_chapter.location),
include_child_info=True,
include_children_predicate=ALWAYS,
)
self.validate_publishing_info(xblock_info)

def test_empty_section_publishing_info(self):
chapter = self._create_child(self.course, 'chapter', "Test Chapter")
self._create_child(chapter, 'sequential', "Empty Sequential")
xblock_info = create_xblock_info(
modulestore().get_item(chapter.location),
include_child_info=True,
include_children_predicate=ALWAYS,
)
self.validate_publishing_info(xblock_info)
self.validate_publishing_info(self.get_child(xblock_info, 0))

def test_published_unit_publishing_info(self):
chapter = self._create_child(self.course, 'chapter', "Test Chapter")
sequential = self._create_child(chapter, 'sequential', "Test Sequential")
self._create_child(sequential, 'vertical', "Published Unit", publish_item=True)
xblock_info = create_xblock_info(
modulestore().get_item(chapter.location),
include_child_info=True,
include_children_predicate=ALWAYS,
)
self.validate_publishing_info(xblock_info, fully_published=True)
sequential_child_info = self.get_child(xblock_info, 0)
self.validate_publishing_info(sequential_child_info, fully_published=True)
unit_child_info = self.get_child(sequential_child_info, 0)
self.validate_publishing_info(unit_child_info, fully_published=True)

def test_released_unit_publishing_info(self):
chapter = self._create_child(self.course, 'chapter', "Test Chapter")
sequential = self._create_child(chapter, 'sequential', "Test Sequential")
unit = self._create_child(sequential, 'vertical', "Published Unit", publish_item=True)
chapter_block = modulestore().get_item(chapter.location)
chapter_block.start = datetime.now(UTC) - timedelta(days=1)
self.store.publish(unit.location, self.user.id)
xblock_info = create_xblock_info(
chapter_block,
include_child_info=True,
include_children_predicate=ALWAYS,
)
self.validate_publishing_info(xblock_info, fully_visible_to_students=True, fully_published=True)
sequential_child_info = self.get_child(xblock_info, 0)
self.validate_publishing_info(sequential_child_info, fully_visible_to_students=True, fully_published=True)
unit_child_info = self.get_child(sequential_child_info, 0)
self.validate_publishing_info(unit_child_info, fully_visible_to_students=True, fully_published=True)

def validate_publishing_info(self, xblock_info, fully_visible_to_students=False, fully_published=False,
is_staff_only=False, release_date=None):
self.assertEqual(xblock_info.get('fully_published', False), fully_published)
self.assertEqual(xblock_info.get('fully_visible_to_students', False), fully_visible_to_students)
self.assertEqual(xblock_info.get('visible_to_staff_only', False), is_staff_only)
self.assertEqual(xblock_info.get('release_date', False), release_date)
14 changes: 12 additions & 2 deletions cms/static/js/models/xblock_info.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,19 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
*/
"has_changes": null,
/**
* True iff a published version of the xblock exists.
* True if either:
* 1) The block itself has never been published.
* 2) At least one of the block's children has never been published.
*/
"published": null,
"has_unpublished_content": null,
/**
* True iff the xblock and all of its children are published (excluding staff-only items)
*/
"fully_published": null,
/**
* True iff the xblock and all of its children are visible to students (excluding staff-only items)
*/
"fully_visible_to_students": null,
/**
* If true, only course staff can see the xblock regardless of publish status or
* release date status.
Expand Down
Loading