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): ...@@ -95,11 +95,6 @@ def preview_module_system(request, preview_id, descriptor):
descriptor: An XModuleDescriptor 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 course_id = get_course_for_item(descriptor.location).location.course_id
if descriptor.location.category == 'static_tab': if descriptor.location.category == 'static_tab':
...@@ -118,7 +113,6 @@ def preview_module_system(request, preview_id, descriptor): ...@@ -118,7 +113,6 @@ def preview_module_system(request, preview_id, descriptor):
debug=True, debug=True,
replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_id=course_id), replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_id=course_id),
user=request.user, user=request.user,
xmodule_field_data=preview_field_data,
can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
mixins=settings.XBLOCK_MIXINS, mixins=settings.XBLOCK_MIXINS,
course_id=course_id, course_id=course_id,
...@@ -136,7 +130,8 @@ def preview_module_system(request, preview_id, descriptor): ...@@ -136,7 +130,8 @@ def preview_module_system(request, preview_id, descriptor):
getattr(descriptor, 'data_dir', descriptor.location.course), getattr(descriptor, 'data_dir', descriptor.location.course),
course_id=descriptor.location.org + '/' + descriptor.location.course + '/BOGUS_RUN_REPLACE_WHEN_AVAILABLE', 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): ...@@ -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 preview_id (str): An identifier specifying which preview this module is used for
descriptor: An XModuleDescriptor descriptor: An XModuleDescriptor
""" """
system = preview_module_system(request, preview_id, descriptor) student_data = DbModel(SessionKeyValueStore(request))
try: descriptor.bind_for_student(
module = descriptor.xmodule(system) preview_module_system(request, preview_id, descriptor),
except: lms_field_data(descriptor._field_data, student_data), # pylint: disable=protected-access
log.debug("Unable to load preview module", exc_info=True) )
module = ErrorDescriptor.from_descriptor( return descriptor
descriptor,
error_msg=exc_info_to_str(sys.exc_info())
).xmodule(system)
return module
def get_preview_html(request, descriptor, idx): def get_preview_html(request, descriptor, idx):
......
...@@ -134,7 +134,7 @@ def add_histogram(user, block, view, frag, context): # pylint: disable=unused-a ...@@ -134,7 +134,7 @@ def add_histogram(user, block, view, frag, context): # pylint: disable=unused-a
return frag return frag
block_id = block.id block_id = block.id
if block.descriptor.has_score: if block.has_score:
histogram = grade_histogram(block_id) histogram = grade_histogram(block_id)
render_histogram = len(histogram) > 0 render_histogram = len(histogram) > 0
else: else:
...@@ -142,7 +142,7 @@ def add_histogram(user, block, view, frag, context): # pylint: disable=unused-a ...@@ -142,7 +142,7 @@ def add_histogram(user, block, view, frag, context): # pylint: disable=unused-a
render_histogram = False render_histogram = False
if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'): 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 osfs = block.system.filestore
if filename is not None and osfs.exists(filename): if filename is not None and osfs.exists(filename):
# if original, unmangled filename exists then use it (github # 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 ...@@ -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 # TODO (ichuang): use _has_access_descriptor.can_load in lms.courseware.access, instead of now>mstart comparison here
now = datetime.datetime.now(UTC()) now = datetime.datetime.now(UTC())
is_released = "unknown" is_released = "unknown"
mstart = block.descriptor.start mstart = block.start
if mstart is not None: if mstart is not None:
is_released = "<font color='red'>Yes!</font>" if (now > mstart) else "<font color='green'>Not yet</font>" 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()], 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, 'location': block.location,
'xqa_key': block.xqa_key, 'xqa_key': block.xqa_key,
'source_file': source_file, 'source_file': source_file,
......
...@@ -15,7 +15,7 @@ from capa.responsetypes import StudentInputError, \ ...@@ -15,7 +15,7 @@ from capa.responsetypes import StudentInputError, \
ResponseError, LoncapaProblemError ResponseError, LoncapaProblemError
from capa.util import convert_files_to_filenames from capa.util import convert_files_to_filenames
from .progress import Progress 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.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError from xmodule.exceptions import NotFoundError, ProcessingError
from xblock.fields import Scope, String, Boolean, Dict, Integer, Float from xblock.fields import Scope, String, Boolean, Dict, Integer, Float
...@@ -1193,3 +1193,33 @@ class CapaDescriptor(CapaFields, RawDescriptor): ...@@ -1193,3 +1193,33 @@ class CapaDescriptor(CapaFields, RawDescriptor):
CapaDescriptor.force_save_button, CapaDescriptor.markdown, CapaDescriptor.force_save_button, CapaDescriptor.markdown,
CapaDescriptor.text_customization]) CapaDescriptor.text_customization])
return non_editable_fields 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')
...@@ -496,7 +496,7 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor): ...@@ -496,7 +496,7 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
metadata_translations = { metadata_translations = {
'is_graded': 'graded', 'is_graded': 'graded',
'attempts': 'max_attempts', 'attempts': 'max_attempts',
} }
def get_context(self): def get_context(self):
_context = RawDescriptor.get_context(self) _context = RawDescriptor.get_context(self)
......
...@@ -18,6 +18,7 @@ log = logging.getLogger('mitx.' + __name__) ...@@ -18,6 +18,7 @@ log = logging.getLogger('mitx.' + __name__)
class ConditionalFields(object): class ConditionalFields(object):
has_children = True
show_tag_list = List(help="Poll answers", scope=Scope.content) show_tag_list = List(help="Poll answers", scope=Scope.content)
......
...@@ -115,15 +115,15 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -115,15 +115,15 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
child = self.get_display_items()[0] child = self.get_display_items()[0]
out = self.runtime.render_child(child, None, 'student_view').content out = self.runtime.render_child(child, None, 'student_view').content
# The event listener uses the ajax url to find the child. # The event listener uses the ajax url to find the child.
child_url = child.runtime.ajax_url child_id = child.id
except IndexError: except IndexError:
out = u"Error in loading crowdsourced hinter - can't find child problem." 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. # 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, ajax_url=self.runtime.ajax_url,
child_url=child_url child_id=child_id
) )
return out return out
......
...@@ -77,7 +77,7 @@ class ErrorDescriptor(ErrorFields, XModuleDescriptor): ...@@ -77,7 +77,7 @@ class ErrorDescriptor(ErrorFields, XModuleDescriptor):
module_class = ErrorModule module_class = ErrorModule
def get_html(self): def get_html(self):
return '' return u''
@classmethod @classmethod
def _construct(cls, system, contents, error_msg, location): def _construct(cls, system, contents, error_msg, location):
......
<li id="vert-0" data-id="i4x://Me/19.002/crowdsource_hinter/crowdsource_hinter_def7a1142dd0"> <li id="vert-0" data-id="i4x://Me/19.002/crowdsource_hinter/crowdsource_hinter_def7a1142dd0">
<section class="xmodule_display xmodule_CrowdsourceHinterModule" data-type="Hinter" id="hinter-root"> <section class="xmodule_display xmodule_CrowdsourceHinterModule" data-type="Hinter" id="hinter-root">
<section class="xmodule_display xmodule_CapaModule" data-type="Problem" id="problem"> <section class="xmodule_display xmodule_CapaModule" data-type="Problem" id="problem">
<section id="problem_i4x-Me-19_002-problem-Numerical_Input" class="problems-wrapper" data-problem-id="i4x://Me/19.002/problem/Numerical_Input" data-url="/courses/Me/19.002/Test/modx/i4x://Me/19.002/problem/Numerical_Input" data-progress_status="done" data-progress_detail="1/1"> <section id="problem_i4x-Me-19_002-problem-Numerical_Input" class="problems-wrapper" data-problem-id="i4x://Me/19.002/problem/Numerical_Input" data-url="/courses/Me/19.002/Test/modx/i4x://Me/19.002/problem/Numerical_Input" data-progress_status="done" data-progress_detail="1/1">
...@@ -44,7 +44,7 @@ ...@@ -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> </section>
......
...@@ -139,12 +139,12 @@ describe 'Problem', -> ...@@ -139,12 +139,12 @@ describe 'Problem', ->
it 'log the problem_graded event, after the problem is done grading.', -> it 'log the problem_graded event, after the problem is done grading.', ->
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) ->
response = response =
success: 'correct' success: 'correct'
contents: 'mock grader response' contents: 'mock grader response'
callback(response) callback(response)
@problem.check() @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', -> it 'submit the answer for check', ->
spyOn $, 'postWithPrefix' spyOn $, 'postWithPrefix'
......
...@@ -248,7 +248,7 @@ class @Problem ...@@ -248,7 +248,7 @@ class @Problem
@updateProgress response @updateProgress response
else else
@gentle_alert response.success @gentle_alert response.success
Logger.log 'problem_graded', [@answers, response.contents], @url Logger.log 'problem_graded', [@answers, response.contents], @id
if not abort_submission if not abort_submission
$.ajaxWithPrefix("#{@url}/problem_check", settings) $.ajaxWithPrefix("#{@url}/problem_check", settings)
...@@ -271,7 +271,7 @@ class @Problem ...@@ -271,7 +271,7 @@ class @Problem
@el.removeClass 'showed' @el.removeClass 'showed'
else else
@gentle_alert response.success @gentle_alert response.success
Logger.log 'problem_graded', [@answers, response.contents], @url Logger.log 'problem_graded', [@answers, response.contents], @id
reset: => reset: =>
Logger.log 'problem_reset', @answers Logger.log 'problem_reset', @answers
......
...@@ -7,7 +7,7 @@ class @Hinter ...@@ -7,7 +7,7 @@ class @Hinter
constructor: (element) -> constructor: (element) ->
@el = $(element).find('.crowdsource-wrapper') @el = $(element).find('.crowdsource-wrapper')
@url = @el.data('url') @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() @render()
capture_problem: (event_type, data, element) => capture_problem: (event_type, data, element) =>
......
...@@ -81,7 +81,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore): ...@@ -81,7 +81,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
else: else:
return self._data[key.field_name] return self._data[key.field_name]
else: else:
raise InvalidScopeError(key.scope) raise InvalidScopeError(key)
def set(self, key, value): def set(self, key, value):
if key.scope == Scope.children: if key.scope == Scope.children:
...@@ -94,7 +94,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore): ...@@ -94,7 +94,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
else: else:
self._data[key.field_name] = value self._data[key.field_name] = value
else: else:
raise InvalidScopeError(key.scope) raise InvalidScopeError(key)
def delete(self, key): def delete(self, key):
if key.scope == Scope.children: if key.scope == Scope.children:
...@@ -108,7 +108,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore): ...@@ -108,7 +108,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
else: else:
del self._data[key.field_name] del self._data[key.field_name]
else: else:
raise InvalidScopeError(key.scope) raise InvalidScopeError(key)
def has(self, key): def has(self, key):
if key.scope in (Scope.children, Scope.parent): if key.scope in (Scope.children, Scope.parent):
......
...@@ -53,12 +53,12 @@ class SplitMongoKVS(InheritanceKeyValueStore): ...@@ -53,12 +53,12 @@ class SplitMongoKVS(InheritanceKeyValueStore):
raise KeyError() raise KeyError()
else: else:
raise InvalidScopeError(key.scope) raise InvalidScopeError(key)
def set(self, key, value): def set(self, key, value):
# handle any special cases # handle any special cases
if key.scope not in [Scope.children, Scope.settings, Scope.content]: if key.scope not in [Scope.children, Scope.settings, Scope.content]:
raise InvalidScopeError(key.scope) raise InvalidScopeError(key)
if key.scope == Scope.content: if key.scope == Scope.content:
self._load_definition() self._load_definition()
...@@ -75,7 +75,7 @@ class SplitMongoKVS(InheritanceKeyValueStore): ...@@ -75,7 +75,7 @@ class SplitMongoKVS(InheritanceKeyValueStore):
def delete(self, key): def delete(self, key):
# handle any special cases # handle any special cases
if key.scope not in [Scope.children, Scope.settings, Scope.content]: if key.scope not in [Scope.children, Scope.settings, Scope.content]:
raise InvalidScopeError(key.scope) raise InvalidScopeError(key)
if key.scope == Scope.content: if key.scope == Scope.content:
self._load_definition() self._load_definition()
......
...@@ -204,7 +204,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): ...@@ -204,7 +204,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
descriptor.save() descriptor.save()
return descriptor return descriptor
render_template = lambda: '' render_template = lambda template, context: u''
# TODO (vshnayder): we are somewhat architecturally confused in the loading code: # TODO (vshnayder): we are somewhat architecturally confused in the loading code:
# load_item should actually be get_instance, because it expects the course-specific # 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... # policy to be loaded. For now, just add the course_id here...
......
...@@ -6,7 +6,7 @@ from lxml import etree ...@@ -6,7 +6,7 @@ from lxml import etree
from datetime import datetime from datetime import datetime
from pkg_resources import resource_string from pkg_resources import resource_string
from .capa_module import ComplexEncoder 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.raw_module import RawDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from .timeinfo import TimeInfo from .timeinfo import TimeInfo
...@@ -106,7 +106,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -106,7 +106,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
#We need to set the location here so the child modules can use it #We need to set the location here so the child modules can use it
self.runtime.set('location', self.location) 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) self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.system)
else: else:
self.peer_gs = MockPeerGradingService() self.peer_gs = MockPeerGradingService()
...@@ -662,3 +662,19 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor): ...@@ -662,3 +662,19 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
return [self.system.load_item(self.link_to_location)] return [self.system.load_item(self.link_to_location)]
else: else:
return [] 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): ...@@ -45,9 +45,6 @@ class SequenceModule(SequenceFields, XModule):
self.rendered = False self.rendered = False
def get_instance_state(self):
return json.dumps({'position': self.position})
def get_html(self): def get_html(self):
self.render() self.render()
return self.content return self.content
......
...@@ -9,6 +9,7 @@ Run like this: ...@@ -9,6 +9,7 @@ Run like this:
import json import json
import os import os
import pprint
import unittest import unittest
from mock import Mock from mock import Mock
...@@ -18,6 +19,7 @@ from xblock.field_data import DictFieldData ...@@ -18,6 +19,7 @@ from xblock.field_data import DictFieldData
from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin
from xmodule.modulestore.inheritance import InheritanceMixin from xmodule.modulestore.inheritance import InheritanceMixin
from xmodule.mako_module import MakoDescriptorSystem from xmodule.mako_module import MakoDescriptorSystem
from xmodule.error_module import ErrorDescriptor
# Location of common test DATA directory # Location of common test DATA directory
...@@ -54,18 +56,18 @@ def get_test_system(course_id=''): ...@@ -54,18 +56,18 @@ def get_test_system(course_id=''):
ajax_url='courses/course_id/modx/a_location', ajax_url='courses/course_id/modx/a_location',
track_function=Mock(), track_function=Mock(),
get_module=Mock(), get_module=Mock(),
render_template=lambda template, context: repr(context), render_template=mock_render_template,
replace_urls=lambda html: str(html), replace_urls=str,
user=Mock(is_staff=False), user=Mock(is_staff=False),
filestore=Mock(), filestore=Mock(),
debug=True, debug=True,
hostname="edx.org", hostname="edx.org",
xqueue={'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10, 'construct_callback' : Mock(side_effect="/")}, 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"), node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
xmodule_field_data=lambda descriptor: descriptor._field_data,
anonymous_student_id='student', anonymous_student_id='student',
open_ended_grading_interface=open_ended_grading_interface, open_ended_grading_interface=open_ended_grading_interface,
course_id=course_id, course_id=course_id,
error_descriptor_class=ErrorDescriptor,
) )
...@@ -77,11 +79,21 @@ def get_test_descriptor_system(): ...@@ -77,11 +79,21 @@ def get_test_descriptor_system():
load_item=Mock(), load_item=Mock(),
resources_fs=Mock(), resources_fs=Mock(),
error_tracker=Mock(), error_tracker=Mock(),
render_template=lambda template, context: repr(context), render_template=mock_render_template,
mixins=(InheritanceMixin, XModuleMixin), 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): class ModelsTest(unittest.TestCase):
def setUp(self): def setUp(self):
pass pass
......
...@@ -350,11 +350,11 @@ class OpenEndedModuleTest(unittest.TestCase): ...@@ -350,11 +350,11 @@ class OpenEndedModuleTest(unittest.TestCase):
""" """
Test storing answer with the open ended module. Test storing answer with the open ended module.
""" """
# Create a module with no state yet. Important that this start off as a blank slate. # Create a module with no state yet. Important that this start off as a blank slate.
test_module = OpenEndedModule(self.test_system, self.location, test_module = OpenEndedModule(self.test_system, self.location,
self.definition, self.descriptor, self.static_data, self.metadata) self.definition, self.descriptor, self.static_data, self.metadata)
saved_response = "Saved response." saved_response = "Saved response."
submitted_response = "Submitted response." submitted_response = "Submitted response."
...@@ -753,28 +753,36 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore): ...@@ -753,28 +753,36 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
#Simulate a student saving an answer #Simulate a student saving an answer
html = module.handle_ajax("get_html", {}) html = module.handle_ajax("get_html", {})
module.save()
module.handle_ajax("save_answer", {"student_answer": self.answer}) module.handle_ajax("save_answer", {"student_answer": self.answer})
module.save()
html = module.handle_ajax("get_html", {}) html = module.handle_ajax("get_html", {})
module.save()
#Mock a student submitting an assessment #Mock a student submitting an assessment
assessment_dict = MockQueryDict() assessment_dict = MockQueryDict()
assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment}) assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment})
module.handle_ajax("save_assessment", assessment_dict) module.handle_ajax("save_assessment", assessment_dict)
module.save()
task_one_json = json.loads(module.task_states[0]) task_one_json = json.loads(module.task_states[0])
self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment) self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
rubric = module.handle_ajax("get_combined_rubric", {}) rubric = module.handle_ajax("get_combined_rubric", {})
module.save()
#Move to the next step in the problem #Move to the next step in the problem
module.handle_ajax("next_problem", {}) module.handle_ajax("next_problem", {})
module.save()
self.assertEqual(module.current_task_number, 0) self.assertEqual(module.current_task_number, 0)
html = module.get_html() html = module.runtime.render(module, None, 'student_view').content
self.assertIsInstance(html, basestring) self.assertIsInstance(html, basestring)
rubric = module.handle_ajax("get_combined_rubric", {}) rubric = module.handle_ajax("get_combined_rubric", {})
module.save()
self.assertIsInstance(rubric, basestring) self.assertIsInstance(rubric, basestring)
self.assertEqual(module.state, "assessing") self.assertEqual(module.state, "assessing")
module.handle_ajax("reset", {}) module.handle_ajax("reset", {})
module.save()
self.assertEqual(module.current_task_number, 0) self.assertEqual(module.current_task_number, 0)
def test_open_ended_flow_correct(self): def test_open_ended_flow_correct(self):
...@@ -789,31 +797,36 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore): ...@@ -789,31 +797,36 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
#Simulate a student saving an answer #Simulate a student saving an answer
module.handle_ajax("save_answer", {"student_answer": self.answer}) module.handle_ajax("save_answer", {"student_answer": self.answer})
module.save()
status = module.handle_ajax("get_status", {}) status = module.handle_ajax("get_status", {})
module.save()
self.assertIsInstance(status, basestring) self.assertIsInstance(status, basestring)
#Mock a student submitting an assessment #Mock a student submitting an assessment
assessment_dict = MockQueryDict() assessment_dict = MockQueryDict()
assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment}) assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment})
module.handle_ajax("save_assessment", assessment_dict) module.handle_ajax("save_assessment", assessment_dict)
module.save()
task_one_json = json.loads(module.task_states[0]) task_one_json = json.loads(module.task_states[0])
self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment) self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
#Move to the next step in the problem #Move to the next step in the problem
try: try:
module.handle_ajax("next_problem", {}) module.handle_ajax("next_problem", {})
module.save()
except GradingServiceError: except GradingServiceError:
#This error is okay. We don't have a grading service to connect to! #This error is okay. We don't have a grading service to connect to!
pass pass
self.assertEqual(module.current_task_number, 1) self.assertEqual(module.current_task_number, 1)
try: try:
module.get_html() module.runtime.render(module, None, 'student_view')
except GradingServiceError: except GradingServiceError:
#This error is okay. We don't have a grading service to connect to! #This error is okay. We don't have a grading service to connect to!
pass pass
#Try to get the rubric from the module #Try to get the rubric from the module
module.handle_ajax("get_combined_rubric", {}) module.handle_ajax("get_combined_rubric", {})
module.save()
#Make a fake reply from the queue #Make a fake reply from the queue
queue_reply = { queue_reply = {
...@@ -832,22 +845,27 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore): ...@@ -832,22 +845,27 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
} }
module.handle_ajax("check_for_score", {}) module.handle_ajax("check_for_score", {})
module.save()
#Update the module with the fake queue reply #Update the module with the fake queue reply
module.handle_ajax("score_update", queue_reply) module.handle_ajax("score_update", queue_reply)
module.save()
self.assertFalse(module.ready_to_reset) self.assertFalse(module.ready_to_reset)
self.assertEqual(module.current_task_number, 1) self.assertEqual(module.current_task_number, 1)
#Get html and other data client will request #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.handle_ajax("skip_post_assessment", {})
module.save()
#Get all results #Get all results
module.handle_ajax("get_combined_rubric", {}) module.handle_ajax("get_combined_rubric", {})
module.save()
#reset the problem #reset the problem
module.handle_ajax("reset", {}) module.handle_ajax("reset", {})
module.save()
self.assertEqual(module.state, "initial") self.assertEqual(module.state, "initial")
...@@ -876,31 +894,37 @@ class OpenEndedModuleXmlAttemptTest(unittest.TestCase, DummyModulestore): ...@@ -876,31 +894,37 @@ class OpenEndedModuleXmlAttemptTest(unittest.TestCase, DummyModulestore):
""" """
assessment = [0, 1] assessment = [0, 1]
module = self.get_module_from_location(self.problem_location, COURSE) module = self.get_module_from_location(self.problem_location, COURSE)
module.save()
#Simulate a student saving an answer #Simulate a student saving an answer
module.handle_ajax("save_answer", {"student_answer": self.answer}) module.handle_ajax("save_answer", {"student_answer": self.answer})
module.save()
#Mock a student submitting an assessment #Mock a student submitting an assessment
assessment_dict = MockQueryDict() assessment_dict = MockQueryDict()
assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment}) assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment})
module.handle_ajax("save_assessment", assessment_dict) module.handle_ajax("save_assessment", assessment_dict)
module.save()
task_one_json = json.loads(module.task_states[0]) task_one_json = json.loads(module.task_states[0])
self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment) self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
#Move to the next step in the problem #Move to the next step in the problem
module.handle_ajax("next_problem", {}) module.handle_ajax("next_problem", {})
module.save()
self.assertEqual(module.current_task_number, 0) self.assertEqual(module.current_task_number, 0)
html = module.get_html() html = module.runtime.render(module, None, 'student_view').content
self.assertTrue(isinstance(html, basestring)) self.assertIsInstance(html, basestring)
#Module should now be done #Module should now be done
rubric = module.handle_ajax("get_combined_rubric", {}) rubric = module.handle_ajax("get_combined_rubric", {})
self.assertTrue(isinstance(rubric, basestring)) module.save()
self.assertIsInstance(rubric, basestring)
self.assertEqual(module.state, "done") self.assertEqual(module.state, "done")
#Try to reset, should fail because only 1 attempt is allowed #Try to reset, should fail because only 1 attempt is allowed
reset_data = json.loads(module.handle_ajax("reset", {})) reset_data = json.loads(module.handle_ajax("reset", {}))
module.save()
self.assertEqual(reset_data['success'], False) self.assertEqual(reset_data['success'], False)
class OpenEndedModuleXmlImageUploadTest(unittest.TestCase, DummyModulestore): class OpenEndedModuleXmlImageUploadTest(unittest.TestCase, DummyModulestore):
......
...@@ -10,6 +10,7 @@ from xmodule.crowdsource_hinter import CrowdsourceHinterModule ...@@ -10,6 +10,7 @@ from xmodule.crowdsource_hinter import CrowdsourceHinterModule
from xmodule.vertical_module import VerticalModule, VerticalDescriptor from xmodule.vertical_module import VerticalModule, VerticalDescriptor
from xblock.field_data import DictFieldData from xblock.field_data import DictFieldData
from xblock.fragment import Fragment from xblock.fragment import Fragment
from xblock.core import XBlock
from . import get_test_system from . import get_test_system
...@@ -62,7 +63,8 @@ class CHModuleFactory(object): ...@@ -62,7 +63,8 @@ class CHModuleFactory(object):
""" """
A factory method for making CHM's 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: if hints is not None:
field_data['hints'] = hints field_data['hints'] = hints
...@@ -106,7 +108,8 @@ class CHModuleFactory(object): ...@@ -106,7 +108,8 @@ class CHModuleFactory(object):
# Make the descriptor have a capa problem child. # Make the descriptor have a capa problem child.
capa_descriptor = MagicMock() capa_descriptor = MagicMock()
capa_descriptor.name = 'capa' 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. # Make a fake capa module.
capa_module = MagicMock() capa_module = MagicMock()
...@@ -128,7 +131,7 @@ class CHModuleFactory(object): ...@@ -128,7 +131,7 @@ class CHModuleFactory(object):
responder.compare_answer = compare_answer responder.compare_answer = compare_answer
capa_module.lcp.responders = {'responder0': responder} 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() system = get_test_system()
# Make the system have a marginally-functional get_module # Make the system have a marginally-functional get_module
...@@ -137,8 +140,7 @@ class CHModuleFactory(object): ...@@ -137,8 +140,7 @@ class CHModuleFactory(object):
""" """
A fake module-maker. A fake module-maker.
""" """
if descriptor.name == 'capa': return capa_module
return capa_module
system.get_module = fake_get_module system.get_module = fake_get_module
module = CrowdsourceHinterModule(descriptor, system, DictFieldData(field_data), Mock()) module = CrowdsourceHinterModule(descriptor, system, DictFieldData(field_data), Mock())
...@@ -205,15 +207,15 @@ class VerticalWithModulesFactory(object): ...@@ -205,15 +207,15 @@ class VerticalWithModulesFactory(object):
return module return module
class FakeChild(object): class FakeChild(XBlock):
""" """
A fake Xmodule. A fake Xmodule.
""" """
def __init__(self): def __init__(self):
self.runtime = get_test_system() 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.student_view = Mock(return_value=Fragment(self.get_html()))
self.save = Mock() self.save = Mock()
self.id = 'i4x://this/is/a/fake/id'
def get_html(self): def get_html(self):
""" """
...@@ -243,7 +245,7 @@ class CrowdsourceHinterTest(unittest.TestCase): ...@@ -243,7 +245,7 @@ class CrowdsourceHinterTest(unittest.TestCase):
mock_module.get_display_items = fake_get_display_items mock_module.get_display_items = fake_get_display_items
out_html = mock_module.runtime.render(mock_module, None, 'student_view').content 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 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): def test_gethtml_nochild(self):
""" """
......
...@@ -30,8 +30,8 @@ class TestErrorModule(unittest.TestCase, SetupTestErrorModules): ...@@ -30,8 +30,8 @@ class TestErrorModule(unittest.TestCase, SetupTestErrorModules):
descriptor = error_module.ErrorDescriptor.from_xml( descriptor = error_module.ErrorDescriptor.from_xml(
self.valid_xml, self.system, self.org, self.course, self.error_msg) self.valid_xml, self.system, self.org, self.course, self.error_msg)
self.assertIsInstance(descriptor, error_module.ErrorDescriptor) self.assertIsInstance(descriptor, error_module.ErrorDescriptor)
module = descriptor.xmodule(self.system) descriptor.xmodule_runtime = self.system
context_repr = module.get_html() context_repr = self.system.render(descriptor, None, 'student_view').content
self.assertIn(self.error_msg, context_repr) self.assertIn(self.error_msg, context_repr)
self.assertIn(repr(self.valid_xml), context_repr) self.assertIn(repr(self.valid_xml), context_repr)
...@@ -44,8 +44,8 @@ class TestErrorModule(unittest.TestCase, SetupTestErrorModules): ...@@ -44,8 +44,8 @@ class TestErrorModule(unittest.TestCase, SetupTestErrorModules):
error_descriptor = error_module.ErrorDescriptor.from_descriptor( error_descriptor = error_module.ErrorDescriptor.from_descriptor(
descriptor, self.error_msg) descriptor, self.error_msg)
self.assertIsInstance(error_descriptor, error_module.ErrorDescriptor) self.assertIsInstance(error_descriptor, error_module.ErrorDescriptor)
module = error_descriptor.xmodule(self.system) error_descriptor.xmodule_runtime = self.system
context_repr = module.get_html() context_repr = self.system.render(error_descriptor, None, 'student_view').content
self.assertIn(self.error_msg, context_repr) self.assertIn(self.error_msg, context_repr)
self.assertIn(repr(descriptor), context_repr) self.assertIn(repr(descriptor), context_repr)
...@@ -65,8 +65,8 @@ class TestNonStaffErrorModule(unittest.TestCase, SetupTestErrorModules): ...@@ -65,8 +65,8 @@ class TestNonStaffErrorModule(unittest.TestCase, SetupTestErrorModules):
def test_from_xml_render(self): def test_from_xml_render(self):
descriptor = error_module.NonStaffErrorDescriptor.from_xml( descriptor = error_module.NonStaffErrorDescriptor.from_xml(
self.valid_xml, self.system, self.org, self.course) self.valid_xml, self.system, self.org, self.course)
module = descriptor.xmodule(self.system) descriptor.xmodule_runtime = self.system
context_repr = module.get_html() context_repr = self.system.render(descriptor, None, 'student_view').content
self.assertNotIn(self.error_msg, context_repr) self.assertNotIn(self.error_msg, context_repr)
self.assertNotIn(repr(self.valid_xml), context_repr) self.assertNotIn(repr(self.valid_xml), context_repr)
...@@ -79,7 +79,7 @@ class TestNonStaffErrorModule(unittest.TestCase, SetupTestErrorModules): ...@@ -79,7 +79,7 @@ class TestNonStaffErrorModule(unittest.TestCase, SetupTestErrorModules):
error_descriptor = error_module.NonStaffErrorDescriptor.from_descriptor( error_descriptor = error_module.NonStaffErrorDescriptor.from_descriptor(
descriptor, self.error_msg) descriptor, self.error_msg)
self.assertIsInstance(error_descriptor, error_module.ErrorDescriptor) self.assertIsInstance(error_descriptor, error_module.ErrorDescriptor)
module = error_descriptor.xmodule(self.system) error_descriptor.xmodule_runtime = self.system
context_repr = module.get_html() context_repr = self.system.render(error_descriptor, None, 'student_view').content
self.assertNotIn(self.error_msg, context_repr) self.assertNotIn(self.error_msg, context_repr)
self.assertNotIn(str(descriptor), context_repr) self.assertNotIn(str(descriptor), context_repr)
...@@ -199,6 +199,7 @@ class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore): ...@@ -199,6 +199,7 @@ class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore):
html = peer_grading.peer_grading() html = peer_grading.peer_grading()
self.assertIn("Peer-Graded", html) self.assertIn("Peer-Graded", html)
class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore): class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore):
""" """
Test peer grading that is linked to an open ended module. Test peer grading that is linked to an open ended module.
......
...@@ -137,6 +137,6 @@ class ModuleProgressTest(unittest.TestCase): ...@@ -137,6 +137,6 @@ class ModuleProgressTest(unittest.TestCase):
''' '''
def test_xmodule_default(self): def test_xmodule_default(self):
'''Make sure default get_progress exists, returns None''' '''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() p = xm.get_progress()
self.assertEqual(p, None) self.assertEqual(p, None)
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
Tests for the wrapping layer that provides the XBlock API using XModule/Descriptor Tests for the wrapping layer that provides the XBlock API using XModule/Descriptor
functionality functionality
""" """
# For tests, ignore access to protected members
# pylint: disable=protected-access
from nose.tools import assert_equal # pylint: disable=E0611 from nose.tools import assert_equal # pylint: disable=E0611
from unittest.case import SkipTest from unittest.case import SkipTest
...@@ -17,6 +19,7 @@ from xmodule.capa_module import CapaDescriptor ...@@ -17,6 +19,7 @@ from xmodule.capa_module import CapaDescriptor
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from xmodule.combined_open_ended_module import CombinedOpenEndedDescriptor from xmodule.combined_open_ended_module import CombinedOpenEndedDescriptor
from xmodule.discussion_module import DiscussionDescriptor from xmodule.discussion_module import DiscussionDescriptor
from xmodule.error_module import ErrorDescriptor
from xmodule.gst_module import GraphicalSliderToolDescriptor from xmodule.gst_module import GraphicalSliderToolDescriptor
from xmodule.html_module import HtmlDescriptor from xmodule.html_module import HtmlDescriptor
from xmodule.peer_grading_module import PeerGradingDescriptor from xmodule.peer_grading_module import PeerGradingDescriptor
...@@ -29,7 +32,7 @@ from xmodule.conditional_module import ConditionalDescriptor ...@@ -29,7 +32,7 @@ from xmodule.conditional_module import ConditionalDescriptor
from xmodule.randomize_module import RandomizeDescriptor from xmodule.randomize_module import RandomizeDescriptor
from xmodule.vertical_module import VerticalDescriptor from xmodule.vertical_module import VerticalDescriptor
from xmodule.wrapper_module import WrapperDescriptor 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 = ( LEAF_XMODULES = (
AnnotatableDescriptor, AnnotatableDescriptor,
...@@ -40,21 +43,20 @@ LEAF_XMODULES = ( ...@@ -40,21 +43,20 @@ LEAF_XMODULES = (
HtmlDescriptor, HtmlDescriptor,
PeerGradingDescriptor, PeerGradingDescriptor,
PollDescriptor, PollDescriptor,
WordCloudDescriptor,
# This is being excluded because it has dependencies on django # This is being excluded because it has dependencies on django
#VideoDescriptor, #VideoDescriptor,
WordCloudDescriptor,
) )
CONTAINER_XMODULES = ( CONTAINER_XMODULES = (
CrowdsourceHinterDescriptor,
CourseDescriptor,
SequenceDescriptor,
ConditionalDescriptor, ConditionalDescriptor,
CourseDescriptor,
CrowdsourceHinterDescriptor,
RandomizeDescriptor, RandomizeDescriptor,
SequenceDescriptor,
VerticalDescriptor, VerticalDescriptor,
WrapperDescriptor, WrapperDescriptor,
CourseDescriptor,
) )
# These modules are editable in studio yet # These modules are editable in studio yet
...@@ -66,26 +68,24 @@ NOT_STUDIO_EDITABLE = ( ...@@ -66,26 +68,24 @@ NOT_STUDIO_EDITABLE = (
class TestXBlockWrapper(object): class TestXBlockWrapper(object):
@property @property
def leaf_module_runtime(self): def leaf_module_runtime(self):
runtime = ModuleSystem( 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', anonymous_student_id='dummy_anonymous_student_id',
open_ended_grading_interface={}, open_ended_grading_interface={},
static_url='/static', static_url='/static',
ajax_url='dummy_ajax_url', ajax_url='dummy_ajax_url',
xmodule_field_data=lambda d: d._field_data,
get_module=Mock(), get_module=Mock(),
replace_urls=Mock(), replace_urls=Mock(),
track_function=Mock(), track_function=Mock(),
error_descriptor_class=ErrorDescriptor,
) )
return runtime return runtime
def leaf_descriptor(self, descriptor_cls): def leaf_descriptor(self, descriptor_cls):
location = 'i4x://org/course/category/name' location = 'i4x://org/course/category/name'
runtime = get_test_descriptor_system() runtime = get_test_descriptor_system()
runtime.render_template = lambda *args, **kwargs: u'{!r}, {!r}'.format(args, kwargs)
return runtime.construct_xblock_from_class( return runtime.construct_xblock_from_class(
descriptor_cls, descriptor_cls,
ScopeIds(None, descriptor_cls.__name__, location, location), ScopeIds(None, descriptor_cls.__name__, location, location),
...@@ -93,7 +93,10 @@ class TestXBlockWrapper(object): ...@@ -93,7 +93,10 @@ class TestXBlockWrapper(object):
) )
def leaf_module(self, descriptor_cls): 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): def container_module_runtime(self, depth):
runtime = self.leaf_module_runtime runtime = self.leaf_module_runtime
...@@ -104,10 +107,16 @@ class TestXBlockWrapper(object): ...@@ -104,10 +107,16 @@ class TestXBlockWrapper(object):
runtime.position = 2 runtime.position = 2
return runtime 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' location = 'i4x://org/course/category/name'
runtime = get_test_descriptor_system() 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( return runtime.construct_xblock_from_class(
descriptor_cls, descriptor_cls,
ScopeIds(None, descriptor_cls.__name__, location, location), ScopeIds(None, descriptor_cls.__name__, location, location),
...@@ -117,7 +126,10 @@ class TestXBlockWrapper(object): ...@@ -117,7 +126,10 @@ class TestXBlockWrapper(object):
) )
def container_module(self, descriptor_cls, depth): 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): class TestStudentView(TestXBlockWrapper):
...@@ -131,9 +143,11 @@ class TestStudentView(TestXBlockWrapper): ...@@ -131,9 +143,11 @@ class TestStudentView(TestXBlockWrapper):
# Check that when an xmodule is instantiated from descriptor_cls # Check that when an xmodule is instantiated from descriptor_cls
# it generates the same thing from student_view that it does from get_html # it generates the same thing from student_view that it does from get_html
def check_student_view_leaf_node(self, descriptor_cls): def check_student_view_leaf_node(self, descriptor_cls):
xmodule = self.leaf_module(descriptor_cls) descriptor = self.leaf_module(descriptor_cls)
assert_equal(xmodule.get_html(), xmodule.runtime.render(xmodule, None, 'student_view').content) assert_equal(
descriptor._xmodule.get_html(),
descriptor.runtime.render(descriptor, None, 'student_view').content
)
# Test that for all container XModule Descriptors, # Test that for all container XModule Descriptors,
# their corresponding XModule renders the same thing using student_view # their corresponding XModule renders the same thing using student_view
...@@ -147,13 +161,15 @@ class TestStudentView(TestXBlockWrapper): ...@@ -147,13 +161,15 @@ class TestStudentView(TestXBlockWrapper):
yield self.check_student_view_container_node_mixed, descriptor_cls yield self.check_student_view_container_node_mixed, descriptor_cls
yield self.check_student_view_container_node_xblocks_only, descriptor_cls yield self.check_student_view_container_node_xblocks_only, descriptor_cls
# Check that when an xmodule is generated from descriptor_cls # Check that when an xmodule is generated from descriptor_cls
# with only xmodule children, it generates the same html from student_view # with only xmodule children, it generates the same html from student_view
# as it does using get_html # as it does using get_html
def check_student_view_container_node_xmodules_only(self, descriptor_cls): def check_student_view_container_node_xmodules_only(self, descriptor_cls):
xmodule = self.container_module(descriptor_cls, 2) descriptor = self.container_module(descriptor_cls, 2)
assert_equal(xmodule.get_html(), xmodule.runtime.render(xmodule, None, 'student_view').content) assert_equal(
descriptor._xmodule.get_html(),
descriptor.runtime.render(descriptor, None, 'student_view').content
)
# Check that when an xmodule is generated from descriptor_cls # Check that when an xmodule is generated from descriptor_cls
# with mixed xmodule and xblock children, it generates the same html from student_view # with mixed xmodule and xblock children, it generates the same html from student_view
...@@ -206,7 +222,7 @@ class TestStudioView(TestXBlockWrapper): ...@@ -206,7 +222,7 @@ class TestStudioView(TestXBlockWrapper):
if descriptor_cls in NOT_STUDIO_EDITABLE: if descriptor_cls in NOT_STUDIO_EDITABLE:
raise SkipTest(descriptor_cls.__name__ + "is not editable in studio") 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) assert_equal(descriptor.get_html(), descriptor.runtime.render(descriptor, None, 'studio_view').content)
# Check that when a descriptor is generated from descriptor_cls # Check that when a descriptor is generated from descriptor_cls
......
...@@ -157,10 +157,6 @@ class VideoModule(VideoFields, XModule): ...@@ -157,10 +157,6 @@ class VideoModule(VideoFields, XModule):
log.debug(u"DISPATCH {0}".format(dispatch)) log.debug(u"DISPATCH {0}".format(dispatch))
raise Http404() raise Http404()
def get_instance_state(self):
"""Return information about state (position)."""
return json.dumps({'position': self.position})
def get_html(self): def get_html(self):
caption_asset_path = "/static/subs/" caption_asset_path = "/static/subs/"
......
<sequential> <sequential>
<video display_name="default" youtube_id_0_75="JMD_ifUUfsU" youtube_id_1_0="OEoXaMPEzfM" youtube_id_1_25="AKqURZnYqpk" youtube_id_1_5="DYpADpL7jAY" name="sample_video"/> <video display_name="default" youtube_id_0_75="JMD_ifUUfsU" youtube_id_1_0="OEoXaMPEzfM" youtube_id_1_25="AKqURZnYqpk" youtube_id_1_5="DYpADpL7jAY" name="sample_video"/>
<video url_name="separate_file_video"/> <video url_name="separate_file_video"/>
<poll_question name="T1_changemind_poll_foo_2" display_name="Change your answer" reset="false"> <poll_question name="T1_changemind_poll_foo_2" display_name="Change your answer" reset="false">
<p>Have you changed your mind?</p> <p>Have you changed your mind?</p>
<answer id="yes">Yes</answer> <answer id="yes">Yes</answer>
<answer id="no">No</answer> <answer id="no">No</answer>
......
...@@ -305,9 +305,9 @@ def progress_summary(student, request, course, field_data_cache): ...@@ -305,9 +305,9 @@ def progress_summary(student, request, course, field_data_cache):
graded = section_module.graded graded = section_module.graded
scores = [] 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 course_id = course.id
(correct, total) = get_score(course_id, student, module_descriptor, module_creator, field_data_cache) (correct, total) = get_score(course_id, student, module_descriptor, module_creator, field_data_cache)
......
...@@ -289,7 +289,7 @@ class DjangoKeyValueStore(KeyValueStore): ...@@ -289,7 +289,7 @@ class DjangoKeyValueStore(KeyValueStore):
def get(self, key): def get(self, key):
if key.scope not in self._allowed_scopes: if key.scope not in self._allowed_scopes:
raise InvalidScopeError(key.scope) raise InvalidScopeError(key)
field_object = self._field_data_cache.find(key) field_object = self._field_data_cache.find(key)
if field_object is None: if field_object is None:
...@@ -320,7 +320,7 @@ class DjangoKeyValueStore(KeyValueStore): ...@@ -320,7 +320,7 @@ class DjangoKeyValueStore(KeyValueStore):
for field in kv_dict: for field in kv_dict:
# Check field for validity # Check field for validity
if field.scope not in self._allowed_scopes: 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. # If the field is valid and isn't already in the dictionary, add it.
field_object = self._field_data_cache.find_or_create(field) field_object = self._field_data_cache.find_or_create(field)
...@@ -352,7 +352,7 @@ class DjangoKeyValueStore(KeyValueStore): ...@@ -352,7 +352,7 @@ class DjangoKeyValueStore(KeyValueStore):
def delete(self, key): def delete(self, key):
if key.scope not in self._allowed_scopes: if key.scope not in self._allowed_scopes:
raise InvalidScopeError(key.scope) raise InvalidScopeError(key)
field_object = self._field_data_cache.find(key) field_object = self._field_data_cache.find(key)
if field_object is None: if field_object is None:
...@@ -368,7 +368,7 @@ class DjangoKeyValueStore(KeyValueStore): ...@@ -368,7 +368,7 @@ class DjangoKeyValueStore(KeyValueStore):
def has(self, key): def has(self, key):
if key.scope not in self._allowed_scopes: if key.scope not in self._allowed_scopes:
raise InvalidScopeError(key.scope) raise InvalidScopeError(key)
field_object = self._field_data_cache.find(key) field_object = self._field_data_cache.find(key)
if field_object is None: if field_object is None:
......
...@@ -219,6 +219,9 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -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): if not has_access(user, descriptor, 'load', course_id):
return None 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 # Setup system context for module instance
ajax_url = reverse( ajax_url = reverse(
'modx_dispatch', 'modx_dispatch',
...@@ -294,10 +297,6 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -294,10 +297,6 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
position, wrap_xmodule_display, grade_bucket_type, position, wrap_xmodule_display, grade_bucket_type,
static_asset_path) 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): def publish(event):
"""A function that allows XModules to publish events. This only supports grade changes right now.""" """A function that allows XModules to publish events. This only supports grade changes right now."""
if event.get('event_name') != 'grade': if event.get('event_name') != 'grade':
...@@ -405,7 +404,6 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -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': ''}) jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': course_id, 'module_id': ''})
), ),
node_path=settings.NODE_PATH, node_path=settings.NODE_PATH,
xmodule_field_data=xmodule_field_data,
publish=publish, publish=publish,
anonymous_student_id=unique_id_for_user(user), anonymous_student_id=unique_id_for_user(user),
course_id=course_id, course_id=course_id,
...@@ -426,27 +424,17 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -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()) make_psychometrics_data_update_handler(course_id, user, descriptor.location.url())
) )
try: system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id))
module = descriptor.xmodule(system)
except:
log.exception("Error creating module from descriptor {0}".format(descriptor))
# make an ErrorDescriptor -- assuming that the descriptor's system is ok
if has_access(user, descriptor.location, 'staff', course_id):
err_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())
)
# Make an error module # make an ErrorDescriptor -- assuming that the descriptor's system is ok
return err_descriptor.xmodule(system) if has_access(user, descriptor.location, 'staff', course_id):
system.error_descriptor_class = ErrorDescriptor
else:
system.error_descriptor_class = NonStaffErrorDescriptor
system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id)) descriptor.xmodule_runtime = system
return module 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): def find_target_student_module(request, user_id, course_id, mod_id):
......
...@@ -16,7 +16,7 @@ from student.tests.factories import UserFactory, CourseEnrollmentFactory ...@@ -16,7 +16,7 @@ from student.tests.factories import UserFactory, CourseEnrollmentFactory
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from xblock.field_data import DictFieldData from xblock.field_data import DictFieldData
from xblock.fields import Scope 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 import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
...@@ -49,11 +49,26 @@ class BaseTestXmodule(ModuleStoreTestCase): ...@@ -49,11 +49,26 @@ class BaseTestXmodule(ModuleStoreTestCase):
DATA = '' DATA = ''
MODEL_DATA = {'data': '<some_module></some_module>'} MODEL_DATA = {'data': '<some_module></some_module>'}
def xmodule_field_data(self, descriptor): def new_module_runtime(self):
field_data = {} """
field_data.update(self.MODEL_DATA) Generate a new ModuleSystem that is minimally set up for testing
student_data = DictFieldData(field_data) """
return lms_field_data(descriptor._field_data, student_data) 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): def setUp(self):
...@@ -87,16 +102,15 @@ class BaseTestXmodule(ModuleStoreTestCase): ...@@ -87,16 +102,15 @@ class BaseTestXmodule(ModuleStoreTestCase):
data=self.DATA data=self.DATA
) )
self.runtime = get_test_system(course_id=self.course.id) self.runtime = self.new_descriptor_runtime()
# 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.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() self.item_url = Location(self.item_module.location).url()
...@@ -119,7 +133,11 @@ class BaseTestXmodule(ModuleStoreTestCase): ...@@ -119,7 +133,11 @@ class BaseTestXmodule(ModuleStoreTestCase):
class XModuleRenderingTestBase(BaseTestXmodule): 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): ...@@ -39,7 +39,7 @@ class TestLTI(BaseTestXmodule):
u'oauth_consumer_key': u'', u'oauth_consumer_key': u'',
u'oauth_signature_method': u'HMAC-SHA1', u'oauth_signature_method': u'HMAC-SHA1',
u'oauth_version': u'1.0', 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'role': u'student',
u'oauth_signature': mocked_decoded_signature u'oauth_signature': mocked_decoded_signature
} }
...@@ -69,12 +69,14 @@ class TestLTI(BaseTestXmodule): ...@@ -69,12 +69,14 @@ class TestLTI(BaseTestXmodule):
""" """
Makes sure that all parameters extracted. Makes sure that all parameters extracted.
""" """
self.runtime.render_template = lambda template, context: context generated_context = self.item_module.runtime.render(self.item_module, None, 'student_view').content
generated_context = self.item_module.get_html()
expected_context = { expected_context = {
'input_fields': self.correct_headers, 'input_fields': self.correct_headers,
'element_class': self.item_module.location.category, 'element_class': self.item_module.location.category,
'element_id': self.item_module.location.html_id(), 'element_id': self.item_module.location.html_id(),
'launch_url': 'http://www.example.com', # default value '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): ...@@ -74,7 +74,7 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
) )
# get the rendered HTML output which should have the rewritten link # 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 # See if the url got rewritten to the target link
# note if the URL mapping changes then this assertion will break # note if the URL mapping changes then this assertion will break
......
...@@ -17,10 +17,13 @@ class TestTimeLimitModuleRendering(XModuleRenderingTestBase): ...@@ -17,10 +17,13 @@ class TestTimeLimitModuleRendering(XModuleRenderingTestBase):
""" """
def test_with_children(self): def test_with_children(self):
block = ItemFactory.create(category='timelimit') 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) 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): def test_without_children(self):
block = ItemFactory.create(category='timelimit') 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): ...@@ -13,14 +13,6 @@ class TestVideo(BaseTestXmodule):
CATEGORY = "video" CATEGORY = "video"
DATA = SOURCE_XML 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): def test_handle_ajax_dispatch(self):
responses = { responses = {
user.username: self.clients[user.username].post( user.username: self.clients[user.username].post(
...@@ -40,7 +32,7 @@ class TestVideo(BaseTestXmodule): ...@@ -40,7 +32,7 @@ class TestVideo(BaseTestXmodule):
def test_video_constructor(self): def test_video_constructor(self):
"""Make sure that all parameters extracted correclty from xml""" """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 = { sources = {
'main': u'example.mp4', 'main': u'example.mp4',
...@@ -53,7 +45,7 @@ class TestVideo(BaseTestXmodule): ...@@ -53,7 +45,7 @@ class TestVideo(BaseTestXmodule):
'data_dir': getattr(self, 'data_dir', None), 'data_dir': getattr(self, 'data_dir', None),
'caption_asset_path': '/static/subs/', 'caption_asset_path': '/static/subs/',
'show_captions': 'true', 'show_captions': 'true',
'display_name': 'A Name', 'display_name': u'A Name',
'end': 3610.0, 'end': 3610.0,
'id': self.item_module.location.html_id(), 'id': self.item_module.location.html_id(),
'sources': sources, 'sources': sources,
...@@ -66,8 +58,10 @@ class TestVideo(BaseTestXmodule): ...@@ -66,8 +58,10 @@ class TestVideo(BaseTestXmodule):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/' 'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/'
} }
self.maxDiff = None self.assertEqual(
self.assertEqual(context, expected_context) context,
self.item_module.xmodule_runtime.render_template('video.html', expected_context)
)
class TestVideoNonYouTube(TestVideo): class TestVideoNonYouTube(TestVideo):
...@@ -93,24 +87,24 @@ class TestVideoNonYouTube(TestVideo): ...@@ -93,24 +87,24 @@ class TestVideoNonYouTube(TestVideo):
the template generates an empty string for the YouTube streams. the template generates an empty string for the YouTube streams.
""" """
sources = { sources = {
u'main': u'example.mp4', 'main': u'example.mp4',
u'mp4': u'example.mp4', u'mp4': u'example.mp4',
u'webm': u'example.webm', u'webm': u'example.webm',
u'ogv': u'example.ogv' 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 = { expected_context = {
'data_dir': getattr(self, 'data_dir', None), 'data_dir': getattr(self, 'data_dir', None),
'caption_asset_path': '/static/subs/', 'caption_asset_path': '/static/subs/',
'show_captions': 'true', 'show_captions': 'true',
'display_name': 'A Name', 'display_name': u'A Name',
'end': 3610.0, 'end': 3610.0,
'id': self.item_module.location.html_id(), 'id': self.item_module.location.html_id(),
'sources': sources, 'sources': sources,
'start': 3603.0, 'start': 3603.0,
'sub': 'a_sub_file.srt.sjson', 'sub': u'a_sub_file.srt.sjson',
'track': '', 'track': '',
'youtube_streams': '1.00:OEoXaMPEzfM', 'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.MITX_FEATURES.get('AUTOPLAY_VIDEOS', True), 'autoplay': settings.MITX_FEATURES.get('AUTOPLAY_VIDEOS', True),
...@@ -118,4 +112,7 @@ class TestVideoNonYouTube(TestVideo): ...@@ -118,4 +112,7 @@ class TestVideoNonYouTube(TestVideo):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/' '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 ...@@ -22,7 +22,7 @@ from django.conf import settings
from xmodule.video_module import VideoDescriptor, _create_youtube_string from xmodule.video_module import VideoDescriptor, _create_youtube_string
from xmodule.modulestore import Location 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.field_data import DictFieldData
from xblock.fields import ScopeIds from xblock.fields import ScopeIds
...@@ -57,23 +57,18 @@ class VideoFactory(object): ...@@ -57,23 +57,18 @@ class VideoFactory(object):
field_data = {'data': VideoFactory.sample_problem_xml_youtube, field_data = {'data': VideoFactory.sample_problem_xml_youtube,
'location': location} 'location': location}
system = get_test_system() system = get_test_descriptor_system()
system.render_template = lambda template, context: context
descriptor = VideoDescriptor(system, DictFieldData(field_data), ScopeIds(None, None, None, None)) descriptor = VideoDescriptor(system, DictFieldData(field_data), ScopeIds(None, None, None, None))
descriptor.xmodule_runtime = get_test_system()
module = descriptor.xmodule(system) return descriptor
return module
class VideoModuleUnitTest(unittest.TestCase): class VideoModuleUnitTest(unittest.TestCase):
"""Unit tests for Video Xmodule.""" """Unit tests for Video Xmodule."""
def test_video_get_html(self): def test_video_get_html(self):
"""Make sure that all parameters extracted correclty from xml""" """Make sure that all parameters extracted correclty from xml"""
module = VideoFactory.create() module = VideoFactory.create()
module.runtime.render_template = lambda template, context: context
sources = { sources = {
'main': 'example.mp4', 'main': 'example.mp4',
...@@ -99,14 +94,10 @@ class VideoModuleUnitTest(unittest.TestCase): ...@@ -99,14 +94,10 @@ class VideoModuleUnitTest(unittest.TestCase):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/' 'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/'
} }
self.assertEqual(module.get_html(), expected_context) self.assertEqual(
module.runtime.render(module, None, 'student_view').content,
def test_video_instance_state(self): module.runtime.render_template('video.html', expected_context)
module = VideoFactory.create() )
self.assertDictEqual(
json.loads(module.get_instance_state()),
{'position': 0})
class VideoModuleLogicTest(LogicTest): class VideoModuleLogicTest(LogicTest):
......
...@@ -245,7 +245,7 @@ class TestWordCloud(BaseTestXmodule): ...@@ -245,7 +245,7 @@ class TestWordCloud(BaseTestXmodule):
fragment = self.runtime.render(self.item_module, None, 'student_view') fragment = self.runtime.render(self.item_module, None, 'student_view')
expected_context = { 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_class': self.item_module.location.category,
'element_id': self.item_module.location.html_id(), 'element_id': self.item_module.location.html_id(),
'num_inputs': 5, # default value 'num_inputs': 5, # default value
......
...@@ -142,7 +142,7 @@ def redirect_to_course_position(course_module): ...@@ -142,7 +142,7 @@ def redirect_to_course_position(course_module):
the first child. the first child.
""" """
urlargs = {'course_id': course_module.descriptor.id} urlargs = {'course_id': course_module.id}
chapter = get_current_child(course_module) chapter = get_current_child(course_module)
if chapter is None: if chapter is None:
# oops. Something bad has happened. # oops. Something bad has happened.
......
...@@ -310,6 +310,7 @@ def rescore_problem_module_state(module_descriptor, student_module, xmodule_inst ...@@ -310,6 +310,7 @@ def rescore_problem_module_state(module_descriptor, student_module, xmodule_inst
raise UpdateProblemModuleStateError(msg) raise UpdateProblemModuleStateError(msg)
result = instance.rescore_problem() result = instance.rescore_problem()
instance.save()
if 'success' not in result: if 'success' not in result:
# don't consider these fatal, but false means that the individual call didn't complete: # 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}: " 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 ...@@ -13,8 +13,6 @@ from xmodule.x_module import ModuleSystem
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
import datetime import datetime
from xblock.field_data import DictFieldData
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
NOTIFICATION_CACHE_TIME = 300 NOTIFICATION_CACHE_TIME = 300
...@@ -70,7 +68,6 @@ def peer_grading_notifications(course, user): ...@@ -70,7 +68,6 @@ def peer_grading_notifications(course, user):
get_module = None, get_module = None,
render_template=render_to_string, render_template=render_to_string,
replace_urls=None, replace_urls=None,
xmodule_field_data=DictFieldData({}),
) )
peer_gs = peer_grading_service.PeerGradingService(settings.OPEN_ENDED_GRADING_INTERFACE, system) peer_gs = peer_grading_service.PeerGradingService(settings.OPEN_ENDED_GRADING_INTERFACE, system)
pending_grading = False pending_grading = False
...@@ -132,7 +129,6 @@ def combined_notifications(course, user): ...@@ -132,7 +129,6 @@ def combined_notifications(course, user):
get_module = None, get_module = None,
render_template=render_to_string, render_template=render_to_string,
replace_urls=None, replace_urls=None,
xmodule_field_data=DictFieldData({})
) )
#Initialize controller query service using our mock system #Initialize controller query service using our mock system
controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, 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 ...@@ -9,8 +9,6 @@ from xmodule.open_ended_grading_classes.grading_service_module import GradingSer
from django.conf import settings from django.conf import settings
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from xblock.field_data import DictFieldData
from courseware.access import has_access from courseware.access import has_access
from util.json_request import expect_json from util.json_request import expect_json
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
...@@ -76,7 +74,6 @@ class StaffGradingService(GradingService): ...@@ -76,7 +74,6 @@ class StaffGradingService(GradingService):
get_module = None, get_module = None,
render_template=render_to_string, render_template=render_to_string,
replace_urls=None, replace_urls=None,
xmodule_field_data=DictFieldData({})
) )
super(StaffGradingService, self).__init__(config) super(StaffGradingService, self).__init__(config)
self.url = config['url'] + config['staff_grading'] self.url = config['url'] + config['staff_grading']
......
...@@ -16,6 +16,7 @@ from xmodule.open_ended_grading_classes import peer_grading_service, controller_ ...@@ -16,6 +16,7 @@ from xmodule.open_ended_grading_classes import peer_grading_service, controller_
from xmodule import peer_grading_module from xmodule import peer_grading_module
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.x_module import ModuleSystem from xmodule.x_module import ModuleSystem
from xmodule.error_module import ErrorDescriptor
from xblock.fields import ScopeIds from xblock.fields import ScopeIds
from open_ended_grading import staff_grading_service, views, utils from open_ended_grading import staff_grading_service, views, utils
...@@ -251,13 +252,14 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -251,13 +252,14 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
get_module=None, get_module=None,
render_template=render_to_string, render_template=render_to_string,
replace_urls=None, replace_urls=None,
xmodule_field_data=lambda d: d._field_data,
s3_interface=test_util_open_ended.S3_INTERFACE, s3_interface=test_util_open_ended.S3_INTERFACE,
open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE, open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
mixins=settings.XBLOCK_MIXINS, mixins=settings.XBLOCK_MIXINS,
error_descriptor_class=ErrorDescriptor,
) )
self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system, field_data, ScopeIds(None, None, None, None)) 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.peer_module.peer_gs = self.mock_service
self.logout() self.logout()
......
...@@ -36,7 +36,6 @@ system = ModuleSystem( ...@@ -36,7 +36,6 @@ system = ModuleSystem(
get_module=None, get_module=None,
render_template=render_to_string, render_template=render_to_string,
replace_urls=None, 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