Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
cbbf38b4
Commit
cbbf38b4
authored
Mar 13, 2014
by
Christina Roberts
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2838 from edx/andya/container-publishing
Support publishing of nested xblocks
parents
e457135e
5166461c
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
109 additions
and
32 deletions
+109
-32
cms/djangoapps/contentstore/utils.py
+14
-9
cms/djangoapps/contentstore/views/component.py
+5
-4
cms/djangoapps/contentstore/views/helpers.py
+1
-1
cms/djangoapps/contentstore/views/item.py
+2
-2
cms/djangoapps/contentstore/views/tests/test_container.py
+8
-5
cms/djangoapps/contentstore/views/tests/test_helpers.py
+5
-5
cms/djangoapps/contentstore/views/tests/test_item.py
+46
-1
cms/static/sass/views/_container.scss
+12
-1
cms/templates/container.html
+9
-0
cms/templates/ux/reference/container.html
+5
-2
cms/templates/widgets/units.html
+2
-2
No files found.
cms/djangoapps/contentstore/utils.py
View file @
cbbf38b4
...
...
@@ -194,30 +194,35 @@ def course_image_url(course):
return
path
class
UnitState
(
object
):
class
PublishState
(
object
):
"""
The publish state for a given xblock-- either 'draft', 'private', or 'public'.
Currently in CMS, an xblock can only be in 'draft' or 'private' if it is at or below the Unit level.
"""
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 editable
d
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
Unit
State
.
draft
modulestore
(
'direct'
)
.
get_item
(
xblock
.
location
)
return
Publish
State
.
draft
except
ItemNotFoundError
:
return
Unit
State
.
private
return
Publish
State
.
private
else
:
return
Unit
State
.
public
return
Publish
State
.
public
def
add_extra_panel_tab
(
tab_type
,
course
):
...
...
cms/djangoapps/contentstore/views/component.py
View file @
cbbf38b4
...
...
@@ -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
,
Unit
State
,
get_modulestore
from
contentstore.utils
import
get_lms_link_for_item
,
compute_
publish_state
,
Publish
State
,
get_modulestore
from
contentstore.views.helpers
import
get_parent_xblock
from
models.settings.course_grading
import
CourseGradingModel
...
...
@@ -107,8 +107,8 @@ def subsection_handler(request, tag=None, package_id=None, branch=None, version_
can_view_live
=
False
subsection_units
=
item
.
get_children
()
for
unit
in
subsection_units
:
state
=
compute_
unit
_state
(
unit
)
if
state
==
UnitState
.
public
or
state
==
UnitState
.
draft
:
state
=
compute_
publish
_state
(
unit
)
if
state
in
(
PublishState
.
public
,
PublishState
.
draft
)
:
can_view_live
=
True
break
...
...
@@ -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
...
...
@@ -322,6 +322,7 @@ def container_handler(request, tag=None, package_id=None, branch=None, version_g
'context_course'
:
course
,
'xblock'
:
xblock
,
'xblock_locator'
:
locator
,
'unit'
:
None
if
not
ancestor_xblocks
else
ancestor_xblocks
[
0
],
'ancestor_xblocks'
:
ancestor_xblocks
,
})
else
:
...
...
cms/djangoapps/contentstore/views/helpers.py
View file @
cbbf38b4
...
...
@@ -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
)
cms/djangoapps/contentstore/views/item.py
View file @
cbbf38b4
...
...
@@ -297,9 +297,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)
...
...
cms/djangoapps/contentstore/views/tests/test_container.py
View file @
cbbf38b4
...
...
@@ -28,9 +28,9 @@ class ContainerViewTestCase(CourseTestCase):
def
test_container_html
(
self
):
self
.
_test_html_content
(
self
.
child_vertical
,
expected_section_tag
=
'<section class="wrapper-xblock level-page" data-locator="MITx.999.Robot_Super_Course/branch/
published
/block/Child_Vertical"/>'
,
expected_section_tag
=
'<section class="wrapper-xblock level-page" data-locator="MITx.999.Robot_Super_Course/branch/
draft
/block/Child_Vertical"/>'
,
expected_breadcrumbs
=
(
r'<a href="/unit/MITx.999.Robot_Super_Course/branch/
published
/block/Unit"\s*'
r'<a href="/unit/MITx.999.Robot_Super_Course/branch/
draft
/block/Unit"\s*'
r'class="navigation-link navigation-parent">Unit</a>\s*'
r'<a href="#" class="navigation-link navigation-current">Child Vertical</a>'
),
)
...
...
@@ -46,11 +46,11 @@ class ContainerViewTestCase(CourseTestCase):
category
=
"html"
,
display_name
=
"Child HTML"
)
self
.
_test_html_content
(
xblock_with_child
,
expected_section_tag
=
'<section class="wrapper-xblock level-page" data-locator="MITx.999.Robot_Super_Course/branch/
published
/block/Wrapper"/>'
,
expected_section_tag
=
'<section class="wrapper-xblock level-page" data-locator="MITx.999.Robot_Super_Course/branch/
draft
/block/Wrapper"/>'
,
expected_breadcrumbs
=
(
r'<a href="/unit/MITx.999.Robot_Super_Course/branch/
published
/block/Unit"\s*'
r'<a href="/unit/MITx.999.Robot_Super_Course/branch/
draft
/block/Unit"\s*'
r'class="navigation-link navigation-parent">Unit</a>\s*'
r'<a href="/container/MITx.999.Robot_Super_Course/branch/
published
/block/Child_Vertical"\s*'
r'<a href="/container/MITx.999.Robot_Super_Course/branch/
draft
/block/Child_Vertical"\s*'
r'class="navigation-link navigation-parent">Child Vertical</a>\s*'
r'<a href="#" class="navigation-link navigation-current">Wrapper</a>'
),
)
...
...
@@ -67,3 +67,6 @@ class ContainerViewTestCase(CourseTestCase):
self
.
assertIn
(
expected_section_tag
,
html
)
# Verify the navigation link at the top of the page is correct.
self
.
assertRegexpMatches
(
html
,
expected_breadcrumbs
)
# Verify the link that allows users to change publish status.
expected_unit_link
=
'This content is published with unit <a href="/unit/MITx.999.Robot_Super_Course/branch/draft/block/Unit">Unit</a>.'
self
.
assertIn
(
expected_unit_link
,
html
)
cms/djangoapps/contentstore/views/tests/test_helpers.py
View file @
cbbf38b4
...
...
@@ -16,7 +16,7 @@ class HelpersTestCase(CourseTestCase):
# Verify course URL
self
.
assertEqual
(
xblock_studio_url
(
course
),
u'/course/MITx.999.Robot_Super_Course/branch/
published
/block/Robot_Super_Course'
)
u'/course/MITx.999.Robot_Super_Course/branch/
draft
/block/Robot_Super_Course'
)
# Verify chapter URL
chapter
=
ItemFactory
.
create
(
parent_location
=
self
.
course
.
location
,
category
=
'chapter'
,
...
...
@@ -34,17 +34,17 @@ class HelpersTestCase(CourseTestCase):
vertical
=
ItemFactory
.
create
(
parent_location
=
sequential
.
location
,
category
=
'vertical'
,
display_name
=
'Unit'
)
self
.
assertEqual
(
xblock_studio_url
(
vertical
),
u'/unit/MITx.999.Robot_Super_Course/branch/
published
/block/Unit'
)
u'/unit/MITx.999.Robot_Super_Course/branch/
draft
/block/Unit'
)
self
.
assertEqual
(
xblock_studio_url
(
vertical
,
course
),
u'/unit/MITx.999.Robot_Super_Course/branch/
published
/block/Unit'
)
u'/unit/MITx.999.Robot_Super_Course/branch/
draft
/block/Unit'
)
# Verify child vertical URL
child_vertical
=
ItemFactory
.
create
(
parent_location
=
vertical
.
location
,
category
=
'vertical'
,
display_name
=
'Child Vertical'
)
self
.
assertEqual
(
xblock_studio_url
(
child_vertical
),
u'/container/MITx.999.Robot_Super_Course/branch/
published
/block/Child_Vertical'
)
u'/container/MITx.999.Robot_Super_Course/branch/
draft
/block/Child_Vertical'
)
self
.
assertEqual
(
xblock_studio_url
(
child_vertical
,
course
),
u'/container/MITx.999.Robot_Super_Course/branch/
published
/block/Child_Vertical'
)
u'/container/MITx.999.Robot_Super_Course/branch/
draft
/block/Child_Vertical'
)
# Verify video URL
video
=
ItemFactory
.
create
(
parent_location
=
child_vertical
.
location
,
category
=
"video"
,
...
...
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
cbbf38b4
...
...
@@ -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
...
...
@@ -153,7 +154,7 @@ class GetItem(ItemTest):
html
,
# The instance of the wrapper class will have an auto-generated ID (wrapperxxx). Allow anything
# for the 3 characters after wrapper.
(
r'"/container/MITx.999.Robot_Super_Course/branch/
published
/block/wrapper.{3}" class="action-button">\s*'
(
r'"/container/MITx.999.Robot_Super_Course/branch/
draft
/block/wrapper.{3}" class="action-button">\s*'
'<span class="action-button-text">View</span>'
)
)
...
...
@@ -663,6 +664,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
)
...
...
@@ -671,6 +673,49 @@ class TestEditItem(ItemTest):
draft
=
self
.
get_item_from_modulestore
(
self
.
problem_locator
,
True
)
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
):
...
...
cms/static/sass/views/_container.scss
View file @
cbbf38b4
...
...
@@ -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
;
...
...
cms/templates/container.html
View file @
cbbf38b4
...
...
@@ -79,6 +79,15 @@ xblock_info = {
<section
class=
"wrapper-xblock level-page"
data-locator=
"${xblock_locator}"
/>
</article>
<aside
class=
"content-supplementary"
role=
"complimentary"
>
<div
class=
"bit-publishing"
>
<h3
class=
"title-3"
>
${_("Publishing Status")}
</h3>
<p
class=
"copy"
>
${_('This content is published with unit {unit_name}.').format(
unit_name=u'
<a
href=
"{unit_address}"
>
{unit_display_name}
</a>
'.format(
unit_address=xblock_studio_url(unit),
unit_display_name=unit.display_name_with_default,
)
)}
</p>
</div>
<div
class=
"bit"
>
<h3
class=
"title-3"
>
${_("What can I do on this page?")}
</h3>
<ul
class=
"list-details"
>
...
...
cms/templates/ux/reference/container.html
View file @
cbbf38b4
<
%
inherit
file=
"../../base.html"
/>
<
%!
from
django
.
core
.
urlresolvers
import
reverse
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%
block
name=
"title"
>
${_("Container")}
</
%
block>
<
%
block
name=
"title"
>
Container
</
%
block>
<
%
block
name=
"bodyclass"
>
is-signedin course uploads view-container
</
%
block>
<
%
namespace
name=
'static'
file=
'../../static_content.html'
/>
...
...
@@ -426,6 +425,10 @@ from django.utils.translation import ugettext as _
</section>
</article>
<aside
class=
"content-supplementary"
role=
"complimentary"
>
<div
class=
"bit-publishing"
>
<h3
class=
"title-3"
>
Publishing Status
</h3>
<p
class=
"copy"
>
This content is published with unit
<a
href=
""
>
Unit 1
</a>
. To make changes to the content of this container, place
<a
href=
""
>
Unit 1
</a>
in draft mode.
</p>
</div>
<div
class=
"bit"
>
<h3
class=
"title-3"
>
Container Reference Page
</h3>
<ul
class=
"list-details"
>
...
...
cms/templates/widgets/units.html
View file @
cbbf38b4
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%!
from
django
.
core
.
urlresolvers
import
reverse
%
>
<
%!
from
contentstore
.
utils
import
compute_
unit
_state
%
>
<
%!
from
contentstore
.
utils
import
compute_
publish
_state
%
>
<
%!
from
xmodule
.
modulestore
.
django
import
loc_mapper
%
>
<!--
...
...
@@ -25,7 +25,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_
unit
_state(unit)
unit_state =
compute_
publish
_state(unit)
if
unit
.
location =
=
selected:
selected_class =
'editing'
else:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment