Commit 92cc1039 by Calen Pennington

Merge pull request #1088 from cpennington/pseudo-merge-descriptors-and-modules

Proxy to XModule on demand
parents 3cebb4ea 658810a9
...@@ -81,7 +81,7 @@ def preview_component(request, location): ...@@ -81,7 +81,7 @@ def preview_component(request, location):
return render_to_response('component.html', { return render_to_response('component.html', {
'preview': get_preview_html(request, component, 0), 'preview': get_preview_html(request, component, 0),
'editor': component.runtime.render(component, None, 'studio_view').content, 'editor': component.render('studio_view').content,
}) })
...@@ -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):
...@@ -167,4 +157,4 @@ def get_preview_html(request, descriptor, idx): ...@@ -167,4 +157,4 @@ def get_preview_html(request, descriptor, idx):
specified by the descriptor and idx. specified by the descriptor and idx.
""" """
module = load_preview_module(request, str(idx), descriptor) module = load_preview_module(request, str(idx), descriptor)
return module.runtime.render(module, None, "student_view").content return module.render("student_view").content
...@@ -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)
...@@ -148,7 +149,7 @@ class ConditionalModule(ConditionalFields, XModule): ...@@ -148,7 +149,7 @@ class ConditionalModule(ConditionalFields, XModule):
context) context)
return json.dumps({'html': [html], 'message': bool(message)}) return json.dumps({'html': [html], 'message': bool(message)})
html = [self.runtime.render_child(child, None, 'student_view').content for child in self.get_display_items()] html = [child.render('student_view').content for child in self.get_display_items()]
return json.dumps({'html': html}) return json.dumps({'html': html})
......
...@@ -113,17 +113,17 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -113,17 +113,17 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
try: try:
child = self.get_display_items()[0] child = self.get_display_items()[0]
out = self.runtime.render_child(child, None, 'student_view').content out = child.render('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()
......
...@@ -7,7 +7,6 @@ from pytz import UTC ...@@ -7,7 +7,6 @@ from pytz import UTC
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.x_module import XModuleDescriptor from xmodule.x_module import XModuleDescriptor
from xmodule.course_module import CourseDescriptor
class Dummy(object): class Dummy(object):
...@@ -124,16 +123,28 @@ class ItemFactory(XModuleFactory): ...@@ -124,16 +123,28 @@ class ItemFactory(XModuleFactory):
:target_class: is ignored :target_class: is ignored
""" """
# All class attributes (from this class and base classes) are
# passed in via **kwargs. However, some of those aren't actual field values,
# so pop those off for use separately
DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info'] DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info']
# catch any old style users before they get into trouble # catch any old style users before they get into trouble
assert not 'template' in kwargs assert 'template' not in kwargs
data = kwargs.get('data') parent_location = Location(kwargs.pop('parent_location', None))
category = kwargs.get('category') data = kwargs.pop('data', None)
display_name = kwargs.get('display_name') category = kwargs.pop('category', None)
metadata = kwargs.get('metadata', {}) display_name = kwargs.pop('display_name', None)
location = kwargs.get('location') metadata = kwargs.pop('metadata', {})
if kwargs.get('boilerplate') is not None: location = kwargs.pop('location')
template_id = kwargs.get('boilerplate') assert location != parent_location
store = kwargs.pop('modulestore')
# This code was based off that in cms/djangoapps/contentstore/views.py
parent = kwargs.pop('parent', None) or store.get_item(parent_location)
if 'boilerplate' in kwargs:
template_id = kwargs.pop('boilerplate')
clz = XModuleDescriptor.load_class(category) clz = XModuleDescriptor.load_class(category)
template = clz.get_template(template_id) template = clz.get_template(template_id)
assert template is not None assert template is not None
...@@ -141,21 +152,20 @@ class ItemFactory(XModuleFactory): ...@@ -141,21 +152,20 @@ class ItemFactory(XModuleFactory):
if not isinstance(data, basestring): if not isinstance(data, basestring):
data.update(template.get('data')) data.update(template.get('data'))
store = kwargs.get('modulestore')
# replace the display name with an optional parameter passed in from the caller # replace the display name with an optional parameter passed in from the caller
if display_name is not None: if display_name is not None:
metadata['display_name'] = display_name metadata['display_name'] = display_name
store.create_and_save_xmodule(location, metadata=metadata, definition_data=data) module = store.create_and_save_xmodule(location, metadata=metadata, definition_data=data)
if location.category not in DETACHED_CATEGORIES: module = store.get_item(location)
parent_location = Location(kwargs.get('parent_location')) for attr, val in kwargs.items():
assert location != parent_location setattr(module, attr, val)
module.save()
# This code was based off that in cms/djangoapps/contentstore/views.py store.save_xmodule(module)
parent = kwargs.get('parent') or store.get_item(parent_location)
if location.category not in DETACHED_CATEGORIES:
parent.children.append(location.url()) parent.children.append(location.url())
store.update_children(parent_location, parent.children) store.update_children(parent_location, parent.children)
......
...@@ -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')
...@@ -82,7 +82,7 @@ class RandomizeModule(RandomizeFields, XModule): ...@@ -82,7 +82,7 @@ class RandomizeModule(RandomizeFields, XModule):
# raise error instead? In fact, could complain on descriptor load... # raise error instead? In fact, could complain on descriptor load...
return u"<div>Nothing to randomize between</div>" return u"<div>Nothing to randomize between</div>"
return self.runtime.render_child(self.child, None, 'student_view').content return self.child.render('student_view').content
def get_icon_class(self): def get_icon_class(self):
return self.child.get_icon_class() if self.child else 'other' return self.child.get_icon_class() if self.child else 'other'
......
...@@ -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
...@@ -82,7 +79,7 @@ class SequenceModule(SequenceFields, XModule): ...@@ -82,7 +79,7 @@ class SequenceModule(SequenceFields, XModule):
for child in self.get_display_items(): for child in self.get_display_items():
progress = child.get_progress() progress = child.get_progress()
childinfo = { childinfo = {
'content': self.runtime.render_child(child, None, 'student_view').content, 'content': child.render('student_view').content,
'title': "\n".join( 'title': "\n".join(
grand_child.display_name grand_child.display_name
for grand_child in child.get_children() for grand_child in child.get_children()
......
...@@ -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."
...@@ -519,7 +519,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): ...@@ -519,7 +519,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
""" """
See if we can get the max score from the actual xmodule See if we can get the max score from the actual xmodule
""" """
#The progress view requires that this function be exposed # The progress view requires that this function be exposed
max_score = self.combinedoe_container.max_score() max_score = self.combinedoe_container.max_score()
self.assertEqual(max_score, None) self.assertEqual(max_score, None)
...@@ -751,30 +751,38 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore): ...@@ -751,30 +751,38 @@ class OpenEndedModuleXmlTest(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)
#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.render('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):
...@@ -784,38 +792,43 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore): ...@@ -784,38 +792,43 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
@return: @return:
""" """
assessment = [1, 1] assessment = [1, 1]
#Load the module # Load the module
module = self.get_module_from_location(self.problem_location, COURSE) module = self.get_module_from_location(self.problem_location, COURSE)
#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.render('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 = {
'queuekey': "", 'queuekey': "",
'xqueue_body': json.dumps({ 'xqueue_body': json.dumps({
...@@ -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.render('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.render('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):
...@@ -929,7 +953,7 @@ class OpenEndedModuleXmlImageUploadTest(unittest.TestCase, DummyModulestore): ...@@ -929,7 +953,7 @@ class OpenEndedModuleXmlImageUploadTest(unittest.TestCase, DummyModulestore):
""" """
module = self.get_module_from_location(self.problem_location, COURSE) module = self.get_module_from_location(self.problem_location, COURSE)
#Simulate a student saving an answer # Simulate a student saving an answer
response = module.handle_ajax("save_answer", {"student_answer": self.answer_text}) response = module.handle_ajax("save_answer", {"student_answer": self.answer_text})
response = json.loads(response) response = json.loads(response)
self.assertFalse(response['success']) self.assertFalse(response['success'])
...@@ -949,7 +973,7 @@ class OpenEndedModuleXmlImageUploadTest(unittest.TestCase, DummyModulestore): ...@@ -949,7 +973,7 @@ class OpenEndedModuleXmlImageUploadTest(unittest.TestCase, DummyModulestore):
""" """
module = self.get_module_from_location(self.problem_location, COURSE) module = self.get_module_from_location(self.problem_location, COURSE)
#Simulate a student saving an answer with a file # Simulate a student saving an answer with a file
response = module.handle_ajax("save_answer", { response = module.handle_ajax("save_answer", {
"student_answer": self.answer_text, "student_answer": self.answer_text,
"valid_files_attached": True, "valid_files_attached": True,
......
...@@ -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):
""" """
...@@ -241,9 +243,9 @@ class CrowdsourceHinterTest(unittest.TestCase): ...@@ -241,9 +243,9 @@ class CrowdsourceHinterTest(unittest.TestCase):
""" """
return [FakeChild()] return [FakeChild()]
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.render('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):
""" """
...@@ -258,7 +260,7 @@ class CrowdsourceHinterTest(unittest.TestCase): ...@@ -258,7 +260,7 @@ class CrowdsourceHinterTest(unittest.TestCase):
""" """
return [] return []
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.render('student_view').content
self.assertTrue('Error in loading crowdsourced hinter' in out_html) self.assertTrue('Error in loading crowdsourced hinter' in out_html)
@unittest.skip("Needs to be finished.") @unittest.skip("Needs to be finished.")
...@@ -269,7 +271,7 @@ class CrowdsourceHinterTest(unittest.TestCase): ...@@ -269,7 +271,7 @@ class CrowdsourceHinterTest(unittest.TestCase):
NOT WORKING RIGHT NOW NOT WORKING RIGHT NOW
""" """
mock_module = VerticalWithModulesFactory.create() mock_module = VerticalWithModulesFactory.create()
out_html = mock_module.runtime.render(mock_module, None, 'student_view').content out_html = mock_module.render('student_view').content
self.assertTrue('Test numerical problem.' in out_html) self.assertTrue('Test numerical problem.' in out_html)
self.assertTrue('Another test numerical problem.' in out_html) self.assertTrue('Another test numerical problem.' in out_html)
......
...@@ -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, '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, '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, '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, '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.render('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.render('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
...@@ -184,7 +200,7 @@ class TestStudioView(TestXBlockWrapper): ...@@ -184,7 +200,7 @@ class TestStudioView(TestXBlockWrapper):
raise SkipTest(descriptor_cls.__name__ + "is not editable in studio") raise SkipTest(descriptor_cls.__name__ + "is not editable in studio")
descriptor = self.leaf_descriptor(descriptor_cls) descriptor = self.leaf_descriptor(descriptor_cls)
assert_equal(descriptor.get_html(), descriptor.runtime.render(descriptor, None, 'studio_view').content) assert_equal(descriptor.get_html(), descriptor.render('studio_view').content)
# Test that for all of the Descriptors listed in CONTAINER_XMODULES # Test that for all of the Descriptors listed in CONTAINER_XMODULES
...@@ -206,8 +222,8 @@ class TestStudioView(TestXBlockWrapper): ...@@ -206,8 +222,8 @@ 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.render('studio_view').content)
# Check that when a descriptor is generated from descriptor_cls # Check that when a descriptor is generated from descriptor_cls
# with mixed xmodule and xblock children, it generates the same html from studio_view # with mixed xmodule and xblock children, it generates the same html from studio_view
......
...@@ -89,7 +89,7 @@ class TimeLimitModule(TimeLimitFields, XModule): ...@@ -89,7 +89,7 @@ class TimeLimitModule(TimeLimitFields, XModule):
children = self.get_display_items() children = self.get_display_items()
if children: if children:
child = children[0] child = children[0]
return self.runtime.render_child(child, None, 'student_view').content return child.render('student_view').content
else: else:
return u"" return u""
......
...@@ -23,7 +23,7 @@ class VerticalModule(VerticalFields, XModule): ...@@ -23,7 +23,7 @@ class VerticalModule(VerticalFields, XModule):
if self.contents is None: if self.contents is None:
self.contents = [{ self.contents = [{
'id': child.id, 'id': child.id,
'content': self.runtime.render_child(child, None, 'student_view').content 'content': child.render('student_view').content
} for child in self.get_display_items()] } for child in self.get_display_items()]
return self.system.render_template('vert_module.html', { return self.system.render_template('vert_module.html', {
......
...@@ -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/"
......
<section class="about">
<h2>About This Course</h2>
<p>Include your long course description here. The long course description should contain 150-400 words.</p>
<p>This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags.</p>
</section>
<section class="prerequisites">
<h2>Prerequisites</h2>
<p>Add information about course prerequisites here.</p>
</section>
<section class="course-staff">
<h2>Course Staff</h2>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
</div>
<h3>Staff Member #1</h3>
<p>Biography of instructor/staff member #1</p>
</article>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
</div>
<h3>Staff Member #2</h3>
<p>Biography of instructor/staff member #2</p>
</article>
</section>
<section class="faq">
<section class="responses">
<h2>Frequently Asked Questions</h2>
<article class="response">
<h3>Do I need to buy a textbook?</h3>
<p>No, a free online version of Chemistry: Principles, Patterns, and Applications, First Edition by Bruce Averill and Patricia Eldredge will be available, though you can purchase a printed version (published by FlatWorld Knowledge) if you’d like.</p>
</article>
<article class="response">
<h3>Question #2</h3>
<p>Your answer would be displayed here.</p>
</article>
</section>
</section>
<chapter display_name="Section">
<sequential url_name="c804fa32227142a1bd9d5bc183d4a20d"/>
</chapter>
<course url_name="2013_fall" org="edX" course="due_date"/>
<course display_name="due_date">
<chapter url_name="c8ee0db7e5a84c85bac80b7013cf6512"/>
</course>
{"GRADER": [{"short_label": "HW", "min_count": 12, "type": "Homework", "drop_count": 2, "weight": 0.15}, {"min_count": 12, "type": "Lab", "drop_count": 2, "weight": 0.15}, {"short_label": "Midterm", "min_count": 1, "type": "Midterm Exam", "drop_count": 0, "weight": 0.3}, {"short_label": "Final", "min_count": 1, "type": "Final Exam", "drop_count": 0, "weight": 0.4}], "GRADE_CUTOFFS": {"Pass": 0.5}}
\ No newline at end of file
{"course/2013_fall": {"tabs": [{"type": "courseware"}, {"type": "course_info", "name": "Course Info"}, {"type": "textbooks"}, {"type": "discussion", "name": "Discussion"}, {"type": "wiki", "name": "Wiki"}, {"type": "progress", "name": "Progress"}], "display_name": "due_date", "discussion_topics": {"General": {"id": "i4x-edX-due_date-course-2013_fall"}}, "show_timezone": "false"}}
\ No newline at end of file
<problem display_name="Multiple Choice" markdown="A multiple choice problem presents radio buttons for student input. Students can only select a single &#10;option presented. Multiple Choice questions have been the subject of many areas of research due to the early &#10;invention and adoption of bubble sheets.&#10;&#10;One of the main elements that goes into a good multiple choice question is the existence of good distractors. &#10;That is, each of the alternate responses presented to the student should be the result of a plausible mistake &#10;that a student might make.&#10;&#10;What Apple device competed with the portable CD player?&#10; ( ) The iPad&#10; ( ) Napster&#10; (x) The iPod&#10; ( ) The vegetable peeler&#10; &#10;[explanation]&#10;The release of the iPod allowed consumers to carry their entire music library with them in a &#10;format that did not rely on fragile and energy-intensive spinning disks.&#10;[explanation]&#10;">
<p>
A multiple choice problem presents radio buttons for student
input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p>
<p> One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.
</p>
<p>What Apple device competed with the portable CD player?</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false" name="ipad">The iPad</choice>
<choice correct="false" name="beatles">Napster</choice>
<choice correct="true" name="ipod">The iPod</choice>
<choice correct="false" name="peeler">The vegetable peeler</choice>
</choicegroup>
</multiplechoiceresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks. </p>
</div>
</solution>
</problem>
<sequential display_name="Subsection" due="2013-09-18T11:30:00Z" start="1970-01-01T00:00:00Z">
<vertical url_name="45640305a210424ebcc6f8e045fad0be"/>
</sequential>
<vertical display_name="New Unit">
<problem url_name="d392c80f5c044e45a4a5f2d62f94efc5"/>
</vertical>
<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>
......
...@@ -164,7 +164,7 @@ def get_course_about_section(course, section_key): ...@@ -164,7 +164,7 @@ def get_course_about_section(course, section_key):
html = '' html = ''
if about_module is not None: if about_module is not None:
html = about_module.runtime.render(about_module, None, 'student_view').content html = about_module.render('student_view').content
return html return html
...@@ -213,7 +213,7 @@ def get_course_info_section(request, course, section_key): ...@@ -213,7 +213,7 @@ def get_course_info_section(request, course, section_key):
html = '' html = ''
if info_module is not None: if info_module is not None:
html = info_module.runtime.render(info_module, None, 'student_view').content html = info_module.render('student_view').content
return html return html
......
...@@ -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):
......
...@@ -419,6 +419,6 @@ def get_static_tab_contents(request, course, tab): ...@@ -419,6 +419,6 @@ def get_static_tab_contents(request, course, tab):
html = '' html = ''
if tab_module is not None: if tab_module is not None:
html = tab_module.runtime.render(tab_module, None, 'student_view').content html = tab_module.render('student_view').content
return html return html
...@@ -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.render('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.render('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
...@@ -297,7 +297,7 @@ class TestHtmlModifiers(ModuleStoreTestCase): ...@@ -297,7 +297,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.course.id, self.course.id,
wrap_xmodule_display=True, wrap_xmodule_display=True,
) )
result_fragment = module.runtime.render(module, None, 'student_view') result_fragment = module.render('student_view')
self.assertIn('section class="xmodule_display xmodule_HtmlModule"', result_fragment.content) self.assertIn('section class="xmodule_display xmodule_HtmlModule"', result_fragment.content)
...@@ -310,7 +310,7 @@ class TestHtmlModifiers(ModuleStoreTestCase): ...@@ -310,7 +310,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.course.id, self.course.id,
wrap_xmodule_display=False, wrap_xmodule_display=False,
) )
result_fragment = module.runtime.render(module, None, 'student_view') result_fragment = module.render('student_view')
self.assertNotIn('section class="xmodule_display xmodule_HtmlModule"', result_fragment.content) self.assertNotIn('section class="xmodule_display xmodule_HtmlModule"', result_fragment.content)
...@@ -322,7 +322,7 @@ class TestHtmlModifiers(ModuleStoreTestCase): ...@@ -322,7 +322,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.field_data_cache, self.field_data_cache,
self.course.id, self.course.id,
) )
result_fragment = module.runtime.render(module, None, 'student_view') result_fragment = module.render('student_view')
self.assertIn( self.assertIn(
'/c4x/{org}/{course}/asset/foo_content'.format( '/c4x/{org}/{course}/asset/foo_content'.format(
...@@ -340,7 +340,7 @@ class TestHtmlModifiers(ModuleStoreTestCase): ...@@ -340,7 +340,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.field_data_cache, self.field_data_cache,
self.course.id, self.course.id,
) )
result_fragment = module.runtime.render(module, None, 'student_view') result_fragment = module.render('student_view')
self.assertIn( self.assertIn(
'/c4x/{org}/{course}/asset/_file.jpg'.format( '/c4x/{org}/{course}/asset/_file.jpg'.format(
...@@ -364,7 +364,7 @@ class TestHtmlModifiers(ModuleStoreTestCase): ...@@ -364,7 +364,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.course.id, self.course.id,
static_asset_path="toy_course_dir", static_asset_path="toy_course_dir",
) )
result_fragment = module.runtime.render(module, None, 'student_view') result_fragment = module.render('student_view')
self.assertIn('href="/static/toy_course_dir', result_fragment.content) self.assertIn('href="/static/toy_course_dir', result_fragment.content)
def test_course_image(self): def test_course_image(self):
...@@ -390,7 +390,7 @@ class TestHtmlModifiers(ModuleStoreTestCase): ...@@ -390,7 +390,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.field_data_cache, self.field_data_cache,
self.course.id, self.course.id,
) )
result_fragment = module.runtime.render(module, None, 'student_view') result_fragment = module.render('student_view')
self.assertIn( self.assertIn(
'/courses/{course_id}/bar/content'.format( '/courses/{course_id}/bar/content'.format(
...@@ -408,7 +408,7 @@ class TestHtmlModifiers(ModuleStoreTestCase): ...@@ -408,7 +408,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.field_data_cache, self.field_data_cache,
self.course.id, self.course.id,
) )
result_fragment = module.runtime.render(module, None, 'student_view') result_fragment = module.render('student_view')
self.assertIn( self.assertIn(
'Staff Debug', 'Staff Debug',
......
...@@ -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, block.render('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, block.render('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.render('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.render('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.render('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):
......
""" """
Tests courseware views.py Tests courseware views.py
""" """
from mock import MagicMock, patch
import datetime
import unittest import unittest
from mock import MagicMock, patch
from datetime import datetime
from pytz import UTC
from django.test import TestCase from django.test import TestCase
from django.http import Http404 from django.http import Http404
...@@ -18,12 +19,13 @@ from student.models import CourseEnrollment ...@@ -18,12 +19,13 @@ from student.models import CourseEnrollment
from student.tests.factories import AdminFactory from student.tests.factories import AdminFactory
from mitxmako.middleware import MakoMiddleware from mitxmako.middleware import MakoMiddleware
from xmodule.modulestore.django import modulestore, clear_existing_modulestores from xmodule.modulestore import Location
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from student.tests.factories import UserFactory
import courseware.views as views import courseware.views as views
from xmodule.modulestore import Location
from pytz import UTC
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from course_modes.models import CourseMode from course_modes.models import CourseMode
import shoppingcart import shoppingcart
...@@ -73,7 +75,7 @@ class ViewsTestCase(TestCase): ...@@ -73,7 +75,7 @@ class ViewsTestCase(TestCase):
def setUp(self): def setUp(self):
self.user = User.objects.create(username='dummy', password='123456', self.user = User.objects.create(username='dummy', password='123456',
email='test@mit.edu') email='test@mit.edu')
self.date = datetime.datetime(2013, 1, 22, tzinfo=UTC) self.date = datetime(2013, 1, 22, tzinfo=UTC)
self.course_id = 'edX/toy/2012_Fall' self.course_id = 'edX/toy/2012_Fall'
self.enrollment = CourseEnrollment.enroll(self.user, self.course_id) self.enrollment = CourseEnrollment.enroll(self.user, self.course_id)
self.enrollment.created = self.date self.enrollment.created = self.date
...@@ -109,7 +111,6 @@ class ViewsTestCase(TestCase): ...@@ -109,7 +111,6 @@ class ViewsTestCase(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn(in_cart_span, response.content) self.assertIn(in_cart_span, response.content)
def test_user_groups(self): def test_user_groups(self):
# depreciated function # depreciated function
mock_user = MagicMock() mock_user = MagicMock()
...@@ -254,98 +255,115 @@ class ViewsTestCase(TestCase): ...@@ -254,98 +255,115 @@ class ViewsTestCase(TestCase):
response = self.client.get(url) response = self.client.get(url)
self.assertFalse('<script>' in response.content) self.assertFalse('<script>' in response.content)
def test_accordion_due_date(self):
"""
Tests the formatting of due dates in the accordion view.
"""
def get_accordion():
""" Returns the HTML for the accordion """
return views.render_accordion(
request, modulestore().get_course("edX/due_date/2013_fall"),
"c804fa32227142a1bd9d5bc183d4a20d", None, None
)
request = self.request_factory.get("foo") @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
self.verify_due_date(request, get_accordion) class BaseDueDateTests(ModuleStoreTestCase):
"""
def test_progress_due_date(self): Base class that verifies that due dates are rendered correctly on a page
""" """
Tests the formatting of due dates in the progress page. __test__ = False
"""
def get_progress():
""" Returns the HTML for the progress page """
return views.progress(request, "edX/due_date/2013_fall", self.user.id).content
request = self.request_factory.get("foo") def get_text(self, course): # pylint: disable=unused-argument
self.verify_due_date(request, get_progress) """Return the rendered text for the page to be verified"""
raise NotImplementedError
def verify_due_date(self, request, get_text): def set_up_course(self, **course_kwargs):
""" """
Verifies that due dates are formatted properly in text returned by get_text function. Create a stock course with a specific due date.
:param course_kwargs: All kwargs are passed to through to the :class:`CourseFactory`
""" """
def set_show_timezone(show_timezone): course = CourseFactory(**course_kwargs)
""" chapter = ItemFactory(category='chapter', parent_location=course.location) # pylint: disable=no-member
Sets the show_timezone property and returns value from get_text function. section = ItemFactory(category='sequential', parent_location=chapter.location, due=datetime(2013, 9, 18, 11, 30, 00))
vertical = ItemFactory(category='vertical', parent_location=section.location)
Note that show_timezone is deprecated and cannot be set by the user. ItemFactory(category='problem', parent_location=vertical.location)
"""
course.show_timezone = show_timezone
course.save()
return get_text()
def set_due_date_format(due_date_format):
"""
Sets the due_date_display_format property and returns value from get_text function.
"""
course.due_date_display_format = due_date_format
course.save()
return get_text()
request.user = self.user course = modulestore().get_instance(course.id, course.location) # pylint: disable=no-member
# Clear out the modulestores, so we start with the test course in its default state. self.assertIsNotNone(course.get_children()[0].get_children()[0].due)
clear_existing_modulestores() return course
course = modulestore().get_course("edX/due_date/2013_fall")
def setUp(self):
self.request_factory = RequestFactory()
self.user = UserFactory.create()
self.request = self.request_factory.get("foo")
self.request.user = self.user
time_with_utc = "due Sep 18, 2013 at 11:30 UTC" self.time_with_utc = "due Sep 18, 2013 at 11:30 UTC"
time_without_utc = "due Sep 18, 2013 at 11:30" self.time_without_utc = "due Sep 18, 2013 at 11:30"
def test_backwards_compatability(self):
# The test course being used has show_timezone = False in the policy file # The test course being used has show_timezone = False in the policy file
# (and no due_date_display_format set). This is to test our backwards compatibility-- # (and no due_date_display_format set). This is to test our backwards compatibility--
# in course_module's init method, the date_display_format will be set accordingly to # in course_module's init method, the date_display_format will be set accordingly to
# remove the timezone. # remove the timezone.
text = get_text() course = self.set_up_course(due_date_display_format=None, show_timezone=False)
self.assertIn(time_without_utc, text) text = self.get_text(course)
self.assertNotIn(time_with_utc, text) self.assertIn(self.time_without_utc, text)
self.assertNotIn(self.time_with_utc, text)
# Test that show_timezone has been cleared (which means you get the default value of True). # Test that show_timezone has been cleared (which means you get the default value of True).
self.assertTrue(course.show_timezone) self.assertTrue(course.show_timezone)
# Clear out the due date format and verify you get the default (with timezone). def test_defaults(self):
delattr(course, 'due_date_display_format') course = self.set_up_course()
course.save() text = self.get_text(course)
text = get_text() self.assertIn(self.time_with_utc, text)
self.assertIn(time_with_utc, text)
def test_format_none(self):
# Same for setting the due date to None # Same for setting the due date to None
text = set_due_date_format(None) course = self.set_up_course(due_date_display_format=None)
self.assertIn(time_with_utc, text) text = self.get_text(course)
self.assertIn(self.time_with_utc, text)
def test_format_plain_text(self):
# plain text due date # plain text due date
text = set_due_date_format("foobar") course = self.set_up_course(due_date_display_format="foobar")
self.assertNotIn(time_with_utc, text) text = self.get_text(course)
self.assertNotIn(self.time_with_utc, text)
self.assertIn("due foobar", text) self.assertIn("due foobar", text)
def test_format_date(self):
# due date with no time # due date with no time
text = set_due_date_format(u"%b %d %y") course = self.set_up_course(due_date_display_format=u"%b %d %y")
self.assertNotIn(time_with_utc, text) text = self.get_text(course)
self.assertNotIn(self.time_with_utc, text)
self.assertIn("due Sep 18 13", text) self.assertIn("due Sep 18 13", text)
def test_format_hidden(self):
# hide due date completely # hide due date completely
text = set_due_date_format(u"") course = self.set_up_course(due_date_display_format=u"")
text = self.get_text(course)
self.assertNotIn("due ", text) self.assertNotIn("due ", text)
def test_format_invalid(self):
# improperly formatted due_date_display_format falls through to default # improperly formatted due_date_display_format falls through to default
# (value of show_timezone does not matter-- setting to False to make that clear). # (value of show_timezone does not matter-- setting to False to make that clear).
set_show_timezone(False) course = self.set_up_course(due_date_display_format=u"%%%", show_timezone=False)
text = set_due_date_format(u"%%%") text = self.get_text(course)
self.assertNotIn("%%%", text) self.assertNotIn("%%%", text)
self.assertIn(time_with_utc, text) self.assertIn(self.time_with_utc, text)
class TestProgressDueDate(BaseDueDateTests):
"""
Test that the progress page displays due dates correctly
"""
__test__ = True
def get_text(self, course):
""" Returns the HTML for the progress page """
return views.progress(self.request, course.id, self.user.id).content
class TestAccordionDueDate(BaseDueDateTests):
"""
Test that the accordion page displays due dates correctly
"""
__test__ = True
def get_text(self, course):
""" Returns the HTML for the accordion """
return views.render_accordion(
self.request, course, course.get_children()[0].id, None, None
)
...@@ -242,10 +242,10 @@ class TestWordCloud(BaseTestXmodule): ...@@ -242,10 +242,10 @@ class TestWordCloud(BaseTestXmodule):
def test_word_cloud_constructor(self): def test_word_cloud_constructor(self):
"""Make sure that all parameters extracted correclty from xml""" """Make sure that all parameters extracted correclty from xml"""
fragment = self.runtime.render(self.item_module, None, 'student_view') fragment = self.runtime.render(self.item_module, '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.
...@@ -407,7 +407,7 @@ def index(request, course_id, chapter=None, section=None, ...@@ -407,7 +407,7 @@ def index(request, course_id, chapter=None, section=None,
# add in the appropriate timer information to the rendering context: # add in the appropriate timer information to the rendering context:
context.update(check_for_active_timelimit_module(request, course_id, course)) context.update(check_for_active_timelimit_module(request, course_id, course))
context['content'] = section_module.runtime.render(section_module, None, 'student_view').content context['content'] = section_module.render('student_view').content
else: else:
# section is none, so display a message # section is none, so display a message
prev_section = get_current_child(chapter_module) prev_section = get_current_child(chapter_module)
......
...@@ -817,7 +817,7 @@ def instructor_dashboard(request, course_id): ...@@ -817,7 +817,7 @@ def instructor_dashboard(request, course_id):
# HTML editor for email # HTML editor for email
if idash_mode == 'Email' and is_studio_course: if idash_mode == 'Email' and is_studio_course:
html_module = HtmlDescriptor(course.system, DictFieldData({'data': html_message}), ScopeIds(None, None, None, None)) html_module = HtmlDescriptor(course.system, DictFieldData({'data': html_message}), ScopeIds(None, None, None, None))
fragment = course.system.render(html_module, None, 'studio_view') fragment = course.system.render(html_module, 'studio_view')
fragment = wrap_xmodule('xmodule_edit.html', html_module, 'studio_view', fragment, None) fragment = wrap_xmodule('xmodule_edit.html', html_module, 'studio_view', fragment, None)
email_editor = fragment.content email_editor = fragment.content
......
...@@ -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({}),
) )
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
-e git+https://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk -e git+https://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk
# Our libraries: # Our libraries:
-e git+https://github.com/edx/XBlock.git@8a66ca3#egg=XBlock -e git+https://github.com/edx/XBlock.git@cee38a15f#egg=XBlock
-e git+https://github.com/edx/codejail.git@0a1b468#egg=codejail -e git+https://github.com/edx/codejail.git@0a1b468#egg=codejail
-e git+https://github.com/edx/diff-cover.git@v0.2.5#egg=diff_cover -e git+https://github.com/edx/diff-cover.git@v0.2.5#egg=diff_cover
-e git+https://github.com/edx/js-test-tool.git@v0.1.1#egg=js_test_tool -e git+https://github.com/edx/js-test-tool.git@v0.1.1#egg=js_test_tool
......
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