Commit cd868efc by Christina Roberts

Merge pull request #1580 from edx/christina/item

Change delete_item to RESTful URL.
parents f17ab1ec 16766a5e
...@@ -11,6 +11,7 @@ from django.conf import settings ...@@ -11,6 +11,7 @@ from django.conf import settings
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.locator import CourseLocator, Locator from xmodule.modulestore.locator import CourseLocator, Locator
from xmodule.modulestore.django import loc_mapper from xmodule.modulestore.django import loc_mapper
from xmodule.modulestore.exceptions import InvalidLocationError
# define a couple of simple roles, we just need ADMIN and EDITOR now for our purposes # define a couple of simple roles, we just need ADMIN and EDITOR now for our purposes
...@@ -32,11 +33,14 @@ def get_course_groupname_for_role(location, role): ...@@ -32,11 +33,14 @@ def get_course_groupname_for_role(location, role):
# if it exists, then use that one, otherwise use a <role>_<course_id> which contains # if it exists, then use that one, otherwise use a <role>_<course_id> which contains
# more information # more information
groupnames = [] groupnames = []
groupnames.append('{0}_{1}'.format(role, location.course_id)) try:
groupnames.append('{0}_{1}'.format(role, location.course_id))
except InvalidLocationError: # will occur on old locations where location is not of category course
pass
if isinstance(location, Location): if isinstance(location, Location):
groupnames.append('{0}_{1}'.format(role, location.course)) groupnames.append('{0}_{1}'.format(role, location.course))
elif isinstance(location, CourseLocator): elif isinstance(location, CourseLocator):
old_location = loc_mapper().translate_locator_to_location(location) old_location = loc_mapper().translate_locator_to_location(location, get_course=True)
if old_location: if old_location:
groupnames.append('{0}_{1}'.format(role, old_location.course_id)) groupnames.append('{0}_{1}'.format(role, old_location.course_id))
......
...@@ -488,11 +488,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -488,11 +488,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# make sure the parent points to the child object which is to be deleted # make sure the parent points to the child object which is to be deleted
self.assertTrue(sequential.location.url() in chapter.children) self.assertTrue(sequential.location.url() in chapter.children)
self.client.post( location = loc_mapper().translate_location(course_location.course_id, sequential.location, False, True)
reverse('delete_item'), self.client.delete(location.url_reverse('xblock'), {'recurse': True, 'all_versions': True})
json.dumps({'id': sequential.location.url(), 'delete_children': 'true', 'delete_all_versions': 'true'}),
"application/json"
)
found = False found = False
try: try:
...@@ -1640,29 +1637,24 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1640,29 +1637,24 @@ class ContentStoreTest(ModuleStoreTestCase):
kwargs={'location': unit_location.url()})) kwargs={'location': unit_location.url()}))
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
def delete_item(category, name):
""" Helper method for testing the deletion of an xblock item. """
del_loc = loc.replace(category=category, name=name)
del_location = loc_mapper().translate_location(loc.course_id, del_loc, False, True)
resp = self.client.delete(del_location.url_reverse('xblock'))
self.assertEqual(resp.status_code, 204)
# delete a component # delete a component
del_loc = loc.replace(category='html', name='test_html') delete_item(category='html', name='test_html')
resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(resp.status_code, 204)
# delete a unit # delete a unit
del_loc = loc.replace(category='vertical', name='test_vertical') delete_item(category='vertical', name='test_vertical')
resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(resp.status_code, 204)
# delete a unit # delete a unit
del_loc = loc.replace(category='sequential', name='test_sequence') delete_item(category='sequential', name='test_sequence')
resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(resp.status_code, 204)
# delete a chapter # delete a chapter
del_loc = loc.replace(category='chapter', name='chapter_2') delete_item(category='chapter', name='chapter_2')
resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(resp.status_code, 204)
def test_import_into_new_course_id(self): def test_import_into_new_course_id(self):
module_store = modulestore('direct') module_store = modulestore('direct')
......
...@@ -12,7 +12,7 @@ from xmodule.modulestore.django import modulestore ...@@ -12,7 +12,7 @@ from xmodule.modulestore.django import modulestore
class DeleteItem(CourseTestCase): class DeleteItem(CourseTestCase):
"""Tests for '/delete_item' url.""" """Tests for '/xblock' DELETE url."""
def setUp(self): def setUp(self):
""" Creates the test course with a static page in it. """ """ Creates the test course with a static page in it. """
super(DeleteItem, self).setUp() super(DeleteItem, self).setUp()
...@@ -33,11 +33,8 @@ class DeleteItem(CourseTestCase): ...@@ -33,11 +33,8 @@ class DeleteItem(CourseTestCase):
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
# Now delete it. There was a bug that the delete was failing (static tabs do not exist in draft modulestore). # Now delete it. There was a bug that the delete was failing (static tabs do not exist in draft modulestore).
resp = self.client.post( resp_content = json.loads(resp.content)
reverse('delete_item'), resp = self.client.delete(resp_content['update_url'])
resp.content,
"application/json"
)
self.assertEqual(resp.status_code, 204) self.assertEqual(resp.status_code, 204)
......
...@@ -16,6 +16,7 @@ from mitxmako.shortcuts import render_to_response ...@@ -16,6 +16,7 @@ from mitxmako.shortcuts import render_to_response
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.util.date_utils import get_default_time_display from xmodule.util.date_utils import get_default_time_display
from xmodule.modulestore.django import loc_mapper
from xblock.fields import Scope from xblock.fields import Scope
from util.json_request import expect_json, JsonResponse from util.json_request import expect_json, JsonResponse
...@@ -174,6 +175,12 @@ def edit_unit(request, location): ...@@ -174,6 +175,12 @@ def edit_unit(request, location):
course_id=course.location.course_id course_id=course.location.course_id
) )
# Note that the unit_state (draft, public, private) does not match up with the published value
# passed to translate_location. The two concepts are different at this point.
unit_update_url = loc_mapper().translate_location(
course.location.course_id, Location(location), False, True
).url_reverse("xblock", "")
component_templates = defaultdict(list) component_templates = defaultdict(list)
for category in COMPONENT_TYPES: for category in COMPONENT_TYPES:
component_class = load_mixed_class(category) component_class = load_mixed_class(category)
...@@ -238,7 +245,12 @@ def edit_unit(request, location): ...@@ -238,7 +245,12 @@ def edit_unit(request, location):
) )
components = [ components = [
component.location.url() [
component.location.url(),
loc_mapper().translate_location(
course.location.course_id, component.location, False, True
).url_reverse("xblock")
]
for component for component
in item.get_children() in item.get_children()
] ]
...@@ -283,12 +295,11 @@ def edit_unit(request, location): ...@@ -283,12 +295,11 @@ def edit_unit(request, location):
index=index index=index
) )
unit_state = compute_unit_state(item)
return render_to_response('unit.html', { return render_to_response('unit.html', {
'context_course': course, 'context_course': course,
'unit': item, 'unit': item,
'unit_location': location, 'unit_location': location,
'unit_update_url': unit_update_url,
'components': components, 'components': components,
'component_templates': component_templates, 'component_templates': component_templates,
'draft_preview_link': preview_lms_link, 'draft_preview_link': preview_lms_link,
...@@ -300,7 +311,7 @@ def edit_unit(request, location): ...@@ -300,7 +311,7 @@ def edit_unit(request, location):
), ),
'section': containing_section, 'section': containing_section,
'new_unit_category': 'vertical', 'new_unit_category': 'vertical',
'unit_state': unit_state, 'unit_state': compute_unit_state(item),
'published_date': ( 'published_date': (
get_default_time_display(item.published_date) get_default_time_display(item.published_date)
if item.published_date is not None else None if item.published_date is not None else None
......
...@@ -16,16 +16,16 @@ from util.json_request import expect_json, JsonResponse ...@@ -16,16 +16,16 @@ from util.json_request import expect_json, JsonResponse
from ..transcripts_utils import manage_video_subtitles_save from ..transcripts_utils import manage_video_subtitles_save
from ..utils import get_modulestore from ..utils import get_modulestore, get_course_for_item
from .access import has_access from .access import has_access
from .helpers import _xmodule_recurse from .helpers import _xmodule_recurse
from xmodule.x_module import XModuleDescriptor from xmodule.x_module import XModuleDescriptor
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from xmodule.modulestore.locator import CourseLocator, BlockUsageLocator from xmodule.modulestore.locator import BlockUsageLocator
from student.models import CourseEnrollment from student.models import CourseEnrollment
__all__ = ['save_item', 'create_item', 'delete_item', 'orphan'] __all__ = ['save_item', 'create_item', 'orphan', 'xblock_handler']
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -33,6 +33,31 @@ log = logging.getLogger(__name__) ...@@ -33,6 +33,31 @@ log = logging.getLogger(__name__)
DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info'] DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info']
@require_http_methods(("DELETE"))
@login_required
@expect_json
def xblock_handler(request, tag=None, course_id=None, branch=None, version_guid=None, block=None):
"""
The restful handler for xblock requests.
DELETE
json: delete this xblock instance from the course. Supports query parameters "recurse" to delete
all children and "all_versions" to delete from all (mongo) versions.
"""
if request.method == 'DELETE':
location = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
if not has_access(request.user, location):
raise PermissionDenied()
old_location = loc_mapper().translate_locator_to_location(location)
delete_children = bool(request.REQUEST.get('recurse', False))
delete_all_versions = bool(request.REQUEST.get('all_versions', False))
_delete_item_at_location(old_location, delete_children, delete_all_versions)
return JsonResponse()
@login_required @login_required
@expect_json @expect_json
def save_item(request): def save_item(request):
...@@ -171,24 +196,18 @@ def create_item(request): ...@@ -171,24 +196,18 @@ def create_item(request):
if category not in DETACHED_CATEGORIES: if category not in DETACHED_CATEGORIES:
get_modulestore(parent.location).update_children(parent_location, parent.children + [dest_location.url()]) get_modulestore(parent.location).update_children(parent_location, parent.children + [dest_location.url()])
return JsonResponse({'id': dest_location.url()}) locator = loc_mapper().translate_location(
get_course_for_item(parent_location).location.course_id, dest_location, False, True
)
return JsonResponse({'id': dest_location.url(), "update_url": locator.url_reverse("xblock")})
@login_required
@expect_json
def delete_item(request):
"""View for removing items."""
item_location = request.json['id']
item_location = Location(item_location)
# check permissions for this user within this course def _delete_item_at_location(item_location, delete_children=False, delete_all_versions=False):
if not has_access(request.user, item_location): """
raise PermissionDenied() Deletes the item at with the given Location.
# optional parameter to delete all children (default False)
delete_children = request.json.get('delete_children', False)
delete_all_versions = request.json.get('delete_all_versions', False)
It is assumed that course permissions have already been checked.
"""
store = get_modulestore(item_location) store = get_modulestore(item_location)
item = store.get_item(item_location) item = store.get_item(item_location)
...@@ -211,7 +230,6 @@ def delete_item(request): ...@@ -211,7 +230,6 @@ def delete_item(request):
parent.children = children parent.children = children
modulestore('direct').update_children(parent.location, parent.children) modulestore('direct').update_children(parent.location, parent.children)
return JsonResponse()
# pylint: disable=W0613 # pylint: disable=W0613
@login_required @login_required
......
...@@ -12,6 +12,7 @@ from mitxmako.shortcuts import render_to_response ...@@ -12,6 +12,7 @@ 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
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.django import loc_mapper
from ..utils import get_course_for_item, get_modulestore from ..utils import get_course_for_item, get_modulestore
...@@ -117,7 +118,12 @@ def edit_tabs(request, org, course, coursename): ...@@ -117,7 +118,12 @@ def edit_tabs(request, org, course, coursename):
static_tabs.append(modulestore('direct').get_item(static_tab_loc)) static_tabs.append(modulestore('direct').get_item(static_tab_loc))
components = [ components = [
static_tab.location.url() [
static_tab.location.url(),
loc_mapper().translate_location(
course_item.location.course_id, static_tab.location, False, True
).url_reverse("xblock")
]
for static_tab for static_tab
in static_tabs in static_tabs
] ]
......
...@@ -70,6 +70,7 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1", ...@@ -70,6 +70,7 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1",
(data) => (data) =>
@model.set(id: data.id) @model.set(id: data.id)
@$el.data('id', data.id) @$el.data('id', data.id)
@$el.data('update_url', data.update_url)
@render() @render()
) )
......
...@@ -82,9 +82,10 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views ...@@ -82,9 +82,10 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views
deleting = new NotificationView.Mini deleting = new NotificationView.Mini
title: gettext('Deleting&hellip;') title: gettext('Deleting&hellip;')
deleting.show() deleting.show()
$.postJSON('/delete_item', { $.ajax({
id: $component.data('id') type: 'DELETE',
}, => url: $component.data('update_url')
}).success(=>
$component.remove() $component.remove()
deleting.hide() deleting.hide()
) )
......
...@@ -134,9 +134,10 @@ define ["jquery", "jquery.ui", "gettext", "backbone", ...@@ -134,9 +134,10 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
title: gettext('Deleting&hellip;'), title: gettext('Deleting&hellip;'),
deleting.show() deleting.show()
$component = $(event.currentTarget).parents('.component') $component = $(event.currentTarget).parents('.component')
$.postJSON('/delete_item', { $.ajax({
id: $component.data('id') type: 'DELETE',
}, => url: $component.data('update_url')
}).success(=>
deleting.hide() deleting.hide()
analytics.track "Deleted a Component", analytics.track "Deleted a Component",
course: course_location_analytics course: course_location_analytics
...@@ -162,16 +163,16 @@ define ["jquery", "jquery.ui", "gettext", "backbone", ...@@ -162,16 +163,16 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
deleteDraft: (event) -> deleteDraft: (event) ->
@wait(true) @wait(true)
$.ajax({
type: 'DELETE',
url: @$el.data('update_url') + "?" + $.param({recurse: true})
}).success(=>
$.postJSON('/delete_item', { analytics.track "Deleted Draft",
id: @$el.data('id') course: course_location_analytics
delete_children: true unit_id: unit_location_analytics
}, =>
analytics.track "Deleted Draft",
course: course_location_analytics
unit_id: unit_location_analytics
window.location.reload() window.location.reload()
) )
createDraft: (event) -> createDraft: (event) ->
......
...@@ -279,15 +279,14 @@ function _deleteItem($el, type) { ...@@ -279,15 +279,14 @@ function _deleteItem($el, type) {
}); });
deleting.show(); deleting.show();
$.postJSON('/delete_item', $.ajax({
{'id': id, type: 'DELETE',
'delete_children': true, url: $el.data('update_url')+'?'+ $.param({recurse: true, all_versions: true}),
'delete_all_versions': true}, success: function () {
function(data) { $el.remove();
$el.remove(); deleting.hide();
deleting.hide(); }
} });
);
} }
}, },
secondary: { secondary: {
......
...@@ -61,8 +61,8 @@ require(["coffee/src/views/tabs", "coffee/src/models/module"], function(TabsEdit ...@@ -61,8 +61,8 @@ require(["coffee/src/views/tabs", "coffee/src/models/module"], function(TabsEdit
<div class="tab-list"> <div class="tab-list">
<ol class='components'> <ol class='components'>
% for id in components: % for id, update_url in components:
<li class="component" data-id="${id}"/> <li class="component" data-id="${id}" data-update_url="${update_url}"/>
% endfor % endfor
<li class="new-component-item"> <li class="new-component-item">
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
from xmodule.util import date_utils from xmodule.util import date_utils
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from xmodule.modulestore.django import loc_mapper
%> %>
<%block name="title">${_("Course Outline")}</%block> <%block name="title">${_("Course Outline")}</%block>
<%block name="bodyclass">is-signedin course view-outline</%block> <%block name="bodyclass">is-signedin course view-outline</%block>
...@@ -141,7 +142,13 @@ require(["domReady!", "jquery", "js/models/location", "js/models/section", "js/v ...@@ -141,7 +142,13 @@ require(["domReady!", "jquery", "js/models/location", "js/models/section", "js/v
<div class="wrapper-dnd"> <div class="wrapper-dnd">
<article class="courseware-overview" data-id="${context_course.location.url()}"> <article class="courseware-overview" data-id="${context_course.location.url()}">
% for section in sections: % for section in sections:
<section class="courseware-section branch is-draggable" data-id="${section.location}" data-parent-id="${context_course.location.url()}"> <%
section_update_url = loc_mapper().translate_location(
context_course.location.course_id, section.location, False, True
).url_reverse('xblock')
%>
<section class="courseware-section branch is-draggable" data-id="${section.location}"
data-parent-id="${context_course.location.url()}" data-update_url="${section_update_url}">
<%include file="widgets/_ui-dnd-indicator-before.html" /> <%include file="widgets/_ui-dnd-indicator-before.html" />
...@@ -184,7 +191,13 @@ require(["domReady!", "jquery", "js/models/location", "js/models/section", "js/v ...@@ -184,7 +191,13 @@ require(["domReady!", "jquery", "js/models/location", "js/models/section", "js/v
</div> </div>
<ol class="sortable-subsection-list" data-id="${section.location.url()}"> <ol class="sortable-subsection-list" data-id="${section.location.url()}">
% for subsection in section.get_children(): % for subsection in section.get_children():
<li class="courseware-subsection branch collapsed id-holder is-draggable" data-id="${subsection.location}" data-parent-id="${section.location.url()}"> <%
subsection_update_url = loc_mapper().translate_location(
context_course.location.course_id, subsection.location, False, True
).url_reverse('xblock')
%>
<li class="courseware-subsection branch collapsed id-holder is-draggable" data-id="${subsection.location}"
data-parent-id="${section.location.url()}" data-update_url="${subsection_update_url}">
<%include file="widgets/_ui-dnd-indicator-before.html" /> <%include file="widgets/_ui-dnd-indicator-before.html" />
......
...@@ -34,7 +34,7 @@ require(["domReady!", "jquery", "coffee/src/models/module", "coffee/src/views/un ...@@ -34,7 +34,7 @@ require(["domReady!", "jquery", "coffee/src/models/module", "coffee/src/views/un
</%block> </%block>
<%block name="content"> <%block name="content">
<div class="main-wrapper edit-state-${unit_state}" data-id="${unit_location}"> <div class="main-wrapper edit-state-${unit_state}" data-id="${unit_location}" data-update_url="${unit_update_url}">
<div class="inner-wrapper"> <div class="inner-wrapper">
<div class="alert editing-draft-alert"> <div class="alert editing-draft-alert">
<p class="alert-message"><strong>${_("You are editing a draft.")}</strong> <p class="alert-message"><strong>${_("You are editing a draft.")}</strong>
...@@ -48,8 +48,8 @@ require(["domReady!", "jquery", "coffee/src/models/module", "coffee/src/views/un ...@@ -48,8 +48,8 @@ require(["domReady!", "jquery", "coffee/src/models/module", "coffee/src/views/un
<article class="unit-body window"> <article class="unit-body window">
<p class="unit-name-input"><label>${_("Display Name:")}</label><input type="text" value="${unit.display_name_with_default | h}" class="unit-display-name-input" /></p> <p class="unit-name-input"><label>${_("Display Name:")}</label><input type="text" value="${unit.display_name_with_default | h}" class="unit-display-name-input" /></p>
<ol class="components"> <ol class="components">
% for id in components: % for id, component_update_url in components:
<li class="component" data-id="${id}"/> <li class="component" data-id="${id}" data-update_url="${component_update_url}"/>
% endfor % endfor
<li class="new-component-item adding"> <li class="new-component-item adding">
<div class="new-component"> <div class="new-component">
......
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%! from contentstore.utils import compute_unit_state %> <%! from contentstore.utils import compute_unit_state %>
<%! from xmodule.modulestore.django import loc_mapper %>
<!-- <!--
This def will enumerate through a passed in subsection and list all of the units This def will enumerate through a passed in subsection and list all of the units
...@@ -11,7 +12,11 @@ This def will enumerate through a passed in subsection and list all of the units ...@@ -11,7 +12,11 @@ This def will enumerate through a passed in subsection and list all of the units
subsection_units = subsection.get_children() subsection_units = subsection.get_children()
%> %>
% for unit in subsection_units: % for unit in subsection_units:
<li class="courseware-unit leaf unit is-draggable" data-id="${unit.location}" data-parent-id="${subsection.location.url()}"> <%
unit_update_url = loc_mapper().translate_location(context_course.location.course_id, unit.location, False, True).url_reverse('xblock')
%>
<li class="courseware-unit leaf unit is-draggable" data-id="${unit.location}" data-parent-id="${subsection.location.url()}"
data-update_url="${unit_update_url}" >
<%include file="_ui-dnd-indicator-before.html" /> <%include file="_ui-dnd-indicator-before.html" />
...@@ -29,7 +34,9 @@ This def will enumerate through a passed in subsection and list all of the units ...@@ -29,7 +34,9 @@ This def will enumerate through a passed in subsection and list all of the units
</a> </a>
% if actions: % if actions:
<div class="item-actions"> <div class="item-actions">
<a href="#" data-tooltip="Delete this unit" class="delete-button" data-id="${unit.location}"><span class="delete-icon"></span></a> <a href="#" data-tooltip="Delete this unit" class="delete-button" data-id="${unit.location}"
data-update_url="${unit_update_url}">
<span class="delete-icon"></span></a>
<span data-tooltip="Drag to sort" class="drag-handle unit-drag-handle"></span> <span data-tooltip="Drag to sort" class="drag-handle unit-drag-handle"></span>
</div> </div>
% endif % endif
......
import re
from django.conf import settings from django.conf import settings
from django.conf.urls import patterns, include, url from django.conf.urls import patterns, include, url
...@@ -17,7 +16,6 @@ urlpatterns = patterns('', # nopep8 ...@@ -17,7 +16,6 @@ urlpatterns = patterns('', # nopep8
url(r'^subsection/(?P<location>.*?)$', 'contentstore.views.edit_subsection', name='edit_subsection'), url(r'^subsection/(?P<location>.*?)$', 'contentstore.views.edit_subsection', name='edit_subsection'),
url(r'^preview_component/(?P<location>.*?)$', 'contentstore.views.preview_component', name='preview_component'), url(r'^preview_component/(?P<location>.*?)$', 'contentstore.views.preview_component', name='preview_component'),
url(r'^save_item$', 'contentstore.views.save_item', name='save_item'), url(r'^save_item$', 'contentstore.views.save_item', name='save_item'),
url(r'^delete_item$', 'contentstore.views.delete_item', name='delete_item'),
url(r'^create_item$', 'contentstore.views.create_item', name='create_item'), url(r'^create_item$', 'contentstore.views.create_item', name='create_item'),
url(r'^transcripts/upload$', 'contentstore.views.upload_transcripts', name='upload_transcripts'), url(r'^transcripts/upload$', 'contentstore.views.upload_transcripts', name='upload_transcripts'),
...@@ -121,6 +119,7 @@ urlpatterns += patterns( ...@@ -121,6 +119,7 @@ urlpatterns += patterns(
url(r'(?ix)^import/{}$'.format(parsers.URL_RE_SOURCE), 'import_handler'), url(r'(?ix)^import/{}$'.format(parsers.URL_RE_SOURCE), 'import_handler'),
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'),
) )
js_info_dict = { js_info_dict = {
......
...@@ -17,7 +17,7 @@ def expect_json(view_function): ...@@ -17,7 +17,7 @@ def expect_json(view_function):
def parse_json_into_request(request, *args, **kwargs): def parse_json_into_request(request, *args, **kwargs):
# cdodge: fix postback errors in CMS. The POST 'content-type' header can include additional information # cdodge: fix postback errors in CMS. The POST 'content-type' header can include additional information
# e.g. 'charset', so we can't do a direct string compare # e.g. 'charset', so we can't do a direct string compare
if "application/json" in request.META.get('CONTENT_TYPE', ''): if "application/json" in request.META.get('CONTENT_TYPE', '') and request.body:
request.json = json.loads(request.body) request.json = json.loads(request.body)
else: else:
request.json = {} request.json = {}
......
...@@ -167,7 +167,7 @@ class LocMapperStore(object): ...@@ -167,7 +167,7 @@ class LocMapperStore(object):
return BlockUsageLocator(course_id=entry['course_id'], branch=branch, usage_id=usage_id) return BlockUsageLocator(course_id=entry['course_id'], branch=branch, usage_id=usage_id)
def translate_locator_to_location(self, locator): def translate_locator_to_location(self, locator, get_course=False):
""" """
Returns an old style Location for the given Locator if there's an appropriate entry in the Returns an old style Location for the given Locator if there's an appropriate entry in the
mapping collection. Note, it requires that the course was previously mapped (a side effect of mapping collection. Note, it requires that the course was previously mapped (a side effect of
...@@ -175,6 +175,9 @@ class LocMapperStore(object): ...@@ -175,6 +175,9 @@ class LocMapperStore(object):
the block's usage_id was previously stored in the the block's usage_id was previously stored in the
map (a side effect of translate_location or via add|update_block_location). map (a side effect of translate_location or via add|update_block_location).
If get_course, then rather than finding the map for this locator, it finds the 'course' root
for the mapped course.
If there are no matches, it returns None. If there are no matches, it returns None.
If there's more than one location to locator mapping to the same course_id, it looks for the first If there's more than one location to locator mapping to the same course_id, it looks for the first
...@@ -191,7 +194,16 @@ class LocMapperStore(object): ...@@ -191,7 +194,16 @@ class LocMapperStore(object):
for candidate in maps: for candidate in maps:
for old_name, cat_to_usage in candidate['block_map'].iteritems(): for old_name, cat_to_usage in candidate['block_map'].iteritems():
for category, usage_id in cat_to_usage.iteritems(): for category, usage_id in cat_to_usage.iteritems():
if usage_id == locator.usage_id: if get_course:
if category == 'course':
return Location(
'i4x',
candidate['_id']['org'],
candidate['_id']['course'],
'course',
self._decode_from_mongo(old_name),
None)
elif usage_id == locator.usage_id:
# figure out revision # figure out revision
# enforce the draft only if category in [..] logic # enforce the draft only if category in [..] logic
if category in draft.DIRECT_ONLY_CATEGORIES: if category in draft.DIRECT_ONLY_CATEGORIES:
......
...@@ -263,7 +263,7 @@ class CourseLocator(Locator): ...@@ -263,7 +263,7 @@ class CourseLocator(Locator):
version_guid=self.version_guid, version_guid=self.version_guid,
branch=self.branch) branch=self.branch)
def url_reverse(self, prefix, postfix): def url_reverse(self, prefix, postfix=''):
""" """
Do what reverse is supposed to do but seems unable to do. Generate a url using prefix unicode(self) postfix Do what reverse is supposed to do but seems unable to do. Generate a url using prefix unicode(self) postfix
:param prefix: the beginning of the url (will be forced to begin and end with / if non-empty) :param prefix: the beginning of the url (will be forced to begin and end with / if non-empty)
......
...@@ -81,7 +81,7 @@ class DraftModuleStore(MongoModuleStore): ...@@ -81,7 +81,7 @@ class DraftModuleStore(MongoModuleStore):
try: try:
return wrap_draft(super(DraftModuleStore, self).get_item(as_draft(location), depth=depth)) return wrap_draft(super(DraftModuleStore, self).get_item(as_draft(location), depth=depth))
except ItemNotFoundError: except ItemNotFoundError:
return wrap_draft(super(DraftModuleStore, self).get_item(location, depth=depth)) return wrap_draft(super(DraftModuleStore, self).get_item(as_published(location), depth=depth))
def get_instance(self, course_id, location, depth=0): def get_instance(self, course_id, location, depth=0):
""" """
...@@ -169,7 +169,7 @@ class DraftModuleStore(MongoModuleStore): ...@@ -169,7 +169,7 @@ class DraftModuleStore(MongoModuleStore):
try: try:
draft_item = self.get_item(location) draft_item = self.get_item(location)
if not getattr(draft_item, 'is_draft', False): if not getattr(draft_item, 'is_draft', False):
self.convert_to_draft(location) self.convert_to_draft(as_published(location))
except ItemNotFoundError, e: except ItemNotFoundError, e:
if not allow_not_found: if not allow_not_found:
raise e raise e
...@@ -187,7 +187,7 @@ class DraftModuleStore(MongoModuleStore): ...@@ -187,7 +187,7 @@ class DraftModuleStore(MongoModuleStore):
draft_loc = as_draft(location) draft_loc = as_draft(location)
draft_item = self.get_item(location) draft_item = self.get_item(location)
if not getattr(draft_item, 'is_draft', False): if not getattr(draft_item, 'is_draft', False):
self.convert_to_draft(location) self.convert_to_draft(as_published(location))
return super(DraftModuleStore, self).update_children(draft_loc, children) return super(DraftModuleStore, self).update_children(draft_loc, children)
...@@ -203,7 +203,7 @@ class DraftModuleStore(MongoModuleStore): ...@@ -203,7 +203,7 @@ class DraftModuleStore(MongoModuleStore):
draft_item = self.get_item(location) draft_item = self.get_item(location)
if not getattr(draft_item, 'is_draft', False): if not getattr(draft_item, 'is_draft', False):
self.convert_to_draft(location) self.convert_to_draft(as_published(location))
if 'is_draft' in metadata: if 'is_draft' in metadata:
del metadata['is_draft'] del metadata['is_draft']
...@@ -262,7 +262,7 @@ class DraftModuleStore(MongoModuleStore): ...@@ -262,7 +262,7 @@ class DraftModuleStore(MongoModuleStore):
""" """
Turn the published version into a draft, removing the published version Turn the published version into a draft, removing the published version
""" """
self.convert_to_draft(location) self.convert_to_draft(as_published(location))
super(DraftModuleStore, self).delete_item(location) super(DraftModuleStore, self).delete_item(location)
def _query_children_for_cache_children(self, items): def _query_children_for_cache_children(self, items):
......
...@@ -258,13 +258,17 @@ class TestLocationMapper(unittest.TestCase): ...@@ -258,13 +258,17 @@ class TestLocationMapper(unittest.TestCase):
new_style_course_id, new_style_course_id,
block_map={ block_map={
'abc123': {'problem': 'problem2'}, 'abc123': {'problem': 'problem2'},
'48f23a10395384929234': {'chapter': 'chapter48f'} '48f23a10395384929234': {'chapter': 'chapter48f'},
'baz_run': {'course': 'root'},
} }
) )
# only one course matches # only one course matches
prob_location = loc_mapper().translate_locator_to_location(prob_locator) prob_location = loc_mapper().translate_locator_to_location(prob_locator)
# default branch # default branch
self.assertEqual(prob_location, Location('i4x', org, course, 'problem', 'abc123', None)) self.assertEqual(prob_location, Location('i4x', org, course, 'problem', 'abc123', None))
# test get_course keyword
prob_location = loc_mapper().translate_locator_to_location(prob_locator, get_course=True)
self.assertEqual(prob_location, Location('i4x', org, course, 'course', 'baz_run', None))
# explicit branch # explicit branch
prob_locator = BlockUsageLocator( prob_locator = BlockUsageLocator(
course_id=prob_locator.course_id, branch='draft', usage_id=prob_locator.usage_id course_id=prob_locator.course_id, branch='draft', usage_id=prob_locator.usage_id
......
...@@ -97,15 +97,16 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d ...@@ -97,15 +97,16 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d
if len(draft_verticals) > 0: if len(draft_verticals) > 0:
draft_course_dir = export_fs.makeopendir('drafts') draft_course_dir = export_fs.makeopendir('drafts')
for draft_vertical in draft_verticals: for draft_vertical in draft_verticals:
parent_locs = draft_modulestore.get_parent_locations(draft_vertical.location, course.location.course_id) if getattr(draft_vertical, 'is_draft', False):
# Don't try to export orphaned items. parent_locs = draft_modulestore.get_parent_locations(draft_vertical.location, course.location.course_id)
if len(parent_locs) > 0: # Don't try to export orphaned items.
logging.debug('parent_locs = {0}'.format(parent_locs)) if len(parent_locs) > 0:
draft_vertical.xml_attributes['parent_sequential_url'] = Location(parent_locs[0]).url() logging.debug('parent_locs = {0}'.format(parent_locs))
sequential = modulestore.get_item(Location(parent_locs[0])) draft_vertical.xml_attributes['parent_sequential_url'] = Location(parent_locs[0]).url()
index = sequential.children.index(draft_vertical.location.url()) sequential = modulestore.get_item(Location(parent_locs[0]))
draft_vertical.xml_attributes['index_in_children_list'] = str(index) index = sequential.children.index(draft_vertical.location.url())
draft_vertical.export_to_xml(draft_course_dir) draft_vertical.xml_attributes['index_in_children_list'] = str(index)
draft_vertical.export_to_xml(draft_course_dir)
def export_extra_content(export_fs, modulestore, course_id, course_location, category_type, dirname, file_suffix=''): def export_extra_content(export_fs, modulestore, course_id, course_location, category_type, dirname, file_suffix=''):
......
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