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 ...@@ -11,6 +11,7 @@ from dogapi import dog_stats_api
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.cache import cache from django.core.cache import cache
from django.core.context_processors import csrf
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
...@@ -29,7 +30,7 @@ from student.models import anonymous_id_for_user, user_by_anonymous_id ...@@ -29,7 +30,7 @@ from student.models import anonymous_id_for_user, user_by_anonymous_id
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import Scope from xblock.fields import Scope
from xblock.runtime import KvsFieldData, KeyValueStore 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 xblock.django.request import django_to_webob_request, webob_to_django_response
from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError from xmodule.exceptions import NotFoundError, ProcessingError
...@@ -562,23 +563,19 @@ def xblock_resource(request, block_type, uri): # pylint: disable=unused-argumen ...@@ -562,23 +563,19 @@ def xblock_resource(request, block_type, uri): # pylint: disable=unused-argumen
return HttpResponse(content, mimetype=mimetype) 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) location = unquote_slashes(usage_id)
user = request.user
# Check parameters and fail fast if there's a problem # Check parameters and fail fast if there's a problem
if not Location.is_valid(location): if not Location.is_valid(location):
raise Http404("Invalid 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: try:
descriptor = modulestore().get_instance(course_id, location) descriptor = modulestore().get_instance(course_id, location)
except ItemNotFoundError: except ItemNotFoundError:
...@@ -590,13 +587,6 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user): ...@@ -590,13 +587,6 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
) )
raise Http404 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( field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
course_id, course_id,
user, user,
...@@ -609,6 +599,29 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, 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) log.debug("No module %s for user %s -- access denied?", location, user)
raise Http404 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) req = django_to_webob_request(request)
try: try:
with tracker.get_tracker().context(tracking_context_name, tracking_context): 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): ...@@ -637,6 +650,30 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
return webob_to_django_response(resp) 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): def get_score_bucket(grade, max_grade):
""" """
Function to split arbitrary score ranges into 3 buckets. Function to split arbitrary score ranges into 3 buckets.
......
...@@ -276,6 +276,14 @@ if FEATURES.get('AUTH_USE_CAS'): ...@@ -276,6 +276,14 @@ if FEATURES.get('AUTH_USE_CAS'):
HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS = ENV_TOKENS.get('HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS',{}) 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 ############### ############################## SECURE AUTH ITEMS ###############
# Secret things: passwords, access keys, etc. # Secret things: passwords, access keys, etc.
......
...@@ -113,6 +113,9 @@ FEATURES = { ...@@ -113,6 +113,9 @@ FEATURES = {
# with Shib. Feature was requested by Stanford's office of general counsel # with Shib. Feature was requested by Stanford's office of general counsel
'SHIB_DISABLE_TOS': False, '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. # Can be turned off if course lists need to be hidden. Effects views and templates.
'COURSES_ARE_BROWSABLE': True, 'COURSES_ARE_BROWSABLE': True,
...@@ -1243,6 +1246,14 @@ if FEATURES.get('AUTH_USE_CAS'): ...@@ -1243,6 +1246,14 @@ if FEATURES.get('AUTH_USE_CAS'):
INSTALLED_APPS += ('django_cas',) INSTALLED_APPS += ('django_cas',)
MIDDLEWARE_CLASSES += ('django_cas.middleware.CASMiddleware',) 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 ################################## ###################### Registration ##################################
# For each of the fields, give one of the following values: # For each of the fields, give one of the following values:
......
...@@ -4,6 +4,7 @@ Module implementing `xblock.runtime.Runtime` functionality for the LMS ...@@ -4,6 +4,7 @@ Module implementing `xblock.runtime.Runtime` functionality for the LMS
import re import re
from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from user_api import user_service from user_api import user_service
...@@ -106,10 +107,11 @@ class LmsHandlerUrls(object): ...@@ -106,10 +107,11 @@ class LmsHandlerUrls(object):
""" """
local_resource_url for Studio local_resource_url for Studio
""" """
return reverse('xblock_resource_url', kwargs={ path = reverse('xblock_resource_url', kwargs={
'block_type': block.scope_ids.block_type, 'block_type': block.scope_ids.block_type,
'uri': uri, 'uri': uri,
}) })
return '//{}{}'.format(settings.SITE_NAME, path)
class LmsPartitionService(PartitionService): class LmsPartitionService(PartitionService):
......
...@@ -185,6 +185,9 @@ if settings.COURSEWARE_ENABLED: ...@@ -185,6 +185,9 @@ if settings.COURSEWARE_ENABLED:
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/xblock/(?P<usage_id>[^/]*)/handler/(?P<handler>[^/]*)(?:/(?P<suffix>.*))?$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/xblock/(?P<usage_id>[^/]*)/handler/(?P<handler>[^/]*)(?:/(?P<suffix>.*))?$',
'courseware.module_render.handle_xblock_callback', 'courseware.module_render.handle_xblock_callback',
name='xblock_handler'), 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>.*))?$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/xblock/(?P<usage_id>[^/]*)/handler_noauth/(?P<handler>[^/]*)(?:/(?P<suffix>.*))?$',
'courseware.module_render.handle_xblock_callback_noauth', 'courseware.module_render.handle_xblock_callback_noauth',
name='xblock_handler_noauth'), name='xblock_handler_noauth'),
......
...@@ -91,6 +91,9 @@ sphinx_rtd_theme==0.1.5 ...@@ -91,6 +91,9 @@ sphinx_rtd_theme==0.1.5
Babel==1.3 Babel==1.3
transifex-client==0.10 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, # 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 # 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: # 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