diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index c93efa34c2..a5999f263d 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -194,30 +194,30 @@ def course_image_url(course): return path -class UnitState(object): +class PublishState(object): draft = 'draft' private = 'private' public = 'public' -def compute_unit_state(unit): +def compute_publish_state(xblock): """ - Returns whether this unit is 'draft', 'public', or 'private'. + Returns whether this xblock is 'draft', 'public', or 'private'. 'draft' content is in the process of being edited, but still has a previous version visible in the LMS 'public' content is locked and visible in the LMS - 'private' content is editabled and not visible in the LMS + 'private' content is editable and not visible in the LMS """ - if getattr(unit, 'is_draft', False): + if getattr(xblock, 'is_draft', False): try: - modulestore('direct').get_item(unit.location) - return UnitState.draft + modulestore('direct').get_item(xblock.location) + return PublishState.draft except ItemNotFoundError: - return UnitState.private + return PublishState.private else: - return UnitState.public + return PublishState.public def add_extra_panel_tab(tab_type, course): diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index a7946c55c9..e3c67ee0db 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -26,7 +26,7 @@ from xblock.runtime import Mixologist from lms.lib.xblock.runtime import unquote_slashes -from contentstore.utils import get_lms_link_for_item, compute_unit_state, UnitState, get_modulestore +from contentstore.utils import get_lms_link_for_item, compute_publish_state, PublishState, get_modulestore from contentstore.views.helpers import get_parent_xblock from models.settings.course_grading import CourseGradingModel @@ -282,7 +282,7 @@ def unit_handler(request, tag=None, package_id=None, branch=None, version_guid=N ), 'section': containing_section, 'new_unit_category': 'vertical', - 'unit_state': compute_unit_state(item), + 'unit_state': compute_publish_state(item), 'published_date': ( get_default_time_display(item.published_date) if item.published_date is not None else None diff --git a/cms/djangoapps/contentstore/views/helpers.py b/cms/djangoapps/contentstore/views/helpers.py index f351f481f5..946fa1637d 100644 --- a/cms/djangoapps/contentstore/views/helpers.py +++ b/cms/djangoapps/contentstore/views/helpers.py @@ -97,5 +97,5 @@ def xblock_studio_url(xblock, course=None): course_id = None if course: course_id = course.location.course_id - locator = loc_mapper().translate_location(course_id, xblock.location) + locator = loc_mapper().translate_location(course_id, xblock.location, published=False) return locator.url_reverse(prefix) diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 5696e213ad..1f7a56b142 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -296,9 +296,9 @@ def _save_item(request, usage_loc, item_location, data=None, children=None, meta if publish == 'make_private': _xmodule_recurse(existing_item, lambda i: modulestore().unpublish(i.location)) elif publish == 'create_draft': - # This clones the existing item location to a draft location (the draft is + # This recursively clones the existing item location to a draft location (the draft is # implicit, because modulestore is a Draft modulestore) - modulestore().convert_to_draft(item_location) + _xmodule_recurse(existing_item, lambda i: modulestore().convert_to_draft(i.location)) if data: # TODO Allow any scope.content fields not just "data" (exactly like the get below this) diff --git a/cms/djangoapps/contentstore/views/tests/test_item.py b/cms/djangoapps/contentstore/views/tests/test_item.py index e8e3a5092c..65d3642edf 100644 --- a/cms/djangoapps/contentstore/views/tests/test_item.py +++ b/cms/djangoapps/contentstore/views/tests/test_item.py @@ -15,6 +15,7 @@ from django.test.client import RequestFactory from contentstore.views.component import component_handler from contentstore.tests.utils import CourseTestCase +from contentstore.utils import compute_publish_state, PublishState from student.tests.factories import UserFactory from xmodule.capa_module import CapaDescriptor from xmodule.modulestore.django import modulestore @@ -647,6 +648,7 @@ class TestEditItem(ItemTest): self.assertEqual(resp.status_code, 200) # Activate the editing view + view_url = '/xblock/{locator}/studio_view'.format(locator=self.problem_locator) resp = self.client.get(view_url, HTTP_ACCEPT='application/json') self.assertEqual(resp.status_code, 200) @@ -656,6 +658,50 @@ class TestEditItem(ItemTest): self.assertNotEqual(draft.data, published.data) + def test_publish_states_of_nested_xblocks(self): + """ Test publishing of a unit page containing a nested xblock """ + + resp = self.create_xblock(parent_locator=self.seq_locator, display_name='Test Unit', category='vertical') + unit_locator = self.response_locator(resp) + resp = self.create_xblock(parent_locator=unit_locator, category='wrapper') + wrapper_locator = self.response_locator(resp) + resp = self.create_xblock(parent_locator=wrapper_locator, category='html') + html_locator = self.response_locator(resp) + + # The unit and its children should be private initially + unit_update_url = '/xblock/' + unit_locator + unit = self.get_item_from_modulestore(unit_locator, True) + html = self.get_item_from_modulestore(html_locator, True) + self.assertEqual(compute_publish_state(unit), PublishState.private) + self.assertEqual(compute_publish_state(html), PublishState.private) + + # Make the unit public and verify that the problem is also made public + resp = self.client.ajax_post( + unit_update_url, + data={'publish': 'make_public'} + ) + self.assertEqual(resp.status_code, 200) + unit = self.get_item_from_modulestore(unit_locator, True) + html = self.get_item_from_modulestore(html_locator, True) + self.assertEqual(compute_publish_state(unit), PublishState.public) + self.assertEqual(compute_publish_state(html), PublishState.public) + + # Make a draft for the unit and verify that the problem also has a draft + resp = self.client.ajax_post( + unit_update_url, + data={ + 'id': unit_locator, + 'metadata': {}, + 'publish': 'create_draft' + } + ) + self.assertEqual(resp.status_code, 200) + unit = self.get_item_from_modulestore(unit_locator, True) + html = self.get_item_from_modulestore(html_locator, True) + self.assertEqual(compute_publish_state(unit), PublishState.draft) + self.assertEqual(compute_publish_state(html), PublishState.draft) + + @ddt.ddt class TestComponentHandler(TestCase): def setUp(self):