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
a3590653
Commit
a3590653
authored
Jul 16, 2014
by
Andy Armstrong
Committed by
cahrens
Aug 07, 2014
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Integrate visual styling into the course outline
parent
c027f90d
Hide whitespace changes
Inline
Side-by-side
Showing
39 changed files
with
793 additions
and
445 deletions
+793
-445
cms/djangoapps/contentstore/features/common.py
+3
-3
cms/djangoapps/contentstore/features/course-outline.py
+3
-3
cms/djangoapps/contentstore/features/courses.py
+1
-1
cms/djangoapps/contentstore/tests/test_contentstore.py
+1
-1
cms/djangoapps/contentstore/tests/utils.py
+14
-14
cms/djangoapps/contentstore/utils.py
+3
-3
cms/djangoapps/contentstore/views/component.py
+2
-2
cms/djangoapps/contentstore/views/course.py
+2
-2
cms/djangoapps/contentstore/views/item.py
+76
-11
cms/djangoapps/contentstore/views/tests/test_course_index.py
+3
-3
cms/djangoapps/contentstore/views/tests/test_item.py
+157
-25
cms/static/js/models/xblock_info.js
+13
-15
cms/static/js/spec/views/pages/container_subviews_spec.js
+75
-79
cms/static/js/spec/views/pages/course_outline_spec.js
+71
-62
cms/static/js/spec/views/unit_outline_spec.js
+15
-10
cms/static/js/views/baseview.js
+5
-1
cms/static/js/views/pages/container.js
+4
-0
cms/static/js/views/pages/container_subviews.js
+13
-15
cms/static/js/views/pages/course_outline.js
+15
-15
cms/static/js/views/unit_outline.js
+3
-2
cms/static/js/views/utils/view_utils.js
+6
-2
cms/static/js/views/xblock_outline.js
+11
-3
cms/static/sass/elements/_modules.scss
+1
-1
cms/static/sass/views/_container.scss
+3
-2
cms/static/sass/views/_outline.scss
+31
-0
cms/templates/container.html
+0
-1
cms/templates/course_outline.html
+3
-3
cms/templates/js/course-outline.underscore
+115
-53
cms/templates/js/mock/mock-course-outline-page.underscore
+4
-4
cms/templates/js/publish-history.underscore
+15
-16
cms/templates/js/publish-xblock.underscore
+34
-30
cms/templates/js/unit-outline.underscore
+42
-19
cms/templates/js/xblock-outline.underscore
+3
-3
common/lib/xmodule/xmodule/modulestore/__init__.py
+8
-8
common/lib/xmodule/xmodule/modulestore/mixed.py
+3
-3
common/lib/xmodule/xmodule/modulestore/mongo/draft.py
+8
-8
common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py
+8
-7
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
+7
-7
common/test/acceptance/pages/studio/overview.py
+12
-8
No files found.
cms/djangoapps/contentstore/features/common.py
View file @
a3590653
...
...
@@ -203,8 +203,8 @@ def create_a_course():
def
add_section
():
world
.
css_click
(
'.
course-outline .add-button
'
)
assert_true
(
world
.
is_css_present
(
'.outline-
item-
section .xblock-field-value'
))
world
.
css_click
(
'.
outline .button-new
'
)
assert_true
(
world
.
is_css_present
(
'.outline-section .xblock-field-value'
))
def
set_date_and_time
(
date_css
,
desired_date
,
time_css
,
desired_time
,
key
=
None
):
...
...
@@ -241,7 +241,7 @@ def create_unit_from_course_outline():
The end result is the page where the user is editing the new unit.
"""
css_selectors
=
[
'.outline-
item-subsection .expand-collapse'
,
'.outline-item-subsection .add-button
'
'.outline-
subsection .expand-collapse'
,
'.outline-subsection .button-new
'
]
for
selector
in
css_selectors
:
world
.
css_click
(
selector
)
...
...
cms/djangoapps/contentstore/features/course-outline.py
View file @
a3590653
...
...
@@ -69,7 +69,7 @@ def i_add_a_section(step):
@step
(
u'I press the "section" delete icon'
)
def
i_press_the_section_delete_icon
(
step
):
delete_locator
=
'section .outline-
item-
section > .wrapper-xblock-header a.delete-button'
delete_locator
=
'section .outline-section > .wrapper-xblock-header a.delete-button'
world
.
css_click
(
delete_locator
)
...
...
@@ -110,9 +110,9 @@ def i_click_the_collapse_expand_all_span(step, text):
@step
(
u'I ([^"]*) the first section$'
)
def
i_collapse_expand_a_section
(
step
,
text
):
if
text
==
"collapse"
:
locator
=
'section .outline-
item-
section .ui-toggle-expansion'
locator
=
'section .outline-section .ui-toggle-expansion'
elif
text
==
"expand"
:
locator
=
'section .outline-
item-
section .ui-toggle-expansion'
locator
=
'section .outline-section .ui-toggle-expansion'
world
.
css_click
(
locator
)
...
...
cms/djangoapps/contentstore/features/courses.py
View file @
a3590653
...
...
@@ -66,5 +66,5 @@ def i_am_on_tab(step, tab_name):
@step
(
'I see a link for adding a new section$'
)
def
i_see_new_section_link
(
step
):
link_css
=
'.
course-outline .add-button
'
link_css
=
'.
outline .button-new
'
assert
world
.
css_has_text
(
link_css
,
'New Section'
)
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
a3590653
...
...
@@ -1209,7 +1209,7 @@ class ContentStoreTest(ContentStoreTestCase):
resp
=
self
.
_show_course_overview
(
course
.
id
)
self
.
assertContains
(
resp
,
'<article class="
course-
outline" data-locator="{locator}" data-course-key="{course_key}">'
.
format
(
'<article class="outline" data-locator="{locator}" data-course-key="{course_key}">'
.
format
(
locator
=
'i4x://MITx/999/course/Robot_Super_Course'
,
course_key
=
'MITx/999/Robot_Super_Course'
,
),
...
...
cms/djangoapps/contentstore/tests/utils.py
View file @
a3590653
...
...
@@ -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
PublishState
,
ModuleStoreEnum
from
xmodule.modulestore
import
LegacyPublishState
,
ModuleStoreEnum
,
mongo
from
xmodule.modulestore.inheritance
import
own_metadata
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
...
...
@@ -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
),
PublishState
.
draft
)
self
.
assertEqual
(
self
.
store
.
compute_publish_state
(
draft_vertical
),
Legacy
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
),
PublishState
.
private
)
self
.
assertEqual
(
self
.
store
.
compute_publish_state
(
private_vertical
),
Legacy
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
),
PublishState
.
public
)
self
.
assertEqual
(
self
.
store
.
compute_publish_state
(
public_vertical
),
Legacy
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
(
PublishState
.
private
,
PublishState
.
draft
):
if
publish_state
in
(
LegacyPublishState
.
private
,
Legacy
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
,
PublishState
.
draft
)
vertical
=
get_and_verify_publish_state
(
'vertical'
,
self
.
TEST_VERTICAL
,
Legacy
PublishState
.
draft
)
for
child
in
vertical
.
get_children
():
verify_item_publish_state
(
child
,
PublishState
.
draft
)
verify_item_publish_state
(
child
,
Legacy
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
,
PublishState
.
public
)
sequential
=
get_and_verify_publish_state
(
'sequential'
,
self
.
SEQUENTIAL
,
Legacy
PublishState
.
public
)
# verify that we have the private vertical
private_vertical
=
get_and_verify_publish_state
(
'vertical'
,
self
.
PRIVATE_VERTICAL
,
PublishState
.
private
)
private_vertical
=
get_and_verify_publish_state
(
'vertical'
,
self
.
PRIVATE_VERTICAL
,
Legacy
PublishState
.
private
)
# verify that we have the public vertical
public_vertical
=
get_and_verify_publish_state
(
'vertical'
,
self
.
PUBLISHED_VERTICAL
,
PublishState
.
public
)
public_vertical
=
get_and_verify_publish_state
(
'vertical'
,
self
.
PUBLISHED_VERTICAL
,
Legacy
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
==
PublishState
.
draft
and
isinstance
(
item
.
runtime
.
modulestore
,
DraftModuleStore
):
if
supposed_state
==
Legacy
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
PublishState
.
public
elif
supposed_state
==
PublishState
.
public
and
item
.
location
.
category
in
DIRECT_ONLY_CATEGORIES
:
return
Legacy
PublishState
.
public
elif
supposed_state
==
Legacy
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
PublishState
.
draft
return
Legacy
PublishState
.
draft
else
:
return
supposed_state
else
:
...
...
cms/djangoapps/contentstore/utils.py
View file @
a3590653
...
...
@@ -155,10 +155,10 @@ def compute_publish_state(xblock):
Returns whether this xblock is draft, public, or private.
Returns:
PublishState.draft - content is in the process of being edited, but still has a previous
Legacy
PublishState.draft - content is in the process of being edited, but still has a previous
version deployed to LMS
PublishState.public - content is locked and deployed to LMS
PublishState.private - content is editable and not deployed to LMS
Legacy
PublishState.public - content is locked and deployed to LMS
Legacy
PublishState.private - content is editable and not deployed to LMS
"""
return
modulestore
()
.
compute_publish_state
(
xblock
)
...
...
cms/djangoapps/contentstore/views/component.py
View file @
a3590653
...
...
@@ -12,7 +12,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
from
edxmako.shortcuts
import
render_to_response
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore
import
PublishState
from
xmodule.modulestore
import
Legacy
PublishState
from
xblock.core
import
XBlock
from
xblock.django.request
import
webob_to_django_response
,
django_to_webob_request
...
...
@@ -101,7 +101,7 @@ def subsection_handler(request, usage_key_string):
subsection_units
=
item
.
get_children
()
for
unit
in
subsection_units
:
state
=
compute_publish_state
(
unit
)
if
state
in
(
PublishState
.
public
,
PublishState
.
draft
):
if
state
in
(
LegacyPublishState
.
public
,
Legacy
PublishState
.
draft
):
can_view_live
=
True
break
...
...
cms/djangoapps/contentstore/views/course.py
View file @
a3590653
...
...
@@ -404,7 +404,7 @@ def course_outline_initial_state(locator_to_show, course_structure):
"""
if
xblock_info
[
'id'
]
==
locator
:
return
xblock_info
children
=
xblock_info
[
'child_info'
][
'children'
]
if
xblock_info
[
'child_info'
]
else
None
children
=
xblock_info
[
'child_info'
][
'children'
]
if
xblock_info
.
get
(
'child_info'
,
None
)
else
None
if
children
:
for
child_xblock_info
in
children
:
result
=
find_xblock_info
(
child_xblock_info
,
locator
)
...
...
@@ -417,7 +417,7 @@ def course_outline_initial_state(locator_to_show, course_structure):
Collect all the locators for an xblock and its children.
"""
locators
.
append
(
xblock_info
[
'id'
])
children
=
xblock_info
[
'child_info'
][
'children'
]
if
xblock_info
[
'child_info'
]
else
None
children
=
xblock_info
[
'child_info'
][
'children'
]
if
xblock_info
.
get
(
'child_info'
,
None
)
else
None
if
children
:
for
child_xblock_info
in
children
:
collect_all_locators
(
locators
,
child_xblock_info
)
...
...
cms/djangoapps/contentstore/views/item.py
View file @
a3590653
...
...
@@ -601,10 +601,6 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
In addition, an optional include_children_predicate argument can be provided to define whether or
not a particular xblock should have its children included.
"""
published
=
modulestore
()
.
has_item
(
xblock
.
location
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
# Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
release_date
=
get_default_time_display
(
xblock
.
start
)
if
xblock
.
start
!=
DEFAULT_START_DATE
else
None
def
safe_get_username
(
user_id
):
"""
...
...
@@ -623,11 +619,23 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
return
None
# Compute the child info first so it can be included in aggregate information for the parent
if
include_child_info
and
xblock
.
has_children
:
child_info
=
_create_xblock_child_info
(
xblock
,
include_children_predicate
=
include_children_predicate
)
else
:
child_info
=
None
# Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
release_date
=
get_default_time_display
(
xblock
.
start
)
if
xblock
.
start
!=
DEFAULT_START_DATE
else
None
published
=
modulestore
()
.
has_item
(
xblock
.
location
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
currently_visible_to_students
=
is_currently_visible_to_students
(
xblock
)
xblock_info
=
{
"id"
:
unicode
(
xblock
.
location
),
"display_name"
:
xblock
.
display_name_with_default
,
"category"
:
xblock
.
category
,
"has_changes"
:
modulestore
()
.
has_changes
(
xblock
.
location
),
"published"
:
published
,
"edited_on"
:
get_default_time_display
(
xblock
.
subtree_edited_on
)
if
xblock
.
subtree_edited_on
else
None
,
"edited_by"
:
safe_get_username
(
xblock
.
subtree_edited_by
),
...
...
@@ -637,8 +645,8 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"released_to_students"
:
datetime
.
now
(
UTC
)
>
xblock
.
start
,
"release_date"
:
release_date
,
"release_date_from"
:
_get_release_date_from
(
xblock
)
if
release_date
else
None
,
"
visible_to_staff_only"
:
xblock
.
visible_to_staff_only
,
"
currently_visible_to_students"
:
is_currently_visible_to_students
(
xblock
),
"
currently_visible_to_students"
:
currently_visible_to_students
,
"
publish_state"
:
_compute_publish_state
(
xblock
,
child_info
)
if
not
xblock
.
category
==
'course'
else
None
}
if
data
is
not
None
:
xblock_info
[
"data"
]
=
data
...
...
@@ -646,13 +654,70 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
xblock_info
[
"metadata"
]
=
metadata
if
include_ancestor_info
:
xblock_info
[
'ancestor_info'
]
=
_create_xblock_ancestor_info
(
xblock
)
if
include_child_info
and
xblock
.
has_children
:
xblock_info
[
'child_info'
]
=
_create_xblock_child_info
(
xblock
,
include_children_predicate
=
include_children_predicate
)
if
child_info
:
xblock_info
[
'child_info'
]
=
child_info
return
xblock_info
class
PublishState
(
object
):
"""
Represents the possible publish states for an xblock:
live - the block and all of its children are live to students (except for staff only items)
ready - the block and all of its children are ready to go live in the future
unscheduled - the block and all of its children are unscheduled
has_unpublished_content - the block or its children have unpublished content that is not staff only
staff_only - all of the block's content is to be shown to staff only
"""
live
=
'live'
ready
=
'ready'
unscheduled
=
'unscheduled'
has_unpublished_content
=
'has_unpublished_content'
staff_only
=
'staff_only'
def
_compute_publish_state
(
xblock
,
child_info
):
"""
Returns the current publish state for the specified xblock and its children
"""
if
xblock
.
visible_to_staff_only
:
return
PublishState
.
staff_only
elif
is_unit
(
xblock
)
and
modulestore
()
.
has_changes
(
xblock
.
location
):
return
PublishState
.
has_unpublished_content
is_unscheduled
=
xblock
.
start
==
DEFAULT_START_DATE
children
=
child_info
and
child_info
[
'children'
]
if
children
and
len
(
children
)
>
0
:
all_staff_only
=
True
all_unscheduled
=
True
all_live
=
True
for
child
in
child_info
[
'children'
]:
child_state
=
child
[
'publish_state'
]
if
child_state
==
PublishState
.
has_unpublished_content
:
return
child_state
elif
not
child_state
==
PublishState
.
staff_only
:
all_staff_only
=
False
if
not
child_state
==
PublishState
.
unscheduled
:
all_unscheduled
=
False
if
not
child_state
==
PublishState
.
live
:
all_live
=
False
if
all_staff_only
:
return
PublishState
.
staff_only
elif
all_unscheduled
:
if
not
is_unscheduled
:
return
PublishState
.
has_unpublished_content
else
:
return
PublishState
.
unscheduled
elif
all_live
:
return
PublishState
.
live
else
:
return
PublishState
.
ready
if
is_unscheduled
:
return
PublishState
.
unscheduled
elif
datetime
.
now
(
UTC
)
>
xblock
.
start
:
return
PublishState
.
live
else
:
return
PublishState
.
ready
def
_create_xblock_ancestor_info
(
xblock
):
"""
Returns information about the ancestors of an xblock. Note that the direct parent will also return
...
...
cms/djangoapps/contentstore/views/tests/test_course_index.py
View file @
a3590653
...
...
@@ -10,6 +10,7 @@ from contentstore.views.access import has_course_access
from
contentstore.views.course
import
course_outline_initial_state
from
course_action_state.models
import
CourseRerunState
from
contentstore.views.item
import
create_xblock_info
from
contentstore.views.item
import
create_xblock_info
,
PublishState
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
opaque_keys.edx.locator
import
CourseLocator
...
...
@@ -229,7 +230,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
.
assert
True
(
json_response
[
'published
'
])
self
.
assert
IsNone
(
json_response
[
'publish_state
'
])
# Now verify the first child
children
=
json_response
[
'child_info'
][
'children'
]
...
...
@@ -238,7 +239,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
.
assert
True
(
first_child_response
[
'published'
]
)
self
.
assert
Equal
(
first_child_response
[
'publish_state'
],
PublishState
.
unscheduled
)
self
.
assertTrue
(
len
(
first_child_response
[
'child_info'
][
'children'
])
>
0
)
# Finally, validate the entire response for consistency
...
...
@@ -251,7 +252,6 @@ class TestCourseOutline(CourseTestCase):
self
.
assertIsNotNone
(
json_response
[
'display_name'
])
self
.
assertIsNotNone
(
json_response
[
'id'
])
self
.
assertIsNotNone
(
json_response
[
'category'
])
self
.
assertIsNotNone
(
json_response
[
'published'
])
if
json_response
.
get
(
'child_info'
,
None
):
for
child_response
in
json_response
[
'child_info'
][
'children'
]:
self
.
assert_correct_json_response
(
child_response
)
...
...
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
a3590653
"""Tests for items views."""
import
json
from
datetime
import
datetime
from
datetime
import
datetime
,
timedelta
import
ddt
from
mock
import
patch
...
...
@@ -19,12 +19,11 @@ from contentstore.views.component import (
component_handler
,
get_component_templates
)
from
contentstore.views.item
import
create_xblock_info
,
ALWAYS
from
contentstore.views.item
import
create_xblock_info
,
ALWAYS
,
PublishState
from
contentstore.tests.utils
import
CourseTestCase
from
student.tests.factories
import
UserFactory
from
xmodule.capa_module
import
CapaDescriptor
from
xmodule.modulestore
import
PublishState
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
LegacyPublishState
,
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.factories
import
ItemFactory
from
xmodule.x_module
import
STUDIO_VIEW
,
STUDENT_VIEW
...
...
@@ -433,7 +432,8 @@ class TestEditItem(ItemTest):
"""
item
=
self
.
get_item_from_modulestore
(
usage_key
,
(
expected_publish_state
==
PublishState
.
private
)
or
(
expected_publish_state
==
PublishState
.
draft
)
(
expected_publish_state
==
LegacyPublishState
.
private
)
or
(
expected_publish_state
==
LegacyPublishState
.
draft
)
)
self
.
assertEqual
(
expected_publish_state
,
self
.
store
.
compute_publish_state
(
item
))
return
item
...
...
@@ -546,12 +546,12 @@ class TestEditItem(ItemTest):
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
,
PublishState
.
private
)
self
.
verify_publish_state
(
self
.
problem_usage_key
,
Legacy
PublishState
.
private
)
self
.
client
.
ajax_post
(
self
.
problem_update_url
,
data
=
{
'publish'
:
'make_public'
}
)
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
public
)
self
.
verify_publish_state
(
self
.
problem_usage_key
,
Legacy
PublishState
.
public
)
def
test_make_draft
(
self
):
""" Test creating a draft version of a public problem. """
...
...
@@ -564,7 +564,7 @@ class TestEditItem(ItemTest):
self
.
problem_update_url
,
data
=
{
'publish'
:
'discard_changes'
}
)
published
=
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
public
)
published
=
self
.
verify_publish_state
(
self
.
problem_usage_key
,
Legacy
PublishState
.
public
)
self
.
assertIsNone
(
published
.
due
)
def
test_republish
(
self
):
...
...
@@ -576,7 +576,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
,
PublishState
.
private
)
self
.
verify_publish_state
(
self
.
problem_usage_key
,
Legacy
PublishState
.
private
)
# Republishing when only in draft will update the draft but not cause a public item to be created.
self
.
client
.
ajax_post
(
...
...
@@ -588,7 +588,7 @@ class TestEditItem(ItemTest):
}
}
)
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
private
)
self
.
verify_publish_state
(
self
.
problem_usage_key
,
Legacy
PublishState
.
private
)
draft
=
self
.
get_item_from_modulestore
(
self
.
problem_usage_key
,
verify_is_draft
=
True
)
self
.
assertEqual
(
draft
.
display_name
,
new_display_name
)
...
...
@@ -609,7 +609,7 @@ class TestEditItem(ItemTest):
}
}
)
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
public
)
self
.
verify_publish_state
(
self
.
problem_usage_key
,
Legacy
PublishState
.
public
)
published
=
modulestore
()
.
get_item
(
self
.
problem_usage_key
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
...
...
@@ -625,7 +625,7 @@ class TestEditItem(ItemTest):
self
.
problem_update_url
,
data
=
{
'publish'
:
'make_public'
}
)
published
=
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
public
)
published
=
self
.
verify_publish_state
(
self
.
problem_usage_key
,
Legacy
PublishState
.
public
)
# Update the draft version and check that published is different.
self
.
client
.
ajax_post
(
...
...
@@ -659,7 +659,7 @@ class TestEditItem(ItemTest):
self
.
problem_update_url
,
data
=
{
'publish'
:
'make_public'
}
)
published
=
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
public
)
published
=
self
.
verify_publish_state
(
self
.
problem_usage_key
,
Legacy
PublishState
.
public
)
# Now make a draft
self
.
client
.
ajax_post
(
...
...
@@ -704,8 +704,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
,
PublishState
.
private
)
self
.
verify_publish_state
(
html_usage_key
,
PublishState
.
private
)
self
.
verify_publish_state
(
unit_usage_key
,
Legacy
PublishState
.
private
)
self
.
verify_publish_state
(
html_usage_key
,
Legacy
PublishState
.
private
)
# Make the unit public and verify that the problem is also made public
resp
=
self
.
client
.
ajax_post
(
...
...
@@ -713,8 +713,8 @@ class TestEditItem(ItemTest):
data
=
{
'publish'
:
'make_public'
}
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
verify_publish_state
(
unit_usage_key
,
PublishState
.
public
)
self
.
verify_publish_state
(
html_usage_key
,
PublishState
.
public
)
self
.
verify_publish_state
(
unit_usage_key
,
Legacy
PublishState
.
public
)
self
.
verify_publish_state
(
html_usage_key
,
Legacy
PublishState
.
public
)
# Make a draft for the unit and verify that the problem also has a draft
resp
=
self
.
client
.
ajax_post
(
...
...
@@ -725,8 +725,8 @@ class TestEditItem(ItemTest):
}
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
verify_publish_state
(
unit_usage_key
,
PublishState
.
draft
)
self
.
verify_publish_state
(
html_usage_key
,
PublishState
.
draft
)
self
.
verify_publish_state
(
unit_usage_key
,
Legacy
PublishState
.
draft
)
self
.
verify_publish_state
(
html_usage_key
,
Legacy
PublishState
.
draft
)
class
TestEditSplitModule
(
ItemTest
):
...
...
@@ -1148,7 +1148,6 @@ class TestXBlockInfo(ItemTest):
self
.
assertEqual
(
xblock_info
[
'category'
],
'course'
)
self
.
assertEqual
(
xblock_info
[
'id'
],
'i4x://MITx/999/course/Robot_Super_Course'
)
self
.
assertEqual
(
xblock_info
[
'display_name'
],
'Robot Super Course'
)
self
.
assertTrue
(
xblock_info
[
'published'
])
# Finally, validate the entire response for consistency
self
.
validate_xblock_info_consistency
(
xblock_info
,
has_child_info
=
has_child_info
)
...
...
@@ -1160,7 +1159,6 @@ class TestXBlockInfo(ItemTest):
self
.
assertEqual
(
xblock_info
[
'category'
],
'chapter'
)
self
.
assertEqual
(
xblock_info
[
'id'
],
'i4x://MITx/999/chapter/Week_1'
)
self
.
assertEqual
(
xblock_info
[
'display_name'
],
'Week 1'
)
self
.
assertTrue
(
xblock_info
[
'published'
])
self
.
assertEqual
(
xblock_info
[
'edited_by'
],
'testuser'
)
# Finally, validate the entire response for consistency
...
...
@@ -1173,7 +1171,6 @@ class TestXBlockInfo(ItemTest):
self
.
assertEqual
(
xblock_info
[
'category'
],
'sequential'
)
self
.
assertEqual
(
xblock_info
[
'id'
],
'i4x://MITx/999/sequential/Lesson_1'
)
self
.
assertEqual
(
xblock_info
[
'display_name'
],
'Lesson 1'
)
self
.
assertTrue
(
xblock_info
[
'published'
])
self
.
assertEqual
(
xblock_info
[
'edited_by'
],
'testuser'
)
# Finally, validate the entire response for consistency
...
...
@@ -1186,7 +1183,6 @@ class TestXBlockInfo(ItemTest):
self
.
assertEqual
(
xblock_info
[
'category'
],
'vertical'
)
self
.
assertEqual
(
xblock_info
[
'id'
],
'i4x://MITx/999/vertical/Unit_1'
)
self
.
assertEqual
(
xblock_info
[
'display_name'
],
'Unit 1'
)
self
.
assertTrue
(
xblock_info
[
'published'
])
self
.
assertEqual
(
xblock_info
[
'edited_by'
],
'testuser'
)
# Validate that the correct ancestor info has been included
...
...
@@ -1208,7 +1204,6 @@ class TestXBlockInfo(ItemTest):
self
.
assertEqual
(
xblock_info
[
'category'
],
'video'
)
self
.
assertEqual
(
xblock_info
[
'id'
],
'i4x://MITx/999/video/My_Video'
)
self
.
assertEqual
(
xblock_info
[
'display_name'
],
'My Video'
)
self
.
assertTrue
(
xblock_info
[
'published'
])
self
.
assertEqual
(
xblock_info
[
'edited_by'
],
'testuser'
)
# Finally, validate the entire response for consistency
...
...
@@ -1221,7 +1216,6 @@ class TestXBlockInfo(ItemTest):
self
.
assertIsNotNone
(
xblock_info
[
'display_name'
])
self
.
assertIsNotNone
(
xblock_info
[
'id'
])
self
.
assertIsNotNone
(
xblock_info
[
'category'
])
self
.
assertIsNotNone
(
xblock_info
[
'published'
])
self
.
assertEqual
(
xblock_info
[
'edited_by'
],
'testuser'
)
if
has_ancestor_info
:
self
.
assertIsNotNone
(
xblock_info
.
get
(
'ancestor_info'
,
None
))
...
...
@@ -1243,3 +1237,141 @@ class TestXBlockInfo(ItemTest):
)
else
:
self
.
assertIsNone
(
xblock_info
.
get
(
'child_info'
,
None
))
class
TestXBlockPublishingInfo
(
ItemTest
):
"""
Unit tests for XBlock's outline handling.
"""
def
_create_child
(
self
,
parent
,
category
,
display_name
,
publish_item
=
False
):
return
ItemFactory
.
create
(
parent_location
=
parent
.
location
,
category
=
category
,
display_name
=
display_name
,
user_id
=
self
.
user
.
id
,
publish_item
=
publish_item
)
def
_get_child
(
self
,
xblock_info
,
index
):
"""
Returns the child at the specified index.
"""
children
=
xblock_info
[
'child_info'
][
'children'
]
self
.
assertTrue
(
len
(
children
)
>
index
)
return
children
[
index
]
def
_get_xblock_info
(
self
,
location
):
"""
Returns the xblock info for the specified location.
"""
return
create_xblock_info
(
modulestore
()
.
get_item
(
location
),
include_child_info
=
True
,
include_children_predicate
=
ALWAYS
,
)
def
_set_release_date
(
self
,
location
,
start
):
"""
Sets the release date for the specified xblock.
"""
xblock
=
modulestore
()
.
get_item
(
location
)
xblock
.
start
=
start
self
.
store
.
update_item
(
xblock
,
self
.
user
.
id
)
def
_set_staff_only
(
self
,
location
,
staff_only
):
"""
Sets staff only for the specified xblock.
"""
xblock
=
modulestore
()
.
get_item
(
location
)
xblock
.
visible_to_staff_only
=
staff_only
self
.
store
.
update_item
(
xblock
,
self
.
user
.
id
)
def
_set_display_name
(
self
,
location
,
display_name
):
"""
Sets the display name for the specified xblock.
"""
xblock
=
modulestore
()
.
get_item
(
location
)
xblock
.
display_name
=
display_name
self
.
store
.
update_item
(
xblock
,
self
.
user
.
id
)
def
test_empty_chapter_publishing_info
(
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
)
def
test_empty_section_publishing_info
(
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
)
def
test_published_unit_publishing_info
(
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
.
_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
)
def
test_released_unit_publishing_info
(
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
.
_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
)
def
test_partially_released_section_publishing_info
(
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
.
_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
.
_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
)
# 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
)
# Finally verify the state of the chapter
self
.
assertEqual
(
xblock_info
[
'publish_state'
],
PublishState
.
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
):
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
)
cms/static/js/models/xblock_info.js
View file @
a3590653
...
...
@@ -25,21 +25,6 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
*/
"ancestor_info"
:
null
,
/**
* True iff:
* 1) Edits have been made to the xblock and no published version exists.
* 2) Edits have been made to the xblock since the last published version.
*/
"has_changes"
:
null
,
/**
* True iff a published version of the xblock exists.
*/
"published"
:
null
,
/**
* If true, only course staff can see the xblock regardless of publish status or
* release date status.
*/
"visible_to_staff_only"
:
null
,
/**
* Date of the last edit to this xblock or any of its descendants.
*/
"edited_on"
:
null
,
...
...
@@ -48,6 +33,10 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
*/
"edited_by"
:
null
,
/**
* True iff a published version of the xblock exists.
*/
"published"
:
null
,
/**
* Date of the last publish of this xblock, or null if never published.
*/
"published_on"
:
null
,
...
...
@@ -56,6 +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
*/
"publish_state"
:
null
,
/**
* True iff the release date of the xblock is in the past.
*/
"released_to_students"
:
null
,
...
...
cms/static/js/spec/views/pages/container_subviews_spec.js
View file @
a3590653
...
...
@@ -23,11 +23,9 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
id
:
'locator-container'
,
display_name
:
'Test Container'
,
category
:
'vertical'
,
published
:
false
,
has_changes
:
false
,
publish_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"
,
visible_to_staff_only
:
false
,
currently_visible_to_students
:
false
};
...
...
@@ -79,31 +77,30 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
var
viewPublishedCss
=
'.button-view'
,
previewCss
=
'.button-preview'
;
it
(
'renders correctly for
private
unit'
,
function
()
{
it
(
'renders correctly for
unscheduled
unit'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
expect
(
containerPage
.
$
(
viewPublishedCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
previewCss
)).
not
.
toHaveClass
(
disabledCss
);
});
it
(
'updates when publish
ed attribu
te changes'
,
function
()
{
it
(
'updates when publish
sta
te changes'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
true
});
fetch
({
publish_state
:
'ready'
});
expect
(
containerPage
.
$
(
viewPublishedCss
)).
not
.
toHaveClass
(
disabledCss
);
fetch
({
"published"
:
false
});
fetch
({
publish_state
:
'unscheduled'
});
expect
(
containerPage
.
$
(
viewPublishedCss
)).
toHaveClass
(
disabledCss
);
});
it
(
'updates when has_changes attribute changes'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"has_changes"
:
true
});
fetch
({
publish_state
:
'has-unpublished-changes'
});
expect
(
containerPage
.
$
(
previewCss
)).
not
.
toHaveClass
(
disabledCss
);
fetch
({
"published"
:
true
,
"has_changes"
:
false
});
fetch
({
publish_state
:
'ready'
});
expect
(
containerPage
.
$
(
previewCss
)).
toHaveClass
(
disabledCss
);
// If published is false, preview is always enabled.
fetch
({
"published"
:
false
,
"has_changes"
:
false
});
fetch
({
publish_state
:
'unscheduled'
});
expect
(
containerPage
.
$
(
previewCss
)).
not
.
toHaveClass
(
disabledCss
);
});
});
...
...
@@ -111,9 +108,10 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
describe
(
"Publisher"
,
function
()
{
var
headerCss
=
'.pub-status'
,
bitPublishingCss
=
"div.bit-publishing"
,
publishedBit
=
"is-published"
,
draftBit
=
"is-draft"
,
staffOnlyBit
=
"is-staff-only"
,
liveClass
=
"is-live"
,
readyClass
=
"is-ready"
,
staffOnlyClass
=
"is-staff-only"
,
hasWarningsClass
=
'has-warnings'
,
publishButtonCss
=
".action-publish"
,
discardChangesButtonCss
=
".action-discard"
,
lastDraftCss
=
".wrapper-last-draft"
,
...
...
@@ -125,9 +123,9 @@ 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
({
"published"
:
true
,
"has_changes"
:
true
});
fetch
({
publish_state
:
'has_unpublished_content'
});
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
not
.
toHaveClass
(
'is-disabled'
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
draftBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
hasWarningsClass
);
// Click discard changes
containerPage
.
$
(
discardChangesButtonCss
).
click
();
...
...
@@ -145,43 +143,43 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
promptSpies
.
show
.
andReturn
(
this
.
promptSpies
);
});
it
(
'renders correctly with
private
content'
,
function
()
{
it
(
'renders correctly with
unscheduled
content'
,
function
()
{
var
verifyPrivateState
=
function
()
{
expect
(
containerPage
.
$
(
headerCss
).
text
()).
toContain
(
'Draft (Never published)'
);
expect
(
containerPage
.
$
(
publishButtonCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
draftBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
publishedBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
readyClass
);
};
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
false
,
"has_changes"
:
false
});
verifyPrivateState
();
fetch
({
"published"
:
false
,
"has_changes"
:
true
});
fetch
({
publishState
:
'unscheduled'
});
verifyPrivateState
();
});
it
(
'renders correctly with publi
c
content'
,
function
()
{
it
(
'renders correctly with publi
shed
content'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
true
,
"has_changes"
:
false
});
fetch
({
publish_state
:
'ready'
});
expect
(
containerPage
.
$
(
headerCss
).
text
()).
toContain
(
'Published'
);
expect
(
containerPage
.
$
(
publishButtonCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
publishedBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
readyClass
);
fetch
({
"published"
:
true
,
"has_changes"
:
true
});
fetch
({
publish_state
:
'has_unpublished_content'
});
expect
(
containerPage
.
$
(
headerCss
).
text
()).
toContain
(
'Draft (Unpublished changes)'
);
expect
(
containerPage
.
$
(
publishButtonCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
not
.
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
draftBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
hasWarningsClass
);
fetch
({
publish_state
:
'live'
});
expect
(
containerPage
.
$
(
headerCss
).
text
()).
toContain
(
'Published'
);
expect
(
containerPage
.
$
(
publishButtonCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
toHaveClass
(
disabledCss
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
liveClass
);
});
it
(
'can publish private content'
,
function
()
{
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
false
,
"has_changes"
:
false
});
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
draftBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
publishedBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
readyClass
);
// Click publish
containerPage
.
$
(
publishButtonCss
).
click
();
...
...
@@ -197,18 +195,17 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
create_sinon
.
expectJsonRequest
(
requests
,
"GET"
,
"/xblock/locator-container"
);
// Response to fetch
respondWithJson
(
{
"id"
:
"locator-container"
,
"published"
:
true
,
"has_changes"
:
false
}
);
respondWithJson
(
createXBlockInfo
({
publish_state
:
'ready'
})
);
// Verify updates displayed
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
publishedBit
);
// Verify that the "publish
ed" value has been cleared out of the model.
expect
(
containerPage
.
model
.
get
(
"publish
"
)).
toBeNull
(
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
readyClass
);
// Verify that the "publish
_state" value has been updated
expect
(
containerPage
.
model
.
get
(
"publish
_state"
)).
toBe
(
'ready'
);
});
it
(
'can does not fetch if publish fails'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
false
});
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
publishedBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
readyClass
);
// Click publish
containerPage
.
$
(
publishButtonCss
).
click
();
...
...
@@ -220,9 +217,9 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
expect
(
requests
.
length
).
toEqual
(
numRequests
);
// Verify still in draft state.
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
publishedBit
);
// Verify that the "publish
ed" value has been cleared out of the model.
expect
(
containerPage
.
model
.
get
(
"publish
"
)).
toBeNull
(
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
readyClass
);
// Verify that the "publish
_state" value has been updated
expect
(
containerPage
.
model
.
get
(
"publish
_state"
)).
toBe
(
'unscheduled'
);
});
it
(
'can discard changes'
,
function
()
{
...
...
@@ -263,11 +260,11 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
});
it
(
'does not discard changes on cancel'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
true
,
"has_changes"
:
true
});
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
publish_state
:
'has_unpublished_content'
});
var
numRequests
=
requests
.
length
;
// Click discard changes
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
not
.
toHaveClass
(
'is-disabled'
);
containerPage
.
$
(
discardChangesButtonCss
).
click
();
// Click cancel to confirmation.
...
...
@@ -280,14 +277,17 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'renders the last published date and user when there are no changes'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published_on"
:
"Jul 01, 2014 at 12:45 UTC"
,
"published_by"
:
"amako"
});
fetch
({
published_on
:
"Jul 01, 2014 at 12:45 UTC"
,
published_by
:
"amako"
});
expect
(
containerPage
.
$
(
lastDraftCss
).
text
()).
toContain
(
"Last published Jul 01, 2014 at 12:45 UTC by amako"
);
});
it
(
'renders the last saved date and user when there are changes'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"has_changes"
:
true
,
"edited_on"
:
"Jul 02, 2014 at 14:20 UTC"
,
"edited_by"
:
"joe"
});
fetch
({
publish_state
:
'has_unpublished_content'
,
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"
);
});
...
...
@@ -295,8 +295,10 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
describe
(
"Release Date"
,
function
()
{
it
(
'renders correctly when unreleased'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
true
,
"released_to_students"
:
false
,
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
fetch
({
publish_state
:
'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:"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
toContain
(
'Jul 02, 2014 at 14:20 UTC with Section "Week 1"'
);
...
...
@@ -304,8 +306,10 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
'renders correctly when released'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
true
,
"released_to_students"
:
true
,
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
fetch
({
publish_state
:
'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:"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
toContain
(
'Jul 02, 2014 at 14:20 UTC with Section "Week 1"'
);
...
...
@@ -313,17 +317,20 @@ 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
({
"published"
:
true
,
"released_to_students"
:
false
,
"release_date"
:
null
,
"release_date_from"
:
null
});
fetch
({
publish_state
:
'unscheduled'
,
"released_to_students"
:
false
,
release_date
:
null
,
release_date_from
:
null
});
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Release:"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
toContain
(
"Unscheduled"
);
});
it
(
'renders correctly when the unit is not published'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
false
,
"released_to_students"
:
true
,
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
// Force a render because none of the fetched fields will trigger a render
fetch
({
publish_state
:
'has_unpublished_content'
,
released_to_students
:
true
,
release_date
:
"Jul 02, 2014 at 14:20 UTC"
,
release_date_from
:
'Section "Week 1"'
});
containerPage
.
xblockPublisher
.
render
();
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Release:"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
...
...
@@ -355,8 +362,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
});
create_sinon
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/locator-container'
);
create_sinon
.
respondWithJson
(
requests
,
createXBlockInfo
({
published
:
containerPage
.
model
.
get
(
'published'
),
visible_to_staff_only
:
isStaffOnly
publish_state
:
isStaffOnly
?
'staff_only'
:
'unscheduled'
}));
};
...
...
@@ -364,11 +370,11 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
if
(
isStaffOnly
)
{
expect
(
containerPage
.
$
(
'.action-staff-lock i'
)).
toHaveClass
(
'icon-check'
);
expect
(
containerPage
.
$
(
'.wrapper-visibility .copy'
).
text
()).
toBe
(
'Staff Only'
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
staffOnly
Bit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
staffOnly
Class
);
}
else
{
expect
(
containerPage
.
$
(
'.action-staff-lock i'
)).
toHaveClass
(
'icon-check-empty'
);
expect
(
containerPage
.
$
(
'.wrapper-visibility .copy'
).
text
()).
toBe
(
'Staff and Students'
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
staffOnly
Bit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
staffOnly
Class
);
}
};
...
...
@@ -386,27 +392,16 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it
(
"can remove staff only setting"
,
function
()
{
promptSpy
=
edit_helpers
.
createPromptSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
requestStaffOnly
(
true
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
publish_state
:
'staff_only'
});
requestStaffOnly
(
false
);
verifyStaffOnly
(
false
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
publishedBit
);
});
it
(
"can remove staff only setting from published unit"
,
function
()
{
promptSpy
=
edit_helpers
.
createPromptSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
published
:
true
});
requestStaffOnly
(
true
);
requestStaffOnly
(
false
);
verifyStaffOnly
(
false
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
toHaveClass
(
publishedBit
);
expect
(
containerPage
.
$
(
bitPublishingCss
)).
not
.
toHaveClass
(
readyClass
);
});
it
(
"does not refresh if removing staff only is canceled"
,
function
()
{
var
requestCount
;
promptSpy
=
edit_helpers
.
createPromptSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
requestStaffOnly
(
true
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
publish_state
:
'staff_only'
});
requestCount
=
requests
.
length
;
containerPage
.
$
(
'.action-staff-lock'
).
click
();
edit_helpers
.
confirmPrompt
(
promptSpy
,
true
);
// Click 'No' to cancel
...
...
@@ -429,25 +424,26 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
describe
(
"PublishHistory"
,
function
()
{
var
lastPublishCss
=
".wrapper-last-publish"
;
it
(
'renders never published when the block is unpublished'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
published
:
false
,
published_on
:
null
,
published_by
:
null
});
expect
(
containerPage
.
$
(
lastPublishCss
).
text
()).
toContain
(
"Never published"
);
});
it
(
'renders the last published date and user when the block is published'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
true
,
"published_on"
:
"Jul 01, 2014 at 12:45 UTC"
,
"published_by"
:
"amako"
published
:
true
,
published_on
:
"Jul 01, 2014 at 12:45 UTC"
,
published_by
:
"amako"
});
expect
(
containerPage
.
$
(
lastPublishCss
).
text
()).
toContain
(
"Last published Jul 01, 2014 at 12:45 UTC by amako"
);
});
it
(
'renders never published when the block is unpublished'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
false
});
expect
(
containerPage
.
$
(
lastPublishCss
).
text
()).
toContain
(
"Never published"
);
});
it
(
'renders correctly when the block is published without publish info'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
fetch
({
"published"
:
true
,
"published_on"
:
null
,
"published_by"
:
null
published
:
true
,
published_on
:
null
,
published_by
:
null
});
expect
(
containerPage
.
$
(
lastPublishCss
).
text
()).
toContain
(
"Previously published"
);
});
...
...
cms/static/js/spec/views/pages/course_outline_spec.js
View file @
a3590653
...
...
@@ -4,7 +4,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
describe
(
"CourseOutlinePage"
,
function
()
{
var
createCourseOutlinePage
,
displayNameInput
,
model
,
outlinePage
,
requests
,
get
HeaderElement
,
expandAndVerifyState
,
collapse
AndVerifyState
,
get
ItemsOfType
,
getItemHeaders
,
verifyItemsExpanded
,
expandItemsAndVerifyState
,
collapseItems
AndVerifyState
,
createMockCourseJSON
,
createMockSectionJSON
,
createMockSubsectionJSON
,
mockCourseJSON
,
mockEmptyCourseJSON
,
mockSingleSectionCourseJSON
,
mockOutlinePage
=
readFixtures
(
'mock/mock-course-outline-page.underscore'
);
...
...
@@ -64,21 +64,31 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
};
};
getHeaderElement
=
function
(
selector
)
{
var
element
=
outlinePage
.
$
(
selector
);
return
element
.
find
(
'> .wrapper-xblock-header'
);
getItemsOfType
=
function
(
type
)
{
return
outlinePage
.
$
(
'.outline-'
+
type
);
};
expandAndVerifyState
=
function
(
selector
)
{
var
element
=
outlinePage
.
$
(
selector
);
getHeaderElement
(
selector
).
find
(
'.ui-toggle-expansion'
).
click
();
expect
(
element
).
not
.
toHaveClass
(
'collapsed'
);
getItemHeaders
=
function
(
type
)
{
return
getItemsOfType
(
type
).
find
(
'> .'
+
type
+
'-header'
);
};
collapseAndVerifyState
=
function
(
selector
)
{
var
element
=
outlinePage
.
$
(
selector
);
getHeaderElement
(
selector
).
find
(
'.ui-toggle-expansion'
).
click
();
expect
(
element
).
toHaveClass
(
'collapsed'
);
verifyItemsExpanded
=
function
(
type
,
isExpanded
)
{
var
element
=
getItemsOfType
(
type
);
if
(
isExpanded
)
{
expect
(
element
).
not
.
toHaveClass
(
'is-collapsed'
);
}
else
{
expect
(
element
).
toHaveClass
(
'is-collapsed'
);
}
};
expandItemsAndVerifyState
=
function
(
type
)
{
getItemHeaders
(
type
).
find
(
'.ui-toggle-expansion'
).
click
();
verifyItemsExpanded
(
type
,
true
);
};
collapseItemsAndVerifyState
=
function
(
type
)
{
getItemHeaders
(
type
).
find
(
'.ui-toggle-expansion'
).
click
();
verifyItemsExpanded
(
type
,
false
);
};
createCourseOutlinePage
=
function
(
test
,
courseJSON
,
createOnly
)
{
...
...
@@ -108,12 +118,10 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
category
:
'vertical'
,
studio_url
:
'/container/mock-unit'
,
is_container
:
true
,
has_changes
:
true
,
published
:
false
,
publish_state
:
'unscheduled'
,
edited_on
:
'Jul 02, 2014 at 20:56 UTC'
,
edited_by
:
'MockUser'
}
])
}])
])
]);
mockEmptyCourseJSON
=
createMockCourseJSON
(
'mock-course'
,
'Mock Course'
,
[]);
...
...
@@ -129,9 +137,9 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
describe
(
'Initial display'
,
function
()
{
it
(
'can render itself'
,
function
()
{
createCourseOutlinePage
(
this
,
mockCourseJSON
);
expect
(
outlinePage
.
$
(
'.
sortable-course-list
'
)).
toExist
();
expect
(
outlinePage
.
$
(
'.
sortable-section-list
'
)).
toExist
();
expect
(
outlinePage
.
$
(
'.
sortable-subsection-list
'
)).
toExist
();
expect
(
outlinePage
.
$
(
'.
list-sections
'
)).
toExist
();
expect
(
outlinePage
.
$
(
'.
list-subsections
'
)).
toExist
();
expect
(
outlinePage
.
$
(
'.
list-units
'
)).
toExist
();
});
it
(
'shows a loading indicator'
,
function
()
{
...
...
@@ -142,18 +150,16 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
});
it
(
'shows subsections initially collapsed'
,
function
()
{
var
subsectionElement
;
createCourseOutlinePage
(
this
,
mockCourseJSON
);
subsectionElement
=
outlinePage
.
$
(
'.outline-item-subsection'
);
expect
(
subsectionElement
).
toHaveClass
(
'collapsed'
);
expect
(
outlinePage
.
$
(
'.outline-item-unit'
)).
not
.
toExist
();
verifyItemsExpanded
(
'subsection'
,
false
);
expect
(
getItemsOfType
(
'unit'
)).
not
.
toExist
();
});
});
describe
(
"Button bar"
,
function
()
{
it
(
'can add a section'
,
function
()
{
createCourseOutlinePage
(
this
,
mockEmptyCourseJSON
);
outlinePage
.
$
(
'.nav-actions .
add-button
'
).
click
();
outlinePage
.
$
(
'.nav-actions .
button-new
'
).
click
();
create_sinon
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
'category'
:
'chapter'
,
'display_name'
:
'Section'
,
...
...
@@ -166,13 +172,13 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
create_sinon
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/outline/mock-course'
);
create_sinon
.
respondWithJson
(
requests
,
mockSingleSectionCourseJSON
);
expect
(
outlinePage
.
$
(
'.no-content'
)).
not
.
toExist
();
expect
(
outlinePage
.
$
(
'.
sortable-course-list
li'
).
data
(
'locator'
)).
toEqual
(
'mock-section'
);
expect
(
outlinePage
.
$
(
'.
list-sections
li'
).
data
(
'locator'
)).
toEqual
(
'mock-section'
);
});
it
(
'can add a second section'
,
function
()
{
var
sectionElements
;
createCourseOutlinePage
(
this
,
mockSingleSectionCourseJSON
);
outlinePage
.
$
(
'.nav-actions .
add-button
'
).
click
();
outlinePage
.
$
(
'.nav-actions .
button-new
'
).
click
();
create_sinon
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
'category'
:
'chapter'
,
'display_name'
:
'Section'
,
...
...
@@ -186,7 +192,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
create_sinon
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/outline/mock-section-2'
);
create_sinon
.
respondWithJson
(
requests
,
createMockSectionJSON
(
'mock-section-2'
,
'Mock Section 2'
,
[]));
sectionElements
=
outlinePage
.
$
(
'.sortable-course-list .outline-item-
section'
);
sectionElements
=
getItemsOfType
(
'
section'
);
expect
(
sectionElements
.
length
).
toBe
(
2
);
expect
(
$
(
sectionElements
[
0
]).
data
(
'locator'
)).
toEqual
(
'mock-section'
);
expect
(
$
(
sectionElements
[
1
]).
data
(
'locator'
)).
toEqual
(
'mock-section-2'
);
...
...
@@ -194,10 +200,11 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
it
(
'can expand and collapse all sections'
,
function
()
{
createCourseOutlinePage
(
this
,
mockCourseJSON
,
false
);
outlinePage
.
$
(
'.nav-actions .toggle-button-expand-collapse'
).
click
();
expect
(
outlinePage
.
$
(
'.outline-item-section'
)).
toHaveClass
(
'collapsed'
);
outlinePage
.
$
(
'.nav-actions .toggle-button-expand-collapse'
).
click
();
expect
(
outlinePage
.
$
(
'.outline-item-section'
)).
not
.
toHaveClass
(
'collapsed'
);
verifyItemsExpanded
(
'section'
,
true
);
outlinePage
.
$
(
'.nav-actions .button-toggle-expand-collapse .collapse-all'
).
click
();
verifyItemsExpanded
(
'section'
,
false
);
outlinePage
.
$
(
'.nav-actions .button-toggle-expand-collapse .expand-all'
).
click
();
verifyItemsExpanded
(
'section'
,
true
);
});
});
...
...
@@ -205,12 +212,12 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
it
(
'shows an empty course message initially'
,
function
()
{
createCourseOutlinePage
(
this
,
mockEmptyCourseJSON
);
expect
(
outlinePage
.
$
(
'.no-content'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
outlinePage
.
$
(
'.no-content .
add-button
'
)).
toExist
();
expect
(
outlinePage
.
$
(
'.no-content .
button-new
'
)).
toExist
();
});
it
(
'can add a section'
,
function
()
{
createCourseOutlinePage
(
this
,
mockEmptyCourseJSON
);
$
(
'.no-content .
add-button
'
).
click
();
$
(
'.no-content .
button-new
'
).
click
();
create_sinon
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
'category'
:
'chapter'
,
'display_name'
:
'Section'
,
...
...
@@ -223,13 +230,13 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
create_sinon
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/outline/mock-course'
);
create_sinon
.
respondWithJson
(
requests
,
mockSingleSectionCourseJSON
);
expect
(
outlinePage
.
$
(
'.no-content'
)).
not
.
toExist
();
expect
(
outlinePage
.
$
(
'.
sortable-course-list
li'
).
data
(
'locator'
)).
toEqual
(
'mock-section'
);
expect
(
outlinePage
.
$
(
'.
list-sections
li'
).
data
(
'locator'
)).
toEqual
(
'mock-section'
);
});
it
(
'remains empty if an add fails'
,
function
()
{
var
requestCount
;
createCourseOutlinePage
(
this
,
mockEmptyCourseJSON
);
$
(
'.no-content .
add-button
'
).
click
();
$
(
'.no-content .
button-new
'
).
click
();
create_sinon
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
'category'
:
'chapter'
,
'display_name'
:
'Section'
,
...
...
@@ -239,7 +246,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
create_sinon
.
respondWithError
(
requests
);
expect
(
requests
.
length
).
toBe
(
requestCount
);
// No additional requests should be made
expect
(
outlinePage
.
$
(
'.no-content'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
outlinePage
.
$
(
'.no-content .
add-button
'
)).
toExist
();
expect
(
outlinePage
.
$
(
'.no-content .
button-new
'
)).
toExist
();
});
});
...
...
@@ -247,7 +254,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
var
getDisplayNameWrapper
;
getDisplayNameWrapper
=
function
()
{
return
get
HeaderElement
(
'.outline-item-section'
).
find
(
'.wrapper-xblock-field'
).
first
(
);
return
get
ItemHeaders
(
'section'
).
find
(
'.wrapper-xblock-field'
);
};
it
(
'can be deleted'
,
function
()
{
...
...
@@ -256,7 +263,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
createMockSectionJSON
(
'mock-section'
,
'Mock Section'
,
[]),
createMockSectionJSON
(
'mock-section-2'
,
'Mock Section 2'
,
[])
]));
outlinePage
.
$
(
'.outline-item-section
.delete-button'
).
first
().
click
();
getItemHeaders
(
'section'
).
find
(
'
.delete-button'
).
first
().
click
();
view_helpers
.
confirmPrompt
(
promptSpy
);
requestCount
=
requests
.
length
;
create_sinon
.
expectJsonRequest
(
requests
,
'DELETE'
,
'/xblock/mock-section'
);
...
...
@@ -269,32 +276,32 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
it
(
'can be deleted if it is the only section'
,
function
()
{
var
promptSpy
=
view_helpers
.
createPromptSpy
();
createCourseOutlinePage
(
this
,
mockSingleSectionCourseJSON
);
outlinePage
.
$
(
'.outline-item-section
.delete-button'
).
click
();
getItemHeaders
(
'section'
).
find
(
'
.delete-button'
).
click
();
view_helpers
.
confirmPrompt
(
promptSpy
);
create_sinon
.
expectJsonRequest
(
requests
,
'DELETE'
,
'/xblock/mock-section'
);
create_sinon
.
respondWithJson
(
requests
,
{});
create_sinon
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/outline/mock-course'
);
create_sinon
.
respondWithJson
(
requests
,
mockEmptyCourseJSON
);
expect
(
outlinePage
.
$
(
'.no-content'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
outlinePage
.
$
(
'.no-content .
add-button
'
)).
toExist
();
expect
(
outlinePage
.
$
(
'.no-content .
button-new
'
)).
toExist
();
});
it
(
'remains visible if its deletion fails'
,
function
()
{
var
promptSpy
=
view_helpers
.
createPromptSpy
(),
requestCount
;
createCourseOutlinePage
(
this
,
mockSingleSectionCourseJSON
);
outlinePage
.
$
(
'.outline-item-section
.delete-button'
).
click
();
getItemHeaders
(
'section'
).
find
(
'
.delete-button'
).
click
();
view_helpers
.
confirmPrompt
(
promptSpy
);
create_sinon
.
expectJsonRequest
(
requests
,
'DELETE'
,
'/xblock/mock-section'
);
requestCount
=
requests
.
length
;
create_sinon
.
respondWithError
(
requests
);
expect
(
requests
.
length
).
toBe
(
requestCount
);
// No additional requests should be made
expect
(
outlinePage
.
$
(
'.
sortable-course-list
li'
).
data
(
'locator'
)).
toEqual
(
'mock-section'
);
expect
(
outlinePage
.
$
(
'.
list-sections
li'
).
data
(
'locator'
)).
toEqual
(
'mock-section'
);
});
it
(
'can add a subsection'
,
function
()
{
createCourseOutlinePage
(
this
,
mockCourseJSON
);
outlinePage
.
$
(
'.outline-item-section > .add-xblock-component .add-button
'
).
click
();
getItemsOfType
(
'section'
).
find
(
'> .outline-content > .add-subsection .button-new
'
).
click
();
create_sinon
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
'category'
:
'sequential'
,
'display_name'
:
'Subsection'
,
...
...
@@ -329,9 +336,9 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
it
(
'can be expanded and collapsed'
,
function
()
{
createCourseOutlinePage
(
this
,
mockCourseJSON
);
collapse
AndVerifyState
(
'.outline-item-
section'
);
expand
AndVerifyState
(
'.outline-item-
section'
);
collapse
AndVerifyState
(
'.outline-item-
section'
);
collapse
ItemsAndVerifyState
(
'
section'
);
expand
ItemsAndVerifyState
(
'
section'
);
collapse
ItemsAndVerifyState
(
'
section'
);
});
});
...
...
@@ -339,13 +346,13 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
var
getDisplayNameWrapper
;
getDisplayNameWrapper
=
function
()
{
return
get
HeaderElement
(
'.outline-item-subsection'
).
find
(
'.wrapper-xblock-field'
).
first
(
);
return
get
ItemHeaders
(
'subsection'
).
find
(
'.wrapper-xblock-field'
);
};
it
(
'can be deleted'
,
function
()
{
var
promptSpy
=
view_helpers
.
createPromptSpy
();
createCourseOutlinePage
(
this
,
mockCourseJSON
);
get
HeaderElement
(
'.outline-item-
subsection'
).
find
(
'.delete-button'
).
click
();
get
ItemHeaders
(
'
subsection'
).
find
(
'.delete-button'
).
click
();
view_helpers
.
confirmPrompt
(
promptSpy
);
create_sinon
.
expectJsonRequest
(
requests
,
'DELETE'
,
'/xblock/mock-subsection'
);
create_sinon
.
respondWithJson
(
requests
,
{});
...
...
@@ -358,7 +365,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
var
redirectSpy
;
createCourseOutlinePage
(
this
,
mockCourseJSON
);
redirectSpy
=
spyOn
(
ViewUtils
,
'redirect'
);
outlinePage
.
$
(
'.outline-item-subsection > .add-xblock-component .add-button
'
).
click
();
getItemsOfType
(
'subsection'
).
find
(
'> .outline-content > .add-unit .button-new
'
).
click
();
create_sinon
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
'category'
:
'vertical'
,
'display_name'
:
'Unit'
,
...
...
@@ -387,20 +394,18 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
createMockSubsectionJSON
(
'mock-subsection'
,
updatedDisplayName
,
[])
]));
// Find the display name again in the refreshed DOM and verify it
displayNameWrapper
=
get
HeaderElement
(
'.outline-item-subsection'
).
find
(
'.wrapper-xblock-field'
).
first
(
);
displayNameWrapper
=
get
ItemHeaders
(
'subsection'
).
find
(
'.wrapper-xblock-field'
);
view_helpers
.
verifyInlineEditChange
(
displayNameWrapper
,
updatedDisplayName
);
subsectionModel
=
outlinePage
.
model
.
get
(
'child_info'
).
children
[
0
].
get
(
'child_info'
).
children
[
0
];
expect
(
subsectionModel
.
get
(
'display_name'
)).
toBe
(
updatedDisplayName
);
});
it
(
'can be expanded and collapsed'
,
function
()
{
var
subsectionElement
;
createCourseOutlinePage
(
this
,
mockCourseJSON
);
subsectionElement
=
outlinePage
.
$
(
'.outline-item-subsection'
);
expect
(
subsectionElement
).
toHaveClass
(
'collapsed'
);
expandAndVerifyState
(
'.outline-item-subsection'
);
collapseAndVerifyState
(
'.outline-item-subsection'
);
expandAndVerifyState
(
'.outline-item-subsection'
);
verifyItemsExpanded
(
'subsection'
,
false
);
expandItemsAndVerifyState
(
'subsection'
);
collapseItemsAndVerifyState
(
'subsection'
);
expandItemsAndVerifyState
(
'subsection'
);
});
});
...
...
@@ -409,8 +414,8 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
it
(
'can be deleted'
,
function
()
{
var
promptSpy
=
view_helpers
.
createPromptSpy
();
createCourseOutlinePage
(
this
,
mockCourseJSON
);
expand
AndVerifyState
(
'.outline-item-
subsection'
);
get
HeaderElement
(
'.outline-item-
unit'
).
find
(
'.delete-button'
).
click
();
expand
ItemsAndVerifyState
(
'
subsection'
);
get
ItemHeaders
(
'
unit'
).
find
(
'.delete-button'
).
click
();
view_helpers
.
confirmPrompt
(
promptSpy
);
create_sinon
.
expectJsonRequest
(
requests
,
'DELETE'
,
'/xblock/mock-unit'
);
create_sinon
.
respondWithJson
(
requests
,
{});
...
...
@@ -420,12 +425,16 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
});
it
(
'has a link to the unit page'
,
function
()
{
var
a
nchor
;
var
unitA
nchor
;
createCourseOutlinePage
(
this
,
mockCourseJSON
);
expand
AndVerifyState
(
'.outline-item-
subsection'
);
anchor
=
outlinePage
.
$
(
'.outline-item-unit .xblock
-title a'
);
expect
(
a
nchor
.
attr
(
'href'
)).
toBe
(
'/container/mock-unit'
);
expand
ItemsAndVerifyState
(
'
subsection'
);
unitAnchor
=
getItemsOfType
(
'unit'
).
find
(
'.unit
-title a'
);
expect
(
unitA
nchor
.
attr
(
'href'
)).
toBe
(
'/container/mock-unit'
);
});
});
describe
(
"Publishing State"
,
function
()
{
// TODO: implement this!!!!
});
});
});
cms/static/js/spec/views/unit_outline_spec.js
View file @
a3590653
...
...
@@ -25,12 +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'
,
ancestor_info
:
{
ancestors
:
[{
id
:
'mock-subsection'
,
category
:
'sequential'
,
display_name
:
'Mock Subsection'
,
studio_url
:
'/course/mock-course?show=mock-subsection'
,
publish_state
:
'unscheduled'
,
child_info
:
{
category
:
'vertical'
,
display_name
:
'Unit'
,
...
...
@@ -38,24 +40,28 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
id
:
'mock-unit'
,
category
:
'vertical'
,
display_name
:
displayName
,
studio_url
:
'/container/mock-unit'
studio_url
:
'/container/mock-unit'
,
publish_state
:
'unscheduled'
},
{
id
:
'mock-unit-2'
,
category
:
'vertical'
,
display_name
:
'Mock Unit 2'
,
studio_url
:
'/container/mock-unit-2'
studio_url
:
'/container/mock-unit-2'
,
publish_state
:
'unscheduled'
}]
}
},
{
id
:
'mock-section'
,
category
:
'chapter'
,
display_name
:
'Section'
,
studio_url
:
'/course/slashes:mock-course?show=mock-section'
studio_url
:
'/course/slashes:mock-course?show=mock-section'
,
publish_state
:
'unscheduled'
},
{
id
:
'mock-course'
,
category
:
'course'
,
display_name
:
'Mock Course'
,
studio_url
:
'/course/mock-course'
studio_url
:
'/course/mock-course'
,
publish_state
:
'unscheduled'
}]
},
metadata
:
{
...
...
@@ -77,16 +83,16 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
it
(
'can render itself'
,
function
()
{
createUnitOutlineView
(
this
,
createMockXBlockInfo
(
'Mock Unit'
));
expect
(
unitOutlineView
.
$
(
'.
sortable-course-list
'
)).
toExist
();
expect
(
unitOutlineView
.
$
(
'.
sortable-section-list
'
)).
toExist
();
expect
(
unitOutlineView
.
$
(
'.
sortable-subsection-list
'
)).
toExist
();
expect
(
unitOutlineView
.
$
(
'.
list-sections
'
)).
toExist
();
expect
(
unitOutlineView
.
$
(
'.
list-subsections
'
)).
toExist
();
expect
(
unitOutlineView
.
$
(
'.
list-units
'
)).
toExist
();
});
it
(
'can add a unit'
,
function
()
{
var
redirectSpy
;
createUnitOutlineView
(
this
,
createMockXBlockInfo
(
'Mock Unit'
));
redirectSpy
=
spyOn
(
ViewUtils
,
'redirect'
);
unitOutlineView
.
$
(
'.outline-
item-subsection > .add-xblock-component .add-button
'
).
click
();
unitOutlineView
.
$
(
'.outline-
subsection > .outline-content > .add-unit .button-new
'
).
click
();
create_sinon
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
category
:
'vertical'
,
display_name
:
'Unit'
,
...
...
@@ -106,8 +112,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
create_sinon
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/mock-unit'
);
create_sinon
.
respondWithJson
(
requests
,
createMockXBlockInfo
(
updatedDisplayName
));
unitHeader
=
unitOutlineView
.
$
(
'.outline-item-unit .wrapper-xblock-header'
);
expect
(
unitHeader
.
find
(
'.xblock-title'
).
first
().
text
().
trim
()).
toBe
(
updatedDisplayName
);
expect
(
unitOutlineView
.
$
(
'.outline-unit .unit-title'
).
first
().
text
().
trim
()).
toBe
(
updatedDisplayName
);
});
});
});
cms/static/js/views/baseview.js
View file @
a3590653
...
...
@@ -16,6 +16,10 @@ define(["jquery", "underscore", "backbone", "gettext", "js/utils/handle_iframe_b
"click .ui-toggle-expansion"
:
"toggleExpandCollapse"
},
options
:
{
collapsedClass
:
'collapsed'
},
//override the constructor function
constructor
:
function
(
options
)
{
_
.
bindAll
(
this
,
'beforeRender'
,
'render'
,
'afterRender'
);
...
...
@@ -48,7 +52,7 @@ define(["jquery", "underscore", "backbone", "gettext", "js/utils/handle_iframe_b
// this element, e.g. clicking on the element of a child view container in a parent.
event
.
stopPropagation
();
event
.
preventDefault
();
ViewUtils
.
toggleExpandCollapse
(
target
);
ViewUtils
.
toggleExpandCollapse
(
target
,
this
.
options
.
collapsedClass
);
},
/**
...
...
cms/static/js/views/pages/container.js
View file @
a3590653
...
...
@@ -12,6 +12,10 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
var
XBlockContainerPage
=
BasePage
.
extend
({
// takes XBlockInfo as a model
options
:
{
collapsedClass
:
'is-collapsed'
},
view
:
'container_preview'
,
initialize
:
function
(
options
)
{
...
...
cms/static/js/views/pages/container_subviews.js
View file @
a3590653
...
...
@@ -7,7 +7,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
var
disabledCss
=
"is-disabled"
;
/**
* A view that
calls render when "has_changes" or "published" values in
XBlockInfo have changed
* A view that
refreshes the view when certain values in the
XBlockInfo have changed
* after a server sync operation.
*/
var
ContainerStateListenerView
=
BaseView
.
extend
({
...
...
@@ -53,19 +53,20 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
*/
var
PreviewActionController
=
ContainerStateListenerView
.
extend
({
shouldRefresh
:
function
(
model
)
{
return
ViewUtils
.
hasChangedAttributes
(
model
,
[
'
has_changes'
,
'published
'
]);
return
ViewUtils
.
hasChangedAttributes
(
model
,
[
'
edited_on'
,
'published_on'
,
'publish_state
'
]);
},
render
:
function
()
{
var
previewAction
=
this
.
$el
.
find
(
'.button-preview'
),
viewLiveAction
=
this
.
$el
.
find
(
'.button-view'
);
if
(
this
.
model
.
get
(
'published'
))
{
viewLiveAction
=
this
.
$el
.
find
(
'.button-view'
),
publishState
=
this
.
model
.
get
(
'publish_state'
);
if
(
publishState
!==
'unscheduled'
)
{
viewLiveAction
.
removeClass
(
disabledCss
);
}
else
{
viewLiveAction
.
addClass
(
disabledCss
);
}
if
(
this
.
model
.
get
(
'has_changes'
)
||
!
this
.
model
.
get
(
'published'
)
)
{
if
(
publishState
!==
'live'
&&
publishState
!==
'ready'
)
{
previewAction
.
removeClass
(
disabledCss
);
}
else
{
...
...
@@ -98,25 +99,22 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
},
onSync
:
function
(
model
)
{
if
(
ViewUtils
.
hasChangedAttributes
(
model
,
[
'has_changes'
,
'published'
,
'edited_on'
,
'edited_by'
,
'visible_to_staff_only'
]))
{
if
(
ViewUtils
.
hasChangedAttributes
(
model
,
[
'edited_on'
,
'published_on'
,
'publish_state'
]))
{
this
.
render
();
}
},
render
:
function
()
{
this
.
$el
.
html
(
this
.
template
({
hasChanges
:
this
.
model
.
get
(
'has_changes'
),
published
:
this
.
model
.
get
(
'published'
),
publishState
:
this
.
model
.
get
(
'publish_state'
),
editedOn
:
this
.
model
.
get
(
'edited_on'
),
editedBy
:
this
.
model
.
get
(
'edited_by'
),
published
:
this
.
model
.
get
(
'published'
),
publishedOn
:
this
.
model
.
get
(
'published_on'
),
publishedBy
:
this
.
model
.
get
(
'published_by'
),
releasedToStudents
:
this
.
model
.
get
(
'released_to_students'
),
releaseDate
:
this
.
model
.
get
(
'release_date'
),
releaseDateFrom
:
this
.
model
.
get
(
'release_date_from'
),
visibleToStaffOnly
:
this
.
model
.
get
(
'visible_to_staff_only'
)
releaseDateFrom
:
this
.
model
.
get
(
'release_date_from'
)
}));
return
this
;
...
...
@@ -138,7 +136,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
},
discardChanges
:
function
(
e
)
{
var
xblockInfo
=
this
.
model
,
that
=
this
,
renderPage
=
this
.
renderPage
;
var
xblockInfo
=
this
.
model
,
renderPage
=
this
.
renderPage
;
if
(
e
&&
e
.
preventDefault
)
{
e
.
preventDefault
();
}
...
...
@@ -164,7 +162,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
if
(
e
&&
e
.
preventDefault
)
{
e
.
preventDefault
();
}
enableStaffLock
=
!
xblockInfo
.
get
(
'visible_to_staff_only'
)
;
enableStaffLock
=
xblockInfo
.
get
(
'publish_state'
)
!==
'staff_only'
;
revertCheckBox
=
function
()
{
self
.
checkStaffLock
(
!
enableStaffLock
);
...
...
@@ -223,7 +221,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
},
onSync
:
function
(
model
)
{
if
(
ViewUtils
.
hasChangedAttributes
(
model
,
[
'published
'
,
'published_on'
,
'published_by
'
]))
{
if
(
ViewUtils
.
hasChangedAttributes
(
model
,
[
'published
_on
'
]))
{
this
.
render
();
}
},
...
...
cms/static/js/views/pages/course_outline.js
View file @
a3590653
...
...
@@ -8,14 +8,18 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
// takes XBlockInfo as a model
events
:
{
"click .toggle-button-expand-collapse"
:
"toggleExpandCollapse"
"click .button-toggle-expand-collapse"
:
"toggleExpandCollapse"
},
options
:
{
collapsedClass
:
'is-collapsed'
},
initialize
:
function
()
{
var
self
=
this
;
this
.
initialState
=
this
.
options
.
initialState
;
BasePage
.
prototype
.
initialize
.
call
(
this
);
this
.
$
(
'.
add-button
'
).
click
(
function
(
event
)
{
this
.
$
(
'.
button-new
'
).
click
(
function
(
event
)
{
self
.
outlineView
.
handleAddEvent
(
event
);
});
this
.
model
.
on
(
'change'
,
this
.
setCollapseExpandVisibility
,
this
);
...
...
@@ -23,19 +27,18 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
setCollapseExpandVisibility
:
function
()
{
var
has_content
=
this
.
hasContent
(),
collapseExpandButton
=
$
(
'.
toggle-button
-expand-collapse'
);
collapseExpandButton
=
$
(
'.
button-toggle
-expand-collapse'
);
if
(
has_content
)
{
collapseExpandButton
.
show
(
);
collapseExpandButton
.
removeClass
(
'is-hidden'
);
}
else
{
collapseExpandButton
.
hide
(
);
collapseExpandButton
.
addClass
(
'is-hidden'
);
}
},
renderPage
:
function
()
{
var
locatorToShow
;
this
.
setCollapseExpandVisibility
();
this
.
outlineView
=
new
CourseOutlineView
({
el
:
this
.
$
(
'.
course-
outline'
),
el
:
this
.
$
(
'.outline'
),
model
:
this
.
model
,
isRoot
:
true
,
initialState
:
this
.
initialState
...
...
@@ -50,19 +53,16 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
},
toggleExpandCollapse
:
function
(
event
)
{
var
toggleButton
=
this
.
$
(
'.
toggle-button
-expand-collapse'
),
var
toggleButton
=
this
.
$
(
'.
button-toggle
-expand-collapse'
),
collapse
=
toggleButton
.
hasClass
(
'collapse-all'
);
event
.
preventDefault
();
toggleButton
.
toggleClass
(
'collapse-all expand-all'
);
this
.
$
(
'.course-outline > ol > li'
).
each
(
function
(
index
,
domElement
)
{
var
element
=
$
(
domElement
),
expandCollapseElement
=
element
.
find
(
'.expand-collapse'
).
first
();
this
.
$
(
'.list-sections > li'
).
each
(
function
(
index
,
domElement
)
{
var
element
=
$
(
domElement
);
if
(
collapse
)
{
expandCollapseElement
.
removeClass
(
'expand'
).
addClass
(
'collapse'
);
element
.
addClass
(
'collapsed'
);
element
.
addClass
(
'is-collapsed'
);
}
else
{
expandCollapseElement
.
addClass
(
'expand'
).
removeClass
(
'collapse'
);
element
.
removeClass
(
'collapsed'
);
element
.
removeClass
(
'is-collapsed'
);
}
});
}
...
...
cms/static/js/views/unit_outline.js
View file @
a3590653
...
...
@@ -10,6 +10,7 @@ define(['js/views/xblock_outline'],
// takes XBlockInfo as a model
templateName
:
'unit-outline'
,
className
:
'group-configurations-list'
,
render
:
function
()
{
XBlockOutlineView
.
prototype
.
render
.
call
(
this
);
...
...
@@ -23,7 +24,7 @@ define(['js/views/xblock_outline'],
previousAncestor
=
null
;
if
(
this
.
model
.
get
(
'ancestor_info'
))
{
ancestors
=
this
.
model
.
get
(
'ancestor_info'
).
ancestors
;
listElement
=
this
.
$
(
'.sortable-list'
);
listElement
=
this
.
getListElement
(
);
// Note: the ancestors are processed in reverse order because the tree wants to
// start at the root, but the ancestors are ordered by closeness to the unit,
// i.e. subsection and then section.
...
...
@@ -33,7 +34,7 @@ define(['js/views/xblock_outline'],
ancestorView
.
render
();
listElement
.
append
(
ancestorView
.
$el
);
previousAncestor
=
ancestor
;
listElement
=
ancestorView
.
$
(
'.sortable-list'
);
listElement
=
ancestorView
.
getListElement
(
);
}
}
return
ancestorView
;
...
...
cms/static/js/views/utils/view_utils.js
View file @
a3590653
...
...
@@ -10,9 +10,13 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
/**
* Toggles the expanded state of the current element.
*/
toggleExpandCollapse
=
function
(
target
)
{
toggleExpandCollapse
=
function
(
target
,
collapsedClass
)
{
// Support the old 'collapsed' option until fully switched over to is-collapsed
if
(
!
collapsedClass
)
{
collapsedClass
=
'collapsed'
;
}
target
.
closest
(
'.expand-collapse'
).
toggleClass
(
'expand collapse'
);
target
.
closest
(
'.is-collapsible, .window'
).
toggleClass
(
'collapsed'
);
target
.
closest
(
'.is-collapsible, .window'
).
toggleClass
(
collapsedClass
);
target
.
closest
(
'.is-collapsible'
).
children
(
'article'
).
slideToggle
();
};
...
...
cms/static/js/views/xblock_outline.js
View file @
a3590653
...
...
@@ -21,6 +21,10 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
var
XBlockOutlineView
=
BaseView
.
extend
({
// takes XBlockInfo as a model
options
:
{
collapsedClass
:
'is-collapsed'
},
templateName
:
'xblock-outline'
,
initialize
:
function
()
{
...
...
@@ -94,8 +98,12 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
this
.
renderedChildren
=
true
;
},
getListElement
:
function
()
{
return
this
.
$
(
'> .outline-content > ol'
);
},
addChildView
:
function
(
childView
)
{
this
.
$
(
'> .sortable-list'
).
append
(
childView
.
$el
);
this
.
getListElement
(
).
append
(
childView
.
$el
);
},
addNameEditor
:
function
()
{
...
...
@@ -136,7 +144,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
addButtonActions
:
function
(
element
)
{
var
self
=
this
;
element
.
find
(
'.delete-button'
).
click
(
_
.
bind
(
this
.
handleDeleteEvent
,
this
));
element
.
find
(
'.
add-button
'
).
click
(
_
.
bind
(
this
.
handleAddEvent
,
this
));
element
.
find
(
'.
button-new
'
).
click
(
_
.
bind
(
this
.
handleAddEvent
,
this
));
},
shouldRenderChildren
:
function
()
{
...
...
@@ -163,7 +171,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
xblockType
=
'section'
;
}
else
if
(
category
===
'sequential'
)
{
xblockType
=
'subsection'
;
}
else
if
(
category
===
'vertical'
&&
parentInfo
&&
parentInfo
.
get
(
'category'
)
===
'sequential'
)
{
}
else
if
(
category
===
'vertical'
&&
(
!
parentInfo
||
parentInfo
.
get
(
'category'
)
===
'sequential'
)
)
{
xblockType
=
'unit'
;
}
return
xblockType
;
...
...
cms/static/sass/elements/_modules.scss
View file @
a3590653
...
...
@@ -318,7 +318,7 @@ $outline-indent-width: $baseline;
border-left-color
:
$color-live
;
}
// CASE:
has staff-only content
// CASE:
is presented for staff only
&
.is-staff-only
{
border-left-color
:
$color-staff-only
;
}
...
...
cms/static/sass/views/_container.scss
View file @
a3590653
...
...
@@ -25,14 +25,15 @@
font-weight
:
600
;
}
// TODO: abstract out
.is-editable
{
.incontext-editor-input
{
@extend
%t-title4
;
background
:
none
repeat
scroll
0
0
white
;
@extend
%t-strong
;
background
:
none
repeat
scroll
0
0
$white
;
border
:
0
;
box-shadow
:
0
0
2px
2px
$shadow
inset
;
font-weight
:
600
;
}
}
}
...
...
cms/static/sass/views/_outline.scss
View file @
a3590653
...
...
@@ -6,6 +6,37 @@
%outline-item-header
{
@include
clearfix
();
line-height
:
0
;
// CASE: is-editable
// TODO: abstract out
.is-editable
{
.incontext-editor-open-action
{
@include
transition
(
opacity
$tmg-f1
ease-in-out
0
);
opacity
:
0
.0
;
}
.incontext-editor-form
{
width
:
100%
;
}
.incontext-editor-input
{
@extend
%t-title5
;
@extend
%t-strong
;
width
:
100%
;
background
:
none
repeat
scroll
0
0
$white
;
border
:
0
;
box-shadow
:
0
0
2px
2px
$shadow-l1
inset
;
}
// STATE: hover/focus
&
:hover
,
&
:focus
{
.incontext-editor-open-action
{
opacity
:
1
.0
;
}
}
}
}
%outline-item-content-hidden
{
...
...
cms/templates/container.html
View file @
a3590653
...
...
@@ -10,7 +10,6 @@ else:
<
%!
import
json
from
xmodule
.
modulestore
import
PublishState
from
contentstore
.
views
.
helpers
import
xblock_studio_url
,
xblock_type_display_name
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
...
...
cms/templates/course_outline.html
View file @
a3590653
...
...
@@ -47,13 +47,13 @@ from contentstore.utils import reverse_usage_url
<h3
class=
"sr"
>
${_("Page Actions")}
</h3>
<ul>
<li
class=
"nav-item"
>
<a
href=
"#"
class=
"
toggle-button toggle-button
-expand-collapse collapse-all is-hidden"
>
<a
href=
"#"
class=
"
button button-toggle button-toggle
-expand-collapse collapse-all is-hidden"
>
<span
class=
"collapse-all"
><i
class=
"icon-arrow-up"
></i>
<span
class=
"label"
>
${_("Collapse All Sections")}
</span></span>
<span
class=
"expand-all"
><i
class=
"icon-arrow-down"
></i>
<span
class=
"label"
>
${_("Expand All Sections")}
</span></span>
</a>
</li>
<li
class=
"nav-item"
>
<a
href=
"#"
class=
"button
view-button add-button
"
data-category=
"chapter"
data-parent=
"${context_course.location}"
data-default-name=
"Section"
>
<a
href=
"#"
class=
"button
button-new
"
data-category=
"chapter"
data-parent=
"${context_course.location}"
data-default-name=
"Section"
>
<i
class=
"icon-plus"
></i>
${_('New Section')}
</a>
</li>
...
...
@@ -72,7 +72,7 @@ from contentstore.utils import reverse_usage_url
<
%
course_locator =
context_course.location
%
>
<article
class=
"
course-
outline"
data-locator=
"${course_locator}"
data-course-key=
"${course_locator.course_key}"
>
<article
class=
"outline"
data-locator=
"${course_locator}"
data-course-key=
"${course_locator.course_key}"
>
</article>
</div>
<div
class=
"ui-loading"
>
...
...
cms/templates/js/course-outline.underscore
View file @
a3590653
<%
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 listType = 'list-unknown';
if (xblockType === 'course') {
listType = 'list-sections';
} else if (xblockType === 'section') {
listType = 'list-subsections';
} else if (xblockType === 'subsection') {
listType = 'list-units';
}
var statusMessage = null;
var statusType = null;
if (publishState === 'is_staff_only') {
statusType = 'staff-only';
statusMessage = 'Contains staff only content';
} else if (publishState === 'has_unpublished_content') {
if (category === 'vertical') {
statusType = 'warning';
if (releasedToStudents) {
statusMessage = 'Unpublished changes to live content';
} else {
statusMessage = 'Unpublished units will not be released';
}
}
}
var statusIconClass = '';
if (statusType === 'warning') {
statusIconClass = 'icon-file-alt';
} else if (statusType === 'error') {
statusIconClass = 'icon-warning-sign';
} else if (statusType === 'staff-only') {
statusIconClass = 'icon-lock';
}
%>
<% if (parentInfo) { %>
<li class="outline-item outline-
item-<%= xblockType %> <%= includesChildren ? 'is-collapsible' : '' %> is-draggable <%= isCollapsed ? '
collapsed' : '' %>"
<li class="outline-item outline-
<%= xblockType %> <%= publishClass %> is-draggable <%= includesChildren ? 'is-collapsible' : '' %> <%= isCollapsed ? 'is-
collapsed' : '' %>"
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
<div class="wrapper-xblock-header">
<div class="wrapper-xblock-header-primary">
<% if (includesChildren) { %>
<h3 class="xblock-title expand-collapse <%= isCollapsed ? 'expand' : 'collapse' %>" title="<%= gettext('Collapse/Expand this Checklist') %>">
<i class="icon-caret-down ui-toggle-expansion"></i>
<% } else { %>
<h3 class="xblock-title">
<% } %>
<div class="<%= xblockType %>-header">
<% if (includesChildren) { %>
<h3 class="<%= xblockType %>-header-details expand-collapse <%= isCollapsed ? 'expand' : 'collapse' %> ui-toggle-expansion" title="<%= gettext('Collapse/Expand this Checklist') %>">
<i class="icon-caret-down icon"></i>
<% } else { %>
<h3 class="<%= xblockType %>-header-details">
<% } %>
<% if (xblockInfo.get('category') === 'vertical') { %>
<a href="<%= xblockInfo.get('studio_url') %>"><%= xblockInfo.get('display_name') %></a>
<% if (category === 'vertical') { %>
<span class="unit-title item-title">
<a href="<%= xblockInfo.get('studio_url') %>"><%= xblockInfo.get('display_name') %></a>
</span>
<% } else { %>
<span class="wrapper-xblock-field incontext-editor is-editable" data-field="display_name" data-field-display-name="<%= gettext("Display Name") %>">
<span class="xblock-field-value incontext-editor-value"><%= xblockInfo.get('display_name') %></span>
<span class="wrapper-
<%= xblockType %>-title wrapper-
xblock-field incontext-editor is-editable" data-field="display_name" data-field-display-name="<%= gettext("Display Name") %>">
<span class="
<%= xblockType %>-title item-title
xblock-field-value incontext-editor-value"><%= xblockInfo.get('display_name') %></span>
</span>
<% } %>
</h3>
<div class="item-actions">
<ul class="actions-list">
<li class="action-item action-delete">
<a href="#" data-tooltip="<%= gettext('Delete') %>" class="delete-button action-button">
<i class="icon-remove"></i>
<span class="sr"><%= gettext('Delete') %></span>
</a>
</li>
</ul>
</div>
<div class="<%= xblockType %>-header-actions">
<ul class="actions-list">
<li class="action-item action-delete">
<a href="#" data-tooltip="<%= gettext('Delete') %>" class="delete-button action-button">
<i class="icon icon-trash"></i>
<span class="sr action-button-text"><%= gettext('Delete') %></span>
</a>
</li>
</ul>
</div>
<div class="wrapper-xblock-header-secondary">
<% if (xblockInfo.get('edited_on')) { %>
<div class="meta-info">
<% if (xblockInfo.get('published')) { %>
<i class="icon-check"></i>
<%= gettext('Released:') %> Dec 31, 2015 at 21:00 UTC
<% } else { %>
<i class="icon-time"></i>
<%= gettext('Scheduled:') %> Dec 31, 2015 at 21:00 UTC
<% } %>
</div>
<% if (statusMessage) { %>
<div class="<%= xblockType %>-status">
<% if (category !== 'vertical') { %>
<div class="status-release">
<p>
<span class="sr status-release-label">Release Status:</span>
<span class="status-release-value">
<% if (xblockInfo.get('released_to_students')) { %>
<i class="icon icon-check-sign"></i>
<%= gettext('Released:') %>
<% } else if (xblockInfo.get('release_date')) { %>
<i class="icon icon-time"></i>
<%= gettext('Scheduled:') %>
<% } else { %>
<i class="icon icon-file-alt"></i>
<%= gettext('Unscheduled') %>
<% } %>
<% if (xblockInfo.get('release_date')) { %>
<%= xblockInfo.get('release_date') %>
<% } %>
</span>
</p>
</div>
<% } %>
<div class="item-actions">
<ul class="actions-list">
</ul>
<div class="status-message">
<i class="icon <%= statusIconClass %>"></i>
<p class="status-message-copy"><%- statusMessage %></p>
</div>
</div>
<
/div
>
<
% } %
>
<% } %>
<% if (!parentInfo && xblockInfo.get('child_info') && xblockInfo.get('child_info').children.length === 0) { %>
<div class="no-content add-
xblock-component
">
<div class="no-content add-
section
">
<p><%= gettext("You haven't added any content to this course yet.") %>
<a href="#" class="
add-button
" data-category="<%= childCategory %>"
<a href="#" class="
button button-new
" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon-plus"></i><%= addChildLabel %>
<i class="icon
icon
-plus"></i><%= addChildLabel %>
</a>
</p>
</div>
<% } else { %>
<ol class="sortable-list sortable-<%= xblockType %>-list">
</ol>
<% if (childType) { %>
<div class="add-xblock-component">
<a href="#" class="add-button" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon-plus"></i><%= addChildLabel %>
</a>
</div>
<% } %>
<div class="outline-content <%= xblockType %>-content">
<ol class="<%= listType %> is-sortable">
</ol>
<% if (childType) { %>
<div class="add-<%= childType %> add-item">
<a href="#" class="button button-new" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon icon-plus"></i><%= addChildLabel %>
</a>
</div>
<% } %>
</div>
<% } %>
<% if (parentInfo) { %>
...
...
cms/templates/js/mock/mock-course-outline-page.underscore
View file @
a3590653
...
...
@@ -11,13 +11,13 @@
<h3 class="sr">Page Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="
toggle-button toggle-button-expand-collapse collapse-all
">
<a href="#" class="
button button-toggle button-toggle-expand-collapse collapse-all is-hidden
">
<span class="collapse-all"><i class="icon-arrow-up"></i> <span class="label">Collapse All Sections</span></span>
<span class="expand-all"><i class="icon-arrow-down"></i> <span class="label">Expand All Sections</span></span>
</a>
</li>
<li class="nav-item">
<a href="#" class="button
view-button add-button
" data-category="chapter" data-parent="mock-course" data-default-name="Section">
<a href="#" class="button
button-new
" data-category="chapter" data-parent="mock-course" data-default-name="Section">
<i class="icon-plus"></i>New Section
</a>
</li>
...
...
@@ -33,10 +33,10 @@
<section class="content">
<article class="content-primary" role="main">
<div class="wrapper-dnd">
<article class="
course-
outline" data-locator="mock-course" data-course-key="slashes:MockCourse">
<article class="outline" data-locator="mock-course" data-course-key="slashes:MockCourse">
<div class="no-content add-xblock-component">
<p>You haven't added any content to this course yet.
<a href="#" class="
add-button
" data-category="chapter" data-parent="mock-course" data-default-name="Section">
<a href="#" class="
button button-new
" data-category="chapter" data-parent="mock-course" data-default-name="Section">
<i class="icon-plus"></i>Add Section
</a>
</p>
...
...
cms/templates/js/publish-history.underscore
View file @
a3590653
<%
var copy = gettext("Never published");
if (published_on && published_by) {
var message = gettext("Last published %(last_published_date)s by %(publish_username)s");
copy = interpolate(message, {
last_published_date: '<span class="date">' + published_on + '</span>',
publish_username: '<span class="user">' + published_by + '</span>'
}, true);
} else if (published) {
copy = gettext("Previously published");
}
%>
<div class="wrapper-last-publish">
<p class="copy">
<% if (published) {
if (published_on && published_by) {
var message = gettext("Last published %(last_published_date)s by %(publish_username)s"); %>
<%= interpolate(message, {
last_published_date: '<span class="date">' + published_on + '</span>',
publish_username: '<span class="user">' + published_by + '</span>' }, true) %>
<% } else { %>
<%= gettext("Previously published") %>
<% } %>
<% } else { %>
<%= gettext("Never published") %>
<% } %>
</p>
</div>
\ No newline at end of file
<p class="copy"><%= copy %></p>
</div>
cms/templates/js/publish-xblock.underscore
View file @
a3590653
<%
var publishClasses = "";
var title = gettext("Draft (Never published)");
if (published) {
if (published && hasChanges) {
publishClasses = publishClasses + " is-draft";
title = gettext("Draft (Unpublished changes)");
} else {
publishClasses = publishClasses + " is-published";
title = gettext("Published");
}
}
if (releaseDate) {
publishClasses = publishClasses + " is-scheduled";
var publishClass = '';
if (publishState === 'staff_only') {
publishClass = 'is-staff-only';
} else if (publishState === 'live') {
publishClass = 'is-live is-published is-released';
} else if (publishState === 'ready') {
publishClass = 'is-ready is-published';
} else if (publishState === 'has_unpublished_content') {
publishClass = 'has-warnings is-draft';
}
if (visibleToStaffOnly) {
publishClasses = publishClasses + " is-staff-only";
var title = gettext("Draft (Never published)");
if (publishState === 'staff_only') {
title = gettext("Unpublished (Staff only)");
} else if (publishState === 'live') {
title = gettext("Published and Live");
} else if (publishState === 'ready') {
title = gettext("Published");
} else if (publishState === 'has_unpublished_content') {
title = gettext("Draft (Unpublished changes)");
}
var releaseLabel = gettext("Release:");
if (publishState === 'live') {
releaseLabel = gettext("Released:");
} else if (publishState === 'ready') {
releaseLabel = gettext("Scheduled:");
}
var canPublish = publishState !== 'ready' && publishState !== 'live';
var canDiscardChanges = publishState === 'has_unpublished_content';
var visibleToStaffOnly = publishState === 'staff_only';
%>
<div class="bit-publishing <%= publishClass
es
%>">
<div class="bit-publishing <%= publishClass %>">
<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 (
hasChanges
&& editedOn && editedBy) {
<% if (
publishState === 'has_unpublished_content'
&& 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>',
...
...
@@ -42,17 +56,7 @@ if (visibleToStaffOnly) {
</div>
<div class="wrapper-release bar-mod-content">
<h5 class="title">
<% if (published && releaseDate) {
if (releasedToStudents) { %>
<%= gettext("Released:") %>
<% } else { %>
<%= gettext("Scheduled:") %>
<% }
} else { %>
<%= gettext("Release:") %>
<% } %>
</h5>
<h5 class="title"><%= releaseLabel %></h5>
<p class="copy">
<% if (releaseDate) { %>
<% var message = gettext("%(release_date)s with %(section_or_subsection)s") %>
...
...
@@ -87,12 +91,12 @@ if (visibleToStaffOnly) {
<div class="wrapper-pub-actions bar-mod-actions">
<ul class="action-list">
<li class="action-item">
<a class="action-publish action-primary <% if (
published && !hasChanges
) { %>is-disabled<% } %>"
<a class="action-publish action-primary <% if (
!canPublish
) { %>is-disabled<% } %>"
href=""><%= gettext("Publish") %>
</a>
</li>
<li class="action-item">
<a class="action-discard action-secondary <% if (!
published || !has
Changes) { %>is-disabled<% } %>"
<a class="action-discard action-secondary <% if (!
canDiscard
Changes) { %>is-disabled<% } %>"
href=""><%= gettext("Discard Changes") %>
</a>
</li>
...
...
cms/templates/js/unit-outline.underscore
View file @
a3590653
<%
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';
} else if (xblockType === 'section') {
listType = 'list-subsections';
} else if (xblockType === 'subsection') {
listType = 'list-units';
}
%>
<% if (parentInfo) { %>
<li class="outline-item outline-item-<%= xblockType %>"
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
<div class="wrapper-xblock-header">
<div class="wrapper-xblock-header-primary">
<h3 class="xblock-title">
<a href="<%= xblockInfo.get('studio_url') %>"><%= xblockInfo.get('display_name') %></a>
<li class="outline-item outline-<%= xblockType %> <%= publishClass %>"
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
<div class="<%= xblockType %>-header">
<h3 class="<%= xblockType %>-header-details">
<span class="unit-title item-title">
<a href="<%= xblockInfo.get('studio_url') %>"><%= xblockInfo.get('display_name') %></a>
</span>
</h3>
</div>
</div>
<% } %>
<ol class="sortable-list sortable-<%= xblockType %>-list">
</ol>
<% if (childType) { %>
<div class="add-xblock-component">
<a href="#" class="add-button" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon-plus"></i><%= addChildLabel %>
</a>
</div>
<% } %>
<div class="<%= xblockType %>-content outline-content">
<ol class="<%= listType %>">
</ol>
<% if (childType) { %>
<div class="add-<%= childType %> add-item">
<a href="#" class="button button-new" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon icon-plus"></i><%= addChildLabel %>
</a>
</div>
<% } %>
</div>
<% if (parentInfo) { %>
</li>
</li>
<% } %>
cms/templates/js/xblock-outline.underscore
View file @
a3590653
<% if (parentInfo) { %>
<li class="outline-item outline-item-<%= xblockType %> <%= includesChildren ? 'is-collapsible' : '' %> is-draggable <%= isCollapsed ? 'collapsed' : '' %>"
<li class="outline-item outline-item-<%= xblockType %> <%= includesChildren ? 'is-collapsible' : '' %> is-draggable <%= isCollapsed ? '
is-
collapsed' : '' %>"
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
<span class="draggable-drop-indicator draggable-drop-indicator-before"><i class="icon-caret-right"></i></span>
...
...
@@ -56,7 +56,7 @@
<% if (!parentInfo && xblockInfo.get('child_info') && xblockInfo.get('child_info').children.length === 0) { %>
<div class="no-content add-xblock-component">
<p><%= gettext("You haven't added any content to this course yet.") %>
<a href="#" class="
add-button
" data-category="<%= childCategory %>"
<a href="#" class="
button button-new
" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon-plus"></i><%= addChildLabel %>
</a>
...
...
@@ -68,7 +68,7 @@
<% if (childType) { %>
<div class="add-xblock-component">
<a href="#" class="
add-button
" data-category="<%= childCategory %>"
<a href="#" class="
button button-new
" data-category="<%= childCategory %>"
data-parent="<%= xblockInfo.get('id') %>" data-default-name="<%= defaultNewChildName %>">
<i class="icon-plus"></i><%= addChildLabel %>
</a>
...
...
common/lib/xmodule/xmodule/modulestore/__init__.py
View file @
a3590653
...
...
@@ -89,11 +89,11 @@ class ModuleStoreEnum(object):
# user ID to use for tests that do not have a django user available
test
=
-
3
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.
class
LegacyPublishState
(
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.
"""
draft
=
'draft'
private
=
'private'
...
...
@@ -301,10 +301,10 @@ class ModuleStoreRead(object):
Returns whether this xblock is draft, public, or private.
Returns:
PublishState.draft - content is in the process of being edited, but still has a previous
Legacy
PublishState.draft - content is in the process of being edited, but still has a previous
version deployed to LMS
PublishState.public - content is locked and deployed to LMS
PublishState.private - content is editable and not deployed to LMS
Legacy
PublishState.public - content is locked and deployed to LMS
Legacy
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
PublishState
.
public
return
Legacy
PublishState
.
public
def
heartbeat
(
self
):
"""
...
...
common/lib/xmodule/xmodule/modulestore/mixed.py
View file @
a3590653
...
...
@@ -439,10 +439,10 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
Returns whether this xblock is draft, public, or private.
Returns:
PublishState.draft - content is in the process of being edited, but still has a previous
Legacy
PublishState.draft - content is in the process of being edited, but still has a previous
version deployed to LMS
PublishState.public - content is locked and deployed to LMS
PublishState.private - content is editable and not deployed to LMS
Legacy
PublishState.public - content is locked and deployed to LMS
Legacy
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
)
...
...
common/lib/xmodule/xmodule/modulestore/mongo/draft.py
View file @
a3590653
...
...
@@ -11,7 +11,7 @@ import logging
from
opaque_keys.edx.locations
import
Location
from
xmodule.exceptions
import
InvalidVersionError
from
xmodule.modulestore
import
PublishState
,
ModuleStoreEnum
from
xmodule.modulestore
import
Legacy
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
)
!=
PublishState
.
public
:
if
self
.
compute_publish_state
(
item
)
!=
Legacy
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:
PublishState.draft - content is in the process of being edited, but still has a previous
Legacy
PublishState.draft - content is in the process of being edited, but still has a previous
version deployed to LMS
PublishState.public - content is locked and deployed to LMS
PublishState.private - content is editable and not deployed to LMS
Legacy
PublishState.public - content is locked and deployed to LMS
Legacy
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
PublishState
.
private
return
Legacy
PublishState
.
private
else
:
return
PublishState
.
draft
return
Legacy
PublishState
.
draft
else
:
return
PublishState
.
public
return
Legacy
PublishState
.
public
def
_verify_branch_setting
(
self
,
expected_branch_setting
):
"""
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py
View file @
a3590653
...
...
@@ -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
,
PublishState
from
xmodule.modulestore
import
ModuleStoreEnum
,
Legacy
PublishState
from
xmodule.modulestore.exceptions
import
InsufficientSpecificationError
from
xmodule.modulestore.draft_and_published
import
ModuleStoreDraftAndPublished
,
DIRECT_ONLY_CATEGORIES
,
UnsupportedRevisionError
...
...
@@ -251,10 +251,11 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
Returns whether this xblock is draft, public, or private.
Returns:
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
Legacy
PublishState.draft - published exists and is different from draft
Legacy
PublishState.public - published exists and is the same as draft
Legacy
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
):
course_structure
=
self
.
_lookup_course
(
xblock
.
location
.
course_key
.
for_branch
(
branch
))[
'structure'
]
return
self
.
_get_block_from_structure
(
course_structure
,
xblock
.
location
.
block_id
)
...
...
@@ -271,13 +272,13 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
if
not
published_head
:
# published version does not exist
return
PublishState
.
private
return
Legacy
PublishState
.
private
elif
get_version
(
draft_head
)
==
get_version
(
published_head
):
# published and draft versions are equal
return
PublishState
.
public
return
Legacy
PublishState
.
public
else
:
# published and draft versions differ
return
PublishState
.
draft
return
Legacy
PublishState
.
draft
def
convert_to_draft
(
self
,
location
,
user_id
):
"""
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
View file @
a3590653
...
...
@@ -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
,
PublishState
from
xmodule.modulestore
import
ModuleStoreEnum
,
Legacy
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
),
PublishState
.
private
)
self
.
assertEquals
(
self
.
store
.
compute_publish_state
(
item
),
Legacy
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
),
PublishState
.
public
)
self
.
assertEquals
(
self
.
store
.
compute_publish_state
(
item
),
Legacy
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
),
PublishState
.
private
)
self
.
assertEquals
(
self
.
store
.
compute_publish_state
(
item
),
Legacy
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
),
PublishState
.
public
)
self
.
assertEquals
(
self
.
store
.
compute_publish_state
(
item
),
Legacy
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
),
PublishState
.
draft
if
default_ms
==
'draft'
else
PublishState
.
public
LegacyPublishState
.
draft
if
default_ms
==
'draft'
else
Legacy
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
),
PublishState
.
draft
)
self
.
assertEquals
(
self
.
store
.
compute_publish_state
(
item
),
Legacy
PublishState
.
draft
)
@ddt.data
(
'draft'
,
'split'
)
def
test_auto_publish
(
self
,
default_ms
):
...
...
common/test/acceptance/pages/studio/overview.py
View file @
a3590653
...
...
@@ -113,7 +113,7 @@ class CourseOutlineContainer(CourseOutlineItem):
"""
click_css
(
self
,
self
.
_bounded_selector
(
".add-
xblock-component a.add-button
"
),
self
.
_bounded_selector
(
".add-
item a.button-new
"
),
require_notification
=
require_notification
,
)
...
...
@@ -125,7 +125,7 @@ class CourseOutlineContainer(CourseOutlineItem):
self
.
browser
.
execute_script
(
"jQuery.fx.off = true;"
)
def
subsection_expanded
():
add_button
=
self
.
q
(
css
=
self
.
_bounded_selector
(
'> .
add-xblock-component a.add-button
'
))
.
first
.
results
add_button
=
self
.
q
(
css
=
self
.
_bounded_selector
(
'> .
outline-content > .add-item a.button-new
'
))
.
first
.
results
return
add_button
and
add_button
[
0
]
.
is_displayed
()
currently_expanded
=
subsection_expanded
()
...
...
@@ -171,8 +171,8 @@ class CourseOutlineUnit(CourseOutlineChild):
PageObject that wraps a unit link on the Studio Course Outline page.
"""
url
=
None
BODY_SELECTOR
=
'.outline-
item-
unit'
NAME_SELECTOR
=
'.
xblock
-title a'
BODY_SELECTOR
=
'.outline-unit'
NAME_SELECTOR
=
'.
unit
-title a'
def
go_to
(
self
):
"""
...
...
@@ -191,7 +191,9 @@ class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
"""
url
=
None
BODY_SELECTOR
=
'.outline-item-subsection'
BODY_SELECTOR
=
'.outline-subsection'
NAME_SELECTOR
=
'.subsection-title'
NAME_FIELD_WRAPPER_SELECTOR
=
'.subsection-header .wrapper-xblock-field'
CHILD_CLASS
=
CourseOutlineUnit
def
unit
(
self
,
title
):
...
...
@@ -224,7 +226,9 @@ class CourseOutlineSection(CourseOutlineChild, CourseOutlineContainer):
:class`.PageObject` that wraps a section block on the Studio Course Outline page.
"""
url
=
None
BODY_SELECTOR
=
'.outline-item-section'
BODY_SELECTOR
=
'.outline-section'
NAME_SELECTOR
=
'.section-title'
NAME_FIELD_WRAPPER_SELECTOR
=
'.section-header .wrapper-xblock-field'
CHILD_CLASS
=
CourseOutlineSubsection
def
subsection
(
self
,
title
):
...
...
@@ -268,7 +272,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
url_path
=
"course"
CHILD_CLASS
=
CourseOutlineSection
EXPAND_COLLAPSE_CSS
=
'.toggle-button-expand-collapse'
BOTTOM_ADD_SECTION_BUTTON
=
'.
course-outline > .add-xblock-component .add-button
'
BOTTOM_ADD_SECTION_BUTTON
=
'.
outline > .add-section .button-new
'
def
is_browser_on_page
(
self
):
return
self
.
q
(
css
=
'body.view-outline'
)
.
present
...
...
@@ -337,7 +341,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
"""
Clicks the button for adding a section which resides at the top of the screen.
"""
click_css
(
self
,
'.wrapper-mast nav.nav-actions .
add-button
'
)
click_css
(
self
,
'.wrapper-mast nav.nav-actions .
button-new
'
)
def
add_section_from_bottom_button
(
self
):
"""
...
...
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