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
2 changes: 1 addition & 1 deletion cms/djangoapps/contentstore/tests/test_contentstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -1208,7 +1208,7 @@ def test_course_overview_view_with_course(self):
resp = self._show_course_overview(course.id)
self.assertContains(
resp,
'<article class="course-outline" data-locator="{locator}" data-course-key="{course_key}">'.format(
'<article class="outline" data-locator="{locator}" data-course-key="{course_key}">'.format(
locator='i4x://MITx/999/course/Robot_Super_Course',
course_key='MITx/999/Robot_Super_Course',
),
Expand Down
28 changes: 14 additions & 14 deletions cms/djangoapps/contentstore/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.contrib.auth.models import User

from xmodule.contentstore.django import contentstore
from xmodule.modulestore import PublishState, ModuleStoreEnum, mongo
from xmodule.modulestore import LegacyPublishState, ModuleStoreEnum, mongo
from xmodule.modulestore.inheritance import own_metadata
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
Expand Down Expand Up @@ -149,16 +149,16 @@ def import_and_populate_course(self):
# create a Draft vertical
vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1)
draft_vertical = self.store.convert_to_draft(vertical.location, self.user.id)
self.assertEqual(self.store.compute_publish_state(draft_vertical), PublishState.draft)
self.assertEqual(self.store.compute_publish_state(draft_vertical), LegacyPublishState.draft)

# create a Private (draft only) vertical
private_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PRIVATE_VERTICAL)
self.assertEqual(self.store.compute_publish_state(private_vertical), PublishState.private)
self.assertEqual(self.store.compute_publish_state(private_vertical), LegacyPublishState.private)

# create a Published (no draft) vertical
public_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PUBLISHED_VERTICAL)
public_vertical = self.store.publish(public_vertical.location, self.user.id)
self.assertEqual(self.store.compute_publish_state(public_vertical), PublishState.public)
self.assertEqual(self.store.compute_publish_state(public_vertical), LegacyPublishState.public)

# add the new private and new public as children of the sequential
sequential = self.store.get_item(course_id.make_usage_key('sequential', self.SEQUENTIAL))
Expand Down Expand Up @@ -195,7 +195,7 @@ def check_populated_course(self, course_id):

def verify_item_publish_state(item, publish_state):
"""Verifies the publish state of the item is as expected."""
if publish_state in (PublishState.private, PublishState.draft):
if publish_state in (LegacyPublishState.private, LegacyPublishState.draft):
self.assertTrue(getattr(item, 'is_draft', False))
else:
self.assertFalse(getattr(item, 'is_draft', False))
Expand All @@ -208,18 +208,18 @@ def get_and_verify_publish_state(item_type, item_name, publish_state):
return item

# verify that the draft vertical is draft
vertical = get_and_verify_publish_state('vertical', self.TEST_VERTICAL, PublishState.draft)
vertical = get_and_verify_publish_state('vertical', self.TEST_VERTICAL, LegacyPublishState.draft)
for child in vertical.get_children():
verify_item_publish_state(child, PublishState.draft)
verify_item_publish_state(child, LegacyPublishState.draft)

# make sure that we don't have a sequential that is not in draft mode
sequential = get_and_verify_publish_state('sequential', self.SEQUENTIAL, PublishState.public)
sequential = get_and_verify_publish_state('sequential', self.SEQUENTIAL, LegacyPublishState.public)

# verify that we have the private vertical
private_vertical = get_and_verify_publish_state('vertical', self.PRIVATE_VERTICAL, PublishState.private)
private_vertical = get_and_verify_publish_state('vertical', self.PRIVATE_VERTICAL, LegacyPublishState.private)

# verify that we have the public vertical
public_vertical = get_and_verify_publish_state('vertical', self.PUBLISHED_VERTICAL, PublishState.public)
public_vertical = get_and_verify_publish_state('vertical', self.PUBLISHED_VERTICAL, LegacyPublishState.public)

