Commit 0d349037 by David Baumgold

Refactor textbooks to use locator URLs

STUD-945
parent f7d86bff
......@@ -1659,14 +1659,7 @@ class ContentStoreTest(ModuleStoreTestCase):
test_get_html('settings/details')
test_get_html('settings/grading')
test_get_html('settings/advanced')
# textbook index
resp = self.client.get_html(reverse('textbook_index',
kwargs={'org': loc.org,
'course': loc.course,
'name': loc.name}))
self.assertEqual(resp.status_code, 200)
_test_no_locations(self, resp)
test_get_html('textbooks')
# go look at a subsection page
subsection_location = loc.replace(category='sequential', name='test_sequence')
......
......@@ -14,11 +14,7 @@ class TextbookIndexTestCase(CourseTestCase):
def setUp(self):
"Set the URL for tests"
super(TextbookIndexTestCase, self).setUp()
self.url = reverse('textbook_index', kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
})
self.url = self.course_locator.url_reverse('textbooks')
def test_view_index(self):
"Basic check that the textbook index page responds correctly"
......@@ -77,13 +73,13 @@ class TextbookIndexTestCase(CourseTestCase):
obj = json.loads(resp.content)
self.assertEqual(content, obj)
def test_view_index_xhr_post(self):
def test_view_index_xhr_put(self):
"Check that you can save information to the server"
textbooks = [
{"tab_title": "Hi, mom!"},
{"tab_title": "Textbook 2"},
]
resp = self.client.post(
resp = self.client.put(
self.url,
data=json.dumps(textbooks),
content_type="application/json",
......@@ -102,9 +98,9 @@ class TextbookIndexTestCase(CourseTestCase):
no_ids.append(textbook)
self.assertEqual(no_ids, textbooks)
def test_view_index_xhr_post_invalid(self):
def test_view_index_xhr_put_invalid(self):
"Check that you can't save invalid JSON"
resp = self.client.post(
resp = self.client.put(
self.url,
data="invalid",
content_type="application/json",
......@@ -122,11 +118,7 @@ class TextbookCreateTestCase(CourseTestCase):
def setUp(self):
"Set up a url and some textbook content for tests"
super(TextbookCreateTestCase, self).setUp()
self.url = reverse('create_textbook', kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
})
self.url = self.course_locator.url_reverse('textbooks')
self.textbook = {
"tab_title": "Economics",
"chapters": {
......@@ -151,15 +143,6 @@ class TextbookCreateTestCase(CourseTestCase):
del textbook["id"]
self.assertEqual(self.textbook, textbook)
def test_get(self):
"Test that GET is not allowed"
resp = self.client.get(
self.url,
HTTP_ACCEPT="application/json",
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(resp.status_code, 405)
def test_valid_id(self):
"Textbook IDs must begin with a number; try a valid one"
self.textbook["id"] = "7x5"
......@@ -188,12 +171,12 @@ class TextbookCreateTestCase(CourseTestCase):
self.assertNotIn("Location", resp)
class TextbookByIdTestCase(CourseTestCase):
"Test cases for the `textbook_by_id` view"
class TextbookDetailTestCase(CourseTestCase):
"Test cases for the `textbook_detail_handler` view"
def setUp(self):
"Set some useful content and URLs for tests"
super(TextbookByIdTestCase, self).setUp()
super(TextbookDetailTestCase, self).setUp()
self.textbook1 = {
"tab_title": "Economics",
"id": 1,
......@@ -202,12 +185,7 @@ class TextbookByIdTestCase(CourseTestCase):
"url": "/a/b/c/ch1.pdf",
}
}
self.url1 = reverse('textbook_by_id', kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
'tid': 1,
})
self.url1 = self.course_locator.url_reverse("textbooks", "1")
self.textbook2 = {
"tab_title": "Algebra",
"id": 2,
......@@ -216,24 +194,14 @@ class TextbookByIdTestCase(CourseTestCase):
"url": "/a/b/ch11.pdf",
}
}
self.url2 = reverse('textbook_by_id', kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
'tid': 2,
})
self.url2 = self.course_locator.url_reverse("textbooks", "2")
self.course.pdf_textbooks = [self.textbook1, self.textbook2]
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
self.course.save()
self.store = get_modulestore(self.course.location)
self.store.update_metadata(self.course.location, own_metadata(self.course))
self.url_nonexist = reverse('textbook_by_id', kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
'tid': 20,
})
self.url_nonexist = self.course_locator.url_reverse("textbooks", "20")
def test_get_1(self):
"Get the first textbook"
......@@ -275,12 +243,7 @@ class TextbookByIdTestCase(CourseTestCase):
"url": "supercool.pdf",
"id": "1supercool",
}
url = reverse("textbook_by_id", kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
'tid': "1supercool",
})
url = self.course_locator.url_reverse("textbooks", "1supercool")
resp = self.client.post(
url,
data=json.dumps(textbook),
......
......@@ -57,6 +57,7 @@ class AjaxEnabledTestClient(Client):
"""
return self.get(path, data or {}, follow, HTTP_ACCEPT="application/json", **extra)
@override_settings(MODULESTORE=TEST_MODULESTORE)
class CourseTestCase(ModuleStoreTestCase):
def setUp(self):
......@@ -111,7 +112,7 @@ class CourseTestCase(ModuleStoreTestCase):
client = Client()
client.login(username=uname, password=password)
return client, nonstaff
def populateCourse(self):
"""
Add 2 chapters, 4 sections, 8 verticals, 16 problems to self.course (branching 2)
......
......@@ -38,7 +38,7 @@ from models.settings.course_metadata import CourseMetadata
from auth.authz import create_all_course_groups, is_user_in_creator_group
from util.json_request import expect_json
from .access import has_access, get_location_and_verify_access
from .access import has_access
from .tabs import initialize_course_tabs
from .component import (
OPEN_ENDED_COMPONENT_TYPES, NOTE_COMPONENT_TYPES,
......@@ -57,8 +57,20 @@ __all__ = ['course_info_handler', 'course_handler', 'course_info_update_handler'
'settings_handler',
'grading_handler',
'advanced_settings_handler',
'textbook_index', 'textbook_by_id',
'create_textbook']
'textbooks_list_handler', 'textbooks_detail_handler']
def _get_locator_and_course(course_id, branch, version_guid, usage_id, user, depth=0):
"""
Internal method used to calculate and return the locator and course module
for the view functions in this file.
"""
locator = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=usage_id)
if not has_access(user, locator):
raise PermissionDenied()
course_location = loc_mapper().translate_locator_to_location(locator)
course_module = modulestore().get_item(course_location, depth=depth)
return locator, course_module
# pylint: disable=unused-argument
......@@ -168,17 +180,10 @@ def course_index(request, course_id, branch, version_guid, block):
org, course, name: Attributes of the Location for the item to edit
"""
location = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
# TODO: when converting to split backend, if location does not have a usage_id,
# we'll need to get the course's root block_id
if not has_access(request.user, location):
raise PermissionDenied()
old_location = loc_mapper().translate_locator_to_location(location)
lms_link = get_lms_link_for_item(old_location)
course = modulestore().get_item(old_location, depth=3)
locator, course = _get_locator_and_course(
course_id, branch, version_guid, block, request.user, depth=3
)
lms_link = get_lms_link_for_item(course.location)
sections = course.get_children()
return render_to_response('overview.html', {
......@@ -186,9 +191,9 @@ def course_index(request, course_id, branch, version_guid, block):
'lms_link': lms_link,
'sections': sections,
'course_graders': json.dumps(
CourseGradingModel.fetch(location).graders
CourseGradingModel.fetch(locator).graders
),
'parent_locator': location,
'parent_locator': locator,
'new_section_category': 'chapter',
'new_subsection_category': 'sequential',
'new_unit_category': 'vertical',
......@@ -314,22 +319,18 @@ def course_info_handler(request, tag=None, course_id=None, branch=None, version_
GET
html: return html for editing the course info handouts and updates.
"""
course_location = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
course_old_location = loc_mapper().translate_locator_to_location(course_location)
__, course_module = _get_locator_and_course(
course_id, branch, version_guid, block, request.user
)
if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'):
if not has_access(request.user, course_location):
raise PermissionDenied()
course_module = modulestore().get_item(course_old_location)
handouts_old_location = course_old_location.replace(category='course_info', name='handouts')
handouts_old_location = course_module.location.replace(category='course_info', name='handouts')
handouts_locator = loc_mapper().translate_location(
course_old_location.course_id, handouts_old_location, False, True
course_module.location.course_id, handouts_old_location, False, True
)
update_location = course_old_location.replace(category='course_info', name='updates')
update_location = course_module.location.replace(category='course_info', name='updates')
update_locator = loc_mapper().translate_location(
course_old_location.course_id, update_location, False, True
course_module.location.course_id, update_location, False, True
)
return render_to_response(
......@@ -338,7 +339,7 @@ def course_info_handler(request, tag=None, course_id=None, branch=None, version_
'context_course': course_module,
'updates_url': update_locator.url_reverse('course_info_update/'),
'handouts_locator': handouts_locator,
'base_asset_url': StaticContent.get_base_url_path_for_course_assets(course_old_location) + '/'
'base_asset_url': StaticContent.get_base_url_path_for_course_assets(course_module.location) + '/'
}
)
else:
......@@ -407,20 +408,16 @@ def settings_handler(request, tag=None, course_id=None, branch=None, version_gui
PUT
json: update the Course and About xblocks through the CourseDetails model
"""
locator = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
if not has_access(request.user, locator):
raise PermissionDenied()
locator, course_module = _get_locator_and_course(
course_id, branch, version_guid, block, request.user
)
if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':
course_old_location = loc_mapper().translate_locator_to_location(locator)
course_module = modulestore().get_item(course_old_location)
upload_asset_url = locator.url_reverse('assets/')
return render_to_response('settings.html', {
'context_course': course_module,
'course_locator': locator,
'lms_link_for_about_page': utils.get_lms_link_for_about_page(course_old_location),
'lms_link_for_about_page': utils.get_lms_link_for_about_page(course_module.location),
'course_image_url': utils.course_image_url(course_module),
'details_url': locator.url_reverse('/settings/details/'),
'about_page_editable': not settings.FEATURES.get(
......@@ -457,13 +454,11 @@ def grading_handler(request, tag=None, course_id=None, branch=None, version_guid
json no grader_index: update the Course through the CourseGrading model
json w/ grader_index: create or update the specific grader (create if index out of range)
"""
locator = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
if not has_access(request.user, locator):
raise PermissionDenied()
locator, course_module = _get_locator_and_course(
course_id, branch, version_guid, block, request.user
)
if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':
course_old_location = loc_mapper().translate_locator_to_location(locator)
course_module = modulestore().get_item(course_old_location)
course_details = CourseGradingModel.fetch(locator)
return render_to_response('settings_graders.html', {
......@@ -514,8 +509,8 @@ def _config_course_advanced_components(request, course_module):
filter_tabs = True # Exceptional conditions will pull this to False
if ADVANCED_COMPONENT_POLICY_KEY in request.json: # Maps tab types to components
tab_component_map = {
'open_ended':OPEN_ENDED_COMPONENT_TYPES,
'notes':NOTE_COMPONENT_TYPES,
'open_ended': OPEN_ENDED_COMPONENT_TYPES,
'notes': NOTE_COMPONENT_TYPES,
}
# Check to see if the user instantiated any notes or open ended
# components
......@@ -565,13 +560,9 @@ def advanced_settings_handler(request, course_id=None, branch=None, version_guid
metadata dicts. The dict can include a "unsetKeys" entry which is a list
of keys whose values to unset: i.e., revert to default
"""
locator = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
if not has_access(request.user, locator):
raise PermissionDenied()
course_old_location = loc_mapper().translate_locator_to_location(locator)
course_module = modulestore().get_item(course_old_location)
locator, course_module = _get_locator_and_course(
course_id, branch, version_guid, block, request.user
)
if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':
return render_to_response('settings_advanced.html', {
......@@ -657,113 +648,109 @@ def assign_textbook_id(textbook, used_ids=()):
return tid
@require_http_methods(("GET", "POST", "PUT"))
@login_required
@ensure_csrf_cookie
def textbook_index(request, org, course, name):
def textbooks_list_handler(request, tag=None, course_id=None, branch=None, version_guid=None, block=None):
"""
Display an editable textbook overview.
A RESTful handler for textbook collections.
org, course, name: Attributes of the Location for the item to edit
GET
html: return textbook list page (Backbone application)
json: return JSON representation of all textbooks in this course
POST
json: create a new textbook for this course
PUT
json: overwrite all textbooks in the course with the given list
"""
location = get_location_and_verify_access(request, org, course, name)
store = get_modulestore(location)
course_module = store.get_item(location, depth=3)
locator, course = _get_locator_and_course(
course_id, branch, version_guid, block, request.user
)
store = get_modulestore(course.location)
if request.is_ajax():
if request.method == 'GET':
return JsonResponse(course_module.pdf_textbooks)
# can be either and sometimes django is rewriting one to the other:
elif request.method in ('POST', 'PUT'):
try:
textbooks = validate_textbooks_json(request.body)
except TextbookValidationError as err:
return JsonResponse({"error": err.message}, status=400)
tids = set(t["id"] for t in textbooks if "id" in t)
for textbook in textbooks:
if not "id" in textbook:
tid = assign_textbook_id(textbook, tids)
textbook["id"] = tid
tids.add(tid)
if not any(tab['type'] == 'pdf_textbooks' for tab in course_module.tabs):
course_module.tabs.append({"type": "pdf_textbooks"})
course_module.pdf_textbooks = textbooks
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
course_module.save()
store.update_metadata(
course_module.location,
own_metadata(course_module)
)
return JsonResponse(course_module.pdf_textbooks)
else:
new_loc = loc_mapper().translate_location(location.course_id, location, False, True)
upload_asset_url = new_loc.url_reverse('assets/', '')
textbook_url = reverse('textbook_index', kwargs={
'org': org,
'course': course,
'name': name,
})
if not "application/json" in request.META.get('HTTP_ACCEPT', 'text/html'):
# return HTML page
upload_asset_url = locator.url_reverse('assets/', '')
textbook_url = locator.url_reverse('/textbooks')
return render_to_response('textbooks.html', {
'context_course': course_module,
'course': course_module,
'context_course': course,
'course': course,
'upload_asset_url': upload_asset_url,
'textbook_url': textbook_url,
})
# from here on down, we know the client has requested JSON
if request.method == 'GET':
return JsonResponse(course.pdf_textbooks)
elif request.method == 'PUT':
try:
textbooks = validate_textbooks_json(request.body)
except TextbookValidationError as err:
return JsonResponse({"error": err.message}, status=400)
@require_POST
@login_required
@ensure_csrf_cookie
def create_textbook(request, org, course, name):
"""
JSON API endpoint for creating a textbook. Used by the Backbone application.
"""
location = get_location_and_verify_access(request, org, course, name)
store = get_modulestore(location)
course_module = store.get_item(location, depth=0)
tids = set(t["id"] for t in textbooks if "id" in t)
for textbook in textbooks:
if not "id" in textbook:
tid = assign_textbook_id(textbook, tids)
textbook["id"] = tid
tids.add(tid)
try:
textbook = validate_textbook_json(request.body)
except TextbookValidationError as err:
return JsonResponse({"error": err.message}, status=400)
if not textbook.get("id"):
tids = set(t["id"] for t in course_module.pdf_textbooks if "id" in t)
textbook["id"] = assign_textbook_id(textbook, tids)
existing = course_module.pdf_textbooks
existing.append(textbook)
course_module.pdf_textbooks = existing
if not any(tab['type'] == 'pdf_textbooks' for tab in course_module.tabs):
tabs = course_module.tabs
tabs.append({"type": "pdf_textbooks"})
course_module.tabs = tabs
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
course_module.save()
store.update_metadata(course_module.location, own_metadata(course_module))
resp = JsonResponse(textbook, status=201)
resp["Location"] = reverse("textbook_by_id", kwargs={
'org': org,
'course': course,
'name': name,
'tid': textbook["id"],
})
return resp
if not any(tab['type'] == 'pdf_textbooks' for tab in course.tabs):
course.tabs.append({"type": "pdf_textbooks"})
course.pdf_textbooks = textbooks
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
course.save()
store.update_metadata(
course.location,
own_metadata(course)
)
return JsonResponse(course.pdf_textbooks)
elif request.method == 'POST':
# create a new textbook for the course
try:
textbook = validate_textbook_json(request.body)
except TextbookValidationError as err:
return JsonResponse({"error": err.message}, status=400)
if not textbook.get("id"):
tids = set(t["id"] for t in course.pdf_textbooks if "id" in t)
textbook["id"] = assign_textbook_id(textbook, tids)
existing = course.pdf_textbooks
existing.append(textbook)
course.pdf_textbooks = existing
if not any(tab['type'] == 'pdf_textbooks' for tab in course.tabs):
tabs = course.tabs
tabs.append({"type": "pdf_textbooks"})
course.tabs = tabs
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
course.save()
store.update_metadata(course.location, own_metadata(course))
resp = JsonResponse(textbook, status=201)
resp["Location"] = locator.url_reverse('textbooks', textbook["id"])
return resp
@login_required
@ensure_csrf_cookie
@require_http_methods(("GET", "POST", "PUT", "DELETE"))
def textbook_by_id(request, org, course, name, tid):
def textbooks_detail_handler(request, tid, tag=None, course_id=None, branch=None, version_guid=None, block=None):
"""
JSON API endpoint for manipulating a textbook via its internal ID.
Used by the Backbone application.
GET
json: return JSON representation of textbook
POST or PUT
json: update textbook based on provided information
DELETE
json: remove textbook
"""
location = get_location_and_verify_access(request, org, course, name)
store = get_modulestore(location)
course_module = store.get_item(location, depth=3)
matching_id = [tb for tb in course_module.pdf_textbooks
__, course = _get_locator_and_course(
course_id, branch, version_guid, block, request.user
)
store = get_modulestore(course.location)
matching_id = [tb for tb in course.pdf_textbooks
if str(tb.get("id")) == str(tid)]
if matching_id:
textbook = matching_id[0]
......@@ -782,32 +769,32 @@ def textbook_by_id(request, org, course, name, tid):
return JsonResponse({"error": err.message}, status=400)
new_textbook["id"] = tid
if textbook:
i = course_module.pdf_textbooks.index(textbook)
new_textbooks = course_module.pdf_textbooks[0:i]
i = course.pdf_textbooks.index(textbook)
new_textbooks = course.pdf_textbooks[0:i]
new_textbooks.append(new_textbook)
new_textbooks.extend(course_module.pdf_textbooks[i + 1:])
course_module.pdf_textbooks = new_textbooks
new_textbooks.extend(course.pdf_textbooks[i + 1:])
course.pdf_textbooks = new_textbooks
else:
course_module.pdf_textbooks.append(new_textbook)
course.pdf_textbooks.append(new_textbook)
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
course_module.save()
course.save()
store.update_metadata(
course_module.location,
own_metadata(course_module)
course.location,
own_metadata(course)
)
return JsonResponse(new_textbook, status=201)
elif request.method == 'DELETE':
if not textbook:
return JsonResponse(status=404)
i = course_module.pdf_textbooks.index(textbook)
new_textbooks = course_module.pdf_textbooks[0:i]
new_textbooks.extend(course_module.pdf_textbooks[i + 1:])
course_module.pdf_textbooks = new_textbooks
course_module.save()
i = course.pdf_textbooks.index(textbook)
new_textbooks = course.pdf_textbooks[0:i]
new_textbooks.extend(course.pdf_textbooks[i + 1:])
course.pdf_textbooks = new_textbooks
course.save()
store.update_metadata(
course_module.location,
own_metadata(course_module)
course.location,
own_metadata(course)
)
return JsonResponse()
......
require ["jquery", "backbone", "coffee/src/main", "sinon", "jasmine-stealth"],
require ["jquery", "backbone", "coffee/src/main", "sinon", "jasmine-stealth", "jquery.cookie"],
($, Backbone, main, sinon) ->
describe "CMS", ->
it "should initialize URL", ->
......
......@@ -11,6 +11,10 @@ define ["backbone", "js/models/textbook", "js/collections/textbook", "js/models/
beforeEach ->
main()
@model = new Textbook()
CMS.URL.TEXTBOOKS = "/textbooks"
afterEach ->
delete CMS.URL.TEXTBOOKS
describe "Basic", ->
it "should have an empty name by default", ->
......@@ -28,8 +32,9 @@ define ["backbone", "js/models/textbook", "js/collections/textbook", "js/models/
it "should be empty by default", ->
expect(@model.isEmpty()).toBeTruthy()
it "should have a URL set", ->
expect(@model.url()).toBeTruthy()
it "should have a URL root", ->
urlRoot = _.result(@model, 'urlRoot')
expect(urlRoot).toBeTruthy()
it "should be able to reset itself", ->
@model.set("name", "foobar")
......@@ -135,12 +140,8 @@ define ["backbone", "js/models/textbook", "js/collections/textbook", "js/models/
delete CMS.URL.TEXTBOOKS
it "should have a url set", ->
expect(@collection.url()).toEqual("/textbooks")
it "can call save", ->
spyOn(@collection, "sync")
@collection.save()
expect(@collection.sync).toHaveBeenCalledWith("update", @collection, undefined)
url = _.result(@collection, 'url')
expect(url).toEqual("/textbooks")
describe "Chapter model", ->
......
......@@ -81,9 +81,11 @@ define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js
@savingSpies = spyOnConstructor(Notification, "Mini",
["show", "hide"])
@savingSpies.show.andReturn(@savingSpies)
CMS.URL.TEXTBOOKS = "/textbooks"
afterEach ->
@xhr.restore()
delete CMS.URL.TEXTBOOKS
it "should destroy itself on confirmation", ->
@view.render().$(".delete").click()
......
......@@ -2,10 +2,7 @@ define(["backbone", "js/models/textbook"],
function(Backbone, TextbookModel) {
var TextbookCollection = Backbone.Collection.extend({
model: TextbookModel,
url: function() { return CMS.URL.TEXTBOOKS; },
save: function(options) {
return this.sync('update', this, options);
}
url: function() { return CMS.URL.TEXTBOOKS; }
});
return TextbookCollection;
});
define(["backbone", "underscore", "js/models/chapter", "js/collections/chapter", "backbone.associations"],
define(["backbone", "underscore", "js/models/chapter", "js/collections/chapter",
"backbone.associations", "coffee/src/main"],
function(Backbone, _, ChapterModel, ChapterCollection) {
var Textbook = Backbone.AssociatedModel.extend({
......@@ -32,13 +33,7 @@ define(["backbone", "underscore", "js/models/chapter", "js/collections/chapter",
isEmpty: function() {
return !this.get('name') && this.get('chapters').isEmpty();
},
url: function() {
if(this.isNew()) {
return CMS.URL.TEXTBOOKS + "/new";
} else {
return CMS.URL.TEXTBOOKS + "/" + this.id;
}
},
urlRoot: function() { return CMS.URL.TEXTBOOKS; },
parse: function(response) {
var ret = $.extend(true, {}, response);
if("tab_title" in ret && !("name" in ret)) {
......
......@@ -13,13 +13,14 @@
<h1 class="branding"><a href="/"><img src="${static.url("img/logo-edx-studio.png")}" alt="edX Studio" /></a></h1>
% if context_course:
<%
<%
ctx_loc = context_course.location
location = loc_mapper().translate_location(ctx_loc.course_id, ctx_loc, False, True)
index_url = location.url_reverse('course')
checklists_url = location.url_reverse('checklists')
course_team_url = location.url_reverse('course_team')
assets_url = location.url_reverse('assets')
textbooks_url = location.url_reverse('textbooks')
import_url = location.url_reverse('import')
course_info_url = location.url_reverse('course_info')
export_url = location.url_reverse('export')
......@@ -58,7 +59,7 @@
<a href="${assets_url}">${_("Files &amp; Uploads")}</a>
</li>
<li class="nav-item nav-course-courseware-textbooks">
<a href="${reverse('textbook_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">${_("Textbooks")}</a>
<a href="${textbooks_url}">${_("Textbooks")}</a>
</li>
</ul>
</div>
......
......@@ -23,13 +23,6 @@ urlpatterns = patterns('', # nopep8
url(r'^preview/xblock/(?P<usage_id>.*?)/handler/(?P<handler>[^/]*)(?:/(?P<suffix>[^/]*))?$',
'contentstore.views.preview_handler', name='preview_handler'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)$',
'contentstore.views.textbook_index', name='textbook_index'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)/new$',
'contentstore.views.create_textbook', name='create_textbook'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)/(?P<tid>\d[^/]*)$',
'contentstore.views.textbook_by_id', name='textbook_by_id'),
# temporary landing page for a course
url(r'^edge/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$',
'contentstore.views.landing', name='landing'),
......@@ -89,6 +82,8 @@ urlpatterns += patterns(
url(r'(?ix)^settings/details/{}$'.format(parsers.URL_RE_SOURCE), 'settings_handler'),
url(r'(?ix)^settings/grading/{}(/)?(?P<grader_index>\d+)?$'.format(parsers.URL_RE_SOURCE), 'grading_handler'),
url(r'(?ix)^settings/advanced/{}$'.format(parsers.URL_RE_SOURCE), 'advanced_settings_handler'),
url(r'(?ix)^textbooks/{}$'.format(parsers.URL_RE_SOURCE), 'textbooks_list_handler'),
url(r'(?ix)^textbooks/{}/(?P<tid>\d[^/]*)$'.format(parsers.URL_RE_SOURCE), 'textbooks_detail_handler'),
)
js_info_dict = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment