Apply code review comments and fix tests
This commit is contained in:
@@ -9,7 +9,7 @@ from django.test.client import Client
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.modulestore import LegacyPublishState, ModuleStoreEnum, mongo
|
||||
from xmodule.modulestore import PublishState, 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
|
||||
@@ -151,16 +151,16 @@ class CourseTestCase(ModuleStoreTestCase):
|
||||
# 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), LegacyPublishState.draft)
|
||||
self.assertEqual(self.store.compute_publish_state(draft_vertical), PublishState.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), LegacyPublishState.private)
|
||||
self.assertEqual(self.store.compute_publish_state(private_vertical), PublishState.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), LegacyPublishState.public)
|
||||
self.assertEqual(self.store.compute_publish_state(public_vertical), PublishState.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))
|
||||
@@ -197,7 +197,7 @@ class CourseTestCase(ModuleStoreTestCase):
|
||||
|
||||
def verify_item_publish_state(item, publish_state):
|
||||
"""Verifies the publish state of the item is as expected."""
|
||||
if publish_state in (LegacyPublishState.private, LegacyPublishState.draft):
|
||||
if publish_state in (PublishState.private, PublishState.draft):
|
||||
self.assertTrue(getattr(item, 'is_draft', False))
|
||||
else:
|
||||
self.assertFalse(getattr(item, 'is_draft', False))
|
||||
@@ -210,18 +210,18 @@ class CourseTestCase(ModuleStoreTestCase):
|
||||
return item
|
||||
|
||||
# verify that the draft vertical is draft
|
||||
vertical = get_and_verify_publish_state('vertical', self.TEST_VERTICAL, LegacyPublishState.draft)
|
||||
vertical = get_and_verify_publish_state('vertical', self.TEST_VERTICAL, PublishState.draft)
|
||||
for child in vertical.get_children():
|
||||
verify_item_publish_state(child, LegacyPublishState.draft)
|
||||
verify_item_publish_state(child, PublishState.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, LegacyPublishState.public)
|
||||
sequential = get_and_verify_publish_state('sequential', self.SEQUENTIAL, PublishState.public)
|
||||
|
||||
# verify that we have the private vertical
|
||||
private_vertical = get_and_verify_publish_state('vertical', self.PRIVATE_VERTICAL, LegacyPublishState.private)
|
||||
private_vertical = get_and_verify_publish_state('vertical', self.PRIVATE_VERTICAL, PublishState.private)
|
||||
|
||||
# verify that we have the public vertical
|
||||
public_vertical = get_and_verify_publish_state('vertical', self.PUBLISHED_VERTICAL, LegacyPublishState.public)
|
||||
public_vertical = get_and_verify_publish_state('vertical', self.PUBLISHED_VERTICAL, PublishState.public)
|
||||
|
||||
# verify verticals are children of sequential
|
||||
for vert in [vertical, private_vertical, public_vertical]:
|
||||
@@ -332,7 +332,7 @@ class CourseTestCase(ModuleStoreTestCase):
|
||||
it'll return public in that case
|
||||
"""
|
||||
supposed_state = self.store.compute_publish_state(item)
|
||||
if supposed_state == LegacyPublishState.draft and isinstance(item.runtime.modulestore, DraftModuleStore):
|
||||
if supposed_state == PublishState.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():
|
||||
@@ -345,13 +345,13 @@ class CourseTestCase(ModuleStoreTestCase):
|
||||
# checking children: if published differs from item, return draft
|
||||
return supposed_state
|
||||
# published == item in all respects, so return public
|
||||
return LegacyPublishState.public
|
||||
elif supposed_state == LegacyPublishState.public and item.location.category in DIRECT_ONLY_CATEGORIES:
|
||||
return PublishState.public
|
||||
elif supposed_state == PublishState.public and item.location.category in DIRECT_ONLY_CATEGORIES:
|
||||
if not all([
|
||||
self.store.has_item(child_loc, revision=ModuleStoreEnum.RevisionOption.draft_only)
|
||||
for child_loc in item.children
|
||||
]):
|
||||
return LegacyPublishState.draft
|
||||
return PublishState.draft
|
||||
else:
|
||||
return supposed_state
|
||||
else:
|
||||
|
||||
@@ -150,20 +150,6 @@ def course_image_url(course):
|
||||
return path
|
||||
|
||||
|
||||
def compute_publish_state(xblock):
|
||||
"""
|
||||
Returns whether this xblock is draft, public, or private.
|
||||
|
||||
Returns:
|
||||
LegacyPublishState.draft - content is in the process of being edited, but still has a previous
|
||||
version deployed to LMS
|
||||
LegacyPublishState.public - content is locked and deployed to LMS
|
||||
LegacyPublishState.private - content is editable and not deployed to LMS
|
||||
"""
|
||||
|
||||
return modulestore().compute_publish_state(xblock)
|
||||
|
||||
|
||||
def is_currently_visible_to_students(xblock):
|
||||
"""
|
||||
Returns true if there is a published version of the xblock that is currently visible to students.
|
||||
|
||||
@@ -11,8 +11,8 @@ from django.conf import settings
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from edxmako.shortcuts import render_to_response
|
||||
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore import LegacyPublishState
|
||||
|
||||
from xblock.core import XBlock
|
||||
from xblock.django.request import webob_to_django_response, django_to_webob_request
|
||||
@@ -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
|
||||
from contentstore.utils import get_lms_link_for_item
|
||||
from contentstore.views.helpers import get_parent_xblock, is_unit, xblock_type_display_name
|
||||
from contentstore.views.item import create_xblock_info
|
||||
|
||||
@@ -100,8 +100,8 @@ def subsection_handler(request, usage_key_string):
|
||||
can_view_live = False
|
||||
subsection_units = item.get_children()
|
||||
for unit in subsection_units:
|
||||
state = compute_publish_state(unit)
|
||||
if state in (LegacyPublishState.public, LegacyPublishState.draft):
|
||||
has_published = modulestore().has_item(unit.location, revision=ModuleStoreEnum.RevisionOption.published_only)
|
||||
if has_published:
|
||||
can_view_live = True
|
||||
break
|
||||
|
||||
@@ -178,6 +178,10 @@ def container_handler(request, usage_key_string):
|
||||
# about the block's ancestors and siblings for use by the Unit Outline.
|
||||
xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page)
|
||||
|
||||
# On the unit page only, add 'has_changes' to indicate when there are changes that can be discarded.
|
||||
if is_unit_page:
|
||||
xblock_info['has_changes'] = modulestore().has_changes(xblock.location)
|
||||
|
||||
# Create the link for preview.
|
||||
preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE')
|
||||
# need to figure out where this item is in the list of children as the
|
||||
|
||||
@@ -636,9 +636,9 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
"id": unicode(xblock.location),
|
||||
"display_name": xblock.display_name_with_default,
|
||||
"category": xblock.category,
|
||||
"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": published,
|
||||
"published_on": get_default_time_display(xblock.published_date) if xblock.published_date else None,
|
||||
"published_by": safe_get_username(xblock.published_by),
|
||||
'studio_url': xblock_studio_url(xblock),
|
||||
@@ -646,7 +646,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
"release_date": release_date,
|
||||
"release_date_from": _get_release_date_from(xblock) if release_date else None,
|
||||
"currently_visible_to_students": currently_visible_to_students,
|
||||
"publish_state": _compute_publish_state(xblock, child_info) if not xblock.category == 'course' else None
|
||||
"visibility_state": _compute_visibility_state(xblock, child_info) if not xblock.category == 'course' else None
|
||||
}
|
||||
if data is not None:
|
||||
xblock_info["data"] = data
|
||||
@@ -659,63 +659,71 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
return xblock_info
|
||||
|
||||
|
||||
class PublishState(object):
|
||||
class VisibilityState(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
|
||||
Represents the possible visibility states for an xblock:
|
||||
|
||||
live - the block and all of its descendants are live to students (excluding staff only items)
|
||||
Note: Live means both published and released.
|
||||
|
||||
ready - the block is ready to go live and all of its descendants are live or ready (excluding staff only items)
|
||||
Note: content is ready when it is published and scheduled with a release date in the future.
|
||||
|
||||
unscheduled - the block and all of its descendants have no release date (excluding staff only items)
|
||||
Note: it is valid for items to be published with no release date in which case they are still unscheduled.
|
||||
|
||||
needs_attention - the block or its descendants are not fully live, ready or unscheduled (excluding staff only items)
|
||||
For example: one subsection has draft content, or there's both unreleased and released content in one section.
|
||||
|
||||
staff_only - all of the block's content is to be shown to staff only
|
||||
Note: staff only items do not affect their parent's state.
|
||||
"""
|
||||
live = 'live'
|
||||
ready = 'ready'
|
||||
unscheduled = 'unscheduled'
|
||||
has_unpublished_content = 'has_unpublished_content'
|
||||
unscheduled = 'unscheduled' # unscheduled
|
||||
needs_attention = 'needs_attention'
|
||||
staff_only = 'staff_only'
|
||||
|
||||
|
||||
def _compute_publish_state(xblock, child_info):
|
||||
def _compute_visibility_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
|
||||
return VisibilityState.staff_only
|
||||
elif is_unit(xblock) and modulestore().has_changes(xblock.location):
|
||||
return PublishState.has_unpublished_content
|
||||
return VisibilityState.needs_attention
|
||||
is_unscheduled = xblock.start == DEFAULT_START_DATE
|
||||
is_live = datetime.now(UTC) > xblock.start
|
||||
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:
|
||||
child_state = child['visibility_state']
|
||||
if child_state == VisibilityState.needs_attention:
|
||||
return child_state
|
||||
elif not child_state == PublishState.staff_only:
|
||||
elif not child_state == VisibilityState.staff_only:
|
||||
all_staff_only = False
|
||||
if not child_state == PublishState.unscheduled:
|
||||
if not child_state == VisibilityState.unscheduled:
|
||||
all_unscheduled = False
|
||||
if not child_state == PublishState.live:
|
||||
if not child_state == VisibilityState.live:
|
||||
all_live = False
|
||||
if all_staff_only:
|
||||
return PublishState.staff_only
|
||||
return VisibilityState.staff_only
|
||||
elif all_unscheduled:
|
||||
if not is_unscheduled:
|
||||
return PublishState.has_unpublished_content
|
||||
else:
|
||||
return PublishState.unscheduled
|
||||
return VisibilityState.unscheduled if is_unscheduled else VisibilityState.needs_attention
|
||||
elif all_live:
|
||||
return PublishState.live
|
||||
return VisibilityState.live if is_live else VisibilityState.needs_attention
|
||||
else:
|
||||
return PublishState.ready
|
||||
return VisibilityState.ready if not is_unscheduled else VisibilityState.needs_attention
|
||||
if is_unscheduled:
|
||||
return PublishState.unscheduled
|
||||
elif datetime.now(UTC) > xblock.start:
|
||||
return PublishState.live
|
||||
return VisibilityState.unscheduled
|
||||
elif is_live:
|
||||
return VisibilityState.live
|
||||
else:
|
||||
return PublishState.ready
|
||||
return VisibilityState.ready
|
||||
|
||||
|
||||
def _create_xblock_ancestor_info(xblock):
|
||||
|
||||
@@ -8,6 +8,7 @@ from contentstore.tests.utils import CourseTestCase
|
||||
from contentstore.utils import reverse_course_url, add_instructor
|
||||
from contentstore.views.access import has_course_access
|
||||
from contentstore.views.course import course_outline_initial_state
|
||||
from contentstore.views.item import create_xblock_info, VisibilityState
|
||||
from course_action_state.models import CourseRerunState
|
||||
from contentstore.views.item import create_xblock_info
|
||||
from contentstore.views.item import create_xblock_info, PublishState
|
||||
@@ -230,7 +231,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
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.assertIsNone(json_response['publish_state'])
|
||||
self.assertIsNone(json_response['visibility_state'])
|
||||
|
||||
# Now verify the first child
|
||||
children = json_response['child_info']['children']
|
||||
@@ -239,7 +240,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
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.assertEqual(first_child_response['publish_state'], PublishState.unscheduled)
|
||||
self.assertEqual(first_child_response['visibility_state'], VisibilityState.unscheduled)
|
||||
self.assertTrue(len(first_child_response['child_info']['children']) > 0)
|
||||
|
||||
# Finally, validate the entire response for consistency
|
||||
|
||||
@@ -19,11 +19,11 @@ from contentstore.views.component import (
|
||||
component_handler, get_component_templates
|
||||
)
|
||||
|
||||
from contentstore.views.item import create_xblock_info, ALWAYS, PublishState
|
||||
from contentstore.views.item import create_xblock_info, ALWAYS, VisibilityState
|
||||
from contentstore.tests.utils import CourseTestCase
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.capa_module import CapaDescriptor
|
||||
from xmodule.modulestore import LegacyPublishState, ModuleStoreEnum
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.factories import ItemFactory
|
||||
from xmodule.x_module import STUDIO_VIEW, STUDENT_VIEW
|
||||
@@ -425,19 +425,6 @@ class TestEditItem(ItemTest):
|
||||
|
||||
self.course_update_url = reverse_usage_url("xblock_handler", self.usage_key)
|
||||
|
||||
def verify_publish_state(self, usage_key, expected_publish_state):
|
||||
"""
|
||||
Helper method that gets the item from the module store and verifies that the publish state is as expected.
|
||||
Returns the item corresponding to the given usage_key.
|
||||
"""
|
||||
item = self.get_item_from_modulestore(
|
||||
usage_key,
|
||||
(expected_publish_state == LegacyPublishState.private) or
|
||||
(expected_publish_state == LegacyPublishState.draft)
|
||||
)
|
||||
self.assertEqual(expected_publish_state, self.store.compute_publish_state(item))
|
||||
return item
|
||||
|
||||
def test_delete_field(self):
|
||||
"""
|
||||
Sending null in for a field 'deletes' it
|
||||
@@ -543,15 +530,18 @@ class TestEditItem(ItemTest):
|
||||
self.assertEqual(unit1_usage_key, children[2])
|
||||
self.assertEqual(unit2_usage_key, children[1])
|
||||
|
||||
def _is_location_published(self, location):
|
||||
return modulestore().has_item(location, revision=ModuleStoreEnum.RevisionOption.published_only)
|
||||
|
||||
def test_make_public(self):
|
||||
""" Test making a private problem public (publishing it). """
|
||||
# When the problem is first created, it is only in draft (because of its category).
|
||||
self.verify_publish_state(self.problem_usage_key, LegacyPublishState.private)
|
||||
self.assertFalse(self._is_location_published(self.problem_usage_key))
|
||||
self.client.ajax_post(
|
||||
self.problem_update_url,
|
||||
data={'publish': 'make_public'}
|
||||
)
|
||||
self.verify_publish_state(self.problem_usage_key, LegacyPublishState.public)
|
||||
self.assertTrue(self._is_location_published(self.problem_usage_key))
|
||||
|
||||
def test_make_draft(self):
|
||||
""" Test creating a draft version of a public problem. """
|
||||
@@ -564,7 +554,8 @@ class TestEditItem(ItemTest):
|
||||
self.problem_update_url,
|
||||
data={'publish': 'discard_changes'}
|
||||
)
|
||||
published = self.verify_publish_state(self.problem_usage_key, LegacyPublishState.public)
|
||||
self.assertTrue(self._is_location_published(self.problem_usage_key))
|
||||
published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only)
|
||||
self.assertIsNone(published.due)
|
||||
|
||||
def test_republish(self):
|
||||
@@ -576,7 +567,7 @@ class TestEditItem(ItemTest):
|
||||
}
|
||||
|
||||
# When the problem is first created, it is only in draft (because of its category).
|
||||
self.verify_publish_state(self.problem_usage_key, LegacyPublishState.private)
|
||||
self.assertFalse(self._is_location_published(self.problem_usage_key))
|
||||
|
||||
# Republishing when only in draft will update the draft but not cause a public item to be created.
|
||||
self.client.ajax_post(
|
||||
@@ -588,7 +579,7 @@ class TestEditItem(ItemTest):
|
||||
}
|
||||
}
|
||||
)
|
||||
self.verify_publish_state(self.problem_usage_key, LegacyPublishState.private)
|
||||
self.assertFalse(self._is_location_published(self.problem_usage_key))
|
||||
draft = self.get_item_from_modulestore(self.problem_usage_key, verify_is_draft=True)
|
||||
self.assertEqual(draft.display_name, new_display_name)
|
||||
|
||||
@@ -609,7 +600,7 @@ class TestEditItem(ItemTest):
|
||||
}
|
||||
}
|
||||
)
|
||||
self.verify_publish_state(self.problem_usage_key, LegacyPublishState.public)
|
||||
self.assertTrue(self._is_location_published(self.problem_usage_key))
|
||||
published = modulestore().get_item(
|
||||
self.problem_usage_key,
|
||||
revision=ModuleStoreEnum.RevisionOption.published_only
|
||||
@@ -625,7 +616,8 @@ class TestEditItem(ItemTest):
|
||||
self.problem_update_url,
|
||||
data={'publish': 'make_public'}
|
||||
)
|
||||
published = self.verify_publish_state(self.problem_usage_key, LegacyPublishState.public)
|
||||
self.assertTrue(self._is_location_published(self.problem_usage_key))
|
||||
published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only)
|
||||
|
||||
# Update the draft version and check that published is different.
|
||||
self.client.ajax_post(
|
||||
@@ -659,7 +651,8 @@ class TestEditItem(ItemTest):
|
||||
self.problem_update_url,
|
||||
data={'publish': 'make_public'}
|
||||
)
|
||||
published = self.verify_publish_state(self.problem_usage_key, LegacyPublishState.public)
|
||||
self.assertTrue(self._is_location_published(self.problem_usage_key))
|
||||
published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only)
|
||||
|
||||
# Now make a draft
|
||||
self.client.ajax_post(
|
||||
@@ -704,8 +697,8 @@ class TestEditItem(ItemTest):
|
||||
|
||||
# The unit and its children should be private initially
|
||||
unit_update_url = reverse_usage_url('xblock_handler', unit_usage_key)
|
||||
self.verify_publish_state(unit_usage_key, LegacyPublishState.private)
|
||||
self.verify_publish_state(html_usage_key, LegacyPublishState.private)
|
||||
self.assertFalse(self._is_location_published(unit_usage_key))
|
||||
self.assertFalse(self._is_location_published(html_usage_key))
|
||||
|
||||
# Make the unit public and verify that the problem is also made public
|
||||
resp = self.client.ajax_post(
|
||||
@@ -713,8 +706,8 @@ class TestEditItem(ItemTest):
|
||||
data={'publish': 'make_public'}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.verify_publish_state(unit_usage_key, LegacyPublishState.public)
|
||||
self.verify_publish_state(html_usage_key, LegacyPublishState.public)
|
||||
self.assertTrue(self._is_location_published(unit_usage_key))
|
||||
self.assertTrue(self._is_location_published(html_usage_key))
|
||||
|
||||
# Make a draft for the unit and verify that the problem also has a draft
|
||||
resp = self.client.ajax_post(
|
||||
@@ -725,8 +718,10 @@ class TestEditItem(ItemTest):
|
||||
}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.verify_publish_state(unit_usage_key, LegacyPublishState.draft)
|
||||
self.verify_publish_state(html_usage_key, LegacyPublishState.draft)
|
||||
self.assertTrue(self._is_location_published(unit_usage_key))
|
||||
self.assertTrue(self._is_location_published(html_usage_key))
|
||||
self.assertTrue(modulestore().get_item(unit_usage_key).has_changes())
|
||||
self.assertTrue(modulestore().get_item(html_usage_key).has_changes())
|
||||
|
||||
|
||||
class TestEditSplitModule(ItemTest):
|
||||
@@ -1243,10 +1238,14 @@ class TestXBlockPublishingInfo(ItemTest):
|
||||
"""
|
||||
Unit tests for XBlock's outline handling.
|
||||
"""
|
||||
def _create_child(self, parent, category, display_name, publish_item=False):
|
||||
FIRST_SUBSECTION_PATH = [0]
|
||||
FIRST_UNIT_PATH = [0, 0]
|
||||
SECOND_UNIT_PATH = [0, 1]
|
||||
|
||||
def _create_child(self, parent, category, display_name, publish_item=False, staff_only=False):
|
||||
return ItemFactory.create(
|
||||
parent_location=parent.location, category=category, display_name=display_name,
|
||||
user_id=self.user.id, publish_item=publish_item
|
||||
user_id=self.user.id, publish_item=publish_item, visible_to_staff_only=staff_only
|
||||
)
|
||||
|
||||
def _get_child(self, xblock_info, index):
|
||||
@@ -1291,87 +1290,122 @@ class TestXBlockPublishingInfo(ItemTest):
|
||||
xblock.display_name = display_name
|
||||
self.store.update_item(xblock, self.user.id)
|
||||
|
||||
def test_empty_chapter_publishing_info(self):
|
||||
def _verify_visibility_state(self, xblock_info, expected_state, path=None):
|
||||
"""
|
||||
Verify the publish state of an item in the xblock_info. If no path is provided
|
||||
then the root item is verified.
|
||||
"""
|
||||
if path:
|
||||
direct_child = self._get_child(xblock_info, path[0])
|
||||
remaining_path = path[1:] if len(path) > 1 else None
|
||||
self._verify_visibility_state(direct_child, expected_state, remaining_path)
|
||||
else:
|
||||
self.assertEqual(xblock_info['visibility_state'], expected_state)
|
||||
|
||||
def test_empty_chapter(self):
|
||||
empty_chapter = self._create_child(self.course, 'chapter', "Empty Chapter")
|
||||
xblock_info = self._get_xblock_info(empty_chapter.location)
|
||||
self.assertEqual(xblock_info['publish_state'], PublishState.unscheduled)
|
||||
self.assertEqual(xblock_info['visibility_state'], VisibilityState.unscheduled)
|
||||
|
||||
def test_empty_section_publishing_info(self):
|
||||
def test_empty_sequential(self):
|
||||
chapter = self._create_child(self.course, 'chapter', "Test Chapter")
|
||||
self._create_child(chapter, 'sequential', "Empty Sequential")
|
||||
xblock_info = self._get_xblock_info(chapter.location)
|
||||
self.assertEqual(xblock_info['publish_state'], PublishState.unscheduled)
|
||||
self.assertEqual(self._get_child(xblock_info, 0)['publish_state'], PublishState.unscheduled)
|
||||
self.assertEqual(xblock_info['visibility_state'], VisibilityState.unscheduled)
|
||||
self.assertEqual(self._get_child(xblock_info, 0)['visibility_state'], VisibilityState.unscheduled)
|
||||
|
||||
def test_published_unit_publishing_info(self):
|
||||
def test_published_unit(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)
|
||||
self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True)
|
||||
self._set_release_date(chapter.location, datetime.now(UTC) + timedelta(days=1))
|
||||
xblock_info = self._get_xblock_info(chapter.location)
|
||||
self.assertEqual(xblock_info['publish_state'], PublishState.ready)
|
||||
sequential_child_info = self._get_child(xblock_info, 0)
|
||||
self.assertEqual(sequential_child_info['publish_state'], PublishState.ready)
|
||||
unit_child_info = self._get_child(sequential_child_info, 0)
|
||||
self.assertEqual(unit_child_info['publish_state'], PublishState.ready)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.ready)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.ready, path=self.FIRST_SUBSECTION_PATH)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.ready, path=self.FIRST_UNIT_PATH)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH)
|
||||
|
||||
def test_released_unit_publishing_info(self):
|
||||
def test_released_unit(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)
|
||||
self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True)
|
||||
self._set_release_date(chapter.location, datetime.now(UTC) - timedelta(days=1))
|
||||
xblock_info = self._get_xblock_info(chapter.location)
|
||||
self.assertEqual(xblock_info['publish_state'], PublishState.live)
|
||||
sequential_child_info = self._get_child(xblock_info, 0)
|
||||
self.assertEqual(sequential_child_info['publish_state'], PublishState.live)
|
||||
unit_child_info = self._get_child(sequential_child_info, 0)
|
||||
self.assertEqual(unit_child_info['publish_state'], PublishState.live)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.live)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH)
|
||||
|
||||
def test_partially_released_section_publishing_info(self):
|
||||
def test_unpublished_changes(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)
|
||||
self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True)
|
||||
self._set_display_name(unit.location, 'Updated Unit')
|
||||
xblock_info = self._get_xblock_info(chapter.location)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.needs_attention)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.needs_attention, path=self.FIRST_SUBSECTION_PATH)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.needs_attention, path=self.FIRST_UNIT_PATH)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH)
|
||||
|
||||
def test_partially_released_section(self):
|
||||
chapter = self._create_child(self.course, 'chapter', "Test Chapter")
|
||||
released_sequential = self._create_child(chapter, 'sequential', "Released Sequential")
|
||||
self._create_child(released_sequential, 'vertical', "Released Unit", publish_item=True)
|
||||
self._create_child(released_sequential, 'vertical', "Staff Only Unit", staff_only=True)
|
||||
self._set_release_date(chapter.location, datetime.now(UTC) - timedelta(days=1))
|
||||
published_sequential = self._create_child(chapter, 'sequential', "Published Sequential")
|
||||
self._create_child(published_sequential, 'vertical', "Published Unit", publish_item=True)
|
||||
self._create_child(published_sequential, 'vertical', "Staff Only Unit", staff_only=True)
|
||||
self._set_release_date(published_sequential.location, datetime.now(UTC) + timedelta(days=1))
|
||||
xblock_info = self._get_xblock_info(chapter.location)
|
||||
|
||||
# Verify the state of the released sequential
|
||||
released_sequential_child_info = self._get_child(xblock_info, 0)
|
||||
released_unit_child_info = self._get_child(released_sequential_child_info, 0)
|
||||
self.assertEqual(released_unit_child_info['publish_state'], PublishState.live)
|
||||
self.assertEqual(released_sequential_child_info['publish_state'], PublishState.live)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.live, path=[0])
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.live, path=[0, 0])
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=[0, 1])
|
||||
|
||||
# Verify the state of the published sequential
|
||||
public_sequential_child_info = self._get_child(xblock_info, 1)
|
||||
public_unit_child_info = self._get_child(public_sequential_child_info, 0)
|
||||
self.assertEqual(public_sequential_child_info['publish_state'], PublishState.ready)
|
||||
self.assertEqual(public_unit_child_info['publish_state'], PublishState.ready)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.ready, path=[1])
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.ready, path=[1, 0])
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=[1, 1])
|
||||
|
||||
# Finally verify the state of the chapter
|
||||
self.assertEqual(xblock_info['publish_state'], PublishState.ready)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.ready)
|
||||
|
||||
def test_unpublished_changes_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)
|
||||
self._set_display_name(unit.location, 'Updated Unit')
|
||||
xblock_info = self._get_xblock_info(chapter.location)
|
||||
self.assertEqual(xblock_info['publish_state'], PublishState.has_unpublished_content)
|
||||
sequential_child_info = self._get_child(xblock_info, 0)
|
||||
self.assertEqual(sequential_child_info['publish_state'], PublishState.has_unpublished_content)
|
||||
unit_child_info = self._get_child(sequential_child_info, 0)
|
||||
self.assertEqual(unit_child_info['publish_state'], PublishState.has_unpublished_content)
|
||||
|
||||
def test_staff_only_publishing_info(self):
|
||||
def test_staff_only(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")
|
||||
self._set_staff_only(unit.location, True)
|
||||
xblock_info = self._get_xblock_info(chapter.location)
|
||||
self.assertEqual(xblock_info['publish_state'], PublishState.staff_only)
|
||||
sequential_child_info = self._get_child(xblock_info, 0)
|
||||
self.assertEqual(sequential_child_info['publish_state'], PublishState.staff_only)
|
||||
unit_child_info = self._get_child(sequential_child_info, 0)
|
||||
self.assertEqual(unit_child_info['publish_state'], PublishState.staff_only)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.FIRST_SUBSECTION_PATH)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.FIRST_UNIT_PATH)
|
||||
|
||||
def test_unscheduled_section_with_live_subsection(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)
|
||||
self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True)
|
||||
self._set_release_date(sequential.location, datetime.now(UTC) - timedelta(days=1))
|
||||
xblock_info = self._get_xblock_info(chapter.location)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.needs_attention)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH)
|
||||
|
||||
def test_unreleased_section_with_live_subsection(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)
|
||||
self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True)
|
||||
self._set_release_date(chapter.location, datetime.now(UTC) + timedelta(days=1))
|
||||
self._set_release_date(sequential.location, datetime.now(UTC) - timedelta(days=1))
|
||||
xblock_info = self._get_xblock_info(chapter.location)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.needs_attention)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_SUBSECTION_PATH)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.live, path=self.FIRST_UNIT_PATH)
|
||||
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH)
|
||||
|
||||
@@ -45,14 +45,15 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
|
||||
*/
|
||||
"published_by": null,
|
||||
/**
|
||||
* Represents the possible publish states for an xblock:
|
||||
* is_live - the block and all of its children are live to students (except for staff only items)
|
||||
* is_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
|
||||
* is_staff_only - all of the block's content is to be shown to staff only
|
||||
* True if the xblock has changes.
|
||||
* Note: this is not always provided as a performance optimization.
|
||||
*/
|
||||
"publish_state": 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,
|
||||
/**
|
||||
* True iff the release date of the xblock is in the past.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers",
|
||||
"js/views/feedback_prompt", "js/views/pages/container", "js/views/pages/container_subviews",
|
||||
"js/models/xblock_info"],
|
||||
function ($, _, str, create_sinon, edit_helpers, Prompt, ContainerPage, ContainerSubviews, XBlockInfo) {
|
||||
"js/models/xblock_info", "js/views/utils/xblock_utils"],
|
||||
function ($, _, str, create_sinon, edit_helpers, Prompt, ContainerPage, ContainerSubviews,
|
||||
XBlockInfo, XBlockUtils) {
|
||||
var VisibilityState = XBlockUtils.VisibilityState;
|
||||
|
||||
describe("Container Subviews", function() {
|
||||
var model, containerPage, requests, createContainerPage, renderContainerPage,
|
||||
@@ -23,7 +25,9 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
id: 'locator-container',
|
||||
display_name: 'Test Container',
|
||||
category: 'vertical',
|
||||
publish_state: 'unscheduled',
|
||||
published: false,
|
||||
has_changes: false,
|
||||
visibility_state: 'unscheduled',
|
||||
edited_on: "Jul 02, 2014 at 14:20 UTC", edited_by: "joe",
|
||||
published_on: "Jul 01, 2014 at 12:45 UTC", published_by: "amako",
|
||||
currently_visible_to_students: false
|
||||
@@ -85,22 +89,23 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
|
||||
it('updates when publish state changes', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({publish_state: 'ready'});
|
||||
fetch({published: true});
|
||||
expect(containerPage.$(viewPublishedCss)).not.toHaveClass(disabledCss);
|
||||
|
||||
fetch({publish_state: 'unscheduled'});
|
||||
fetch({published: false});
|
||||
expect(containerPage.$(viewPublishedCss)).toHaveClass(disabledCss);
|
||||
});
|
||||
|
||||
it('updates when has_changes attribute changes', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({publish_state: 'has-unpublished-changes'});
|
||||
fetch({has_changes: true});
|
||||
expect(containerPage.$(previewCss)).not.toHaveClass(disabledCss);
|
||||
|
||||
fetch({publish_state: 'ready'});
|
||||
fetch({published: true, has_changes: false});
|
||||
expect(containerPage.$(previewCss)).toHaveClass(disabledCss);
|
||||
|
||||
fetch({publish_state: 'unscheduled'});
|
||||
// If published is false, preview is always enabled.
|
||||
fetch({published: false, has_changes: false});
|
||||
expect(containerPage.$(previewCss)).not.toHaveClass(disabledCss);
|
||||
});
|
||||
});
|
||||
@@ -123,7 +128,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
// Helper function to do the discard operation, up until the server response.
|
||||
containerPage.render();
|
||||
respondWithHtml(mockContainerXBlockHtml);
|
||||
fetch({publish_state: 'has_unpublished_content'});
|
||||
fetch({published: true, has_changes: true, visibility_state: VisibilityState.needsAttention});
|
||||
expect(containerPage.$(discardChangesButtonCss)).not.toHaveClass('is-disabled');
|
||||
expect(containerPage.$(bitPublishingCss)).toHaveClass(hasWarningsClass);
|
||||
// Click discard changes
|
||||
@@ -151,26 +156,29 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
|
||||
};
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({publishState: 'unscheduled'});
|
||||
fetch({published: false, has_changes: false});
|
||||
verifyPrivateState();
|
||||
|
||||
fetch({published: false, has_changes: true});
|
||||
verifyPrivateState();
|
||||
});
|
||||
|
||||
it('renders correctly with published content', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({publish_state: 'ready'});
|
||||
fetch({published: true, has_changes: false, visibility_state: VisibilityState.ready});
|
||||
expect(containerPage.$(headerCss).text()).toContain('Published');
|
||||
expect(containerPage.$(publishButtonCss)).toHaveClass(disabledCss);
|
||||
expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
|
||||
expect(containerPage.$(bitPublishingCss)).toHaveClass(readyClass);
|
||||
|
||||
fetch({publish_state: 'has_unpublished_content'});
|
||||
fetch({published: true, has_changes: true, visibility_state: VisibilityState.needsAttention});
|
||||
expect(containerPage.$(headerCss).text()).toContain('Draft (Unpublished changes)');
|
||||
expect(containerPage.$(publishButtonCss)).not.toHaveClass(disabledCss);
|
||||
expect(containerPage.$(discardChangesButtonCss)).not.toHaveClass(disabledCss);
|
||||
expect(containerPage.$(bitPublishingCss)).toHaveClass(hasWarningsClass);
|
||||
|
||||
fetch({publish_state: 'live'});
|
||||
expect(containerPage.$(headerCss).text()).toContain('Published');
|
||||
fetch({published: true, has_changes: false, visibility_state: VisibilityState.live});
|
||||
expect(containerPage.$(headerCss).text()).toContain('Published and Live');
|
||||
expect(containerPage.$(publishButtonCss)).toHaveClass(disabledCss);
|
||||
expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
|
||||
expect(containerPage.$(bitPublishingCss)).toHaveClass(liveClass);
|
||||
@@ -179,7 +187,9 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
it('can publish private content', function () {
|
||||
var notificationSpy = edit_helpers.createNotificationSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(hasWarningsClass);
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(liveClass);
|
||||
|
||||
// Click publish
|
||||
containerPage.$(publishButtonCss).click();
|
||||
@@ -195,15 +205,17 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
|
||||
create_sinon.expectJsonRequest(requests, "GET", "/xblock/locator-container");
|
||||
// Response to fetch
|
||||
respondWithJson(createXBlockInfo({publish_state: 'ready'}));
|
||||
respondWithJson(createXBlockInfo({
|
||||
published: true, has_changes: false, visibility_state: VisibilityState.ready
|
||||
}));
|
||||
|
||||
// Verify updates displayed
|
||||
expect(containerPage.$(bitPublishingCss)).toHaveClass(readyClass);
|
||||
// Verify that the "publish_state" value has been updated
|
||||
expect(containerPage.model.get("publish_state")).toBe('ready');
|
||||
// Verify that the "published" value has been cleared out of the model.
|
||||
expect(containerPage.model.get("publish")).toBeNull();
|
||||
});
|
||||
|
||||
it('can does not fetch if publish fails', function () {
|
||||
it('does not refresh if publish fails', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
|
||||
|
||||
@@ -218,8 +230,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
|
||||
// Verify still in draft state.
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
|
||||
// Verify that the "publish_state" value has been updated
|
||||
expect(containerPage.model.get("publish_state")).toBe('unscheduled');
|
||||
// Verify that the "published" value has been cleared out of the model.
|
||||
expect(containerPage.model.get("publish")).toBeNull();
|
||||
});
|
||||
|
||||
it('can discard changes', function () {
|
||||
@@ -260,7 +272,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
});
|
||||
|
||||
it('does not discard changes on cancel', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml, { publish_state: 'has_unpublished_content' });
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({published: true, has_changes: true});
|
||||
var numRequests = requests.length;
|
||||
|
||||
// Click discard changes
|
||||
@@ -284,10 +297,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
|
||||
it('renders the last saved date and user when there are changes', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({
|
||||
publish_state: 'has_unpublished_content',
|
||||
edited_on: "Jul 02, 2014 at 14:20 UTC", edited_by: "joe"
|
||||
});
|
||||
fetch({has_changes: true, edited_on: "Jul 02, 2014 at 14:20 UTC", edited_by: "joe"});
|
||||
expect(containerPage.$(lastDraftCss).text()).
|
||||
toContain("Draft saved on Jul 02, 2014 at 14:20 UTC by joe");
|
||||
});
|
||||
@@ -296,7 +306,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
it('renders correctly when unreleased', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({
|
||||
publish_state: 'ready', released_to_students: false,
|
||||
visibility_state: VisibilityState.ready, released_to_students: false,
|
||||
release_date: "Jul 02, 2014 at 14:20 UTC", release_date_from: 'Section "Week 1"'
|
||||
});
|
||||
expect(containerPage.$(releaseDateTitleCss).text()).toContain("Scheduled:");
|
||||
@@ -307,7 +317,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
it('renders correctly when released', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({
|
||||
publish_state: 'live', released_to_students: true,
|
||||
visibility_state: VisibilityState.live, released_to_students: true,
|
||||
release_date: "Jul 02, 2014 at 14:20 UTC", release_date_from: 'Section "Week 1"'
|
||||
});
|
||||
expect(containerPage.$(releaseDateTitleCss).text()).toContain("Released:");
|
||||
@@ -318,7 +328,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
it('renders correctly when the release date is not set', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({
|
||||
publish_state: 'unscheduled', "released_to_students": false,
|
||||
visibility_state: VisibilityState.unscheduled, "released_to_students": false,
|
||||
release_date: null, release_date_from: null
|
||||
});
|
||||
expect(containerPage.$(releaseDateTitleCss).text()).toContain("Release:");
|
||||
@@ -328,7 +338,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
it('renders correctly when the unit is not published', function () {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
fetch({
|
||||
publish_state: 'has_unpublished_content', released_to_students: true,
|
||||
visibility_state: VisibilityState.needsAttention, released_to_students: true,
|
||||
release_date: "Jul 02, 2014 at 14:20 UTC", release_date_from: 'Section "Week 1"'
|
||||
});
|
||||
containerPage.xblockPublisher.render();
|
||||
@@ -362,7 +372,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
});
|
||||
create_sinon.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
|
||||
create_sinon.respondWithJson(requests, createXBlockInfo({
|
||||
publish_state: isStaffOnly ? 'staff_only' : 'unscheduled'
|
||||
published: containerPage.model.get('published'),
|
||||
visibility_state: isStaffOnly ? VisibilityState.staffOnly : VisibilityState.live
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -385,14 +396,15 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
|
||||
it("can be set to staff only", function() {
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
containerPage.$('.action-staff-lock').click();
|
||||
requestStaffOnly(true);
|
||||
verifyStaffOnly(true);
|
||||
});
|
||||
|
||||
it("can remove staff only setting", function() {
|
||||
promptSpy = edit_helpers.createPromptSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml, { publish_state: 'staff_only' });
|
||||
renderContainerPage(this, mockContainerXBlockHtml, {
|
||||
visibility_state: VisibilityState.staffOnly
|
||||
});
|
||||
requestStaffOnly(false);
|
||||
verifyStaffOnly(false);
|
||||
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
|
||||
@@ -401,7 +413,9 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
|
||||
it("does not refresh if removing staff only is canceled", function() {
|
||||
var requestCount;
|
||||
promptSpy = edit_helpers.createPromptSpy();
|
||||
renderContainerPage(this, mockContainerXBlockHtml, { publish_state: 'staff_only' });
|
||||
renderContainerPage(this, mockContainerXBlockHtml, {
|
||||
visibility_state: VisibilityState.staffOnly
|
||||
});
|
||||
requestCount = requests.length;
|
||||
containerPage.$('.action-staff-lock').click();
|
||||
edit_helpers.confirmPrompt(promptSpy, true); // Click 'No' to cancel
|
||||
|
||||
@@ -118,7 +118,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
category: 'vertical',
|
||||
studio_url: '/container/mock-unit',
|
||||
is_container: true,
|
||||
publish_state: 'unscheduled',
|
||||
visibility_state: 'unscheduled',
|
||||
edited_on: 'Jul 02, 2014 at 20:56 UTC',
|
||||
edited_by: 'MockUser'
|
||||
}])
|
||||
@@ -432,9 +432,5 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
expect(unitAnchor.attr('href')).toBe('/container/mock-unit');
|
||||
});
|
||||
});
|
||||
|
||||
describe("Publishing State", function() {
|
||||
// TODO: implement this!!!!
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,14 +25,14 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
category: 'vertical',
|
||||
display_name: displayName,
|
||||
studio_url: '/container/mock-unit',
|
||||
publish_state: 'unscheduled',
|
||||
visibility_state: 'unscheduled',
|
||||
ancestor_info: {
|
||||
ancestors: [{
|
||||
id: 'mock-subsection',
|
||||
category: 'sequential',
|
||||
display_name: 'Mock Subsection',
|
||||
studio_url: '/course/mock-course?show=mock-subsection',
|
||||
publish_state: 'unscheduled',
|
||||
visibility_state: 'unscheduled',
|
||||
child_info: {
|
||||
category: 'vertical',
|
||||
display_name: 'Unit',
|
||||
@@ -41,13 +41,13 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
category: 'vertical',
|
||||
display_name: displayName,
|
||||
studio_url: '/container/mock-unit',
|
||||
publish_state: 'unscheduled'
|
||||
visibility_state: 'unscheduled'
|
||||
}, {
|
||||
id: 'mock-unit-2',
|
||||
category: 'vertical',
|
||||
display_name: 'Mock Unit 2',
|
||||
studio_url: '/container/mock-unit-2',
|
||||
publish_state: 'unscheduled'
|
||||
visibility_state: 'unscheduled'
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
@@ -55,13 +55,13 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
category: 'chapter',
|
||||
display_name: 'Section',
|
||||
studio_url: '/course/slashes:mock-course?show=mock-section',
|
||||
publish_state: 'unscheduled'
|
||||
visibility_state: 'unscheduled'
|
||||
}, {
|
||||
id: 'mock-course',
|
||||
category: 'course',
|
||||
display_name: 'Mock Course',
|
||||
studio_url: '/course/mock-course',
|
||||
publish_state: 'unscheduled'
|
||||
visibility_state: 'unscheduled'
|
||||
}]
|
||||
},
|
||||
metadata: {
|
||||
|
||||
@@ -17,6 +17,9 @@ define(["jquery", "underscore", "backbone", "gettext", "js/utils/handle_iframe_b
|
||||
},
|
||||
|
||||
options: {
|
||||
// UX is moving towards using 'is-collapsed' in preference over 'collapsed',
|
||||
// but use the old scheme as the default so that existing code doesn't need
|
||||
// to be rewritten.
|
||||
collapsedClass: 'collapsed'
|
||||
},
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/**
|
||||
* Subviews (usually small side panels) for XBlockContainerPage.
|
||||
*/
|
||||
define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/view_utils"],
|
||||
function ($, _, gettext, BaseView, ViewUtils) {
|
||||
|
||||
var disabledCss = "is-disabled";
|
||||
define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/view_utils",
|
||||
"js/views/utils/xblock_utils"],
|
||||
function ($, _, gettext, BaseView, ViewUtils, XBlockViewUtils) {
|
||||
var VisibilityState = XBlockViewUtils.VisibilityState,
|
||||
disabledCss = "is-disabled";
|
||||
|
||||
/**
|
||||
* A view that refreshes the view when certain values in the XBlockInfo have changed
|
||||
@@ -53,20 +54,19 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
|
||||
*/
|
||||
var PreviewActionController = ContainerStateListenerView.extend({
|
||||
shouldRefresh: function(model) {
|
||||
return ViewUtils.hasChangedAttributes(model, ['edited_on', 'published_on', 'publish_state']);
|
||||
return ViewUtils.hasChangedAttributes(model, ['has_changes', 'published']);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var previewAction = this.$el.find('.button-preview'),
|
||||
viewLiveAction = this.$el.find('.button-view'),
|
||||
publishState = this.model.get('publish_state');
|
||||
if (publishState !== 'unscheduled') {
|
||||
viewLiveAction = this.$el.find('.button-view');
|
||||
if (this.model.get('published')) {
|
||||
viewLiveAction.removeClass(disabledCss);
|
||||
}
|
||||
else {
|
||||
viewLiveAction.addClass(disabledCss);
|
||||
}
|
||||
if (publishState !== 'live' && publishState !== 'ready') {
|
||||
if (this.model.get('has_changes') || !this.model.get('published')) {
|
||||
previewAction.removeClass(disabledCss);
|
||||
}
|
||||
else {
|
||||
@@ -99,14 +99,18 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
|
||||
},
|
||||
|
||||
onSync: function(model) {
|
||||
if (ViewUtils.hasChangedAttributes(model, [ 'edited_on', 'published_on', 'publish_state' ])) {
|
||||
if (ViewUtils.hasChangedAttributes(model, [
|
||||
'has_changes', 'published', 'edited_on', 'edited_by', 'visibility_state'
|
||||
])) {
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(this.template({
|
||||
publishState: this.model.get('publish_state'),
|
||||
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'),
|
||||
@@ -162,7 +166,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
|
||||
if (e && e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
enableStaffLock = xblockInfo.get('publish_state') !== 'staff_only';
|
||||
enableStaffLock = xblockInfo.get('visibility_state') !== VisibilityState.staffOnly;
|
||||
|
||||
revertCheckBox = function() {
|
||||
self.checkStaffLock(!enableStaffLock);
|
||||
@@ -221,7 +225,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
|
||||
},
|
||||
|
||||
onSync: function(model) {
|
||||
if (ViewUtils.hasChangedAttributes(model, ['published_on'])) {
|
||||
if (ViewUtils.hasChangedAttributes(model, ['published', 'published_on', 'published_by'])) {
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,7 +10,6 @@ define(['js/views/xblock_outline'],
|
||||
// takes XBlockInfo as a model
|
||||
|
||||
templateName: 'unit-outline',
|
||||
className: 'group-configurations-list',
|
||||
|
||||
render: function() {
|
||||
XBlockOutlineView.prototype.render.call(this);
|
||||
|
||||
@@ -3,7 +3,36 @@
|
||||
*/
|
||||
define(["jquery", "underscore", "gettext", "js/views/utils/view_utils", "js/utils/module"],
|
||||
function($, _, gettext, ViewUtils, ModuleUtils) {
|
||||
var addXBlock, deleteXBlock, createUpdateRequestData, updateXBlockField;
|
||||
var addXBlock, deleteXBlock, createUpdateRequestData, updateXBlockField, VisibilityState,
|
||||
getXBlockVisibilityClass;
|
||||
|
||||
/**
|
||||
* Represents the possible visibility states for an xblock:
|
||||
*
|
||||
* live - the block and all of its descendants are live to students (excluding staff only)
|
||||
* Note: Live means both published and released.
|
||||
*
|
||||
* ready - the block is ready to go live and all of its descendants are live or ready (excluding staff only)
|
||||
* Note: content is ready when it is published and scheduled with a release date in the future.
|
||||
*
|
||||
* unscheduled - the block and all of its descendants have no release date (excluding staff only)
|
||||
* Note: it is valid for items to be published with no release date in which case they are unscheduled.
|
||||
*
|
||||
* needsAttention - the block or its descendants need attention
|
||||
* i.e. there is some content that is not fully live, ready, unscheduled or staff only.
|
||||
* For example: one subsection has draft content, or there's both unreleased and released content
|
||||
* in one section.
|
||||
*
|
||||
* staffOnly - all of the block's content is to be shown to staff only
|
||||
* Note: staff only items do not affect their parent's state.
|
||||
*/
|
||||
VisibilityState = {
|
||||
live: 'live',
|
||||
ready: 'ready',
|
||||
unscheduled: 'unscheduled',
|
||||
needsAttention: 'needs_attention',
|
||||
staffOnly: 'staff_only'
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds an xblock based upon the data attributes of the specified add button. A promise
|
||||
@@ -83,9 +112,30 @@ define(["jquery", "underscore", "gettext", "js/views/utils/view_utils", "js/util
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the CSS class to represent the specified xblock visibility state.
|
||||
*/
|
||||
getXBlockVisibilityClass = function(visibilityState) {
|
||||
if (visibilityState === VisibilityState.staffOnly) {
|
||||
return 'is-staff-only';
|
||||
}
|
||||
if (visibilityState === VisibilityState.live) {
|
||||
return 'is-live';
|
||||
}
|
||||
if (visibilityState === VisibilityState.ready) {
|
||||
return 'is-ready';
|
||||
}
|
||||
if (visibilityState === VisibilityState.needsAttention) {
|
||||
return 'has-warnings';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
return {
|
||||
'VisibilityState': VisibilityState,
|
||||
'addXBlock': addXBlock,
|
||||
'deleteXBlock': deleteXBlock,
|
||||
'updateXBlockField': updateXBlockField
|
||||
'updateXBlockField': updateXBlockField,
|
||||
'getXBlockVisibilityClass': getXBlockVisibilityClass
|
||||
};
|
||||
});
|
||||
|
||||
@@ -68,6 +68,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
|
||||
}
|
||||
html = this.template({
|
||||
xblockInfo: xblockInfo,
|
||||
visibilityClass: XBlockViewUtils.getXBlockVisibilityClass(xblockInfo.get('visibility_state')),
|
||||
parentInfo: this.parentInfo,
|
||||
xblockType: xblockType,
|
||||
parentType: parentType,
|
||||
@@ -200,7 +201,11 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
|
||||
} else {
|
||||
locatorElement = this.$('.outline-item[data-locator="' + locatorToShow + '"]');
|
||||
}
|
||||
ViewUtils.setScrollOffset(locatorElement, scrollOffset);
|
||||
if (locatorElement.length > 0) {
|
||||
ViewUtils.setScrollOffset(locatorElement, scrollOffset);
|
||||
} else {
|
||||
console.error("Failed to show item with locator " + locatorToShow + "");
|
||||
}
|
||||
if (editDisplayName) {
|
||||
locatorElement.find('> div[class$="header"] .xblock-field-value-edit').click();
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ from contentstore.utils import reverse_usage_url
|
||||
<%
|
||||
course_locator = context_course.location
|
||||
%>
|
||||
<article class="outline" data-locator="${course_locator}" data-course-key="${course_locator.course_key}">
|
||||
<article class="outline outline-course" data-locator="${course_locator}" data-course-key="${course_locator.course_key}">
|
||||
</article>
|
||||
</div>
|
||||
<div class="ui-loading">
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
<%
|
||||
var category = xblockInfo.get('category');
|
||||
var releasedToStudents = xblockInfo.get('released_to_students');
|
||||
var publishState = xblockInfo.get('publish_state');
|
||||
|
||||
var publishClass = '';
|
||||
if (publishState === 'staff_only') {
|
||||
publishClass = 'is-staff-only';
|
||||
} else if (publishState === 'live') {
|
||||
publishClass = 'is-live';
|
||||
} else if (publishState === 'ready') {
|
||||
publishClass = 'is-ready';
|
||||
} else if (publishState === 'has_unpublished_content') {
|
||||
publishClass = 'has-warnings';
|
||||
}
|
||||
var visibilityState = xblockInfo.get('visibility_state');
|
||||
|
||||
var listType = 'list-unknown';
|
||||
if (xblockType === 'course') {
|
||||
@@ -25,10 +14,10 @@ if (xblockType === 'course') {
|
||||
|
||||
var statusMessage = null;
|
||||
var statusType = null;
|
||||
if (publishState === 'staff_only') {
|
||||
if (visibilityState === 'staff_only') {
|
||||
statusType = 'staff-only';
|
||||
statusMessage = 'Contains staff only content';
|
||||
} else if (publishState === 'has_unpublished_content') {
|
||||
} else if (visibilityState === 'needs_attention') {
|
||||
if (category === 'vertical') {
|
||||
statusType = 'warning';
|
||||
if (releasedToStudents) {
|
||||
@@ -49,7 +38,7 @@ if (statusType === 'warning') {
|
||||
}
|
||||
%>
|
||||
<% if (parentInfo) { %>
|
||||
<li class="outline-item outline-<%= xblockType %> <%= publishClass %> is-draggable <%= includesChildren ? 'is-collapsible' : '' %> <%= isCollapsed ? 'is-collapsed' : '' %>"
|
||||
<li class="outline-item outline-<%= xblockType %> <%= visibilityClass %> is-draggable <%= includesChildren ? 'is-collapsible' : '' %> <%= isCollapsed ? 'is-collapsed' : '' %>"
|
||||
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
|
||||
|
||||
<div class="<%= xblockType %>-header">
|
||||
|
||||
@@ -1,45 +1,32 @@
|
||||
<%
|
||||
var publishClass = '';
|
||||
if (publishState === 'staff_only') {
|
||||
publishClass = 'is-staff-only';
|
||||
} else if (publishState === 'live') {
|
||||
publishClass = 'is-live';
|
||||
} else if (publishState === 'ready') {
|
||||
publishClass = 'is-ready';
|
||||
} else if (publishState === 'has_unpublished_content') {
|
||||
publishClass = 'has-warnings is-draft';
|
||||
}
|
||||
|
||||
var title = gettext("Draft (Never published)");
|
||||
if (publishState === 'staff_only') {
|
||||
if (visibilityState === 'staff_only') {
|
||||
title = gettext("Unpublished (Staff only)");
|
||||
} else if (publishState === 'live') {
|
||||
} else if (visibilityState === 'live') {
|
||||
title = gettext("Published and Live");
|
||||
} else if (publishState === 'ready') {
|
||||
} else if (visibilityState === 'ready') {
|
||||
title = gettext("Published");
|
||||
} else if (publishState === 'has_unpublished_content') {
|
||||
} else if (visibilityState === 'needs_attention') {
|
||||
title = gettext("Draft (Unpublished changes)");
|
||||
}
|
||||
|
||||
var releaseLabel = gettext("Release:");
|
||||
if (publishState === 'live') {
|
||||
if (visibilityState === 'live') {
|
||||
releaseLabel = gettext("Released:");
|
||||
} else if (publishState === 'ready') {
|
||||
} else if (visibilityState === 'ready') {
|
||||
releaseLabel = gettext("Scheduled:");
|
||||
}
|
||||
|
||||
var canPublish = publishState !== 'ready' && publishState !== 'live';
|
||||
var canDiscardChanges = publishState === 'has_unpublished_content';
|
||||
var visibleToStaffOnly = publishState === 'staff_only';
|
||||
var visibleToStaffOnly = visibilityState === 'staff_only';
|
||||
%>
|
||||
<div class="bit-publishing <%= publishClass %>">
|
||||
<div class="bit-publishing <%= visibilityClass %>">
|
||||
<h3 class="bar-mod-title pub-status"><span class="sr"><%= gettext("Publishing Status") %></span>
|
||||
<%= title %>
|
||||
</h3>
|
||||
|
||||
<div class="wrapper-last-draft bar-mod-content">
|
||||
<p class="copy meta">
|
||||
<% if (publishState === 'has_unpublished_content' && editedOn && editedBy) {
|
||||
<% 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">' + editedOn + '</span>',
|
||||
@@ -91,12 +78,12 @@ var visibleToStaffOnly = publishState === 'staff_only';
|
||||
<div class="wrapper-pub-actions bar-mod-actions">
|
||||
<ul class="action-list">
|
||||
<li class="action-item">
|
||||
<a class="action-publish action-primary <% if (!canPublish) { %>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 (!canDiscardChanges) { %>is-disabled<% } %>"
|
||||
<a class="action-discard action-secondary <% if (!published || !hasChanges) { %>is-disabled<% } %>"
|
||||
href=""><%= gettext("Discard Changes") %>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
<%
|
||||
var publishState = xblockInfo.get('publish_state');
|
||||
var publishClass = '';
|
||||
if (publishState === 'staff_only') {
|
||||
publishClass = 'is-staff-only';
|
||||
} else if (publishState === 'live') {
|
||||
publishClass = 'is-live';
|
||||
} else if (publishState === 'ready') {
|
||||
publishClass = 'is-ready';
|
||||
} else if (publishState === 'has_unpublished_content') {
|
||||
publishClass = 'has_warnings';
|
||||
}
|
||||
|
||||
var listType = 'list-for-' + xblockType;
|
||||
if (xblockType === 'course') {
|
||||
listType = 'list-sections';
|
||||
@@ -21,7 +9,7 @@ if (xblockType === 'course') {
|
||||
}
|
||||
%>
|
||||
<% if (parentInfo) { %>
|
||||
<li class="outline-item outline-<%= xblockType %> <%= publishClass %>"
|
||||
<li class="outline-item outline-<%= xblockType %> <%= visibilityClass %>"
|
||||
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
|
||||
<div class="<%= xblockType %>-header">
|
||||
<h3 class="<%= xblockType %>-header-details">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from contentstore.utils import compute_publish_state %>
|
||||
<%! from contentstore.views.helpers import xblock_studio_url %>
|
||||
|
||||
<!--
|
||||
@@ -18,7 +17,7 @@ This def will enumerate through a passed in subsection and list all of the units
|
||||
<%include file="_ui-dnd-indicator-before.html" />
|
||||
|
||||
<%
|
||||
unit_state = compute_publish_state(unit)
|
||||
unit_state = 'draft' # Note: this is a hack since this HTML file is being removed in bulk-publishing
|
||||
if unit.location == selected:
|
||||
selected_class = 'editing'
|
||||
else:
|
||||
|
||||
@@ -90,10 +90,10 @@ class ModuleStoreEnum(object):
|
||||
test = -3
|
||||
|
||||
|
||||
class LegacyPublishState(object):
|
||||
class PublishState(object):
|
||||
"""
|
||||
The legacy publish state for a given xblock-- either 'draft', 'private', or 'public'. These states
|
||||
are no longer used in Studio directly, but are still referenced in a few places.
|
||||
are no longer used in Studio, but they are still referenced in a few places in LMS.
|
||||
"""
|
||||
draft = 'draft'
|
||||
private = 'private'
|
||||
@@ -301,10 +301,10 @@ class ModuleStoreRead(object):
|
||||
Returns whether this xblock is draft, public, or private.
|
||||
|
||||
Returns:
|
||||
LegacyPublishState.draft - content is in the process of being edited, but still has a previous
|
||||
PublishState.draft - content is in the process of being edited, but still has a previous
|
||||
version deployed to LMS
|
||||
LegacyPublishState.public - content is locked and deployed to LMS
|
||||
LegacyPublishState.private - content is editable and not deployed to LMS
|
||||
PublishState.public - content is locked and deployed to LMS
|
||||
PublishState.private - content is editable and not deployed to LMS
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -522,7 +522,7 @@ class ModuleStoreReadBase(ModuleStoreRead):
|
||||
"""
|
||||
Returns PublishState.public since this is a read-only store.
|
||||
"""
|
||||
return LegacyPublishState.public
|
||||
return PublishState.public
|
||||
|
||||
def heartbeat(self):
|
||||
"""
|
||||
|
||||
@@ -439,10 +439,10 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
Returns whether this xblock is draft, public, or private.
|
||||
|
||||
Returns:
|
||||
LegacyPublishState.draft - content is in the process of being edited, but still has a previous
|
||||
PublishState.draft - content is in the process of being edited, but still has a previous
|
||||
version deployed to LMS
|
||||
LegacyPublishState.public - content is locked and deployed to LMS
|
||||
LegacyPublishState.private - content is editable and not deployed to LMS
|
||||
PublishState.public - content is locked and deployed to LMS
|
||||
PublishState.private - content is editable and not deployed to LMS
|
||||
"""
|
||||
course_id = xblock.scope_ids.usage_id.course_key
|
||||
store = self._get_modulestore_for_courseid(course_id)
|
||||
|
||||
@@ -11,7 +11,7 @@ import logging
|
||||
|
||||
from opaque_keys.edx.locations import Location
|
||||
from xmodule.exceptions import InvalidVersionError
|
||||
from xmodule.modulestore import LegacyPublishState, ModuleStoreEnum
|
||||
from xmodule.modulestore import PublishState, ModuleStoreEnum
|
||||
from xmodule.modulestore.exceptions import (
|
||||
ItemNotFoundError, DuplicateItemError, InvalidBranchSetting, DuplicateCourseError
|
||||
)
|
||||
@@ -613,7 +613,7 @@ class DraftModuleStore(MongoModuleStore):
|
||||
return False
|
||||
|
||||
# don't check children if this block has changes (is not public)
|
||||
if self.compute_publish_state(item) != LegacyPublishState.public:
|
||||
if self.compute_publish_state(item) != PublishState.public:
|
||||
return True
|
||||
# if this block doesn't have changes, then check its children
|
||||
elif item.has_children:
|
||||
@@ -792,10 +792,10 @@ class DraftModuleStore(MongoModuleStore):
|
||||
Returns whether this xblock is draft, public, or private.
|
||||
|
||||
Returns:
|
||||
LegacyPublishState.draft - content is in the process of being edited, but still has a previous
|
||||
PublishState.draft - content is in the process of being edited, but still has a previous
|
||||
version deployed to LMS
|
||||
LegacyPublishState.public - content is locked and deployed to LMS
|
||||
LegacyPublishState.private - content is editable and not deployed to LMS
|
||||
PublishState.public - content is locked and deployed to LMS
|
||||
PublishState.private - content is editable and not deployed to LMS
|
||||
"""
|
||||
if getattr(xblock, 'is_draft', False):
|
||||
published_xblock_location = as_published(xblock.location)
|
||||
@@ -803,11 +803,11 @@ class DraftModuleStore(MongoModuleStore):
|
||||
{'_id': published_xblock_location.to_deprecated_son()}
|
||||
)
|
||||
if published_item is None:
|
||||
return LegacyPublishState.private
|
||||
return PublishState.private
|
||||
else:
|
||||
return LegacyPublishState.draft
|
||||
return PublishState.draft
|
||||
else:
|
||||
return LegacyPublishState.public
|
||||
return PublishState.public
|
||||
|
||||
def _verify_branch_setting(self, expected_branch_setting):
|
||||
"""
|
||||
|
||||
@@ -4,7 +4,7 @@ Module for the dual-branch fall-back Draft->Published Versioning ModuleStore
|
||||
|
||||
from ..exceptions import ItemNotFoundError
|
||||
from split import SplitMongoModuleStore, EXCLUDE_ALL
|
||||
from xmodule.modulestore import ModuleStoreEnum, LegacyPublishState
|
||||
from xmodule.modulestore import ModuleStoreEnum, PublishState
|
||||
from xmodule.modulestore.exceptions import InsufficientSpecificationError
|
||||
from xmodule.modulestore.draft_and_published import ModuleStoreDraftAndPublished, DIRECT_ONLY_CATEGORIES, UnsupportedRevisionError
|
||||
|
||||
@@ -251,9 +251,9 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
|
||||
Returns whether this xblock is draft, public, or private.
|
||||
|
||||
Returns:
|
||||
LegacyPublishState.draft - published exists and is different from draft
|
||||
LegacyPublishState.public - published exists and is the same as draft
|
||||
LegacyPublishState.private - no published version exists
|
||||
PublishState.draft - published exists and is different from draft
|
||||
PublishState.public - published exists and is the same as draft
|
||||
PublishState.private - no published version exists
|
||||
"""
|
||||
# TODO figure out what to say if xblock is not from the HEAD of its branch
|
||||
def get_head(branch):
|
||||
@@ -272,13 +272,13 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
|
||||
|
||||
if not published_head:
|
||||
# published version does not exist
|
||||
return LegacyPublishState.private
|
||||
return PublishState.private
|
||||
elif get_version(draft_head) == get_version(published_head):
|
||||
# published and draft versions are equal
|
||||
return LegacyPublishState.public
|
||||
return PublishState.public
|
||||
else:
|
||||
# published and draft versions differ
|
||||
return LegacyPublishState.draft
|
||||
return PublishState.draft
|
||||
|
||||
def convert_to_draft(self, location, user_id):
|
||||
"""
|
||||
|
||||
@@ -9,7 +9,7 @@ from pytz import UTC
|
||||
|
||||
from xmodule.tests import DATA_DIR
|
||||
from opaque_keys.edx.locations import Location
|
||||
from xmodule.modulestore import ModuleStoreEnum, LegacyPublishState
|
||||
from xmodule.modulestore import ModuleStoreEnum, PublishState
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.exceptions import InvalidVersionError
|
||||
|
||||
@@ -991,22 +991,22 @@ class TestMixedModuleStore(unittest.TestCase):
|
||||
item_location = item.location.version_agnostic()
|
||||
mongo_store = self.store._get_modulestore_for_courseid(self._course_key_from_string(self.MONGO_COURSEID))
|
||||
with check_mongo_calls(mongo_store, max_find, max_send):
|
||||
self.assertEquals(self.store.compute_publish_state(item), LegacyPublishState.private)
|
||||
self.assertEquals(self.store.compute_publish_state(item), PublishState.private)
|
||||
|
||||
# Private -> Public
|
||||
self.store.publish(item_location, self.user_id)
|
||||
item = self.store.get_item(item_location)
|
||||
self.assertEquals(self.store.compute_publish_state(item), LegacyPublishState.public)
|
||||
self.assertEquals(self.store.compute_publish_state(item), PublishState.public)
|
||||
|
||||
# Public -> Private
|
||||
self.store.unpublish(item_location, self.user_id)
|
||||
item = self.store.get_item(item_location)
|
||||
self.assertEquals(self.store.compute_publish_state(item), LegacyPublishState.private)
|
||||
self.assertEquals(self.store.compute_publish_state(item), PublishState.private)
|
||||
|
||||
# Private -> Public
|
||||
self.store.publish(item_location, self.user_id)
|
||||
item = self.store.get_item(item_location)
|
||||
self.assertEquals(self.store.compute_publish_state(item), LegacyPublishState.public)
|
||||
self.assertEquals(self.store.compute_publish_state(item), PublishState.public)
|
||||
|
||||
# Public -> Draft with NO changes
|
||||
# Note: This is where Split and Mongo differ
|
||||
@@ -1014,14 +1014,14 @@ class TestMixedModuleStore(unittest.TestCase):
|
||||
item = self.store.get_item(item_location)
|
||||
self.assertEquals(
|
||||
self.store.compute_publish_state(item),
|
||||
LegacyPublishState.draft if default_ms == 'draft' else LegacyPublishState.public
|
||||
PublishState.draft if default_ms == 'draft' else PublishState.public
|
||||
)
|
||||
|
||||
# Draft WITH changes
|
||||
item.display_name = 'new name'
|
||||
item = self.store.update_item(item, self.user_id)
|
||||
self.assertTrue(self.store.has_changes(item.location))
|
||||
self.assertEquals(self.store.compute_publish_state(item), LegacyPublishState.draft)
|
||||
self.assertEquals(self.store.compute_publish_state(item), PublishState.draft)
|
||||
|
||||
@ddt.data('draft', 'split')
|
||||
def test_auto_publish(self, default_ms):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from ..pages.studio.auto_auth import AutoAuthPage
|
||||
from ..fixtures.course import CourseFixture
|
||||
from acceptance.tests.helpers import UniqueCourseTest
|
||||
from .helpers import UniqueCourseTest
|
||||
|
||||
|
||||
|
||||
class StudioCourseTest(UniqueCourseTest):
|
||||
|
||||
@@ -25,7 +25,7 @@ from ..pages.studio.signup import SignupPage
|
||||
from ..pages.studio.textbooks import TextbooksPage
|
||||
from ..fixtures.course import XBlockFixtureDesc
|
||||
|
||||
from acceptance.tests.base_studio_test import StudioCourseTest
|
||||
from .base_studio_test import StudioCourseTest
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
|
||||
@@ -8,7 +8,7 @@ from ..pages.studio.overview import CourseOutlinePage, ContainerPage, ExpandColl
|
||||
from ..pages.lms.courseware import CoursewarePage
|
||||
from ..fixtures.course import XBlockFixtureDesc
|
||||
|
||||
from acceptance.tests.base_studio_test import StudioCourseTest
|
||||
from .base_studio_test import StudioCourseTest
|
||||
|
||||
|
||||
class CourseOutlineTest(StudioCourseTest):
|
||||
|
||||
@@ -20,7 +20,7 @@ from ..pages.studio.utils import add_advanced_component
|
||||
from ..pages.studio.unit import UnitPage
|
||||
from ..pages.xblock.utils import wait_for_xblock_initialization
|
||||
|
||||
from acceptance.tests.base_studio_test import StudioCourseTest
|
||||
from .base_studio_test import StudioCourseTest
|
||||
|
||||
from test_studio_container import ContainerBase
|
||||
|
||||
|
||||
Reference in New Issue
Block a user