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
3f44ff2f
Commit
3f44ff2f
authored
Nov 21, 2013
by
Christina Roberts
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1675 from edx/christina/tab-new
Convert tabs to use RESTful API
parents
7bab863c
1b2be30c
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
226 additions
and
118 deletions
+226
-118
cms/djangoapps/contentstore/features/static-pages.feature
+12
-7
cms/djangoapps/contentstore/features/static-pages.py
+44
-0
cms/djangoapps/contentstore/tests/test_contentstore.py
+58
-34
cms/djangoapps/contentstore/views/tabs.py
+62
-58
cms/static/coffee/spec/main.coffee
+2
-1
cms/static/coffee/src/views/tabs.coffee
+5
-2
cms/static/js/models/explicit_url.js
+14
-0
cms/static/js/spec/models/explicit_url_spec.js
+12
-0
cms/templates/edit-tabs.html
+7
-4
cms/templates/widgets/header.html
+9
-8
cms/urls.py
+1
-4
No files found.
cms/djangoapps/contentstore/features/static-pages.feature
View file @
3f44ff2f
...
@@ -9,10 +9,8 @@ Feature: CMS.Static Pages
...
@@ -9,10 +9,8 @@ Feature: CMS.Static Pages
Then
I should see a static page named
"Empty"
Then
I should see a static page named
"Empty"
Scenario
:
Users can delete static pages
Scenario
:
Users can delete static pages
Given
I have opened a new course in Studio
Given
I have created a static page
And
I go to the static pages page
When
I
"delete"
the static page
And
I add a new page
And
I
"delete"
the static page
Then
I am shown a prompt
Then
I am shown a prompt
When
I confirm the prompt
When
I confirm the prompt
Then
I should not see any static pages
Then
I should not see any static pages
...
@@ -20,9 +18,16 @@ Feature: CMS.Static Pages
...
@@ -20,9 +18,16 @@ Feature: CMS.Static Pages
# Safari won't update the name properly
# Safari won't update the name properly
@skip_safari
@skip_safari
Scenario
:
Users can edit static pages
Scenario
:
Users can edit static pages
Given
I have opened a new course in Studio
Given
I have created a static page
And
I go to the static pages page
And
I add a new page
When
I
"edit"
the static page
When
I
"edit"
the static page
And
I change the name to
"New"
And
I change the name to
"New"
Then
I should see a static page named
"New"
Then
I should see a static page named
"New"
# Safari won't update the name properly
@skip_safari
Scenario
:
Users can reorder static pages
Given
I have created two different static pages
When
I reorder the tabs
Then
the tabs are in the reverse order
And
I reload the page
Then
the tabs are in the reverse order
cms/djangoapps/contentstore/features/static-pages.py
View file @
3f44ff2f
...
@@ -48,3 +48,47 @@ def change_name(step, new_name):
...
@@ -48,3 +48,47 @@ def change_name(step, new_name):
world
.
trigger_event
(
input_css
)
world
.
trigger_event
(
input_css
)
save_button
=
'a.save-button'
save_button
=
'a.save-button'
world
.
css_click
(
save_button
)
world
.
css_click
(
save_button
)
@step
(
u'I reorder the tabs'
)
def
reorder_tabs
(
_step
):
# For some reason, the drag_and_drop method did not work in this case.
draggables
=
world
.
css_find
(
'.drag-handle'
)
source
=
draggables
.
first
target
=
draggables
.
last
source
.
action_chains
.
click_and_hold
(
source
.
_element
)
.
perform
()
source
.
action_chains
.
move_to_element_with_offset
(
target
.
_element
,
0
,
50
)
.
perform
()
source
.
action_chains
.
release
()
.
perform
()
@step
(
u'I have created a static page'
)
def
create_static_page
(
step
):
step
.
given
(
'I have opened a new course in Studio'
)
step
.
given
(
'I go to the static pages page'
)
step
.
given
(
'I add a new page'
)
@step
(
u'I have created two different static pages'
)
def
create_two_pages
(
step
):
step
.
given
(
'I have created a static page'
)
step
.
given
(
'I "edit" the static page'
)
step
.
given
(
'I change the name to "First"'
)
step
.
given
(
'I add a new page'
)
# Verify order of tabs
_verify_tab_names
(
'First'
,
'Empty'
)
@step
(
u'the tabs are in the reverse order'
)
def
tabs_in_reverse_order
(
step
):
_verify_tab_names
(
'Empty'
,
'First'
)
def
_verify_tab_names
(
first
,
second
):
world
.
wait_for
(
func
=
lambda
_
:
len
(
world
.
css_find
(
'.xmodule_StaticTabModule'
))
==
2
,
timeout
=
200
,
timeout_msg
=
"Timed out waiting for two tabs to be present"
)
tabs
=
world
.
css_find
(
'.xmodule_StaticTabModule'
)
assert
tabs
[
0
]
.
text
==
first
assert
tabs
[
1
]
.
text
==
second
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
3f44ff2f
...
@@ -179,7 +179,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -179,7 +179,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# TODO: uncomment after edit_unit not using locations.
# TODO: uncomment after edit_unit not using locations.
# _test_no_locations(self, resp)
# _test_no_locations(self, resp)
def
lockAnA
sset
(
self
,
content_store
,
course_location
):
def
_lock_an_a
sset
(
self
,
content_store
,
course_location
):
"""
"""
Lock an arbitrary asset in the course
Lock an arbitrary asset in the course
:param course_location:
:param course_location:
...
@@ -407,24 +407,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -407,24 +407,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
assertEqual
(
course
.
tabs
,
expected_tabs
)
self
.
assertEqual
(
course
.
tabs
,
expected_tabs
)
def
test_static_tab_reordering
(
self
):
def
test_static_tab_reordering
(
self
):
def
get_tab_locator
(
tab
):
module_store
,
course_location
,
new_location
=
self
.
_create_static_tabs
()
tab_location
=
'i4x://MITx/999/static_tab/{0}'
.
format
(
tab
[
'url_slug'
])
return
unicode
(
loc_mapper
()
.
translate_location
(
course
.
location
.
course_id
,
Location
(
tab_location
),
False
,
True
))
module_store
=
modulestore
(
'direct'
)
locator
=
_course_factory_create_course
()
course_location
=
loc_mapper
()
.
translate_locator_to_location
(
locator
)
ItemFactory
.
create
(
parent_location
=
course_location
,
category
=
"static_tab"
,
display_name
=
"Static_1"
)
ItemFactory
.
create
(
parent_location
=
course_location
,
category
=
"static_tab"
,
display_name
=
"Static_2"
)
course
=
module_store
.
get_item
(
course_location
)
course
=
module_store
.
get_item
(
course_location
)
...
@@ -432,9 +415,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -432,9 +415,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
reverse_tabs
=
[]
reverse_tabs
=
[]
for
tab
in
course
.
tabs
:
for
tab
in
course
.
tabs
:
if
tab
[
'type'
]
==
'static_tab'
:
if
tab
[
'type'
]
==
'static_tab'
:
reverse_tabs
.
insert
(
0
,
get_tab_locator
(
tab
))
reverse_tabs
.
insert
(
0
,
unicode
(
self
.
_get_tab_locator
(
course
,
tab
)
))
self
.
client
.
ajax_post
(
reverse
(
'reorder_static_
tabs'
),
{
'tabs'
:
reverse_tabs
})
self
.
client
.
ajax_post
(
new_location
.
url_reverse
(
'
tabs'
),
{
'tabs'
:
reverse_tabs
})
course
=
module_store
.
get_item
(
course_location
)
course
=
module_store
.
get_item
(
course_location
)
...
@@ -442,10 +425,57 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -442,10 +425,57 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
course_tabs
=
[]
course_tabs
=
[]
for
tab
in
course
.
tabs
:
for
tab
in
course
.
tabs
:
if
tab
[
'type'
]
==
'static_tab'
:
if
tab
[
'type'
]
==
'static_tab'
:
course_tabs
.
append
(
get_tab_locator
(
tab
))
course_tabs
.
append
(
unicode
(
self
.
_get_tab_locator
(
course
,
tab
)
))
self
.
assertEqual
(
reverse_tabs
,
course_tabs
)
self
.
assertEqual
(
reverse_tabs
,
course_tabs
)
def
test_static_tab_deletion
(
self
):
module_store
,
course_location
,
_
=
self
.
_create_static_tabs
()
course
=
module_store
.
get_item
(
course_location
)
num_tabs
=
len
(
course
.
tabs
)
last_tab
=
course
.
tabs
[
num_tabs
-
1
]
url_slug
=
last_tab
[
'url_slug'
]
delete_url
=
self
.
_get_tab_locator
(
course
,
last_tab
)
.
url_reverse
(
'xblock'
)
self
.
client
.
delete
(
delete_url
)
course
=
module_store
.
get_item
(
course_location
)
self
.
assertEqual
(
num_tabs
-
1
,
len
(
course
.
tabs
))
def
tab_matches
(
tab
):
""" Checks if the tab matches the one we deleted """
return
tab
[
'type'
]
==
'static_tab'
and
tab
[
'url_slug'
]
==
url_slug
tab_found
=
any
(
tab_matches
(
tab
)
for
tab
in
course
.
tabs
)
self
.
assertFalse
(
tab_found
,
"tab should have been deleted"
)
def
_get_tab_locator
(
self
,
course
,
tab
):
""" Returns the locator for a given tab. """
tab_location
=
'i4x://MITx/999/static_tab/{0}'
.
format
(
tab
[
'url_slug'
])
return
loc_mapper
()
.
translate_location
(
course
.
location
.
course_id
,
Location
(
tab_location
),
False
,
True
)
def
_create_static_tabs
(
self
):
""" Creates two static tabs in a dummy course. """
module_store
=
modulestore
(
'direct'
)
CourseFactory
.
create
(
org
=
'edX'
,
course
=
'999'
,
display_name
=
'Robot Super Course'
)
course_location
=
Location
([
'i4x'
,
'edX'
,
'999'
,
'course'
,
'Robot_Super_Course'
,
None
])
new_location
=
loc_mapper
()
.
translate_location
(
course_location
.
course_id
,
course_location
,
False
,
True
)
ItemFactory
.
create
(
parent_location
=
course_location
,
category
=
"static_tab"
,
display_name
=
"Static_1"
)
ItemFactory
.
create
(
parent_location
=
course_location
,
category
=
"static_tab"
,
display_name
=
"Static_2"
)
return
module_store
,
course_location
,
new_location
def
test_import_polls
(
self
):
def
test_import_polls
(
self
):
module_store
=
modulestore
(
'direct'
)
module_store
=
modulestore
(
'direct'
)
import_from_xml
(
module_store
,
'common/test/data/'
,
[
'toy'
])
import_from_xml
(
module_store
,
'common/test/data/'
,
[
'toy'
])
...
@@ -633,7 +663,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -633,7 +663,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
thumbnail
=
content_store
.
find
(
thumbnail_location
,
throw_on_not_found
=
False
)
thumbnail
=
content_store
.
find
(
thumbnail_location
,
throw_on_not_found
=
False
)
self
.
assertIsNotNone
(
thumbnail
)
self
.
assertIsNotNone
(
thumbnail
)
def
_delete_asset_in_course
(
self
):
def
_delete_asset_in_course
(
self
):
"""
"""
Helper method for:
Helper method for:
1) importing course from xml
1) importing course from xml
...
@@ -972,7 +1002,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -972,7 +1002,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
assertIn
(
private_location_no_draft
.
url
(),
sequential
.
children
)
self
.
assertIn
(
private_location_no_draft
.
url
(),
sequential
.
children
)
locked_asset
=
self
.
lockAnA
sset
(
content_store
,
location
)
locked_asset
=
self
.
_lock_an_a
sset
(
content_store
,
location
)
locked_asset_attrs
=
content_store
.
get_attrs
(
locked_asset
)
locked_asset_attrs
=
content_store
.
get_attrs
(
locked_asset
)
# the later import will reupload
# the later import will reupload
del
locked_asset_attrs
[
'uploadDate'
]
del
locked_asset_attrs
[
'uploadDate'
]
...
@@ -1226,7 +1256,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -1226,7 +1256,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
handouts_locator
=
loc_mapper
()
.
translate_location
(
'edX/toy/2012_Fall'
,
handout_location
)
handouts_locator
=
loc_mapper
()
.
translate_location
(
'edX/toy/2012_Fall'
,
handout_location
)
# get module info (json)
# get module info (json)
resp
=
self
.
client
.
get
(
handouts_locator
.
url_reverse
(
'/xblock'
,
''
))
resp
=
self
.
client
.
get
(
handouts_locator
.
url_reverse
(
'/xblock'
))
# make sure we got a successful response
# make sure we got a successful response
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
...
@@ -1403,7 +1433,7 @@ class ContentStoreTest(ModuleStoreTestCase):
...
@@ -1403,7 +1433,7 @@ class ContentStoreTest(ModuleStoreTestCase):
second_course_data
=
self
.
assert_created_course
(
number_suffix
=
uuid4
()
.
hex
)
second_course_data
=
self
.
assert_created_course
(
number_suffix
=
uuid4
()
.
hex
)
# unseed the forums for the first course
# unseed the forums for the first course
course_id
=
_get_course_id
(
test_course_data
)
course_id
=
_get_course_id
(
test_course_data
)
delete_course_and_groups
(
course_id
,
commit
=
True
)
delete_course_and_groups
(
course_id
,
commit
=
True
)
self
.
assertFalse
(
are_permissions_roles_seeded
(
course_id
))
self
.
assertFalse
(
are_permissions_roles_seeded
(
course_id
))
...
@@ -1625,6 +1655,7 @@ class ContentStoreTest(ModuleStoreTestCase):
...
@@ -1625,6 +1655,7 @@ class ContentStoreTest(ModuleStoreTestCase):
test_get_html
(
'course_info'
)
test_get_html
(
'course_info'
)
test_get_html
(
'checklists'
)
test_get_html
(
'checklists'
)
test_get_html
(
'assets'
)
test_get_html
(
'assets'
)
test_get_html
(
'tabs'
)
# settings_details
# settings_details
resp
=
self
.
client
.
get_html
(
reverse
(
'settings_details'
,
resp
=
self
.
client
.
get_html
(
reverse
(
'settings_details'
,
...
@@ -1677,13 +1708,6 @@ class ContentStoreTest(ModuleStoreTestCase):
...
@@ -1677,13 +1708,6 @@ class ContentStoreTest(ModuleStoreTestCase):
# TODO: uncomment when edit_unit not using old locations.
# TODO: uncomment when edit_unit not using old locations.
# _test_no_locations(self, resp)
# _test_no_locations(self, resp)
resp
=
self
.
client
.
get_html
(
reverse
(
'edit_tabs'
,
kwargs
=
{
'org'
:
loc
.
org
,
'course'
:
loc
.
course
,
'coursename'
:
loc
.
name
}))
self
.
assertEqual
(
resp
.
status_code
,
200
)
_test_no_locations
(
self
,
resp
)
def
delete_item
(
category
,
name
):
def
delete_item
(
category
,
name
):
""" Helper method for testing the deletion of an xblock item. """
""" Helper method for testing the deletion of an xblock item. """
del_loc
=
loc
.
replace
(
category
=
category
,
name
=
name
)
del_loc
=
loc
.
replace
(
category
=
category
,
name
=
name
)
...
@@ -1997,7 +2021,7 @@ def _create_course(test, course_data):
...
@@ -1997,7 +2021,7 @@ def _create_course(test, course_data):
test
.
assertEqual
(
response
.
status_code
,
200
)
test
.
assertEqual
(
response
.
status_code
,
200
)
data
=
parse_json
(
response
)
data
=
parse_json
(
response
)
test
.
assertNotIn
(
'ErrMsg'
,
data
)
test
.
assertNotIn
(
'ErrMsg'
,
data
)
test
.
assertEqual
(
data
[
'url'
],
new_location
.
url_reverse
(
"course
/"
,
"
"
))
test
.
assertEqual
(
data
[
'url'
],
new_location
.
url_reverse
(
"course"
))
def
_course_factory_create_course
():
def
_course_factory_create_course
():
...
...
cms/djangoapps/contentstore/views/tabs.py
View file @
3f44ff2f
...
@@ -2,12 +2,13 @@
...
@@ -2,12 +2,13 @@
Views related to course tabs
Views related to course tabs
"""
"""
from
access
import
has_access
from
access
import
has_access
from
util.json_request
import
expect_json
from
util.json_request
import
expect_json
,
JsonResponse
from
django.http
import
HttpResponse
,
HttpResponseBadRequest
from
django.http
import
HttpResponse
NotFound
from
django.contrib.auth.decorators
import
login_required
from
django.contrib.auth.decorators
import
login_required
from
django.core.exceptions
import
PermissionDenied
from
django.core.exceptions
import
PermissionDenied
from
django_future.csrf
import
ensure_csrf_cookie
from
django_future.csrf
import
ensure_csrf_cookie
from
django.views.decorators.http
import
require_http_methods
from
mitxmako.shortcuts
import
render_to_response
from
mitxmako.shortcuts
import
render_to_response
from
xmodule.modulestore
import
Location
from
xmodule.modulestore
import
Location
from
xmodule.modulestore.inheritance
import
own_metadata
from
xmodule.modulestore.inheritance
import
own_metadata
...
@@ -19,7 +20,7 @@ from ..utils import get_modulestore
...
@@ -19,7 +20,7 @@ from ..utils import get_modulestore
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
__all__
=
[
'
edit_tabs'
,
'reorder_static_tabs
'
]
__all__
=
[
'
tabs_handler
'
]
def
initialize_course_tabs
(
course
):
def
initialize_course_tabs
(
course
):
...
@@ -43,75 +44,86 @@ def initialize_course_tabs(course):
...
@@ -43,75 +44,86 @@ def initialize_course_tabs(course):
modulestore
(
'direct'
)
.
update_metadata
(
course
.
location
.
url
(),
own_metadata
(
course
))
modulestore
(
'direct'
)
.
update_metadata
(
course
.
location
.
url
(),
own_metadata
(
course
))
@login_required
@expect_json
@expect_json
def
reorder_static_tabs
(
request
):
@login_required
"Order the static tabs in the requested order"
@ensure_csrf_cookie
def
get_location_for_tab
(
tab
):
@require_http_methods
((
"GET"
,
"POST"
,
"PUT"
))
tab_locator
=
BlockUsageLocator
(
tab
)
def
tabs_handler
(
request
,
tag
=
None
,
course_id
=
None
,
branch
=
None
,
version_guid
=
None
,
block
=
None
):
return
loc_mapper
()
.
translate_locator_to_location
(
tab_locator
)
"""
The restful handler for static tabs.
tabs
=
request
.
json
[
'tabs'
]
GET
course_location
=
loc_mapper
()
.
translate_locator_to_location
(
BlockUsageLocator
(
tabs
[
0
]),
get_course
=
True
)
html: return page for editing static tabs
json: not supported
PUT or POST
json: update the tab order. It is expected that the request body contains a JSON-encoded dict with entry "tabs".
The value for "tabs" is an array of tab locators, indicating the desired order of the tabs.
if
not
has_access
(
request
.
user
,
course_location
):
Creating a tab, deleting a tab, or changing its contents is not supported through this method.
Instead use the general xblock URL (see item.xblock_handler).
"""
locator
=
BlockUsageLocator
(
course_id
=
course_id
,
branch
=
branch
,
version_guid
=
version_guid
,
usage_id
=
block
)
if
not
has_access
(
request
.
user
,
locator
):
raise
PermissionDenied
()
raise
PermissionDenied
()
course
=
get_modulestore
(
course_location
)
.
get_item
(
course_location
)
old_location
=
loc_mapper
()
.
translate_locator_to_location
(
locator
)
store
=
get_modulestore
(
old_location
)
course_item
=
store
.
get_item
(
old_location
)
if
'application/json'
in
request
.
META
.
get
(
'HTTP_ACCEPT'
,
'application/json'
):
if
request
.
method
==
'GET'
:
raise
NotImplementedError
(
'coming soon'
)
else
:
if
'tabs'
in
request
.
json
:
def
get_location_for_tab
(
tab
):
""" Returns the location (old-style) for a tab. """
return
loc_mapper
()
.
translate_locator_to_location
(
BlockUsageLocator
(
tab
))
tabs
=
request
.
json
[
'tabs'
]
# get list of existing static tabs in course
# get list of existing static tabs in course
# make sure they are the same lengths (i.e. the number of passed in tabs equals the number
# make sure they are the same lengths (i.e. the number of passed in tabs equals the number
# that we know about) otherwise we can drop some!
# that we know about) otherwise we will inadvertently drop some!
existing_static_tabs
=
[
t
for
t
in
course_item
.
tabs
if
t
[
'type'
]
==
'static_tab'
]
existing_static_tabs
=
[
t
for
t
in
course
.
tabs
if
t
[
'type'
]
==
'static_tab'
]
if
len
(
existing_static_tabs
)
!=
len
(
tabs
):
if
len
(
existing_static_tabs
)
!=
len
(
tabs
):
return
HttpResponseBadRequest
()
return
JsonResponse
(
{
"error"
:
"number of tabs must be {}"
.
format
(
len
(
existing_static_tabs
))},
status
=
400
)
# load all reference tabs, return BadRequest if we can't find any of them
# load all reference tabs, return BadRequest if we can't find any of them
tab_items
=
[]
tab_items
=
[]
for
tab
in
tabs
:
for
tab
in
tabs
:
item
=
modulestore
(
'direct'
)
.
get_item
(
get_location_for_tab
(
tab
))
item
=
modulestore
(
'direct'
)
.
get_item
(
get_location_for_tab
(
tab
))
if
item
is
None
:
if
item
is
None
:
return
HttpResponseBadRequest
()
return
JsonResponse
(
{
"error"
:
"no tab for found location {}"
.
format
(
tab
)},
status
=
400
)
tab_items
.
append
(
item
)
tab_items
.
append
(
item
)
# now just go through the existing course_tabs and re-order the static tabs
# now just go through the existing course_tabs and re-order the static tabs
reordered_tabs
=
[]
reordered_tabs
=
[]
static_tab_idx
=
0
static_tab_idx
=
0
for
tab
in
course
.
tabs
:
for
tab
in
course_item
.
tabs
:
if
tab
[
'type'
]
==
'static_tab'
:
if
tab
[
'type'
]
==
'static_tab'
:
reordered_tabs
.
append
({
'type'
:
'static_tab'
,
reordered_tabs
.
append
(
{
'type'
:
'static_tab'
,
'name'
:
tab_items
[
static_tab_idx
]
.
display_name
,
'name'
:
tab_items
[
static_tab_idx
]
.
display_name
,
'url_slug'
:
tab_items
[
static_tab_idx
]
.
location
.
name
})
'url_slug'
:
tab_items
[
static_tab_idx
]
.
location
.
name
,
}
)
static_tab_idx
+=
1
static_tab_idx
+=
1
else
:
else
:
reordered_tabs
.
append
(
tab
)
reordered_tabs
.
append
(
tab
)
# OK, re-assemble the static tabs in the new order
# OK, re-assemble the static tabs in the new order
course
.
tabs
=
reordered_tabs
course_item
.
tabs
=
reordered_tabs
# Save the data that we've just changed to the underlying
modulestore
(
'direct'
)
.
update_metadata
(
course_item
.
location
,
own_metadata
(
course_item
))
# MongoKeyValueStore before we update the mongo datastore.
return
JsonResponse
()
course
.
save
()
else
:
modulestore
(
'direct'
)
.
update_metadata
(
course
.
location
,
own_metadata
(
course
))
raise
NotImplementedError
(
'Creating or changing tab content is not supported.'
)
# TODO: above two lines are used for the primitive-save case. Maybe factor them out?
elif
request
.
method
==
'GET'
:
# assume html
return
HttpResponse
()
# see tabs have been uninitialized (e.g. supporting courses created before tab support in studio)
@login_required
@ensure_csrf_cookie
def
edit_tabs
(
request
,
org
,
course
,
coursename
):
"Edit tabs"
location
=
[
'i4x'
,
org
,
course
,
'course'
,
coursename
]
store
=
get_modulestore
(
location
)
course_item
=
store
.
get_item
(
location
)
# check that logged in user has permissions to this item
if
not
has_access
(
request
.
user
,
location
):
raise
PermissionDenied
()
# see tabs have been uninitialized (e.g. supporing courses created before tab support in studio)
if
course_item
.
tabs
is
None
or
len
(
course_item
.
tabs
)
==
0
:
if
course_item
.
tabs
is
None
or
len
(
course_item
.
tabs
)
==
0
:
initialize_course_tabs
(
course_item
)
initialize_course_tabs
(
course_item
)
...
@@ -121,7 +133,7 @@ def edit_tabs(request, org, course, coursename):
...
@@ -121,7 +133,7 @@ def edit_tabs(request, org, course, coursename):
static_tabs
=
[]
static_tabs
=
[]
for
static_tab_ref
in
static_tabs_refs
:
for
static_tab_ref
in
static_tabs_refs
:
static_tab_loc
=
Location
(
location
)
.
_
replace
(
category
=
'static_tab'
,
name
=
static_tab_ref
[
'url_slug'
])
static_tab_loc
=
old_location
.
replace
(
category
=
'static_tab'
,
name
=
static_tab_ref
[
'url_slug'
])
static_tabs
.
append
(
modulestore
(
'direct'
)
.
get_item
(
static_tab_loc
))
static_tabs
.
append
(
modulestore
(
'direct'
)
.
get_item
(
static_tab_loc
))
components
=
[
components
=
[
...
@@ -132,15 +144,13 @@ def edit_tabs(request, org, course, coursename):
...
@@ -132,15 +144,13 @@ def edit_tabs(request, org, course, coursename):
in
static_tabs
in
static_tabs
]
]
course_locator
=
loc_mapper
()
.
translate_location
(
course_item
.
location
.
course_id
,
course_item
.
location
,
False
,
True
)
return
render_to_response
(
'edit-tabs.html'
,
{
return
render_to_response
(
'edit-tabs.html'
,
{
'context_course'
:
course_item
,
'context_course'
:
course_item
,
'components'
:
components
,
'components'
:
components
,
'locator'
:
course_
locator
'course_locator'
:
locator
})
})
else
:
return
HttpResponseNotFound
()
# "primitive" tab edit functions driven by the command line.
# "primitive" tab edit functions driven by the command line.
...
@@ -164,7 +174,7 @@ def primitive_delete(course, num):
...
@@ -164,7 +174,7 @@ def primitive_delete(course, num):
# Note for future implementations: if you delete a static_tab, then Chris Dodge
# Note for future implementations: if you delete a static_tab, then Chris Dodge
# points out that there's other stuff to delete beyond this element.
# points out that there's other stuff to delete beyond this element.
# This code happens to not delete static_tab so it doesn't come up.
# This code happens to not delete static_tab so it doesn't come up.
primitive_save
(
course
)
modulestore
(
'direct'
)
.
update_metadata
(
course
.
location
,
own_metadata
(
course
)
)
def
primitive_insert
(
course
,
num
,
tab_type
,
name
):
def
primitive_insert
(
course
,
num
,
tab_type
,
name
):
...
@@ -173,11 +183,5 @@ def primitive_insert(course, num, tab_type, name):
...
@@ -173,11 +183,5 @@ def primitive_insert(course, num, tab_type, name):
new_tab
=
{
u'type'
:
unicode
(
tab_type
),
u'name'
:
unicode
(
name
)}
new_tab
=
{
u'type'
:
unicode
(
tab_type
),
u'name'
:
unicode
(
name
)}
tabs
=
course
.
tabs
tabs
=
course
.
tabs
tabs
.
insert
(
num
,
new_tab
)
tabs
.
insert
(
num
,
new_tab
)
primitive_save
(
course
)
def
primitive_save
(
course
):
"Saves the course back to modulestore."
# This code copied from reorder_static_tabs above
course
.
save
()
modulestore
(
'direct'
)
.
update_metadata
(
course
.
location
,
own_metadata
(
course
))
modulestore
(
'direct'
)
.
update_metadata
(
course
.
location
,
own_metadata
(
course
))
cms/static/coffee/spec/main.coffee
View file @
3f44ff2f
...
@@ -197,7 +197,8 @@ define([
...
@@ -197,7 +197,8 @@ define([
"js/spec/transcripts/videolist_spec"
,
"js/spec/transcripts/message_manager_spec"
,
"js/spec/transcripts/videolist_spec"
,
"js/spec/transcripts/message_manager_spec"
,
"js/spec/transcripts/file_uploader_spec"
,
"js/spec/transcripts/file_uploader_spec"
,
"js/spec/utils/module_spec"
"js/spec/utils/module_spec"
,
"js/spec/models/explicit_url_spec"
# these tests are run separate in the cms-squire suite, due to process
# these tests are run separate in the cms-squire suite, due to process
# isolation issues with Squire.js
# isolation issues with Squire.js
...
...
cms/static/coffee/src/views/tabs.coffee
View file @
3f44ff2f
...
@@ -37,14 +37,17 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views
...
@@ -37,14 +37,17 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views
analytics
.
track
"Reordered Static Pages"
,
analytics
.
track
"Reordered Static Pages"
,
course
:
course_location_analytics
course
:
course_location_analytics
saving
=
new
NotificationView
.
Mini
({
title
:
gettext
(
"Saving…"
)})
saving
.
show
()
$
.
ajax
({
$
.
ajax
({
type
:
'POST'
,
type
:
'POST'
,
url
:
'/reorder_static_tabs'
,
url
:
@
model
.
url
()
,
data
:
JSON
.
stringify
({
data
:
JSON
.
stringify
({
tabs
:
tabs
tabs
:
tabs
}),
}),
contentType
:
'application/json'
contentType
:
'application/json'
})
})
.
success
(
=>
saving
.
hide
())
addNewTab
:
(
event
)
=>
addNewTab
:
(
event
)
=>
event
.
preventDefault
()
event
.
preventDefault
()
...
...
cms/static/js/models/explicit_url.js
0 → 100644
View file @
3f44ff2f
/**
* A model that simply allows the update URL to be passed
* in as an argument.
*/
define
([
"backbone"
],
function
(
Backbone
){
return
Backbone
.
Model
.
extend
({
defaults
:
{
"explicit_url"
:
""
},
url
:
function
()
{
return
this
.
get
(
"explicit_url"
);
}
});
});
cms/static/js/spec/models/explicit_url_spec.js
0 → 100644
View file @
3f44ff2f
define
([
'js/models/explicit_url'
],
function
(
Model
)
{
describe
(
'Model '
,
function
()
{
it
(
'allows url to be passed in constructor'
,
function
()
{
expect
(
new
Model
({
'explicit_url'
:
'/fancy/url'
}).
url
()).
toBe
(
'/fancy/url'
);
});
it
(
'returns empty string if url not set'
,
function
()
{
expect
(
new
Model
().
url
()).
toBe
(
''
);
});
});
}
);
cms/templates/edit-tabs.html
View file @
3f44ff2f
...
@@ -9,12 +9,15 @@
...
@@ -9,12 +9,15 @@
<
%
block
name=
"jsextra"
>
<
%
block
name=
"jsextra"
>
<script
type=
'text/javascript'
>
<script
type=
'text/javascript'
>
require
([
"backbone"
,
"coffee/src/views/tabs"
],
function
(
Backbone
,
TabsEditView
)
{
require
([
"js/models/explicit_url"
,
"coffee/src/views/tabs"
],
function
(
TabsModel
,
TabsEditView
)
{
var
model
=
new
TabsModel
({
id
:
"${course_locator}"
,
explicit_url
:
"${course_locator.url_reverse('tabs')}"
});
new
TabsEditView
({
new
TabsEditView
({
el
:
$
(
'.main-wrapper'
),
el
:
$
(
'.main-wrapper'
),
model
:
new
Backbone
.
Model
({
model
:
model
,
id
:
'${locator}'
}),
mast
:
$
(
'.wrapper-mast'
)
mast
:
$
(
'.wrapper-mast'
)
});
});
});
});
...
...
cms/templates/widgets/header.html
View file @
3f44ff2f
...
@@ -16,13 +16,14 @@
...
@@ -16,13 +16,14 @@
<
%
<
%
ctx_loc =
context_course.location
ctx_loc =
context_course.location
location =
loc_mapper().translate_location(ctx_loc.course_id,
ctx_loc
,
False
,
True
)
location =
loc_mapper().translate_location(ctx_loc.course_id,
ctx_loc
,
False
,
True
)
index_url =
location.url_reverse('course/')
index_url =
location.url_reverse('course')
checklists_url =
location.url_reverse('checklists/')
checklists_url =
location.url_reverse('checklists')
course_team_url =
location.url_reverse('course_team/')
course_team_url =
location.url_reverse('course_team')
assets_url =
location.url_reverse('assets/')
assets_url =
location.url_reverse('assets')
import_url =
location.url_reverse('import/')
import_url =
location.url_reverse('import')
course_info_url =
location.url_reverse('course_info/')
course_info_url =
location.url_reverse('course_info')
export_url =
location.url_reverse('export/',
'')
export_url =
location.url_reverse('export')
tabs_url =
location.url_reverse('tabs')
%
>
%
>
<h2
class=
"info-course"
>
<h2
class=
"info-course"
>
<span
class=
"sr"
>
${_("Current Course:")}
</span>
<span
class=
"sr"
>
${_("Current Course:")}
</span>
...
@@ -48,7 +49,7 @@
...
@@ -48,7 +49,7 @@
<a
href=
"${course_info_url}"
>
${_("Updates")}
</a>
<a
href=
"${course_info_url}"
>
${_("Updates")}
</a>
</li>
</li>
<li
class=
"nav-item nav-course-courseware-pages"
>
<li
class=
"nav-item nav-course-courseware-pages"
>
<a
href=
"${
reverse('edit_tabs', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))
}"
>
${_("Static Pages")}
</a>
<a
href=
"${
tabs_url
}"
>
${_("Static Pages")}
</a>
</li>
</li>
<li
class=
"nav-item nav-course-courseware-uploads"
>
<li
class=
"nav-item nav-course-courseware-uploads"
>
<a
href=
"${assets_url}"
>
${_("Files
&
Uploads")}
</a>
<a
href=
"${assets_url}"
>
${_("Files
&
Uploads")}
</a>
...
...
cms/urls.py
View file @
3f44ff2f
...
@@ -25,7 +25,6 @@ urlpatterns = patterns('', # nopep8
...
@@ -25,7 +25,6 @@ urlpatterns = patterns('', # nopep8
url
(
r'^create_draft$'
,
'contentstore.views.create_draft'
,
name
=
'create_draft'
),
url
(
r'^create_draft$'
,
'contentstore.views.create_draft'
,
name
=
'create_draft'
),
url
(
r'^publish_draft$'
,
'contentstore.views.publish_draft'
,
name
=
'publish_draft'
),
url
(
r'^publish_draft$'
,
'contentstore.views.publish_draft'
,
name
=
'publish_draft'
),
url
(
r'^unpublish_unit$'
,
'contentstore.views.unpublish_unit'
,
name
=
'unpublish_unit'
),
url
(
r'^unpublish_unit$'
,
'contentstore.views.unpublish_unit'
,
name
=
'unpublish_unit'
),
url
(
r'^reorder_static_tabs'
,
'contentstore.views.reorder_static_tabs'
,
name
=
'reorder_static_tabs'
),
url
(
r'^preview/xblock/(?P<usage_id>.*?)/handler/(?P<handler>[^/]*)(?:/(?P<suffix>[^/]*))?$'
,
url
(
r'^preview/xblock/(?P<usage_id>.*?)/handler/(?P<handler>[^/]*)(?:/(?P<suffix>[^/]*))?$'
,
'contentstore.views.preview_handler'
,
name
=
'preview_handler'
),
'contentstore.views.preview_handler'
,
name
=
'preview_handler'
),
...
@@ -48,9 +47,6 @@ urlpatterns = patterns('', # nopep8
...
@@ -48,9 +47,6 @@ urlpatterns = patterns('', # nopep8
url
(
r'^(?P<org>[^/]+)/(?P<course>[^/]+)/(?P<category>[^/]+)/(?P<name>[^/]+)/gradeas.*$'
,
url
(
r'^(?P<org>[^/]+)/(?P<course>[^/]+)/(?P<category>[^/]+)/(?P<name>[^/]+)/gradeas.*$'
,
'contentstore.views.assignment_type_update'
,
name
=
'assignment_type_update'
),
'contentstore.views.assignment_type_update'
,
name
=
'assignment_type_update'
),
url
(
r'^edit_tabs/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$'
,
'contentstore.views.edit_tabs'
,
name
=
'edit_tabs'
),
url
(
r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)$'
,
url
(
r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)$'
,
'contentstore.views.textbook_index'
,
name
=
'textbook_index'
),
'contentstore.views.textbook_index'
,
name
=
'textbook_index'
),
url
(
r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)/new$'
,
url
(
r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)/new$'
,
...
@@ -111,6 +107,7 @@ urlpatterns += patterns(
...
@@ -111,6 +107,7 @@ urlpatterns += patterns(
url
(
r'(?ix)^import_status/{}/(?P<filename>.+)$'
.
format
(
parsers
.
URL_RE_SOURCE
),
'import_status_handler'
),
url
(
r'(?ix)^import_status/{}/(?P<filename>.+)$'
.
format
(
parsers
.
URL_RE_SOURCE
),
'import_status_handler'
),
url
(
r'(?ix)^export/{}$'
.
format
(
parsers
.
URL_RE_SOURCE
),
'export_handler'
),
url
(
r'(?ix)^export/{}$'
.
format
(
parsers
.
URL_RE_SOURCE
),
'export_handler'
),
url
(
r'(?ix)^xblock($|/){}$'
.
format
(
parsers
.
URL_RE_SOURCE
),
'xblock_handler'
),
url
(
r'(?ix)^xblock($|/){}$'
.
format
(
parsers
.
URL_RE_SOURCE
),
'xblock_handler'
),
url
(
r'(?ix)^tabs/{}$'
.
format
(
parsers
.
URL_RE_SOURCE
),
'tabs_handler'
),
)
)
js_info_dict
=
{
js_info_dict
=
{
...
...
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