From ad27107343616b83312db0be1311e1a2ca3fd150 Mon Sep 17 00:00:00 2001 From: Andy Armstrong Date: Wed, 5 Mar 2014 17:37:49 -0500 Subject: [PATCH 1/8] Support making drafts of nested xblocks --- cms/djangoapps/contentstore/utils.py | 18 ++++---- .../contentstore/views/component.py | 4 +- cms/djangoapps/contentstore/views/helpers.py | 2 +- cms/djangoapps/contentstore/views/item.py | 4 +- .../contentstore/views/tests/test_item.py | 46 +++++++++++++++++++ 5 files changed, 60 insertions(+), 14 deletions(-) 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): From b59d7ba9ecab635225b6a52702e90582ebc46fea Mon Sep 17 00:00:00 2001 From: Frances Botsford Date: Wed, 5 Mar 2014 17:09:18 -0500 Subject: [PATCH 2/8] first stab at publishing status for the container page --- cms/static/sass/views/_container.scss | 13 ++++++++++++- cms/templates/ux/reference/container.html | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/cms/static/sass/views/_container.scss b/cms/static/sass/views/_container.scss index 4c50103b81..b7ac403e66 100644 --- a/cms/static/sass/views/_container.scss +++ b/cms/static/sass/views/_container.scss @@ -30,11 +30,22 @@ body.view-container { label { @extend %t-title8; } + + .bit-publishing { + margin-bottom: $baseline; + border-top: 5px solid $blue; + background-color: $white; + padding: ($baseline*.75) ($baseline*.75) ($baseline) ($baseline*.75); + + .copy { + @extend %t-copy-sub1; + } + } } } // UI: xblock rendering -body.view-container .content-primary{ +body.view-container .content-primary { .wrapper-xblock { @extend %wrap-xblock; diff --git a/cms/templates/ux/reference/container.html b/cms/templates/ux/reference/container.html index 00f5e7caea..4eb7e231aa 100644 --- a/cms/templates/ux/reference/container.html +++ b/cms/templates/ux/reference/container.html @@ -426,6 +426,10 @@ from django.utils.translation import ugettext as _