Commit 6b474724 by Calen Pennington

Make XModuleDescriptor and XModule act as a single class

By transparently proxying between the XModuleDescriptor and the XModule,
and between their runtimes, we can make them act as a single class, so
that we can swap in an actual XBlock instead.
parent 3cebb4ea
......@@ -95,11 +95,6 @@ def preview_module_system(request, preview_id, descriptor):
descriptor: An XModuleDescriptor
"""
def preview_field_data(descriptor):
"Helper method to create a DbModel from a descriptor"
student_data = DbModel(SessionKeyValueStore(request))
return lms_field_data(descriptor._field_data, student_data)
course_id = get_course_for_item(descriptor.location).location.course_id
if descriptor.location.category == 'static_tab':
......@@ -118,7 +113,6 @@ def preview_module_system(request, preview_id, descriptor):
debug=True,
replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_id=course_id),
user=request.user,
xmodule_field_data=preview_field_data,
can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
mixins=settings.XBLOCK_MIXINS,
course_id=course_id,
......@@ -136,7 +130,8 @@ def preview_module_system(request, preview_id, descriptor):
getattr(descriptor, 'data_dir', descriptor.location.course),
course_id=descriptor.location.org + '/' + descriptor.location.course + '/BOGUS_RUN_REPLACE_WHEN_AVAILABLE',
),
)
),
error_descriptor_class=ErrorDescriptor,
)
......@@ -148,17 +143,12 @@ def load_preview_module(request, preview_id, descriptor):
preview_id (str): An identifier specifying which preview this module is used for
descriptor: An XModuleDescriptor
"""
system = preview_module_system(request, preview_id, descriptor)
try:
module = descriptor.xmodule(system)
except:
log.debug("Unable to load preview module", exc_info=True)
module = ErrorDescriptor.from_descriptor(
descriptor,
error_msg=exc_info_to_str(sys.exc_info())
).xmodule(system)
return module
student_data = DbModel(SessionKeyValueStore(request))
descriptor.bind_for_student(
preview_module_system(request, preview_id, descriptor),
lms_field_data(descriptor._field_data, student_data), # pylint: disable=protected-access
)
return descriptor
def get_preview_html(request, descriptor, idx):
......
......@@ -134,7 +134,7 @@ def add_histogram(user, block, view, frag, context): # pylint: disable=unused-a
return frag
block_id = block.id
if block.descriptor.has_score:
if block.has_score:
histogram = grade_histogram(block_id)
render_histogram = len(histogram) > 0
else:
......@@ -142,7 +142,7 @@ def add_histogram(user, block, view, frag, context): # pylint: disable=unused-a
render_histogram = False
if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'):
[filepath, filename] = getattr(block.descriptor, 'xml_attributes', {}).get('filename', ['', None])
[filepath, filename] = getattr(block, 'xml_attributes', {}).get('filename', ['', None])
osfs = block.system.filestore
if filename is not None and osfs.exists(filename):
# if original, unmangled filename exists then use it (github
......@@ -163,13 +163,13 @@ def add_histogram(user, block, view, frag, context): # pylint: disable=unused-a
# TODO (ichuang): use _has_access_descriptor.can_load in lms.courseware.access, instead of now>mstart comparison here
now = datetime.datetime.now(UTC())
is_released = "unknown"
mstart = block.descriptor.start
mstart = block.start
if mstart is not None:
is_released = "<font color='red'>Yes!</font>" if (now > mstart) else "<font color='green'>Not yet</font>"
staff_context = {'fields': [(name, field.read_from(block)) for name, field in block.fields.items()],
'xml_attributes': getattr(block.descriptor, 'xml_attributes', {}),
'xml_attributes': getattr(block, 'xml_attributes', {}),
'location': block.location,
'xqa_key': block.xqa_key,
'source_file': source_file,
......
......@@ -15,7 +15,7 @@ from capa.responsetypes import StudentInputError, \
ResponseError, LoncapaProblemError
from capa.util import convert_files_to_filenames
from .progress import Progress
from xmodule.x_module import XModule
from xmodule.x_module import XModule, module_attr
from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError
from xblock.fields import Scope, String, Boolean, Dict, Integer, Float
......@@ -1193,3 +1193,33 @@ class CapaDescriptor(CapaFields, RawDescriptor):
CapaDescriptor.force_save_button, CapaDescriptor.markdown,
CapaDescriptor.text_customization])
return non_editable_fields
# Proxy to CapaModule for access to any of its attributes
answer_available = module_attr('answer_available')
check_button_name = module_attr('check_button_name')
check_problem = module_attr('check_problem')
choose_new_seed = module_attr('choose_new_seed')
closed = module_attr('closed')
get_answer = module_attr('get_answer')
get_problem = module_attr('get_problem')
get_problem_html = module_attr('get_problem_html')
get_state_for_lcp = module_attr('get_state_for_lcp')
handle_input_ajax = module_attr('handle_input_ajax')
handle_problem_html_error = module_attr('handle_problem_html_error')
handle_ungraded_response = module_attr('handle_ungraded_response')
is_attempted = module_attr('is_attempted')
is_correct = module_attr('is_correct')
is_past_due = module_attr('is_past_due')
is_submitted = module_attr('is_submitted')
lcp = module_attr('lcp')
make_dict_of_responses = module_attr('make_dict_of_responses')
new_lcp = module_attr('new_lcp')
publish_grade = module_attr('publish_grade')
rescore_problem = module_attr('rescore_problem')
reset_problem = module_attr('reset_problem')
save_problem = module_attr('save_problem')
set_state_from_lcp = module_attr('set_state_from_lcp')
should_show_check_button = module_attr('should_show_check_button')
should_show_reset_button = module_attr('should_show_reset_button')
should_show_save_button = module_attr('should_show_save_button')
update_score = module_attr('update_score')
......@@ -18,6 +18,7 @@ log = logging.getLogger('mitx.' + __name__)
class ConditionalFields(object):
has_children = True
show_tag_list = List(help="Poll answers", scope=Scope.content)
......
......@@ -115,15 +115,15 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
child = self.get_display_items()[0]
out = self.runtime.render_child(child, None, 'student_view').content
# The event listener uses the ajax url to find the child.
child_url = child.runtime.ajax_url
child_id = child.id
except IndexError:
out = u"Error in loading crowdsourced hinter - can't find child problem."
child_url = ''
child_id = ''
# Wrap the module in a <section>. This lets us pass data attributes to the javascript.
out += u'<section class="crowdsource-wrapper" data-url="{ajax_url}" data-child-url="{child_url}"> </section>'.format(
out += u'<section class="crowdsource-wrapper" data-url="{ajax_url}" data-child-id="{child_id}"> </section>'.format(
ajax_url=self.runtime.ajax_url,
child_url=child_url
child_id=child_id
)
return out
......
......@@ -77,7 +77,7 @@ class ErrorDescriptor(ErrorFields, XModuleDescriptor):
module_class = ErrorModule
def get_html(self):
return ''
return u''
@classmethod
def _construct(cls, system, contents, error_msg, location):
......
......@@ -44,7 +44,7 @@
<section class="crowdsource-wrapper" data-url="/courses/Me/19.002/Test/modx/i4x://Me/19.002/crowdsource_hinter/crowdsource_hinter_def7a1142dd0" data-child-url="/courses/Me/19.002/Test/modx/i4x://Me/19.002/problem/Numerical_Input" style="display: none;"> </section>
<section class="crowdsource-wrapper" data-url="/courses/Me/19.002/Test/modx/i4x://Me/19.002/crowdsource_hinter/crowdsource_hinter_def7a1142dd0" data-child-id="i4x://Me/19.002/problem/Numerical_Input" style="display: none;"> </section>
</section>
......
......@@ -144,7 +144,7 @@ describe 'Problem', ->
contents: 'mock grader response'
callback(response)
@problem.check()
expect(Logger.log).toHaveBeenCalledWith 'problem_graded', ['foo=1&bar=2', 'mock grader response'], @problem.url
expect(Logger.log).toHaveBeenCalledWith 'problem_graded', ['foo=1&bar=2', 'mock grader response'], @problem.id
it 'submit the answer for check', ->
spyOn $, 'postWithPrefix'
......
......@@ -248,7 +248,7 @@ class @Problem
@updateProgress response
else
@gentle_alert response.success
Logger.log 'problem_graded', [@answers, response.contents], @url
Logger.log 'problem_graded', [@answers, response.contents], @id
if not abort_submission
$.ajaxWithPrefix("#{@url}/problem_check", settings)
......@@ -271,7 +271,7 @@ class @Problem
@el.removeClass 'showed'
else
@gentle_alert response.success
Logger.log 'problem_graded', [@answers, response.contents], @url
Logger.log 'problem_graded', [@answers, response.contents], @id
reset: =>
Logger.log 'problem_reset', @answers
......
......@@ -7,7 +7,7 @@ class @Hinter
constructor: (element) ->
@el = $(element).find('.crowdsource-wrapper')
@url = @el.data('url')
Logger.listen('problem_graded', @el.data('child-url'), @capture_problem)
Logger.listen('problem_graded', @el.data('child-id'), @capture_problem)
@render()
capture_problem: (event_type, data, element) =>
......
......@@ -81,7 +81,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
else:
return self._data[key.field_name]
else:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
def set(self, key, value):
if key.scope == Scope.children:
......@@ -94,7 +94,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
else:
self._data[key.field_name] = value
else:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
def delete(self, key):
if key.scope == Scope.children:
......@@ -108,7 +108,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
else:
del self._data[key.field_name]
else:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
def has(self, key):
if key.scope in (Scope.children, Scope.parent):
......
......@@ -53,12 +53,12 @@ class SplitMongoKVS(InheritanceKeyValueStore):
raise KeyError()
else:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
def set(self, key, value):
# handle any special cases
if key.scope not in [Scope.children, Scope.settings, Scope.content]:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
if key.scope == Scope.content:
self._load_definition()
......@@ -75,7 +75,7 @@ class SplitMongoKVS(InheritanceKeyValueStore):
def delete(self, key):
# handle any special cases
if key.scope not in [Scope.children, Scope.settings, Scope.content]:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
if key.scope == Scope.content:
self._load_definition()
......
......@@ -204,7 +204,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
descriptor.save()
return descriptor
render_template = lambda: ''
render_template = lambda template, context: u''
# TODO (vshnayder): we are somewhat architecturally confused in the loading code:
# load_item should actually be get_instance, because it expects the course-specific
# policy to be loaded. For now, just add the course_id here...
......
......@@ -6,7 +6,7 @@ from lxml import etree
from datetime import datetime
from pkg_resources import resource_string
from .capa_module import ComplexEncoder
from .x_module import XModule
from .x_module import XModule, module_attr
from xmodule.raw_module import RawDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from .timeinfo import TimeInfo
......@@ -106,7 +106,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
#We need to set the location here so the child modules can use it
self.runtime.set('location', self.location)
if (self.system.open_ended_grading_interface):
if (self.runtime.open_ended_grading_interface):
self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.system)
else:
self.peer_gs = MockPeerGradingService()
......@@ -662,3 +662,19 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
return [self.system.load_item(self.link_to_location)]
else:
return []
# Proxy to PeerGradingModule so that external callers don't have to know if they're working
# with a module or a descriptor
closed = module_attr('closed')
get_instance_state = module_attr('get_instance_state')
get_next_submission = module_attr('get_next_submission')
is_student_calibrated = module_attr('is_student_calibrated')
peer_grading = module_attr('peer_grading')
peer_grading_closed = module_attr('peer_grading_closed')
peer_grading_problem = module_attr('peer_grading_problem')
peer_gs = module_attr('peer_gs')
query_data_for_location = module_attr('query_data_for_location')
save_calibration_essay = module_attr('save_calibration_essay')
save_grade = module_attr('save_grade')
show_calibration_essay = module_attr('show_calibration_essay')
_find_corresponding_module_for_location = module_attr('_find_corresponding_module_for_location')
......@@ -45,9 +45,6 @@ class SequenceModule(SequenceFields, XModule):
self.rendered = False
def get_instance_state(self):
return json.dumps({'position': self.position})
def get_html(self):
self.render()
return self.content
......
......@@ -9,6 +9,7 @@ Run like this:
import json
import os
import pprint
import unittest
from mock import Mock
......@@ -18,6 +19,7 @@ from xblock.field_data import DictFieldData
from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin
from xmodule.modulestore.inheritance import InheritanceMixin
from xmodule.mako_module import MakoDescriptorSystem
from xmodule.error_module import ErrorDescriptor
# Location of common test DATA directory
......@@ -54,18 +56,18 @@ def get_test_system(course_id=''):
ajax_url='courses/course_id/modx/a_location',
track_function=Mock(),
get_module=Mock(),
render_template=lambda template, context: repr(context),
replace_urls=lambda html: str(html),
render_template=mock_render_template,
replace_urls=str,
user=Mock(is_staff=False),
filestore=Mock(),
debug=True,
hostname="edx.org",
xqueue={'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10, 'construct_callback' : Mock(side_effect="/")},
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
xmodule_field_data=lambda descriptor: descriptor._field_data,
anonymous_student_id='student',
open_ended_grading_interface=open_ended_grading_interface,
course_id=course_id,
error_descriptor_class=ErrorDescriptor,
)
......@@ -77,11 +79,21 @@ def get_test_descriptor_system():
load_item=Mock(),
resources_fs=Mock(),
error_tracker=Mock(),
render_template=lambda template, context: repr(context),
render_template=mock_render_template,
mixins=(InheritanceMixin, XModuleMixin),
)
def mock_render_template(*args, **kwargs):
"""
Pretty-print the args and kwargs.
Allows us to not depend on any actual template rendering mechanism,
while still returning a unicode object
"""
return pprint.pformat((args, kwargs)).decode()
class ModelsTest(unittest.TestCase):
def setUp(self):
pass
......
......@@ -753,28 +753,36 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
#Simulate a student saving an answer
html = module.handle_ajax("get_html", {})
module.save()
module.handle_ajax("save_answer", {"student_answer": self.answer})
module.save()
html = module.handle_ajax("get_html", {})
module.save()
#Mock a student submitting an assessment
assessment_dict = MockQueryDict()
assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment})
module.handle_ajax("save_assessment", assessment_dict)
module.save()
task_one_json = json.loads(module.task_states[0])
self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
rubric = module.handle_ajax("get_combined_rubric", {})
module.save()
#Move to the next step in the problem
module.handle_ajax("next_problem", {})
module.save()
self.assertEqual(module.current_task_number, 0)
html = module.get_html()
html = module.runtime.render(module, None, 'student_view').content
self.assertIsInstance(html, basestring)
rubric = module.handle_ajax("get_combined_rubric", {})
module.save()
self.assertIsInstance(rubric, basestring)
self.assertEqual(module.state, "assessing")
module.handle_ajax("reset", {})
module.save()
self.assertEqual(module.current_task_number, 0)
def test_open_ended_flow_correct(self):
......@@ -789,31 +797,36 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
#Simulate a student saving an answer
module.handle_ajax("save_answer", {"student_answer": self.answer})
module.save()
status = module.handle_ajax("get_status", {})
module.save()
self.assertIsInstance(status, basestring)
#Mock a student submitting an assessment
assessment_dict = MockQueryDict()
assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment})
module.handle_ajax("save_assessment", assessment_dict)
module.save()
task_one_json = json.loads(module.task_states[0])
self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
#Move to the next step in the problem
try:
module.handle_ajax("next_problem", {})
module.save()
except GradingServiceError:
#This error is okay. We don't have a grading service to connect to!
pass
self.assertEqual(module.current_task_number, 1)
try:
module.get_html()
module.runtime.render(module, None, 'student_view')
except GradingServiceError:
#This error is okay. We don't have a grading service to connect to!
pass
#Try to get the rubric from the module
module.handle_ajax("get_combined_rubric", {})
module.save()
#Make a fake reply from the queue
queue_reply = {
......@@ -832,22 +845,27 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
}
module.handle_ajax("check_for_score", {})
module.save()
#Update the module with the fake queue reply
module.handle_ajax("score_update", queue_reply)
module.save()
self.assertFalse(module.ready_to_reset)
self.assertEqual(module.current_task_number, 1)
#Get html and other data client will request
module.get_html()
module.runtime.render(module, None, 'student_view')
module.handle_ajax("skip_post_assessment", {})
module.save()
#Get all results
module.handle_ajax("get_combined_rubric", {})
module.save()
#reset the problem
module.handle_ajax("reset", {})
module.save()
self.assertEqual(module.state, "initial")
......@@ -876,31 +894,37 @@ class OpenEndedModuleXmlAttemptTest(unittest.TestCase, DummyModulestore):
"""
assessment = [0, 1]
module = self.get_module_from_location(self.problem_location, COURSE)
module.save()
#Simulate a student saving an answer
module.handle_ajax("save_answer", {"student_answer": self.answer})
module.save()
#Mock a student submitting an assessment
assessment_dict = MockQueryDict()
assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment})
module.handle_ajax("save_assessment", assessment_dict)
module.save()
task_one_json = json.loads(module.task_states[0])
self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
#Move to the next step in the problem
module.handle_ajax("next_problem", {})
module.save()
self.assertEqual(module.current_task_number, 0)
html = module.get_html()
self.assertTrue(isinstance(html, basestring))
html = module.runtime.render(module, None, 'student_view').content
self.assertIsInstance(html, basestring)
#Module should now be done
rubric = module.handle_ajax("get_combined_rubric", {})
self.assertTrue(isinstance(rubric, basestring))
module.save()
self.assertIsInstance(rubric, basestring)
self.assertEqual(module.state, "done")
#Try to reset, should fail because only 1 attempt is allowed
reset_data = json.loads(module.handle_ajax("reset", {}))
module.save()
self.assertEqual(reset_data['success'], False)
class OpenEndedModuleXmlImageUploadTest(unittest.TestCase, DummyModulestore):
......
from ast import literal_eval
import json
import unittest
......@@ -8,12 +7,11 @@ from mock import Mock, patch
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
from xblock.fragment import Fragment
from xmodule.error_module import NonStaffErrorDescriptor
from xmodule.modulestore import Location
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
from xmodule.conditional_module import ConditionalModule
from xmodule.tests import DATA_DIR, get_test_system
from xmodule.conditional_module import ConditionalDescriptor
from xmodule.tests import DATA_DIR, get_test_system, get_test_descriptor_system
ORG = 'test_org'
......@@ -26,20 +24,15 @@ class DummySystem(ImportSystem):
def __init__(self, load_error_modules):
xmlstore = XMLModuleStore("data_dir", course_dirs=[], load_error_modules=load_error_modules)
course_id = "/".join([ORG, COURSE, 'test_run'])
course_dir = "test_dir"
policy = {}
error_tracker = Mock()
parent_tracker = Mock()
super(DummySystem, self).__init__(
xmlstore,
course_id,
course_dir,
policy,
error_tracker,
parent_tracker,
xmlstore=xmlstore,
course_id='/'.join([ORG, COURSE, 'test_run']),
course_dir='test_dir',
error_tracker=Mock(),
parent_tracker=Mock(),
load_error_modules=load_error_modules,
policy={},
)
def render_template(self, template, context):
......@@ -59,52 +52,55 @@ class ConditionalFactory(object):
if the source_is_error_module flag is set, create a real ErrorModule for the source.
"""
descriptor_system = get_test_descriptor_system()
# construct source descriptor and module:
source_location = Location(["i4x", "edX", "conditional_test", "problem", "SampleProblem"])
if source_is_error_module:
# Make an error descriptor and module
source_descriptor = NonStaffErrorDescriptor.from_xml('some random xml data',
source_descriptor = NonStaffErrorDescriptor.from_xml(
'some random xml data',
system,
org=source_location.org,
course=source_location.course,
error_msg='random error message')
source_module = source_descriptor.xmodule(system)
error_msg='random error message'
)
else:
source_descriptor = Mock()
source_descriptor.location = source_location
source_module = Mock()
source_descriptor.runtime = descriptor_system
# construct other descriptors:
child_descriptor = Mock()
cond_descriptor = Mock()
cond_descriptor.runtime = system
cond_descriptor.get_required_module_descriptors = lambda: [source_descriptor, ]
cond_descriptor.get_children = lambda: [child_descriptor, ]
cond_descriptor.xml_attributes = {"attempted": "true"}
# create child module:
child_module = Mock()
child_module.runtime = system
child_module.get_html.return_value = u'<p>This is a secret</p>'
child_module.student_view.return_value = Fragment(child_module.get_html.return_value)
child_module.displayable_items = lambda: [child_module]
module_map = {source_descriptor: source_module, child_descriptor: child_module}
system.get_module = lambda descriptor: module_map[descriptor]
child_descriptor._xmodule.student_view.return_value.content = u'<p>This is a secret</p>'
child_descriptor.displayable_items.return_value = [child_descriptor]
child_descriptor.runtime = descriptor_system
child_descriptor.xmodule_runtime = get_test_system()
descriptor_system.load_item = {'child': child_descriptor, 'source': source_descriptor}.get
# construct conditional module:
cond_location = Location(["i4x", "edX", "conditional_test", "conditional", "SampleConditional"])
field_data = DictFieldData({'data': '<conditional/>', 'location': cond_location})
cond_module = ConditionalModule(
cond_descriptor,
system,
field_data = DictFieldData({
'data': '<conditional/>',
'xml_attributes': {'attempted': 'true'},
'children': ['child'],
})
cond_descriptor = ConditionalDescriptor(
descriptor_system,
field_data,
ScopeIds(None, None, cond_location, cond_location)
)
cond_descriptor.xmodule_runtime = system
system.get_module = lambda desc: desc
cond_descriptor.get_required_module_descriptors = Mock(return_value=[source_descriptor])
# return dict:
return {'cond_module': cond_module,
'source_module': source_module,
'child_module': child_module}
return {'cond_module': cond_descriptor,
'source_module': source_descriptor,
'child_module': child_descriptor}
class ConditionalModuleBasicTest(unittest.TestCase):
......@@ -129,16 +125,20 @@ class ConditionalModuleBasicTest(unittest.TestCase):
modules = ConditionalFactory.create(self.test_system)
# because get_test_system returns the repr of the context dict passed to render_template,
# we reverse it here
html = modules['cond_module'].get_html()
html_dict = literal_eval(html)
self.assertEqual(html_dict['element_id'], 'i4x-edX-conditional_test-conditional-SampleConditional')
self.assertEqual(html_dict['id'], 'i4x://edX/conditional_test/conditional/SampleConditional')
self.assertEqual(html_dict['depends'], 'i4x-edX-conditional_test-problem-SampleProblem')
html = modules['cond_module'].runtime.render(modules['cond_module'], None, 'student_view').content
expected = modules['cond_module'].xmodule_runtime.render_template('conditional_ajax.html', {
'ajax_url': modules['cond_module'].xmodule_runtime.ajax_url,
'element_id': 'i4x-edX-conditional_test-conditional-SampleConditional',
'id': 'i4x://edX/conditional_test/conditional/SampleConditional',
'depends': 'i4x-edX-conditional_test-problem-SampleProblem',
})
self.assertEquals(expected, html)
def test_handle_ajax(self):
modules = ConditionalFactory.create(self.test_system)
modules['source_module'].is_attempted = "false"
ajax = json.loads(modules['cond_module'].handle_ajax('', ''))
modules['cond_module'].save()
print "ajax: ", ajax
html = ajax['html']
self.assertFalse(any(['This is a secret' in item for item in html]))
......@@ -146,6 +146,7 @@ class ConditionalModuleBasicTest(unittest.TestCase):
# now change state of the capa problem to make it completed
modules['source_module'].is_attempted = "true"
ajax = json.loads(modules['cond_module'].handle_ajax('', ''))
modules['cond_module'].save()
print "post-attempt ajax: ", ajax
html = ajax['html']
self.assertTrue(any(['This is a secret' in item for item in html]))
......@@ -157,6 +158,7 @@ class ConditionalModuleBasicTest(unittest.TestCase):
'''
modules = ConditionalFactory.create(self.test_system, source_is_error_module=True)
ajax = json.loads(modules['cond_module'].handle_ajax('', ''))
modules['cond_module'].save()
html = ajax['html']
self.assertFalse(any(['This is a secret' in item for item in html]))
......@@ -196,7 +198,9 @@ class ConditionalModuleXmlTest(unittest.TestCase):
if isinstance(descriptor, Location):
location = descriptor
descriptor = self.modulestore.get_instance(course.id, location, depth=None)
return descriptor.xmodule(self.test_system)
descriptor.xmodule_runtime = get_test_system()
descriptor.xmodule_runtime.get_module = inner_get_module
return descriptor
# edx - HarvardX
# cond_test - ER22x
......@@ -209,20 +213,28 @@ class ConditionalModuleXmlTest(unittest.TestCase):
module = inner_get_module(location)
print "module: ", module
print "module.conditions_map: ", module.conditions_map
print "module children: ", module.get_children()
print "module display items (children): ", module.get_display_items()
html = module.get_html()
html = module.runtime.render(module, None, 'student_view').content
print "html type: ", type(html)
print "html: ", html
html_expect = "{'ajax_url': 'courses/course_id/modx/a_location', 'element_id': 'i4x-HarvardX-ER22x-conditional-condone', 'id': 'i4x://HarvardX/ER22x/conditional/condone', 'depends': 'i4x-HarvardX-ER22x-problem-choiceprob'}"
html_expect = module.xmodule_runtime.render_template(
'conditional_ajax.html',
{
'ajax_url': 'courses/course_id/modx/a_location',
'element_id': 'i4x-HarvardX-ER22x-conditional-condone',
'id': 'i4x://HarvardX/ER22x/conditional/condone',
'depends': 'i4x-HarvardX-ER22x-problem-choiceprob'
}
)
self.assertEqual(html, html_expect)
gdi = module.get_display_items()
print "gdi=", gdi
ajax = json.loads(module.handle_ajax('', ''))
module.save()
print "ajax: ", ajax
html = ajax['html']
self.assertFalse(any(['This is a secret' in item for item in html]))
......@@ -234,6 +246,7 @@ class ConditionalModuleXmlTest(unittest.TestCase):
inner_module.save()
ajax = json.loads(module.handle_ajax('', ''))
module.save()
print "post-attempt ajax: ", ajax
html = ajax['html']
self.assertTrue(any(['This is a secret' in item for item in html]))
......@@ -10,6 +10,7 @@ from xmodule.crowdsource_hinter import CrowdsourceHinterModule
from xmodule.vertical_module import VerticalModule, VerticalDescriptor
from xblock.field_data import DictFieldData
from xblock.fragment import Fragment
from xblock.core import XBlock
from . import get_test_system
......@@ -62,7 +63,8 @@ class CHModuleFactory(object):
"""
A factory method for making CHM's
"""
field_data = {'data': CHModuleFactory.sample_problem_xml}
# Should have a single child, but it doesn't matter what that child is
field_data = {'data': CHModuleFactory.sample_problem_xml, 'children': [None]}
if hints is not None:
field_data['hints'] = hints
......@@ -106,7 +108,8 @@ class CHModuleFactory(object):
# Make the descriptor have a capa problem child.
capa_descriptor = MagicMock()
capa_descriptor.name = 'capa'
descriptor.get_children = lambda: [capa_descriptor]
capa_descriptor.displayable_items.return_value = [capa_descriptor]
descriptor.get_children.return_value = [capa_descriptor]
# Make a fake capa module.
capa_module = MagicMock()
......@@ -128,7 +131,7 @@ class CHModuleFactory(object):
responder.compare_answer = compare_answer
capa_module.lcp.responders = {'responder0': responder}
capa_module.displayable_items = lambda: [capa_module]
capa_module.displayable_items.return_value = [capa_module]
system = get_test_system()
# Make the system have a marginally-functional get_module
......@@ -137,7 +140,6 @@ class CHModuleFactory(object):
"""
A fake module-maker.
"""
if descriptor.name == 'capa':
return capa_module
system.get_module = fake_get_module
module = CrowdsourceHinterModule(descriptor, system, DictFieldData(field_data), Mock())
......@@ -205,15 +207,15 @@ class VerticalWithModulesFactory(object):
return module
class FakeChild(object):
class FakeChild(XBlock):
"""
A fake Xmodule.
"""
def __init__(self):
self.runtime = get_test_system()
self.runtime.ajax_url = 'this/is/a/fake/ajax/url'
self.student_view = Mock(return_value=Fragment(self.get_html()))
self.save = Mock()
self.id = 'i4x://this/is/a/fake/id'
def get_html(self):
"""
......@@ -243,7 +245,7 @@ class CrowdsourceHinterTest(unittest.TestCase):
mock_module.get_display_items = fake_get_display_items
out_html = mock_module.runtime.render(mock_module, None, 'student_view').content
self.assertTrue('This is supposed to be test html.' in out_html)
self.assertTrue('this/is/a/fake/ajax/url' in out_html)
self.assertTrue('i4x://this/is/a/fake/id' in out_html)
def test_gethtml_nochild(self):
"""
......
......@@ -30,8 +30,8 @@ class TestErrorModule(unittest.TestCase, SetupTestErrorModules):
descriptor = error_module.ErrorDescriptor.from_xml(
self.valid_xml, self.system, self.org, self.course, self.error_msg)
self.assertIsInstance(descriptor, error_module.ErrorDescriptor)
module = descriptor.xmodule(self.system)
context_repr = module.get_html()
descriptor.xmodule_runtime = self.system
context_repr = self.system.render(descriptor, None, 'student_view').content
self.assertIn(self.error_msg, context_repr)
self.assertIn(repr(self.valid_xml), context_repr)
......@@ -44,8 +44,8 @@ class TestErrorModule(unittest.TestCase, SetupTestErrorModules):
error_descriptor = error_module.ErrorDescriptor.from_descriptor(
descriptor, self.error_msg)
self.assertIsInstance(error_descriptor, error_module.ErrorDescriptor)
module = error_descriptor.xmodule(self.system)
context_repr = module.get_html()
error_descriptor.xmodule_runtime = self.system
context_repr = self.system.render(error_descriptor, None, 'student_view').content
self.assertIn(self.error_msg, context_repr)
self.assertIn(repr(descriptor), context_repr)
......@@ -65,8 +65,8 @@ class TestNonStaffErrorModule(unittest.TestCase, SetupTestErrorModules):
def test_from_xml_render(self):
descriptor = error_module.NonStaffErrorDescriptor.from_xml(
self.valid_xml, self.system, self.org, self.course)
module = descriptor.xmodule(self.system)
context_repr = module.get_html()
descriptor.xmodule_runtime = self.system
context_repr = self.system.render(descriptor, None, 'student_view').content
self.assertNotIn(self.error_msg, context_repr)
self.assertNotIn(repr(self.valid_xml), context_repr)
......@@ -79,7 +79,7 @@ class TestNonStaffErrorModule(unittest.TestCase, SetupTestErrorModules):
error_descriptor = error_module.NonStaffErrorDescriptor.from_descriptor(
descriptor, self.error_msg)
self.assertIsInstance(error_descriptor, error_module.ErrorDescriptor)
module = error_descriptor.xmodule(self.system)
context_repr = module.get_html()
error_descriptor.xmodule_runtime = self.system
context_repr = self.system.render(error_descriptor, None, 'student_view').content
self.assertNotIn(self.error_msg, context_repr)
self.assertNotIn(str(descriptor), context_repr)
......@@ -199,6 +199,7 @@ class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore):
html = peer_grading.peer_grading()
self.assertIn("Peer-Graded", html)
class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore):
"""
Test peer grading that is linked to an open ended module.
......
......@@ -137,6 +137,6 @@ class ModuleProgressTest(unittest.TestCase):
'''
def test_xmodule_default(self):
'''Make sure default get_progress exists, returns None'''
xm = x_module.XModule(None, get_test_system(), DictFieldData({'location': 'a://b/c/d/e'}), Mock())
xm = x_module.XModule(Mock(), get_test_system(), DictFieldData({'location': 'a://b/c/d/e'}), Mock())
p = xm.get_progress()
self.assertEqual(p, None)
......@@ -103,7 +103,8 @@ class DummyModulestore(object):
if not isinstance(location, Location):
location = Location(location)
descriptor = self.modulestore.get_instance(course.id, location, depth=None)
return descriptor.xmodule(self.test_system)
descriptor.xmodule_runtime = self.test_system
return descriptor
# Task state for a module with self assessment then instructor assessment.
TEST_STATE_SA_IN = ["{\"child_created\": false, \"child_attempts\": 2, \"version\": 1, \"child_history\": [{\"answer\": \"However venture pursuit he am mr cordial. Forming musical am hearing studied be luckily. Ourselves for determine attending how led gentleman sincerity. Valley afford uneasy joy she thrown though bed set. In me forming general prudent on country carried. Behaved an or suppose justice. Seemed whence how son rather easily and change missed. Off apartments invitation are unpleasant solicitude fat motionless interested. Hardly suffer wisdom wishes valley as an. As friendship advantages resolution it alteration stimulated he or increasing. \\r<br><br>Now led tedious shy lasting females off. Dashwood marianne in of entrance be on wondered possible building. Wondered sociable he carriage in speedily margaret. Up devonshire of he thoroughly insensible alteration. An mr settling occasion insisted distance ladyship so. Not attention say frankness intention out dashwoods now curiosity. Stronger ecstatic as no judgment daughter speedily thoughts. Worse downs nor might she court did nay forth these. \", \"post_assessment\": \"[3, 3, 2, 2, 2]\", \"score\": 12}, {\"answer\": \"Delightful remarkably mr on announcing themselves entreaties favourable. About to in so terms voice at. Equal an would is found seems of. The particular friendship one sufficient terminated frequently themselves. It more shed went up is roof if loud case. Delay music in lived noise an. Beyond genius really enough passed is up. \\r<br><br>John draw real poor on call my from. May she mrs furnished discourse extremely. Ask doubt noisy shade guest did built her him. Ignorant repeated hastened it do. Consider bachelor he yourself expenses no. Her itself active giving for expect vulgar months. Discovery commanded fat mrs remaining son she principle middleton neglected. Be miss he in post sons held. No tried is defer do money scale rooms. \", \"post_assessment\": \"[3, 3, 2, 2, 2]\", \"score\": 12}], \"max_score\": 12, \"child_state\": \"done\"}", "{\"child_created\": false, \"child_attempts\": 0, \"version\": 1, \"child_history\": [{\"answer\": \"However venture pursuit he am mr cordial. Forming musical am hearing studied be luckily. Ourselves for determine attending how led gentleman sincerity. Valley afford uneasy joy she thrown though bed set. In me forming general prudent on country carried. Behaved an or suppose justice. Seemed whence how son rather easily and change missed. Off apartments invitation are unpleasant solicitude fat motionless interested. Hardly suffer wisdom wishes valley as an. As friendship advantages resolution it alteration stimulated he or increasing. \\r<br><br>Now led tedious shy lasting females off. Dashwood marianne in of entrance be on wondered possible building. Wondered sociable he carriage in speedily margaret. Up devonshire of he thoroughly insensible alteration. An mr settling occasion insisted distance ladyship so. Not attention say frankness intention out dashwoods now curiosity. Stronger ecstatic as no judgment daughter speedily thoughts. Worse downs nor might she court did nay forth these. \", \"post_assessment\": \"{\\\"submission_id\\\": 1460, \\\"score\\\": 12, \\\"feedback\\\": \\\"{\\\\\\\"feedback\\\\\\\": \\\\\\\"\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 5413, \\\"grader_type\\\": \\\"IN\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>\\\\nIdeas\\\\n</description><score>3</score><option points='0'>\\\\nDifficult for the reader to discern the main idea. Too brief or too repetitive to establish or maintain a focus.\\\\n</option><option points='1'>\\\\nAttempts a main idea. Sometimes loses focus or ineffectively displays focus.\\\\n</option><option points='2'>\\\\nPresents a unifying theme or main idea, but may include minor tangents. Stays somewhat focused on topic and task.\\\\n</option><option points='3'>\\\\nPresents a unifying theme or main idea without going off on tangents. Stays completely focused on topic and task.\\\\n</option></category><category><description>\\\\nContent\\\\n</description><score>3</score><option points='0'>\\\\nIncludes little information with few or no details or unrelated details. Unsuccessful in attempts to explore any facets of the topic.\\\\n</option><option points='1'>\\\\nIncludes little information and few or no details. Explores only one or two facets of the topic.\\\\n</option><option points='2'>\\\\nIncludes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.) Explores some facets of the topic.\\\\n</option><option points='3'>\\\\nIncludes in-depth information and exceptional supporting details that are fully developed. Explores all facets of the topic.\\\\n</option></category><category><description>\\\\nOrganization\\\\n</description><score>2</score><option points='0'>\\\\nIdeas organized illogically, transitions weak, and response difficult to follow.\\\\n</option><option points='1'>\\\\nAttempts to logically organize ideas. Attempts to progress in an order that enhances meaning, and demonstrates use of transitions.\\\\n</option><option points='2'>\\\\nIdeas organized logically. Progresses in an order that enhances meaning. Includes smooth transitions.\\\\n</option></category><category><description>\\\\nStyle\\\\n</description><score>2</score><option points='0'>\\\\nContains limited vocabulary, with many words used incorrectly. Demonstrates problems with sentence patterns.\\\\n</option><option points='1'>\\\\nContains basic vocabulary, with words that are predictable and common. Contains mostly simple sentences (although there may be an attempt at more varied sentence patterns).\\\\n</option><option points='2'>\\\\nIncludes vocabulary to make explanations detailed and precise. Includes varied sentence patterns, including complex sentences.\\\\n</option></category><category><description>\\\\nVoice\\\\n</description><score>2</score><option points='0'>\\\\nDemonstrates language and tone that may be inappropriate to task and reader.\\\\n</option><option points='1'>\\\\nDemonstrates an attempt to adjust language and tone to task and reader.\\\\n</option><option points='2'>\\\\nDemonstrates effective adjustment of language and tone to task and reader.\\\\n</option></category></rubric>\\\"}\", \"score\": 12}, {\"answer\": \"Delightful remarkably mr on announcing themselves entreaties favourable. About to in so terms voice at. Equal an would is found seems of. The particular friendship one sufficient terminated frequently themselves. It more shed went up is roof if loud case. Delay music in lived noise an. Beyond genius really enough passed is up. \\r<br><br>John draw real poor on call my from. May she mrs furnished discourse extremely. Ask doubt noisy shade guest did built her him. Ignorant repeated hastened it do. Consider bachelor he yourself expenses no. Her itself active giving for expect vulgar months. Discovery commanded fat mrs remaining son she principle middleton neglected. Be miss he in post sons held. No tried is defer do money scale rooms. \", \"post_assessment\": \"{\\\"submission_id\\\": 1462, \\\"score\\\": 12, \\\"feedback\\\": \\\"{\\\\\\\"feedback\\\\\\\": \\\\\\\"\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 5418, \\\"grader_type\\\": \\\"IN\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"<rubric><category><description>\\\\nIdeas\\\\n</description><score>3</score><option points='0'>\\\\nDifficult for the reader to discern the main idea. Too brief or too repetitive to establish or maintain a focus.\\\\n</option><option points='1'>\\\\nAttempts a main idea. Sometimes loses focus or ineffectively displays focus.\\\\n</option><option points='2'>\\\\nPresents a unifying theme or main idea, but may include minor tangents. Stays somewhat focused on topic and task.\\\\n</option><option points='3'>\\\\nPresents a unifying theme or main idea without going off on tangents. Stays completely focused on topic and task.\\\\n</option></category><category><description>\\\\nContent\\\\n</description><score>3</score><option points='0'>\\\\nIncludes little information with few or no details or unrelated details. Unsuccessful in attempts to explore any facets of the topic.\\\\n</option><option points='1'>\\\\nIncludes little information and few or no details. Explores only one or two facets of the topic.\\\\n</option><option points='2'>\\\\nIncludes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.) Explores some facets of the topic.\\\\n</option><option points='3'>\\\\nIncludes in-depth information and exceptional supporting details that are fully developed. Explores all facets of the topic.\\\\n</option></category><category><description>\\\\nOrganization\\\\n</description><score>2</score><option points='0'>\\\\nIdeas organized illogically, transitions weak, and response difficult to follow.\\\\n</option><option points='1'>\\\\nAttempts to logically organize ideas. Attempts to progress in an order that enhances meaning, and demonstrates use of transitions.\\\\n</option><option points='2'>\\\\nIdeas organized logically. Progresses in an order that enhances meaning. Includes smooth transitions.\\\\n</option></category><category><description>\\\\nStyle\\\\n</description><score>2</score><option points='0'>\\\\nContains limited vocabulary, with many words used incorrectly. Demonstrates problems with sentence patterns.\\\\n</option><option points='1'>\\\\nContains basic vocabulary, with words that are predictable and common. Contains mostly simple sentences (although there may be an attempt at more varied sentence patterns).\\\\n</option><option points='2'>\\\\nIncludes vocabulary to make explanations detailed and precise. Includes varied sentence patterns, including complex sentences.\\\\n</option></category><category><description>\\\\nVoice\\\\n</description><score>2</score><option points='0'>\\\\nDemonstrates language and tone that may be inappropriate to task and reader.\\\\n</option><option points='1'>\\\\nDemonstrates an attempt to adjust language and tone to task and reader.\\\\n</option><option points='2'>\\\\nDemonstrates effective adjustment of language and tone to task and reader.\\\\n</option></category></rubric>\\\"}\", \"score\": 12}], \"max_score\": 12, \"child_state\": \"post_assessment\"}"]
......
......@@ -2,6 +2,8 @@
Tests for the wrapping layer that provides the XBlock API using XModule/Descriptor
functionality
"""
# For tests, ignore access to protected members
# pylint: disable=protected-access
from nose.tools import assert_equal # pylint: disable=E0611
from unittest.case import SkipTest
......@@ -17,6 +19,7 @@ from xmodule.capa_module import CapaDescriptor
from xmodule.course_module import CourseDescriptor
from xmodule.combined_open_ended_module import CombinedOpenEndedDescriptor
from xmodule.discussion_module import DiscussionDescriptor
from xmodule.error_module import ErrorDescriptor
from xmodule.gst_module import GraphicalSliderToolDescriptor
from xmodule.html_module import HtmlDescriptor
from xmodule.peer_grading_module import PeerGradingDescriptor
......@@ -29,7 +32,7 @@ from xmodule.conditional_module import ConditionalDescriptor
from xmodule.randomize_module import RandomizeDescriptor
from xmodule.vertical_module import VerticalDescriptor
from xmodule.wrapper_module import WrapperDescriptor
from xmodule.tests import get_test_descriptor_system
from xmodule.tests import get_test_descriptor_system, mock_render_template
LEAF_XMODULES = (
AnnotatableDescriptor,
......@@ -40,21 +43,20 @@ LEAF_XMODULES = (
HtmlDescriptor,
PeerGradingDescriptor,
PollDescriptor,
WordCloudDescriptor,
# This is being excluded because it has dependencies on django
#VideoDescriptor,
WordCloudDescriptor,
)
CONTAINER_XMODULES = (
CrowdsourceHinterDescriptor,
CourseDescriptor,
SequenceDescriptor,
ConditionalDescriptor,
CourseDescriptor,
CrowdsourceHinterDescriptor,
RandomizeDescriptor,
SequenceDescriptor,
VerticalDescriptor,
WrapperDescriptor,
CourseDescriptor,
)
# These modules are editable in studio yet
......@@ -66,26 +68,24 @@ NOT_STUDIO_EDITABLE = (
class TestXBlockWrapper(object):
@property
def leaf_module_runtime(self):
runtime = ModuleSystem(
render_template=lambda *args, **kwargs: u'{!r}, {!r}'.format(args, kwargs),
render_template=mock_render_template,
anonymous_student_id='dummy_anonymous_student_id',
open_ended_grading_interface={},
static_url='/static',
ajax_url='dummy_ajax_url',
xmodule_field_data=lambda d: d._field_data,
get_module=Mock(),
replace_urls=Mock(),
track_function=Mock(),
error_descriptor_class=ErrorDescriptor,
)
return runtime
def leaf_descriptor(self, descriptor_cls):
location = 'i4x://org/course/category/name'
runtime = get_test_descriptor_system()
runtime.render_template = lambda *args, **kwargs: u'{!r}, {!r}'.format(args, kwargs)
return runtime.construct_xblock_from_class(
descriptor_cls,
ScopeIds(None, descriptor_cls.__name__, location, location),
......@@ -93,7 +93,10 @@ class TestXBlockWrapper(object):
)
def leaf_module(self, descriptor_cls):
return self.leaf_descriptor(descriptor_cls).xmodule(self.leaf_module_runtime)
"""Returns a descriptor that is ready to proxy as an xmodule"""
descriptor = self.leaf_descriptor(descriptor_cls)
descriptor.xmodule_runtime = self.leaf_module_runtime
return descriptor
def container_module_runtime(self, depth):
runtime = self.leaf_module_runtime
......@@ -104,10 +107,16 @@ class TestXBlockWrapper(object):
runtime.position = 2
return runtime
def container_descriptor(self, descriptor_cls):
def container_descriptor(self, descriptor_cls, depth):
"""Return an instance of `descriptor_cls` with `depth` levels of children"""
location = 'i4x://org/course/category/name'
runtime = get_test_descriptor_system()
runtime.render_template = lambda *args, **kwargs: u'{!r}, {!r}'.format(args, kwargs)
if depth == 0:
runtime.load_item.side_effect = lambda x: self.leaf_module(HtmlDescriptor)
else:
runtime.load_item.side_effect = lambda x: self.container_module(VerticalDescriptor, depth - 1)
return runtime.construct_xblock_from_class(
descriptor_cls,
ScopeIds(None, descriptor_cls.__name__, location, location),
......@@ -117,7 +126,10 @@ class TestXBlockWrapper(object):
)
def container_module(self, descriptor_cls, depth):
return self.container_descriptor(descriptor_cls).xmodule(self.container_module_runtime(depth))
"""Returns a descriptor that is ready to proxy as an xmodule"""
descriptor = self.container_descriptor(descriptor_cls, depth)
descriptor.xmodule_runtime = self.container_module_runtime(depth)
return descriptor
class TestStudentView(TestXBlockWrapper):
......@@ -131,9 +143,11 @@ class TestStudentView(TestXBlockWrapper):
# Check that when an xmodule is instantiated from descriptor_cls
# it generates the same thing from student_view that it does from get_html
def check_student_view_leaf_node(self, descriptor_cls):
xmodule = self.leaf_module(descriptor_cls)
assert_equal(xmodule.get_html(), xmodule.runtime.render(xmodule, None, 'student_view').content)
descriptor = self.leaf_module(descriptor_cls)
assert_equal(
descriptor._xmodule.get_html(),
descriptor.runtime.render(descriptor, None, 'student_view').content
)
# Test that for all container XModule Descriptors,
# their corresponding XModule renders the same thing using student_view
......@@ -147,13 +161,15 @@ class TestStudentView(TestXBlockWrapper):
yield self.check_student_view_container_node_mixed, descriptor_cls
yield self.check_student_view_container_node_xblocks_only, descriptor_cls
# Check that when an xmodule is generated from descriptor_cls
# with only xmodule children, it generates the same html from student_view
# as it does using get_html
def check_student_view_container_node_xmodules_only(self, descriptor_cls):
xmodule = self.container_module(descriptor_cls, 2)
assert_equal(xmodule.get_html(), xmodule.runtime.render(xmodule, None, 'student_view').content)
descriptor = self.container_module(descriptor_cls, 2)
assert_equal(
descriptor._xmodule.get_html(),
descriptor.runtime.render(descriptor, None, 'student_view').content
)
# Check that when an xmodule is generated from descriptor_cls
# with mixed xmodule and xblock children, it generates the same html from student_view
......@@ -206,7 +222,7 @@ class TestStudioView(TestXBlockWrapper):
if descriptor_cls in NOT_STUDIO_EDITABLE:
raise SkipTest(descriptor_cls.__name__ + "is not editable in studio")
descriptor = self.container_descriptor(descriptor_cls)
descriptor = self.container_descriptor(descriptor_cls, 2)
assert_equal(descriptor.get_html(), descriptor.runtime.render(descriptor, None, 'studio_view').content)
# Check that when a descriptor is generated from descriptor_cls
......
......@@ -157,10 +157,6 @@ class VideoModule(VideoFields, XModule):
log.debug(u"DISPATCH {0}".format(dispatch))
raise Http404()
def get_instance_state(self):
"""Return information about state (position)."""
return json.dumps({'position': self.position})
def get_html(self):
caption_asset_path = "/static/subs/"
......
import logging
import yaml
import os
import sys
from functools import partial
from lxml import etree
from collections import namedtuple
from pkg_resources import resource_listdir, resource_string, resource_isdir
......@@ -13,6 +15,7 @@ from xblock.core import XBlock
from xblock.fields import Scope, Integer, Float, List, XBlockMixin, String
from xblock.fragment import Fragment
from xblock.runtime import Runtime
from xmodule.errortracker import exc_info_to_str
from xmodule.modulestore.locator import BlockUsageLocator
log = logging.getLogger(__name__)
......@@ -214,6 +217,114 @@ class XModuleMixin(XBlockMixin):
return child
return None
def get_icon_class(self):
"""
Return a css class identifying this module in the context of an icon
"""
return self.icon_class
# Functions used in the LMS
def get_score(self):
"""
Score the student received on the problem, or None if there is no
score.
Returns:
dictionary
{'score': integer, from 0 to get_max_score(),
'total': get_max_score()}
NOTE (vshnayder): not sure if this was the intended return value, but
that's what it's doing now. I suspect that we really want it to just
return a number. Would need to change (at least) capa and
modx_dispatch to match if we did that.
"""
return None
def max_score(self):
""" Maximum score. Two notes:
* This is generic; in abstract, a problem could be 3/5 points on one
randomization, and 5/7 on another
* In practice, this is a Very Bad Idea, and (a) will break some code
in place (although that code should get fixed), and (b) break some
analytics we plan to put in place.
"""
return None
def get_progress(self):
""" Return a progress.Progress object that represents how far the
student has gone in this module. Must be implemented to get correct
progress tracking behavior in nesting modules like sequence and
vertical.
If this module has no notion of progress, return None.
"""
return None
def bind_for_student(self, xmodule_runtime, field_data):
"""
Set up this XBlock to act as an XModule instead of an XModuleDescriptor.
:param xmodule_runtime: the runtime to use when accessing student facing methods
:type xmodule_runtime: :class:`ModuleSystem`
:param field_data: The :class:`FieldData` to use for all subsequent data access
:type field_data: :class:`FieldData`
"""
# pylint: disable=attribute-defined-outside-init
self.xmodule_runtime = xmodule_runtime
self._field_data = field_data
class ProxyAttribute(object):
"""
A (python) descriptor that proxies attribute access.
For example:
class Foo(object):
def __init__(self, value):
self.foo_attr = value
class Bar(object):
foo = Foo('x')
foo_attr = ProxyAttribute('foo', 'foo_attr')
bar = Bar()
assert bar.foo_attr == 'x'
bar.foo_attr = 'y'
assert bar.foo.foo_attr == 'y'
del bar.foo_attr
assert not hasattr(bar.foo, 'foo_attr')
"""
def __init__(self, source, name):
"""
:param source: The name of the attribute to proxy to
:param name: The name of the attribute to proxy
"""
self._source = source
self._name = name
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(getattr(instance, self._source), self._name)
def __set__(self, instance, value):
setattr(getattr(instance, self._source), self._name, value)
def __delete__(self, instance):
delattr(getattr(instance, self._source), self._name)
module_attr = partial(ProxyAttribute, '_xmodule') # pylint: disable=invalid-name
descriptor_attr = partial(ProxyAttribute, 'descriptor') # pylint: disable=invalid-name
module_runtime_attr = partial(ProxyAttribute, 'xmodule_runtime') # pylint: disable=invalid-name
class XModule(XModuleMixin, HTMLSnippet, XBlock): # pylint: disable=abstract-method
""" Implements a generic learning module.
......@@ -232,6 +343,11 @@ class XModule(XModuleMixin, HTMLSnippet, XBlock): # pylint: disable=abstract-me
# in the module
icon_class = 'other'
has_score = descriptor_attr('has_score')
_field_data_cache = descriptor_attr('_field_data_cache')
_field_data = descriptor_attr('_field_data')
_dirty_fields = descriptor_attr('_dirty_fields')
def __init__(self, descriptor, *args, **kwargs):
"""
Construct a new xmodule
......@@ -243,10 +359,19 @@ class XModule(XModuleMixin, HTMLSnippet, XBlock): # pylint: disable=abstract-me
field_data: A dictionary-like object that maps field names to values
for those fields.
"""
super(XModule, self).__init__(*args, **kwargs)
self.system = self.runtime
# Set the descriptor first so that we can proxy to it
self.descriptor = descriptor
super(XModule, self).__init__(*args, **kwargs)
self._loaded_children = None
self.system = self.runtime
def __unicode__(self):
return u'<x_module(id={0})>'.format(self.id)
def handle_ajax(self, _dispatch, _data):
""" dispatch is last part of the URL.
data is a dictionary-like object with the content of the request"""
return u""
def get_children(self):
"""
......@@ -268,9 +393,6 @@ class XModule(XModuleMixin, HTMLSnippet, XBlock): # pylint: disable=abstract-me
return self._loaded_children
def __unicode__(self):
return '<x_module(id={0})>'.format(self.id)
def get_child_descriptors(self):
"""
Returns the descriptors of the child modules
......@@ -286,58 +408,12 @@ class XModule(XModuleMixin, HTMLSnippet, XBlock): # pylint: disable=abstract-me
"""
return self.descriptor.get_children()
def get_icon_class(self):
"""
Return a css class identifying this module in the context of an icon
"""
return self.icon_class
# Functions used in the LMS
def get_score(self):
"""
Score the student received on the problem, or None if there is no
score.
Returns:
dictionary
{'score': integer, from 0 to get_max_score(),
'total': get_max_score()}
NOTE (vshnayder): not sure if this was the intended return value, but
that's what it's doing now. I suspect that we really want it to just
return a number. Would need to change (at least) capa and
modx_dispatch to match if we did that.
"""
return None
def max_score(self):
""" Maximum score. Two notes:
* This is generic; in abstract, a problem could be 3/5 points on one
randomization, and 5/7 on another
* In practice, this is a Very Bad Idea, and (a) will break some code
in place (although that code should get fixed), and (b) break some
analytics we plan to put in place.
def displayable_items(self):
"""
return None
def get_progress(self):
""" Return a progress.Progress object that represents how far the
student has gone in this module. Must be implemented to get correct
progress tracking behavior in nesting modules like sequence and
vertical.
If this module has no notion of progress, return None.
Returns list of displayable modules contained by this module. If this
module is visible, should return [self].
"""
return None
def handle_ajax(self, _dispatch, _data):
""" dispatch is last part of the URL.
data is a dictionary-like object with the content of the request"""
return ""
return [self.descriptor]
# ~~~~~~~~~~~~~~~ XBlock API Wrappers ~~~~~~~~~~~~~~~~
def student_view(self, context):
......@@ -489,24 +565,7 @@ class XModuleDescriptor(XModuleMixin, HTMLSnippet, ResourceTemplates, XBlock):
# by following previous until None
# definition_locator is only used by mongostores which separate definitions from blocks
self.edited_by = self.edited_on = self.previous_version = self.update_version = self.definition_locator = None
self._child_instances = None
def xmodule(self, system):
"""
Returns an XModule.
system: Module system
"""
# save any field changes
module = system.construct_xblock_from_class(
self.module_class,
descriptor=self,
scope_ids=self.scope_ids,
field_data=system.xmodule_field_data(self),
)
module.save()
return module
self.xmodule_runtime = None
def has_dynamic_children(self):
"""
......@@ -644,6 +703,42 @@ class XModuleDescriptor(XModuleMixin, HTMLSnippet, ResourceTemplates, XBlock):
return metadata_fields
# ~~~~~~~~~~~~~~~ XModule Indirection ~~~~~~~~~~~~~~~~
@property
def _xmodule(self):
"""
Returns the XModule corresponding to this descriptor. Expects that the system
already supports all of the attributes needed by xmodules
"""
assert self.xmodule_runtime is not None
assert self.xmodule_runtime.error_descriptor_class is not None
if self.xmodule_runtime.xmodule_instance is None:
try:
self.xmodule_runtime.xmodule_instance = self.xmodule_runtime.construct_xblock_from_class(
self.module_class,
descriptor=self,
scope_ids=self.scope_ids,
field_data=self._field_data,
)
self.xmodule_runtime.xmodule_instance.save()
except Exception: # pylint: disable=broad-except
log.exception('Error creating xmodule')
descriptor = self.xmodule_runtime.error_descriptor_class.from_descriptor(
self,
error_msg=exc_info_to_str(sys.exc_info())
)
self.xmodule_runtime.xmodule_instance = descriptor._xmodule # pylint: disable=protected-access
return self.xmodule_runtime.xmodule_instance
displayable_items = module_attr('displayable_items')
get_display_items = module_attr('get_display_items')
get_icon_class = module_attr('get_icon_class')
get_progress = module_attr('get_progress')
get_score = module_attr('get_score')
handle_ajax = module_attr('handle_ajax')
max_score = module_attr('max_score')
student_view = module_attr('student_view')
# ~~~~~~~~~~~~~~~ XBlock API Wrappers ~~~~~~~~~~~~~~~~
def studio_view(self, _context):
"""
......@@ -766,6 +861,13 @@ class DescriptorSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable
result['default_value'] = field.to_json(field.default)
return result
def render(self, block, context, view_name):
if isinstance(block, (XModule, XModuleDescriptor)) and view_name == 'student_view':
assert block.xmodule_runtime is not None
return block.xmodule_runtime.render(block._xmodule, context, view_name)
else:
return super(DescriptorSystem, self).render(block, context, view_name)
class XMLParsingSystem(DescriptorSystem):
def __init__(self, process_xml, policy, **kwargs):
......@@ -795,12 +897,12 @@ class ModuleSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable=abs
"""
def __init__(
self, static_url, ajax_url, track_function, get_module, render_template,
replace_urls, xmodule_field_data, user=None, filestore=None,
replace_urls, user=None, filestore=None,
debug=False, hostname="", xqueue=None, publish=None, node_path="",
anonymous_student_id='', course_id=None,
open_ended_grading_interface=None, s3_interface=None,
cache=None, can_execute_unsafe_code=None, replace_course_urls=None,
replace_jump_to_id_urls=None, **kwargs):
replace_jump_to_id_urls=None, error_descriptor_class=None, **kwargs):
"""
Create a closure around the system environment.
......@@ -842,9 +944,6 @@ class ModuleSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable=abs
publish(event) - A function that allows XModules to publish events (such as grade changes)
xmodule_field_data - A function that constructs a field_data for an xblock from its
corresponding descriptor
cache - A cache object with two methods:
.get(key) returns an object from the cache or None.
.set(key, value, timeout_secs=None) stores a value in the cache with a timeout.
......@@ -852,6 +951,8 @@ class ModuleSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable=abs
can_execute_unsafe_code - A function returning a boolean, whether or
not to allow the execution of unsafe, unsandboxed code.
error_descriptor_class - The class to use to render XModules with errors
"""
# Right now, usage_store is unused, and field_data is always supplanted
......@@ -873,7 +974,6 @@ class ModuleSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable=abs
self.anonymous_student_id = anonymous_student_id
self.course_id = course_id
self.user_is_staff = user is not None and user.is_staff
self.xmodule_field_data = xmodule_field_data
if publish is None:
publish = lambda e: None
......@@ -887,6 +987,8 @@ class ModuleSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable=abs
self.can_execute_unsafe_code = can_execute_unsafe_code or (lambda: False)
self.replace_course_urls = replace_course_urls
self.replace_jump_to_id_urls = replace_jump_to_id_urls
self.error_descriptor_class = error_descriptor_class
self.xmodule_instance = None
def get(self, attr):
""" provide uniform access to attributes (like etree)."""
......
......@@ -305,9 +305,9 @@ def progress_summary(student, request, course, field_data_cache):
graded = section_module.graded
scores = []
module_creator = section_module.system.get_module
module_creator = section_module.xmodule_runtime.get_module
for module_descriptor in yield_dynamic_descriptor_descendents(section_module.descriptor, module_creator):
for module_descriptor in yield_dynamic_descriptor_descendents(section_module, module_creator):
course_id = course.id
(correct, total) = get_score(course_id, student, module_descriptor, module_creator, field_data_cache)
......
......@@ -289,7 +289,7 @@ class DjangoKeyValueStore(KeyValueStore):
def get(self, key):
if key.scope not in self._allowed_scopes:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
field_object = self._field_data_cache.find(key)
if field_object is None:
......@@ -320,7 +320,7 @@ class DjangoKeyValueStore(KeyValueStore):
for field in kv_dict:
# Check field for validity
if field.scope not in self._allowed_scopes:
raise InvalidScopeError(field.scope)
raise InvalidScopeError(field)
# If the field is valid and isn't already in the dictionary, add it.
field_object = self._field_data_cache.find_or_create(field)
......@@ -352,7 +352,7 @@ class DjangoKeyValueStore(KeyValueStore):
def delete(self, key):
if key.scope not in self._allowed_scopes:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
field_object = self._field_data_cache.find(key)
if field_object is None:
......@@ -368,7 +368,7 @@ class DjangoKeyValueStore(KeyValueStore):
def has(self, key):
if key.scope not in self._allowed_scopes:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
field_object = self._field_data_cache.find(key)
if field_object is None:
......
......@@ -219,6 +219,9 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
if not has_access(user, descriptor, 'load', course_id):
return None
student_data = DbModel(DjangoKeyValueStore(field_data_cache))
descriptor._field_data = lms_field_data(descriptor._field_data, student_data)
# Setup system context for module instance
ajax_url = reverse(
'modx_dispatch',
......@@ -294,10 +297,6 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
position, wrap_xmodule_display, grade_bucket_type,
static_asset_path)
def xmodule_field_data(descriptor):
student_data = DbModel(DjangoKeyValueStore(field_data_cache))
return lms_field_data(descriptor._field_data, student_data)
def publish(event):
"""A function that allows XModules to publish events. This only supports grade changes right now."""
if event.get('event_name') != 'grade':
......@@ -405,7 +404,6 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': course_id, 'module_id': ''})
),
node_path=settings.NODE_PATH,
xmodule_field_data=xmodule_field_data,
publish=publish,
anonymous_student_id=unique_id_for_user(user),
course_id=course_id,
......@@ -426,27 +424,17 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
make_psychometrics_data_update_handler(course_id, user, descriptor.location.url())
)
try:
module = descriptor.xmodule(system)
except:
log.exception("Error creating module from descriptor {0}".format(descriptor))
system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id))
# make an ErrorDescriptor -- assuming that the descriptor's system is ok
if has_access(user, descriptor.location, 'staff', course_id):
err_descriptor_class = ErrorDescriptor
system.error_descriptor_class = ErrorDescriptor
else:
err_descriptor_class = NonStaffErrorDescriptor
err_descriptor = err_descriptor_class.from_descriptor(
descriptor,
error_msg=exc_info_to_str(sys.exc_info())
)
system.error_descriptor_class = NonStaffErrorDescriptor
# Make an error module
return err_descriptor.xmodule(system)
system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id))
return module
descriptor.xmodule_runtime = system
descriptor.scope_ids = descriptor.scope_ids._replace(user_id=user.id)
return descriptor
def find_target_student_module(request, user_id, course_id, mod_id):
......
......@@ -16,7 +16,7 @@ from student.tests.factories import UserFactory, CourseEnrollmentFactory
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from xblock.field_data import DictFieldData
from xblock.fields import Scope
from xmodule.tests import get_test_system
from xmodule.tests import get_test_system, get_test_descriptor_system
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
......@@ -49,11 +49,26 @@ class BaseTestXmodule(ModuleStoreTestCase):
DATA = ''
MODEL_DATA = {'data': '<some_module></some_module>'}
def xmodule_field_data(self, descriptor):
field_data = {}
field_data.update(self.MODEL_DATA)
student_data = DictFieldData(field_data)
return lms_field_data(descriptor._field_data, student_data)
def new_module_runtime(self):
"""
Generate a new ModuleSystem that is minimally set up for testing
"""
runtime = get_test_system(course_id=self.course.id)
# When asked for a module out of a descriptor, just create a new xmodule runtime,
# and inject it into the descriptor
def get_module(descr):
descr.xmodule_runtime = self.new_module_runtime()
return descr
runtime.get_module = get_module
return runtime
def new_descriptor_runtime(self):
runtime = get_test_descriptor_system()
runtime.get_block = modulestore().get_item
return runtime
def setUp(self):
......@@ -87,16 +102,15 @@ class BaseTestXmodule(ModuleStoreTestCase):
data=self.DATA
)
self.runtime = get_test_system(course_id=self.course.id)
# Allow us to assert that the template was called in the same way from
# different code paths while maintaining the type returned by render_template
self.runtime.render_template = lambda template, context: u'{!r}, {!r}'.format(template, sorted(context.items()))
self.runtime.xmodule_field_data = self.xmodule_field_data
self.runtime = self.new_descriptor_runtime()
self.runtime.get_module = lambda descr: descr.xmodule(self.runtime)
field_data = {}
field_data.update(self.MODEL_DATA)
student_data = DictFieldData(field_data)
self.item_descriptor._field_data = lms_field_data(self.item_descriptor._field_data, student_data)
self.item_module = self.item_descriptor.xmodule(self.runtime)
self.item_descriptor.xmodule_runtime = self.new_module_runtime()
self.item_module = self.item_descriptor
self.item_url = Location(self.item_module.location).url()
......@@ -119,7 +133,11 @@ class BaseTestXmodule(ModuleStoreTestCase):
class XModuleRenderingTestBase(BaseTestXmodule):
def setUp(self):
super(XModuleRenderingTestBase, self).setUp()
self.runtime.render_template = render_to_string
def new_module_runtime(self):
"""
Create a runtime that actually does html rendering
"""
runtime = super(XModuleRenderingTestBase, self).new_module_runtime()
runtime.render_template = render_to_string
return runtime
......@@ -39,7 +39,7 @@ class TestLTI(BaseTestXmodule):
u'oauth_consumer_key': u'',
u'oauth_signature_method': u'HMAC-SHA1',
u'oauth_version': u'1.0',
u'user_id': self.runtime.anonymous_student_id,
u'user_id': self.item_descriptor.xmodule_runtime.anonymous_student_id,
u'role': u'student',
u'oauth_signature': mocked_decoded_signature
}
......@@ -69,12 +69,14 @@ class TestLTI(BaseTestXmodule):
"""
Makes sure that all parameters extracted.
"""
self.runtime.render_template = lambda template, context: context
generated_context = self.item_module.get_html()
generated_context = self.item_module.runtime.render(self.item_module, None, 'student_view').content
expected_context = {
'input_fields': self.correct_headers,
'element_class': self.item_module.location.category,
'element_id': self.item_module.location.html_id(),
'launch_url': 'http://www.example.com', # default value
}
self.assertDictEqual(generated_context, expected_context)
self.assertEqual(
generated_context,
self.runtime.render_template('lti.html', expected_context),
)
......@@ -74,7 +74,7 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
)
# get the rendered HTML output which should have the rewritten link
html = module.system.render(module, None, 'student_view').content
html = module.runtime.render(module, None, 'student_view').content
# See if the url got rewritten to the target link
# note if the URL mapping changes then this assertion will break
......
......@@ -17,10 +17,13 @@ class TestTimeLimitModuleRendering(XModuleRenderingTestBase):
"""
def test_with_children(self):
block = ItemFactory.create(category='timelimit')
block.xmodule_runtime = self.new_module_runtime()
ItemFactory.create(category='html', data='<html>This is just text</html>', parent=block)
assert_student_view(block, self.runtime.render(block.xmodule(self.runtime), None, 'student_view'))
assert_student_view(block, self.runtime.render(block, None, 'student_view'))
def test_without_children(self):
block = ItemFactory.create(category='timelimit')
assert_student_view(block, self.runtime.render(block.xmodule(self.runtime), None, 'student_view'))
block.xmodule_runtime = self.new_module_runtime()
assert_student_view(block, self.runtime.render(block, None, 'student_view'))
......@@ -13,14 +13,6 @@ class TestVideo(BaseTestXmodule):
CATEGORY = "video"
DATA = SOURCE_XML
def setUp(self):
# Since the VideoDescriptor changes `self._field_data`,
# we need to instantiate `self.item_module` through
# `self.item_descriptor` rather than directly constructing it
super(TestVideo, self).setUp()
self.item_module = self.item_descriptor.xmodule(self.runtime)
self.item_module.runtime.render_template = lambda template, context: context
def test_handle_ajax_dispatch(self):
responses = {
user.username: self.clients[user.username].post(
......@@ -40,7 +32,7 @@ class TestVideo(BaseTestXmodule):
def test_video_constructor(self):
"""Make sure that all parameters extracted correclty from xml"""
context = self.item_module.get_html()
context = self.item_module.runtime.render(self.item_module, None, 'student_view').content
sources = {
'main': u'example.mp4',
......@@ -53,7 +45,7 @@ class TestVideo(BaseTestXmodule):
'data_dir': getattr(self, 'data_dir', None),
'caption_asset_path': '/static/subs/',
'show_captions': 'true',
'display_name': 'A Name',
'display_name': u'A Name',
'end': 3610.0,
'id': self.item_module.location.html_id(),
'sources': sources,
......@@ -66,8 +58,10 @@ class TestVideo(BaseTestXmodule):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/'
}
self.maxDiff = None
self.assertEqual(context, expected_context)
self.assertEqual(
context,
self.item_module.xmodule_runtime.render_template('video.html', expected_context)
)
class TestVideoNonYouTube(TestVideo):
......@@ -93,24 +87,24 @@ class TestVideoNonYouTube(TestVideo):
the template generates an empty string for the YouTube streams.
"""
sources = {
u'main': u'example.mp4',
'main': u'example.mp4',
u'mp4': u'example.mp4',
u'webm': u'example.webm',
u'ogv': u'example.ogv'
}
context = self.item_module.get_html()
context = self.item_module.runtime.render(self.item_module, None, 'student_view').content
expected_context = {
'data_dir': getattr(self, 'data_dir', None),
'caption_asset_path': '/static/subs/',
'show_captions': 'true',
'display_name': 'A Name',
'display_name': u'A Name',
'end': 3610.0,
'id': self.item_module.location.html_id(),
'sources': sources,
'start': 3603.0,
'sub': 'a_sub_file.srt.sjson',
'sub': u'a_sub_file.srt.sjson',
'track': '',
'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.MITX_FEATURES.get('AUTOPLAY_VIDEOS', True),
......@@ -118,4 +112,7 @@ class TestVideoNonYouTube(TestVideo):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/'
}
self.assertEqual(context, expected_context)
self.assertEqual(
context,
self.item_module.xmodule_runtime.render_template('video.html', expected_context)
)
......@@ -22,7 +22,7 @@ from django.conf import settings
from xmodule.video_module import VideoDescriptor, _create_youtube_string
from xmodule.modulestore import Location
from xmodule.tests import get_test_system, LogicTest
from xmodule.tests import get_test_system, LogicTest, get_test_descriptor_system
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
......@@ -57,23 +57,18 @@ class VideoFactory(object):
field_data = {'data': VideoFactory.sample_problem_xml_youtube,
'location': location}
system = get_test_system()
system.render_template = lambda template, context: context
system = get_test_descriptor_system()
descriptor = VideoDescriptor(system, DictFieldData(field_data), ScopeIds(None, None, None, None))
module = descriptor.xmodule(system)
return module
descriptor.xmodule_runtime = get_test_system()
return descriptor
class VideoModuleUnitTest(unittest.TestCase):
"""Unit tests for Video Xmodule."""
def test_video_get_html(self):
"""Make sure that all parameters extracted correclty from xml"""
module = VideoFactory.create()
module.runtime.render_template = lambda template, context: context
sources = {
'main': 'example.mp4',
......@@ -99,14 +94,10 @@ class VideoModuleUnitTest(unittest.TestCase):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/'
}
self.assertEqual(module.get_html(), expected_context)
def test_video_instance_state(self):
module = VideoFactory.create()
self.assertDictEqual(
json.loads(module.get_instance_state()),
{'position': 0})
self.assertEqual(
module.runtime.render(module, None, 'student_view').content,
module.runtime.render_template('video.html', expected_context)
)
class VideoModuleLogicTest(LogicTest):
......
......@@ -245,7 +245,7 @@ class TestWordCloud(BaseTestXmodule):
fragment = self.runtime.render(self.item_module, None, 'student_view')
expected_context = {
'ajax_url': self.item_module.system.ajax_url,
'ajax_url': self.item_module.xmodule_runtime.ajax_url,
'element_class': self.item_module.location.category,
'element_id': self.item_module.location.html_id(),
'num_inputs': 5, # default value
......
......@@ -142,7 +142,7 @@ def redirect_to_course_position(course_module):
the first child.
"""
urlargs = {'course_id': course_module.descriptor.id}
urlargs = {'course_id': course_module.id}
chapter = get_current_child(course_module)
if chapter is None:
# oops. Something bad has happened.
......
......@@ -310,6 +310,7 @@ def rescore_problem_module_state(module_descriptor, student_module, xmodule_inst
raise UpdateProblemModuleStateError(msg)
result = instance.rescore_problem()
instance.save()
if 'success' not in result:
# don't consider these fatal, but false means that the individual call didn't complete:
TASK_LOG.warning(u"error processing rescore call for course {course}, problem {loc} and student {student}: "
......
......@@ -13,8 +13,6 @@ from xmodule.x_module import ModuleSystem
from mitxmako.shortcuts import render_to_string
import datetime
from xblock.field_data import DictFieldData
log = logging.getLogger(__name__)
NOTIFICATION_CACHE_TIME = 300
......@@ -70,7 +68,6 @@ def peer_grading_notifications(course, user):
get_module = None,
render_template=render_to_string,
replace_urls=None,
xmodule_field_data=DictFieldData({}),
)
peer_gs = peer_grading_service.PeerGradingService(settings.OPEN_ENDED_GRADING_INTERFACE, system)
pending_grading = False
......@@ -132,7 +129,6 @@ def combined_notifications(course, user):
get_module = None,
render_template=render_to_string,
replace_urls=None,
xmodule_field_data=DictFieldData({})
)
#Initialize controller query service using our mock system
controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, system)
......
......@@ -9,8 +9,6 @@ from xmodule.open_ended_grading_classes.grading_service_module import GradingSer
from django.conf import settings
from django.http import HttpResponse, Http404
from xblock.field_data import DictFieldData
from courseware.access import has_access
from util.json_request import expect_json
from xmodule.course_module import CourseDescriptor
......@@ -76,7 +74,6 @@ class StaffGradingService(GradingService):
get_module = None,
render_template=render_to_string,
replace_urls=None,
xmodule_field_data=DictFieldData({})
)
super(StaffGradingService, self).__init__(config)
self.url = config['url'] + config['staff_grading']
......
......@@ -16,6 +16,7 @@ from xmodule.open_ended_grading_classes import peer_grading_service, controller_
from xmodule import peer_grading_module
from xmodule.modulestore.django import modulestore
from xmodule.x_module import ModuleSystem
from xmodule.error_module import ErrorDescriptor
from xblock.fields import ScopeIds
from open_ended_grading import staff_grading_service, views, utils
......@@ -251,13 +252,14 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
get_module=None,
render_template=render_to_string,
replace_urls=None,
xmodule_field_data=lambda d: d._field_data,
s3_interface=test_util_open_ended.S3_INTERFACE,
open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
mixins=settings.XBLOCK_MIXINS,
error_descriptor_class=ErrorDescriptor,
)
self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system, field_data, ScopeIds(None, None, None, None))
self.peer_module = self.descriptor.xmodule(self.system)
self.descriptor.xmodule_runtime = self.system
self.peer_module = self.descriptor
self.peer_module.peer_gs = self.mock_service
self.logout()
......
......@@ -36,7 +36,6 @@ system = ModuleSystem(
get_module=None,
render_template=render_to_string,
replace_urls=None,
xmodule_field_data=DictFieldData({}),
)
......
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