Commit e7c06e3a by cahrens

Change preview view method to use RESTful URL.

STUD-848
parent ddd9e0e4
...@@ -454,31 +454,36 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -454,31 +454,36 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
@override_settings(COURSES_WITH_UNSAFE_CODE=['edX/toy/.*']) @override_settings(COURSES_WITH_UNSAFE_CODE=['edX/toy/.*'])
def test_module_preview_in_whitelist(self): def test_module_preview_in_whitelist(self):
''' """
Tests the ajax callback to render an XModule Tests the ajax callback to render an XModule
''' """
direct_store = modulestore('direct') resp = self._test_preview(Location(['i4x', 'edX', 'toy', 'vertical', 'vertical_test', None]))
import_from_xml(direct_store, 'common/test/data/', ['toy']) # These are the data-ids of the xblocks contained in the vertical.
# Ultimately, these must be converted to new locators.
# also try a custom response which will trigger the 'is this course in whitelist' logic self.assertContains(resp, 'i4x://edX/toy/video/sample_video')
problem_module_location = Location(['i4x', 'edX', 'toy', 'vertical', 'vertical_test', None]) self.assertContains(resp, 'i4x://edX/toy/video/separate_file_video')
url = reverse('preview_component', kwargs={'location': problem_module_location.url()}) self.assertContains(resp, 'i4x://edX/toy/video/video_with_end_time')
resp = self.client.get_html(url) self.assertContains(resp, 'i4x://edX/toy/poll_question/T1_changemind_poll_foo_2')
self.assertEqual(resp.status_code, 200)
def test_video_module_caption_asset_path(self): def test_video_module_caption_asset_path(self):
''' """
This verifies that a video caption url is as we expect it to be This verifies that a video caption url is as we expect it to be
''' """
resp = self._test_preview(Location(['i4x', 'edX', 'toy', 'video', 'sample_video', None]))
self.assertContains(resp, 'data-caption-asset-path="/c4x/edX/toy/asset/subs_"')
def _test_preview(self, location):
""" Preview test case. """
direct_store = modulestore('direct') direct_store = modulestore('direct')
import_from_xml(direct_store, 'common/test/data/', ['toy']) _, course_items = import_from_xml(direct_store, 'common/test/data/', ['toy'])
# also try a custom response which will trigger the 'is this course in whitelist' logic # also try a custom response which will trigger the 'is this course in whitelist' logic
video_module_location = Location(['i4x', 'edX', 'toy', 'video', 'sample_video', None]) locator = loc_mapper().translate_location(
url = reverse('preview_component', kwargs={'location': video_module_location.url()}) course_items[0].location.course_id, location, False, True
resp = self.client.get_html(url) )
resp = self.client.get_html(locator.url_reverse('xblock'))
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'data-caption-asset-path="/c4x/edX/toy/asset/subs_"') return resp
def test_delete(self): def test_delete(self):
direct_store = modulestore('direct') direct_store = modulestore('direct')
......
...@@ -249,12 +249,9 @@ def edit_unit(request, location): ...@@ -249,12 +249,9 @@ def edit_unit(request, location):
) )
components = [ components = [
[ loc_mapper().translate_location(
component.location.url(), course.location.course_id, component.location, False, True
loc_mapper().translate_location( )
course.location.course_id, component.location, False, True
)
]
for component for component
in item.get_children() in item.get_children()
] ]
......
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
import logging import logging
from uuid import uuid4 from uuid import uuid4
from functools import partial
from static_replace import replace_static_urls from static_replace import replace_static_urls
from xmodule_modifiers import wrap_xblock
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
...@@ -27,6 +29,8 @@ from xmodule.modulestore.locator import BlockUsageLocator ...@@ -27,6 +29,8 @@ from xmodule.modulestore.locator import BlockUsageLocator
from student.models import CourseEnrollment from student.models import CourseEnrollment
from django.http import HttpResponseBadRequest from django.http import HttpResponseBadRequest
from xblock.fields import Scope from xblock.fields import Scope
from preview import handler_prefix, get_preview_html
from mitxmako.shortcuts import render_to_response, render_to_string
__all__ = ['orphan_handler', 'xblock_handler'] __all__ = ['orphan_handler', 'xblock_handler']
...@@ -51,6 +55,7 @@ def xblock_handler(request, tag=None, course_id=None, branch=None, version_guid= ...@@ -51,6 +55,7 @@ def xblock_handler(request, tag=None, course_id=None, branch=None, version_guid=
all children and "all_versions" to delete from all (mongo) versions. all children and "all_versions" to delete from all (mongo) versions.
GET GET
json: returns representation of the xblock (locator id, data, and metadata). json: returns representation of the xblock (locator id, data, and metadata).
html: returns HTML for rendering the xblock (which includes both the "preview" view and the "editor" view)
PUT or POST PUT or POST
json: if xblock location is specified, update the xblock instance. The json payload can contain json: if xblock location is specified, update the xblock instance. The json payload can contain
these fields, all optional: these fields, all optional:
...@@ -76,8 +81,27 @@ def xblock_handler(request, tag=None, course_id=None, branch=None, version_guid= ...@@ -76,8 +81,27 @@ def xblock_handler(request, tag=None, course_id=None, branch=None, version_guid=
old_location = loc_mapper().translate_locator_to_location(location) old_location = loc_mapper().translate_locator_to_location(location)
if request.method == 'GET': if request.method == 'GET':
rsp = _get_module_info(location) if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
return JsonResponse(rsp) rsp = _get_module_info(location)
return JsonResponse(rsp)
else:
component = modulestore().get_item(old_location)
# Wrap the generated fragment in the xmodule_editor div so that the javascript
# can bind to it correctly
component.runtime.wrappers.append(partial(wrap_xblock, handler_prefix))
try:
content = component.render('studio_view').content
# catch exceptions indiscriminately, since after this point they escape the
# dungeon and surface as uneditable, unsaveable, and undeletable
# component-goblins.
except Exception as exc: # pylint: disable=W0703
content = render_to_string('html_error.html', {'message': str(exc)})
return render_to_response('component.html', {
'preview': get_preview_html(request, component),
'editor': content
})
elif request.method == 'DELETE': elif request.method == 'DELETE':
delete_children = str_to_bool(request.REQUEST.get('recurse', 'False')) delete_children = str_to_bool(request.REQUEST.get('recurse', 'False'))
delete_all_versions = str_to_bool(request.REQUEST.get('all_versions', 'False')) delete_all_versions = str_to_bool(request.REQUEST.get('all_versions', 'False'))
......
...@@ -3,7 +3,7 @@ from functools import partial ...@@ -3,7 +3,7 @@ from functools import partial
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponseBadRequest, HttpResponseForbidden from django.http import Http404, HttpResponseBadRequest
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
...@@ -24,10 +24,9 @@ from util.sandboxing import can_execute_unsafe_code ...@@ -24,10 +24,9 @@ from util.sandboxing import can_execute_unsafe_code
import static_replace import static_replace
from .session_kv_store import SessionKeyValueStore from .session_kv_store import SessionKeyValueStore
from .helpers import render_from_lms from .helpers import render_from_lms
from .access import has_access
from ..utils import get_course_for_item from ..utils import get_course_for_item
__all__ = ['preview_handler', 'preview_component'] __all__ = ['preview_handler']
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -53,13 +52,13 @@ def preview_handler(request, usage_id, handler, suffix=''): ...@@ -53,13 +52,13 @@ def preview_handler(request, usage_id, handler, suffix=''):
usage_id: The usage-id of the block to dispatch to, passed through `quote_slashes` usage_id: The usage-id of the block to dispatch to, passed through `quote_slashes`
handler: The handler to execute handler: The handler to execute
suffix: The remaineder of the url to be passed to the handler suffix: The remainder of the url to be passed to the handler
""" """
location = unquote_slashes(usage_id) location = unquote_slashes(usage_id)
descriptor = modulestore().get_item(location) descriptor = modulestore().get_item(location)
instance = load_preview_module(request, descriptor) instance = _load_preview_module(request, descriptor)
# Let the module handle the AJAX # Let the module handle the AJAX
req = django_to_webob_request(request) req = django_to_webob_request(request)
try: try:
...@@ -85,32 +84,6 @@ def preview_handler(request, usage_id, handler, suffix=''): ...@@ -85,32 +84,6 @@ def preview_handler(request, usage_id, handler, suffix=''):
return webob_to_django_response(resp) return webob_to_django_response(resp)
@login_required
def preview_component(request, location):
"Return the HTML preview of a component"
# TODO (vshnayder): change name from id to location in coffee+html as well.
if not has_access(request.user, location):
return HttpResponseForbidden()
component = modulestore().get_item(location)
# Wrap the generated fragment in the xmodule_editor div so that the javascript
# can bind to it correctly
component.runtime.wrappers.append(partial(wrap_xblock, handler_prefix))
try:
content = component.render('studio_view').content
# catch exceptions indiscriminately, since after this point they escape the
# dungeon and surface as uneditable, unsaveable, and undeletable
# component-goblins.
except Exception as exc: # pylint: disable=W0703
content = render_to_string('html_error.html', {'message': str(exc)})
return render_to_response('component.html', {
'preview': get_preview_html(request, component),
'editor': content
})
class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method
""" """
An XModule ModuleSystem for use in Studio previews An XModule ModuleSystem for use in Studio previews
...@@ -119,7 +92,7 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method ...@@ -119,7 +92,7 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method
return handler_prefix(block, handler_name, suffix) + '?' + query return handler_prefix(block, handler_name, suffix) + '?' + query
def preview_module_system(request, descriptor): def _preview_module_system(request, descriptor):
""" """
Returns a ModuleSystem for the specified descriptor that is specialized for Returns a ModuleSystem for the specified descriptor that is specialized for
rendering module previews. rendering module previews.
...@@ -135,7 +108,7 @@ def preview_module_system(request, descriptor): ...@@ -135,7 +108,7 @@ def preview_module_system(request, descriptor):
# TODO (cpennington): Do we want to track how instructors are using the preview problems? # TODO (cpennington): Do we want to track how instructors are using the preview problems?
track_function=lambda event_type, event: None, track_function=lambda event_type, event: None,
filestore=descriptor.runtime.resources_fs, filestore=descriptor.runtime.resources_fs,
get_module=partial(load_preview_module, request), get_module=partial(_load_preview_module, request),
render_template=render_from_lms, render_template=render_from_lms,
debug=True, debug=True,
replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_id=course_id), replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_id=course_id),
...@@ -162,7 +135,7 @@ def preview_module_system(request, descriptor): ...@@ -162,7 +135,7 @@ def preview_module_system(request, descriptor):
) )
def load_preview_module(request, descriptor): def _load_preview_module(request, descriptor):
""" """
Return a preview XModule instantiated from the supplied descriptor. Return a preview XModule instantiated from the supplied descriptor.
...@@ -171,7 +144,7 @@ def load_preview_module(request, descriptor): ...@@ -171,7 +144,7 @@ def load_preview_module(request, descriptor):
""" """
student_data = DbModel(SessionKeyValueStore(request)) student_data = DbModel(SessionKeyValueStore(request))
descriptor.bind_for_student( descriptor.bind_for_student(
preview_module_system(request, descriptor), _preview_module_system(request, descriptor),
LmsFieldData(descriptor._field_data, student_data), # pylint: disable=protected-access LmsFieldData(descriptor._field_data, student_data), # pylint: disable=protected-access
) )
return descriptor return descriptor
...@@ -182,7 +155,7 @@ def get_preview_html(request, descriptor): ...@@ -182,7 +155,7 @@ def get_preview_html(request, descriptor):
Returns the HTML returned by the XModule's student_view, Returns the HTML returned by the XModule's student_view,
specified by the descriptor and idx. specified by the descriptor and idx.
""" """
module = load_preview_module(request, descriptor) module = _load_preview_module(request, descriptor)
try: try:
content = module.render("student_view").content content = module.render("student_view").content
except Exception as exc: # pylint: disable=W0703 except Exception as exc: # pylint: disable=W0703
......
...@@ -125,12 +125,9 @@ def edit_tabs(request, org, course, coursename): ...@@ -125,12 +125,9 @@ 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 = [
[ loc_mapper().translate_location(
static_tab.location.url(), course_item.location.course_id, static_tab.location, False, True
loc_mapper().translate_location( )
course_item.location.course_id, static_tab.location, False, True
)
]
for static_tab for static_tab
in static_tabs in static_tabs
] ]
......
define ["coffee/src/views/module_edit", "xmodule"], (ModuleEdit) -> define ["coffee/src/views/module_edit", "js/models/module_info", "xmodule"], (ModuleEdit, ModuleModel) ->
describe "ModuleEdit", -> describe "ModuleEdit", ->
beforeEach -> beforeEach ->
@stubModule = jasmine.createSpy("Module") @stubModule = new ModuleModel
@stubModule.id = 'stub-id' id: "stub-id"
@stubModule.get = (param)->
if param == 'old_id'
return 'stub-old-id'
setFixtures """ setFixtures """
<li class="component" id="stub-id"> <li class="component" id="stub-id">
...@@ -62,7 +59,7 @@ define ["coffee/src/views/module_edit", "xmodule"], (ModuleEdit) -> ...@@ -62,7 +59,7 @@ define ["coffee/src/views/module_edit", "xmodule"], (ModuleEdit) ->
@moduleEdit.render() @moduleEdit.render()
it "loads the module preview and editor via ajax on the view element", -> it "loads the module preview and editor via ajax on the view element", ->
expect(@moduleEdit.$el.load).toHaveBeenCalledWith("/preview_component/#{@moduleEdit.model.get('old_id')}", jasmine.any(Function)) expect(@moduleEdit.$el.load).toHaveBeenCalledWith("/xblock/#{@moduleEdit.model.id}", jasmine.any(Function))
@moduleEdit.$el.load.mostRecentCall.args[1]() @moduleEdit.$el.load.mostRecentCall.args[1]()
expect(@moduleEdit.loadDisplay).toHaveBeenCalled() expect(@moduleEdit.loadDisplay).toHaveBeenCalled()
expect(@moduleEdit.delegateEvents).toHaveBeenCalled() expect(@moduleEdit.delegateEvents).toHaveBeenCalled()
......
...@@ -69,15 +69,13 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1", ...@@ -69,15 +69,13 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1",
payload payload
(data) => (data) =>
@model.set(id: data.locator) @model.set(id: data.locator)
@model.set(old_id: data.id)
@$el.data('id', data.id)
@$el.data('locator', data.locator) @$el.data('locator', data.locator)
@render() @render()
) )
render: -> render: ->
if @model.get('old_id') if @model.id
@$el.load("/preview_component/#{@model.get('old_id')}", => @$el.load(@model.url(), =>
@loadDisplay() @loadDisplay()
@delegateEvents() @delegateEvents()
) )
......
...@@ -6,8 +6,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views ...@@ -6,8 +6,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views
initialize: => initialize: =>
@$('.component').each((idx, element) => @$('.component').each((idx, element) =>
model = new ModuleModel({ model = new ModuleModel({
id: $(element).data('locator'), id: $(element).data('locator')
old_id:$(element).data('id')
}) })
new ModuleEditView( new ModuleEditView(
......
...@@ -63,7 +63,6 @@ define ["jquery", "jquery.ui", "gettext", "backbone", ...@@ -63,7 +63,6 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
@$('.component').each (idx, element) => @$('.component').each (idx, element) =>
model = new ModuleModel model = new ModuleModel
id: $(element).data('locator') id: $(element).data('locator')
old_id: $(element).data('id')
new ModuleEditView new ModuleEditView
el: element, el: element,
onDelete: @deleteComponent, onDelete: @deleteComponent,
......
...@@ -61,8 +61,8 @@ require(["backbone", "coffee/src/views/tabs"], function(Backbone, TabsEditView) ...@@ -61,8 +61,8 @@ require(["backbone", "coffee/src/views/tabs"], function(Backbone, TabsEditView)
<div class="tab-list"> <div class="tab-list">
<ol class='components'> <ol class='components'>
% for id, locator in components: % for locator in components:
<li class="component" data-id="${id}" data-locator="${locator}"/> <li class="component" data-locator="${locator}"/>
% endfor % endfor
<li class="new-component-item"> <li class="new-component-item">
......
...@@ -48,8 +48,8 @@ require(["domReady!", "jquery", "js/models/module_info", "coffee/src/views/unit" ...@@ -48,8 +48,8 @@ require(["domReady!", "jquery", "js/models/module_info", "coffee/src/views/unit"
<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, locator in components: % for locator in components:
<li class="component" data-id="${id}" data-locator="${locator}"/> <li class="component" data-locator="${locator}"/>
% endfor % endfor
<li class="new-component-item adding"> <li class="new-component-item adding">
<div class="new-component"> <div class="new-component">
......
...@@ -14,7 +14,6 @@ urlpatterns = patterns('', # nopep8 ...@@ -14,7 +14,6 @@ urlpatterns = patterns('', # nopep8
url(r'^$', 'contentstore.views.howitworks', name='homepage'), url(r'^$', 'contentstore.views.howitworks', name='homepage'),
url(r'^edit/(?P<location>.*?)$', 'contentstore.views.edit_unit', name='edit_unit'), url(r'^edit/(?P<location>.*?)$', 'contentstore.views.edit_unit', name='edit_unit'),
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'^transcripts/upload$', 'contentstore.views.upload_transcripts', name='upload_transcripts'), url(r'^transcripts/upload$', 'contentstore.views.upload_transcripts', name='upload_transcripts'),
url(r'^transcripts/download$', 'contentstore.views.download_transcripts', name='download_transcripts'), url(r'^transcripts/download$', 'contentstore.views.download_transcripts', name='download_transcripts'),
......
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