Skip to content

Commit c4868e0

Browse files
OmarIthawijmbowman
authored andcommitted
Add support for the LTI Consumer XBlock (#142)
1 parent 5b64b51 commit c4868e0

File tree

5 files changed

+103
-2
lines changed

5 files changed

+103
-2
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ geckodriver.log
1515
workbench/static/djpyfs/
1616
workbench.test.*
1717

18+
### Developer Settings
19+
private.py
20+
1821
# Documentation
1922
doc/_build
2023
.pip_cache

workbench/runtime.py

+47-1
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
import itertools
1010
import logging
1111
from collections import defaultdict
12+
from datetime import datetime, timedelta
1213

14+
from mock import Mock
1315
from six import iteritems
1416

1517
import django.utils.translation
1618
from django.conf import settings
19+
from django.contrib.auth.models import User
1720
from django.core.urlresolvers import reverse
1821
from django.template import loader as django_template_loader
1922
from django.templatetags.static import static
@@ -239,8 +242,9 @@ class WorkbenchRuntime(Runtime):
239242
240243
A pre-configured instance of this class will be available to XBlocks as
241244
`self.runtime`.
242-
243245
"""
246+
anonymous_student_id = 'dummydummy000-fake-fake-dummydummy00' # Needed for the LTI XBlock
247+
hostname = '127.0.0.1:8000' # Arbitrary value, needed for the LTI XBlock
244248

245249
def __init__(self, user_id=None):
246250
# TODO: Add params for user, runtime, etc. to service initialization
@@ -264,8 +268,50 @@ def __init__(self, user_id=None):
264268
self.id_generator = ID_MANAGER
265269
self.user_id = user_id
266270

271+
def get_user_role(self):
272+
"""Provide a dummy user role."""
273+
return 'Student'
274+
275+
@property
276+
def descriptor_runtime(self):
277+
"""Provide a dummy course."""
278+
course = Mock(
279+
lti_passports=['test:test:secret'],
280+
display_name_with_default='Test Course',
281+
display_org_with_default='edX',
282+
)
283+
284+
return Mock(modulestore=Mock(
285+
get_course=Mock(return_value=course)
286+
))
287+
288+
def get_real_user(self, _):
289+
"""
290+
Return a dummy user with a Mock profile.
291+
292+
Expected by the LTI Consumer XBlock.
293+
"""
294+
u = User()
295+
u.profile = Mock()
296+
u.profile.name = 'John Doe'
297+
return u
298+
299+
def _patch_xblock(self, block):
300+
"""Add required attributes by some legacy XBlocks such as the LTI Consumer XBlock."""
301+
block.location = Mock(html_id=Mock(return_value='course-v1:edX+Demo+2020'))
302+
block.course_id = block.location.html_id()
303+
block.due = datetime.utcnow()
304+
block.graceperiod = timedelta(seconds=0)
305+
block.category = 'chapter'
306+
307+
def handle(self, block, handler_name, request, suffix=''):
308+
"""Patch the XBlock with required fields."""
309+
self._patch_xblock(block)
310+
return super(WorkbenchRuntime, self).handle(block, handler_name, request, suffix)
311+
267312
def render(self, block, view_name, context=None):
268313
"""Renders using parent class render() method"""
314+
self._patch_xblock(block)
269315
try:
270316
return super(WorkbenchRuntime, self).render(block, view_name, context)
271317
except NoSuchViewError:

workbench/services.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""
2+
Module contains various XBlock services for the workbench.
3+
"""
4+
from __future__ import unicode_literals
5+
6+
from django.conf import settings
7+
8+
9+
class SettingsService(object):
10+
"""
11+
Allows server-wide configuration of XBlocks on a per-type basis.
12+
13+
The service is copied as-is from the Open edX platform:
14+
15+
- `edx-platform/common/lib/xmodule/xmodule/services.py`
16+
"""
17+
xblock_settings_bucket_selector = 'block_settings_key'
18+
19+
def get_settings_bucket(self, block, default=None):
20+
""" Gets xblock settings dictionary from settings. """
21+
if not block:
22+
raise ValueError("Expected XBlock instance, got {block} of type {type}".format(
23+
block=block,
24+
type=type(block)
25+
))
26+
27+
actual_default = default if default is not None else {}
28+
xblock_settings_bucket = getattr(block, self.xblock_settings_bucket_selector, block.unmixed_class.__name__)
29+
xblock_settings = settings.XBLOCK_SETTINGS if hasattr(settings, "XBLOCK_SETTINGS") else {}
30+
return xblock_settings.get(xblock_settings_bucket, actual_default)

workbench/settings.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,12 @@
205205
),
206206

207207
'services': {
208-
'fs': 'xblock.reference.plugins.FSService'
208+
'fs': 'xblock.reference.plugins.FSService',
209+
'settings': 'workbench.services.SettingsService',
209210
}
210211
}
212+
213+
try:
214+
from .private import * # pylint: disable=wildcard-import,import-error,useless-suppression
215+
except ImportError:
216+
pass

workbench/test/test_runtime.py

+16
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ def test_asides(self):
7979
self.assertEqual(self.id_mgr.get_usage_id_from_aside(aside_usage), usage_id)
8080

8181

82+
class WorkbenchRuntimeTests(TestCase):
83+
"""
84+
Tests for the WorkbenchRuntime.
85+
"""
86+
87+
def test_lti_consumer_xblock_requirements(self):
88+
"""
89+
The LTI Consumer XBlock expects a lot of values from the LMS Runtime,
90+
this test ensures that those requirements fulfilled.
91+
"""
92+
runtime = WorkbenchRuntime('test_user')
93+
assert runtime.get_real_user(object()), 'The LTI Consumer XBlock needs this method.'
94+
assert runtime.hostname, 'The LTI Consumer XBlock needs this property.'
95+
assert runtime.anonymous_student_id, 'The LTI Consumer XBlock needs this property.'
96+
97+
8298
class TestKVStore(TestCase):
8399
"""
84100
Test the Workbench KVP Store

0 commit comments

Comments
 (0)