Commit b12583ae by Xavier Antoviaque

xblock-external-ui: Add XBlock API call to render XBlock views

Included commits:
* xblock-external-ui: Include CSRF token in the API answer
* xblock-external-ui: Adds support for CORS headers (cross-domain request)
* xblock-external-ui: Include full path when building local_url
parent 9ee49612
......@@ -11,6 +11,7 @@ from dogapi import dog_stats_api
from django.conf import settings
from django.contrib.auth.models import User
from django.core.cache import cache
from django.core.context_processors import csrf
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponse
......@@ -29,7 +30,7 @@ from student.models import anonymous_id_for_user, user_by_anonymous_id
from xblock.core import XBlock
from xblock.fields import Scope
from xblock.runtime import KvsFieldData, KeyValueStore
from xblock.exceptions import NoSuchHandlerError
from xblock.exceptions import NoSuchHandlerError, NoSuchViewError
from xblock.django.request import django_to_webob_request, webob_to_django_response
from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError
......@@ -562,23 +563,19 @@ def xblock_resource(request, block_type, uri): # pylint: disable=unused-argumen
return HttpResponse(content, mimetype=mimetype)
def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
def _get_module_by_usage_id(request, course_id, usage_id):
"""
Invoke an XBlock handler, either authenticated or not.
Gets a module instance based on its `usage_id` in a course, for a given request/user
Returns (location, descriptor, instance)
"""
location = unquote_slashes(usage_id)
user = request.user
# Check parameters and fail fast if there's a problem
if not Location.is_valid(location):
raise Http404("Invalid location")
# Check submitted files
files = request.FILES or {}
error_msg = _check_files_limits(files)
if error_msg:
return HttpResponse(json.dumps({'success': error_msg}))
try:
descriptor = modulestore().get_instance(course_id, location)
except ItemNotFoundError:
......@@ -590,13 +587,6 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
)
raise Http404
tracking_context_name = 'module_callback_handler'
tracking_context = {
'module': {
'display_name': descriptor.display_name_with_default,
}
}
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
course_id,
user,
......@@ -609,6 +599,29 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
log.debug("No module %s for user %s -- access denied?", location, user)
raise Http404
return (location, descriptor, instance)
def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
"""
Invoke an XBlock handler, either authenticated or not.
"""
location, descriptor, instance = _get_module_by_usage_id(request, course_id, usage_id)
# Check submitted files
files = request.FILES or {}
error_msg = _check_files_limits(files)
if error_msg:
return HttpResponse(json.dumps({'success': error_msg}))
tracking_context_name = 'module_callback_handler'
tracking_context = {
'module': {
'display_name': descriptor.display_name_with_default,
}
}
req = django_to_webob_request(request)
try:
with tracker.get_tracker().context(tracking_context_name, tracking_context):
......@@ -637,6 +650,30 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
return webob_to_django_response(resp)
def xblock_view(request, course_id, usage_id, view_name):
"""
Returns the rendered view of a given XBlock, with related resources
Returns a json object containing two keys:
html: The rendered html of the view
resources: A list of tuples where the first element is the resource hash, and
the second is the resource description
"""
location, descriptor, instance = _get_module_by_usage_id(request, course_id, usage_id)
try:
fragment = instance.render(view_name)
except NoSuchViewError:
log.exception("Attempt to render missing view on %s: %s", instance, view_name)
raise Http404
return JsonResponse({
'html': fragment.content,
'resources': fragment.resources,
'csrf_token': str(csrf(request)['csrf_token']),
})
def get_score_bucket(grade, max_grade):
"""
Function to split arbitrary score ranges into 3 buckets.
......
......@@ -276,6 +276,14 @@ if FEATURES.get('AUTH_USE_CAS'):
HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS = ENV_TOKENS.get('HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS',{})
############# CORS headers for cross-domain requests #################
if FEATURES.get('ENABLE_CORS_HEADERS'):
INSTALLED_APPS += ('corsheaders',)
MIDDLEWARE_CLASSES += ('corsheaders.middleware.CorsMiddleware',)
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = ENV_TOKENS.get('CORS_ORIGIN_WHITELIST', ())
############################## SECURE AUTH ITEMS ###############
# Secret things: passwords, access keys, etc.
......
......@@ -113,6 +113,9 @@ FEATURES = {
# with Shib. Feature was requested by Stanford's office of general counsel
'SHIB_DISABLE_TOS': False,
# Allows to configure the LMS to provide CORS headers to serve requests from other domains
'ENABLE_CORS_HEADERS': False,
# Can be turned off if course lists need to be hidden. Effects views and templates.
'COURSES_ARE_BROWSABLE': True,
......@@ -1243,6 +1246,14 @@ if FEATURES.get('AUTH_USE_CAS'):
INSTALLED_APPS += ('django_cas',)
MIDDLEWARE_CLASSES += ('django_cas.middleware.CASMiddleware',)
############# CORS headers for cross-domain requests #################
if FEATURES.get('ENABLE_CORS_HEADERS'):
INSTALLED_APPS += ('corsheaders',)
MIDDLEWARE_CLASSES += ('corsheaders.middleware.CorsMiddleware',)
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = ('')
###################### Registration ##################################
# For each of the fields, give one of the following values:
......
......@@ -4,6 +4,7 @@ Module implementing `xblock.runtime.Runtime` functionality for the LMS
import re
from django.conf import settings
from django.core.urlresolvers import reverse
from user_api import user_service
......@@ -106,10 +107,11 @@ class LmsHandlerUrls(object):
"""
local_resource_url for Studio
"""
return reverse('xblock_resource_url', kwargs={
path = reverse('xblock_resource_url', kwargs={
'block_type': block.scope_ids.block_type,
'uri': uri,
})
return '//{}{}'.format(settings.SITE_NAME, path)
class LmsPartitionService(PartitionService):
......
......@@ -185,6 +185,9 @@ if settings.COURSEWARE_ENABLED:
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/xblock/(?P<usage_id>[^/]*)/handler/(?P<handler>[^/]*)(?:/(?P<suffix>.*))?$',
'courseware.module_render.handle_xblock_callback',
name='xblock_handler'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/xblock/(?P<usage_id>[^/]*)/view/(?P<view_name>[^/]*)$',
'courseware.module_render.xblock_view',
name='xblock_view'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/xblock/(?P<usage_id>[^/]*)/handler_noauth/(?P<handler>[^/]*)(?:/(?P<suffix>.*))?$',
'courseware.module_render.handle_xblock_callback_noauth',
name='xblock_handler_noauth'),
......
......@@ -91,6 +91,9 @@ sphinx_rtd_theme==0.1.5
Babel==1.3
transifex-client==0.10
# Used to allow to configure CORS headers for cross-domain requests
django-cors-headers==0.12
# We've tried several times to update the debug toolbar to version 1.0.1,
# and had problems each time, resulting in us rolling back to 0.9.4. Before
# submitting another pull request to do this update, check the following:
......
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