# verify verticals are children of sequential
for vert in [vertical, private_vertical, public_vertical]:
Expand Down Expand Up @@ -338,7 +338,7 @@ def compute_real_state(self, item):
it'll return public in that case
"""
supposed_state = self.store.compute_publish_state(item)
if supposed_state == PublishState.draft and isinstance(item.runtime.modulestore, DraftModuleStore):
if supposed_state == LegacyPublishState.draft and isinstance(item.runtime.modulestore, DraftModuleStore):
# see if the draft differs from the published
published = self.store.get_item(item.location, revision=ModuleStoreEnum.RevisionOption.published_only)
if item.get_explicitly_set_fields_by_scope() != published.get_explicitly_set_fields_by_scope():
Expand All @@ -351,13 +351,13 @@ def compute_real_state(self, item):
# checking children: if published differs from item, return draft
return supposed_state
# published == item in all respects, so return public
return PublishState.public
elif supposed_state == PublishState.public and item.location.category in mongo.base.DIRECT_ONLY_CATEGORIES:
return LegacyPublishState.public
elif supposed_state == LegacyPublishState.public and item.location.category in mongo.base.DIRECT_ONLY_CATEGORIES:
if not all([
self.store.has_item(child_loc, revision=ModuleStoreEnum.RevisionOption.draft_only)
for child_loc in item.children
]):
return PublishState.draft
return LegacyPublishState.draft
else:
return supposed_state
else:
Expand Down
4 changes: 2 additions & 2 deletions cms/djangoapps/contentstore/views/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from edxmako.shortcuts import render_to_response

from xmodule.modulestore.django import modulestore
from xmodule.modulestore import PublishState
from xmodule.modulestore import LegacyPublishState

from xblock.core import XBlock
from xblock.django.request import webob_to_django_response, django_to_webob_request
Expand Down Expand Up @@ -123,7 +123,7 @@ def subsection_handler(request, usage_key_string):
subsection_units = item.get_children()
for unit in subsection_units:
state = compute_publish_state(unit)
if state in (PublishState.public, PublishState.draft):
if state in (LegacyPublishState.public, LegacyPublishState.draft):
can_view_live = True
break

Expand Down
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
121 changes: 111 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,21 @@ 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

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)

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 +649,116 @@ 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),
"currently_visible_to_students": currently_visible_to_students,
"publish_state": _compute_publish_state(xblock, child_info) if not xblock.category == 'course' else None
}
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


class PublishState(object):
"""
Represents the possible publish states for an xblock:
live - the block and all of its children are live to students (except for staff only items)
ready - the block and all of its children are ready to go live in the future
unscheduled - the block and all of its children are unscheduled
has_unpublished_content - the block or its children have unpublished content that is not staff only
staff_only - all of the block's content is to be shown to staff only
"""
live = 'live'
ready = 'ready'
unscheduled = 'unscheduled'
has_unpublished_content = 'has_unpublished_content'
staff_only = 'staff_only'


def _compute_publish_state(xblock, child_info):
"""
Returns the current publish state for the specified xblock and its children
"""
if xblock.visible_to_staff_only:
return PublishState.staff_only
elif is_unit(xblock) and modulestore().has_changes(xblock.location):
return PublishState.has_unpublished_content
is_unscheduled = xblock.start == DEFAULT_START_DATE
children = child_info and child_info['children']
if children and len(children) > 0:
all_staff_only = True
all_unscheduled = True
all_live = True
for child in child_info['children']:
child_state = child['publish_state']
if child_state == PublishState.has_unpublished_content:
return child_state
elif not child_state == PublishState.staff_only:
all_staff_only = False
if not child_state == PublishState.unscheduled:
all_unscheduled = False
if not child_state == PublishState.live:
all_live = False
if all_staff_only:
return PublishState.staff_only
elif all_unscheduled:
if not is_unscheduled:
return PublishState.has_unpublished_content
else:
return PublishState.unscheduled
elif all_live:
return PublishState.live
else:
return PublishState.ready
if is_unscheduled:
return PublishState.unscheduled
elif datetime.now(UTC) > xblock.start:
return PublishState.live
else:
return PublishState.ready


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['publish_state'] == PublishState.staff_only for info in child_info['children'])
return False


def _is_unscheduled(xblock, child_info):
"""
Returns true if the specified xblock and all of its children are unscheduled.
"""
if xblock.start != DEFAULT_START_DATE:
return False
elif child_info:
return all(
info['publish_state'] in [PublishState.unscheduled, PublishState.staff_only]
for info in child_info['children']
)
return True


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['publish_state'] == PublishState.has_unpublished_content 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
7 changes: 3 additions & 4 deletions cms/djangoapps/contentstore/views/tests/test_course_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from contentstore.tests.utils import CourseTestCase
from contentstore.utils import reverse_course_url
from contentstore.views.course import course_outline_initial_state
from contentstore.views.item import create_xblock_info
from contentstore.views.item import create_xblock_info, PublishState
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory

Expand Down Expand Up @@ -122,7 +122,7 @@ def test_json_responses(self):
self.assertEqual(json_response['category'], 'course')
self.assertEqual(json_response['id'], 'i4x://MITx/999/course/Robot_Super_Course')
self.assertEqual(json_response['display_name'], 'Robot Super Course')
self.assertTrue(json_response['published'])
self.assertIsNone(json_response['publish_state'])

# Now verify the first child
children = json_response['child_info']['children']
Expand All @@ -131,7 +131,7 @@ def test_json_responses(self):
self.assertEqual(first_child_response['category'], 'chapter')
self.assertEqual(first_child_response['id'], 'i4x://MITx/999/chapter/Week_1')
self.assertEqual(first_child_response['display_name'], 'Week 1')
self.assertTrue(first_child_response['published'])
self.assertEqual(first_child_response['publish_state'], PublishState.unscheduled)
self.assertTrue(len(first_child_response['child_info']['children']) > 0)

# Finally, validate the entire response for consistency
Expand All @@ -144,7 +144,6 @@ def assert_correct_json_response(self, json_response):
self.assertIsNotNone(json_response['display_name'])
self.assertIsNotNone(json_response['id'])
self.assertIsNotNone(json_response['category'])
self.assertIsNotNone(json_response['published'])
if json_response.get('child_info', None):
for child_response in json_response['child_info']['children']:
self.assert_correct_json_response(child_response)
Expand Down
Loading