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):
return render_to_response('component.html', {
'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):
descriptor: An XModuleDescriptor
"""
def preview_field_data(descriptor):
"Helper method to create a DbModel from a descriptor"
student_data = DbModel(SessionKeyValueStore(request))
return lms_field_data(descriptor._field_data, student_data)
course_id = get_course_for_item(descriptor.location).location.course_id
if descriptor.location.category == 'static_tab':
......@@ -118,7 +113,6 @@ def preview_module_system(request, preview_id, descriptor):
debug=True,
replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_id=course_id),
user=request.user,
xmodule_field_data=preview_field_data,
can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
mixins=settings.XBLOCK_MIXINS,
course_id=course_id,
......@@ -136,7 +130,8 @@ def preview_module_system(request, preview_id, descriptor):
getattr(descriptor, 'data_dir', descriptor.location.course),
course_id=descriptor.location.org + '/' + descriptor.location.course + '/BOGUS_RUN_REPLACE_WHEN_AVAILABLE',
),
)
),
error_descriptor_class=ErrorDescriptor,
)
......@@ -148,17 +143,12 @@ def load_preview_module(request, preview_id, descriptor):
preview_id (str): An identifier specifying which preview this module is used for
descriptor: An XModuleDescriptor
"""
system = preview_module_system(request, preview_id, descriptor)
try:
module = descriptor.xmodule(system)
except:
log.debug("Unable to load preview module", exc_info=True)
module = ErrorDescriptor.from_descriptor(
descriptor,
error_msg=exc_info_to_str(sys.exc_info())
).xmodule(system)
return module
student_data = DbModel(SessionKeyValueStore(request))
descriptor.bind_for_student(
preview_module_system(request, preview_id, descriptor),
lms_field_data(descriptor._field_data, student_data), # pylint: disable=protected-access
)
return descriptor
def get_preview_html(request, descriptor, idx):
......@@ -167,4 +157,4 @@ def get_preview_html(request, descriptor, idx):
specified by the descriptor and idx.
"""
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
return frag
block_id = block.id
if block.descriptor.has_score:
if block.has_score:
histogram = grade_histogram(block_id)
render_histogram = len(histogram) > 0
else:
......@@ -142,7 +142,7 @@ def add_histogram(user, block, view, frag, context): # pylint: disable=unused-a
render_histogram = False
if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'):
[filepath, filename] = getattr(block.descriptor, 'xml_attributes', {}).get('filename', ['', None])
[filepath, filename] = getattr(block, 'xml_attributes', {}).get('filename', ['', None])
osfs = block.system.filestore
if filename is not None and osfs.exists(filename):
# if original, unmangled filename exists then use it (github
......@@ -163,13 +163,13 @@ def add_histogram(user, block, view, frag, context): # pylint: disable=unused-a
# TODO (ichuang): use _has_access_descriptor.can_load in lms.courseware.access, instead of now>mstart comparison here
now = datetime.datetime.now(UTC())
is_released = "unknown"
mstart = block.descriptor.start
mstart = block.start
if mstart is not None:
is_released = "<font color='red'>Yes!</font>" if (now > mstart) else "<font color='green'>Not yet</font>"
staff_context = {'fields': [(name, field.read_from(block)) for name, field in block.fields.items()],
'xml_attributes': getattr(block.descriptor, 'xml_attributes', {}),
'xml_attributes': getattr(block, 'xml_attributes', {}),
'location': block.location,
'xqa_key': block.xqa_key,
'source_file': source_file,
......
......@@ -15,7 +15,7 @@ from capa.responsetypes import StudentInputError, \
ResponseError, LoncapaProblemError
from capa.util import convert_files_to_filenames
from .progress import Progress
from xmodule.x_module import XModule
from xmodule.x_module import XModule, module_attr
from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError
from xblock.fields import Scope, String, Boolean, Dict, Integer, Float
......@@ -1193,3 +1193,33 @@ class CapaDescriptor(CapaFields, RawDescriptor):
CapaDescriptor.force_save_button, CapaDescriptor.markdown,
CapaDescriptor.text_customization])
return non_editable_fields
# Proxy to CapaModule for access to any of its attributes
answer_available = module_attr('answer_available')
check_button_name = module_attr('check_button_name')
check_problem = module_attr('check_problem')
choose_new_seed = module_attr('choose_new_seed')
closed = module_attr('closed')
get_answer = module_attr('get_answer')
get_problem = module_attr('get_problem')
get_problem_html = module_attr('get_problem_html')
get_state_for_lcp = module_attr('get_state_for_lcp')
handle_input_ajax = module_attr('handle_input_ajax')
handle_problem_html_error = module_attr('handle_problem_html_error')
handle_ungraded_response = module_attr('handle_ungraded_response')
is_attempted = module_attr('is_attempted')
is_correct = module_attr('is_correct')
is_past_due = module_attr('is_past_due')
is_submitted = module_attr('is_submitted')
lcp = module_attr('lcp')
make_dict_of_responses = module_attr('make_dict_of_responses')
new_lcp = module_attr('new_lcp')
publish_grade = module_attr('publish_grade')
rescore_problem = module_attr('rescore_problem')
reset_problem = module_attr('reset_problem')
save_problem = module_attr('save_problem')
set_state_from_lcp = module_attr('set_state_from_lcp')
should_show_check_button = module_attr('should_show_check_button')
should_show_reset_button = module_attr('should_show_reset_button')
should_show_save_button = module_attr('should_show_save_button')
update_score = module_attr('update_score')
......@@ -18,6 +18,7 @@ log = logging.getLogger('mitx.' + __name__)
class ConditionalFields(object):
has_children = True
show_tag_list = List(help="Poll answers", scope=Scope.content)
......@@ -148,7 +149,7 @@ class ConditionalModule(ConditionalFields, XModule):
context)
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})
......
......@@ -113,17 +113,17 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
try:
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.
child_url = child.runtime.ajax_url
child_id = child.id
except IndexError:
out = u"Error in loading crowdsourced hinter - can't find child problem."
child_url = ''
child_id = ''
# Wrap the module in a <section>. This lets us pass data attributes to the javascript.
out += u'<section class="crowdsource-wrapper" data-url="{ajax_url}" data-child-url="{child_url}"> </section>'.format(
out += u'<section class="crowdsource-wrapper" data-url="{ajax_url}" data-child-id="{child_id}"> </section>'.format(
ajax_url=self.runtime.ajax_url,
child_url=child_url
child_id=child_id
)
return out
......
......@@ -77,7 +77,7 @@ class ErrorDescriptor(ErrorFields, XModuleDescriptor):
module_class = ErrorModule
def get_html(self):
return ''
return u''
@classmethod
def _construct(cls, system, contents, error_msg, location):
......
......@@ -44,7 +44,7 @@
<section class="crowdsource-wrapper" data-url="/courses/Me/19.002/Test/modx/i4x://Me/19.002/crowdsource_hinter/crowdsource_hinter_def7a1142dd0" data-child-url="/courses/Me/19.002/Test/modx/i4x://Me/19.002/problem/Numerical_Input" style="display: none;"> </section>
<section class="crowdsource-wrapper" data-url="/courses/Me/19.002/Test/modx/i4x://Me/19.002/crowdsource_hinter/crowdsource_hinter_def7a1142dd0" data-child-id="i4x://Me/19.002/problem/Numerical_Input" style="display: none;"> </section>
</section>
......
......@@ -144,7 +144,7 @@ describe 'Problem', ->
contents: 'mock grader response'
callback(response)
@problem.check()
expect(Logger.log).toHaveBeenCalledWith 'problem_graded', ['foo=1&bar=2', 'mock grader response'], @problem.url
expect(Logger.log).toHaveBeenCalledWith 'problem_graded', ['foo=1&bar=2', 'mock grader response'], @problem.id
it 'submit the answer for check', ->
spyOn $, 'postWithPrefix'
......
......@@ -248,7 +248,7 @@ class @Problem
@updateProgress response
else
@gentle_alert response.success
Logger.log 'problem_graded', [@answers, response.contents], @url
Logger.log 'problem_graded', [@answers, response.contents], @id
if not abort_submission
$.ajaxWithPrefix("#{@url}/problem_check", settings)
......@@ -271,7 +271,7 @@ class @Problem
@el.removeClass 'showed'
else
@gentle_alert response.success
Logger.log 'problem_graded', [@answers, response.contents], @url
Logger.log 'problem_graded', [@answers, response.contents], @id
reset: =>
Logger.log 'problem_reset', @answers
......
......@@ -7,7 +7,7 @@ class @Hinter
constructor: (element) ->
@el = $(element).find('.crowdsource-wrapper')
@url = @el.data('url')
Logger.listen('problem_graded', @el.data('child-url'), @capture_problem)
Logger.listen('problem_graded', @el.data('child-id'), @capture_problem)
@render()
capture_problem: (event_type, data, element) =>
......
......@@ -81,7 +81,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
else:
return self._data[key.field_name]
else:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
def set(self, key, value):
if key.scope == Scope.children:
......@@ -94,7 +94,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
else:
self._data[key.field_name] = value
else:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
def delete(self, key):
if key.scope == Scope.children:
......@@ -108,7 +108,7 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
else:
del self._data[key.field_name]
else:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
def has(self, key):
if key.scope in (Scope.children, Scope.parent):
......
......@@ -53,12 +53,12 @@ class SplitMongoKVS(InheritanceKeyValueStore):
raise KeyError()
else:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
def set(self, key, value):
# handle any special cases
if key.scope not in [Scope.children, Scope.settings, Scope.content]:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
if key.scope == Scope.content:
self._load_definition()
......@@ -75,7 +75,7 @@ class SplitMongoKVS(InheritanceKeyValueStore):
def delete(self, key):
# handle any special cases
if key.scope not in [Scope.children, Scope.settings, Scope.content]:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
if key.scope == Scope.content:
self._load_definition()
......
......@@ -7,7 +7,6 @@ from pytz import UTC
from xmodule.modulestore import Location
from xmodule.x_module import XModuleDescriptor
from xmodule.course_module import CourseDescriptor
class Dummy(object):
......@@ -124,16 +123,28 @@ class ItemFactory(XModuleFactory):
: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']
# catch any old style users before they get into trouble
assert not 'template' in kwargs
data = kwargs.get('data')
category = kwargs.get('category')
display_name = kwargs.get('display_name')
metadata = kwargs.get('metadata', {})
location = kwargs.get('location')
if kwargs.get('boilerplate') is not None:
template_id = kwargs.get('boilerplate')
assert 'template' not in kwargs
parent_location = Location(kwargs.pop('parent_location', None))
data = kwargs.pop('data', None)
category = kwargs.pop('category', None)
display_name = kwargs.pop('display_name', None)
metadata = kwargs.pop('metadata', {})
location = kwargs.pop('location')
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)
template = clz.get_template(template_id)
assert template is not None
......@@ -141,21 +152,20 @@ class ItemFactory(XModuleFactory):
if not isinstance(data, basestring):
data.update(template.get('data'))
store = kwargs.get('modulestore')
# replace the display name with an optional parameter passed in from the caller
if display_name is not None:
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'))
assert location != parent_location
for attr, val in kwargs.items():
setattr(module, attr, val)
module.save()
# This code was based off that in cms/djangoapps/contentstore/views.py
parent = kwargs.get('parent') or store.get_item(parent_location)
store.save_xmodule(module)
if location.category not in DETACHED_CATEGORIES:
parent.children.append(location.url())
store.update_children(parent_location, parent.children)
......
......@@ -204,7 +204,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
descriptor.save()
return descriptor
render_template = lambda: ''
render_template = lambda template, context: u''
# TODO (vshnayder): we are somewhat architecturally confused in the loading code:
# load_item should actually be get_instance, because it expects the course-specific
# policy to be loaded. For now, just add the course_id here...
......
......@@ -6,7 +6,7 @@ from lxml import etree
from datetime import datetime
from pkg_resources import resource_string
from .capa_module import ComplexEncoder
from .x_module import XModule
from .x_module import XModule, module_attr
from xmodule.raw_module import RawDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from .timeinfo import TimeInfo
......@@ -106,7 +106,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
#We need to set the location here so the child modules can use it
self.runtime.set('location', self.location)
if (self.system.open_ended_grading_interface):
if (self.runtime.open_ended_grading_interface):
self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.system)
else:
self.peer_gs = MockPeerGradingService()
......@@ -662,3 +662,19 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
return [self.system.load_item(self.link_to_location)]
else:
return []
# Proxy to PeerGradingModule so that external callers don't have to know if they're working
# with a module or a descriptor
closed = module_attr('closed')
get_instance_state = module_attr('get_instance_state')
get_next_submission = module_attr('get_next_submission')
is_student_calibrated = module_attr('is_student_calibrated')
peer_grading = module_attr('peer_grading')
peer_grading_closed = module_attr('peer_grading_closed')
peer_grading_problem = module_attr('peer_grading_problem')
peer_gs = module_attr('peer_gs')
query_data_for_location = module_attr('query_data_for_location')
save_calibration_essay = module_attr('save_calibration_essay')
save_grade = module_attr('save_grade')
show_calibration_essay = module_attr('show_calibration_essay')
_find_corresponding_module_for_location = module_attr('_find_corresponding_module_for_location')
......@@ -82,7 +82,7 @@ class RandomizeModule(RandomizeFields, XModule):
# raise error instead? In fact, could complain on descriptor load...
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):
return self.child.get_icon_class() if self.child else 'other'
......
......@@ -45,9 +45,6 @@ class SequenceModule(SequenceFields, XModule):
self.rendered = False
def get_instance_state(self):
return json.dumps({'position': self.position})
def get_html(self):
self.render()
return self.content
......@@ -82,7 +79,7 @@ class SequenceModule(SequenceFields, XModule):
for child in self.get_display_items():
progress = child.get_progress()
childinfo = {
'content': self.runtime.render_child(child, None, 'student_view').content,
'content': child.render('student_view').content,
'title': "\n".join(
grand_child.display_name
for grand_child in child.get_children()
......
......@@ -9,6 +9,7 @@ Run like this:
import json
import os
import pprint
import unittest
from mock import Mock
......@@ -18,6 +19,7 @@ from xblock.field_data import DictFieldData
from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin
from xmodule.modulestore.inheritance import InheritanceMixin
from xmodule.mako_module import MakoDescriptorSystem
from xmodule.error_module import ErrorDescriptor
# Location of common test DATA directory
......@@ -54,18 +56,18 @@ def get_test_system(course_id=''):
ajax_url='courses/course_id/modx/a_location',
track_function=Mock(),
get_module=Mock(),
render_template=lambda template, context: repr(context),
replace_urls=lambda html: str(html),
render_template=mock_render_template,
replace_urls=str,
user=Mock(is_staff=False),
filestore=Mock(),
debug=True,
hostname="edx.org",
xqueue={'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10, 'construct_callback' : Mock(side_effect="/")},
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
xmodule_field_data=lambda descriptor: descriptor._field_data,
anonymous_student_id='student',
open_ended_grading_interface=open_ended_grading_interface,
course_id=course_id,
error_descriptor_class=ErrorDescriptor,
)
......@@ -77,11 +79,21 @@ def get_test_descriptor_system():
load_item=Mock(),
resources_fs=Mock(),
error_tracker=Mock(),
render_template=lambda template, context: repr(context),
render_template=mock_render_template,
mixins=(InheritanceMixin, XModuleMixin),
)
def mock_render_template(*args, **kwargs):
"""
Pretty-print the args and kwargs.
Allows us to not depend on any actual template rendering mechanism,
while still returning a unicode object
"""
return pprint.pformat((args, kwargs)).decode()
class ModelsTest(unittest.TestCase):
def setUp(self):
pass
......
......@@ -519,7 +519,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
"""
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()
self.assertEqual(max_score, None)
......@@ -751,30 +751,38 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
assessment = [0, 1]
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", {})
module.save()
module.handle_ajax("save_answer", {"student_answer": self.answer})
module.save()
html = module.handle_ajax("get_html", {})
module.save()
#Mock a student submitting an assessment
# Mock a student submitting an assessment
assessment_dict = MockQueryDict()
assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment})
module.handle_ajax("save_assessment", assessment_dict)
module.save()
task_one_json = json.loads(module.task_states[0])
self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
rubric = module.handle_ajax("get_combined_rubric", {})
module.save()
#Move to the next step in the problem
# Move to the next step in the problem
module.handle_ajax("next_problem", {})
module.save()
self.assertEqual(module.current_task_number, 0)
html = module.get_html()
html = module.render('student_view').content
self.assertIsInstance(html, basestring)
rubric = module.handle_ajax("get_combined_rubric", {})
module.save()
self.assertIsInstance(rubric, basestring)
self.assertEqual(module.state, "assessing")
module.handle_ajax("reset", {})
module.save()
self.assertEqual(module.current_task_number, 0)
def test_open_ended_flow_correct(self):
......@@ -784,38 +792,43 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
@return:
"""
assessment = [1, 1]
#Load the module
# Load the module
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.save()
status = module.handle_ajax("get_status", {})
module.save()
self.assertIsInstance(status, basestring)
#Mock a student submitting an assessment
# Mock a student submitting an assessment
assessment_dict = MockQueryDict()
assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment})
module.handle_ajax("save_assessment", assessment_dict)
module.save()
task_one_json = json.loads(module.task_states[0])
self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
#Move to the next step in the problem
# Move to the next step in the problem
try:
module.handle_ajax("next_problem", {})
module.save()
except GradingServiceError:
#This error is okay. We don't have a grading service to connect to!
# This error is okay. We don't have a grading service to connect to!
pass
self.assertEqual(module.current_task_number, 1)
try:
module.get_html()
module.render('student_view')
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
#Try to get the rubric from the module
# Try to get the rubric from the module
module.handle_ajax("get_combined_rubric", {})
module.save()
#Make a fake reply from the queue
# Make a fake reply from the queue
queue_reply = {
'queuekey': "",
'xqueue_body': json.dumps({
......@@ -832,22 +845,27 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
}
module.handle_ajax("check_for_score", {})
module.save()
#Update the module with the fake queue reply
# Update the module with the fake queue reply
module.handle_ajax("score_update", queue_reply)
module.save()
self.assertFalse(module.ready_to_reset)
self.assertEqual(module.current_task_number, 1)
#Get html and other data client will request
module.get_html()
# Get html and other data client will request
module.render('student_view')
module.handle_ajax("skip_post_assessment", {})
module.save()
#Get all results
# Get all results
module.handle_ajax("get_combined_rubric", {})
module.save()
#reset the problem
# reset the problem
module.handle_ajax("reset", {})
module.save()
self.assertEqual(module.state, "initial")
......@@ -876,31 +894,37 @@ class OpenEndedModuleXmlAttemptTest(unittest.TestCase, DummyModulestore):
"""
assessment = [0, 1]
module = self.get_module_from_location(self.problem_location, COURSE)
module.save()
#Simulate a student saving an answer
# Simulate a student saving an 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.update({'assessment': sum(assessment), 'score_list[]': assessment})
module.handle_ajax("save_assessment", assessment_dict)
module.save()
task_one_json = json.loads(module.task_states[0])
self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment)
#Move to the next step in the problem
# Move to the next step in the problem
module.handle_ajax("next_problem", {})
module.save()
self.assertEqual(module.current_task_number, 0)
html = module.get_html()
self.assertTrue(isinstance(html, basestring))
html = module.render('student_view').content
self.assertIsInstance(html, basestring)
#Module should now be done
# Module should now be done
rubric = module.handle_ajax("get_combined_rubric", {})
self.assertTrue(isinstance(rubric, basestring))
module.save()
self.assertIsInstance(rubric, basestring)
self.assertEqual(module.state, "done")
#Try to reset, should fail because only 1 attempt is allowed
# Try to reset, should fail because only 1 attempt is allowed
reset_data = json.loads(module.handle_ajax("reset", {}))
module.save()
self.assertEqual(reset_data['success'], False)
class OpenEndedModuleXmlImageUploadTest(unittest.TestCase, DummyModulestore):
......@@ -929,7 +953,7 @@ class OpenEndedModuleXmlImageUploadTest(unittest.TestCase, DummyModulestore):
"""
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 = json.loads(response)
self.assertFalse(response['success'])
......@@ -949,7 +973,7 @@ class OpenEndedModuleXmlImageUploadTest(unittest.TestCase, DummyModulestore):
"""
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", {
"student_answer": self.answer_text,
"valid_files_attached": True,
......
......@@ -10,6 +10,7 @@ from xmodule.crowdsource_hinter import CrowdsourceHinterModule
from xmodule.vertical_module import VerticalModule, VerticalDescriptor
from xblock.field_data import DictFieldData
from xblock.fragment import Fragment
from xblock.core import XBlock
from . import get_test_system
......@@ -62,7 +63,8 @@ class CHModuleFactory(object):
"""
A factory method for making CHM's
"""
field_data = {'data': CHModuleFactory.sample_problem_xml}
# Should have a single child, but it doesn't matter what that child is
field_data = {'data': CHModuleFactory.sample_problem_xml, 'children': [None]}
if hints is not None:
field_data['hints'] = hints
......@@ -106,7 +108,8 @@ class CHModuleFactory(object):
# Make the descriptor have a capa problem child.
capa_descriptor = MagicMock()
capa_descriptor.name = 'capa'
descriptor.get_children = lambda: [capa_descriptor]
capa_descriptor.displayable_items.return_value = [capa_descriptor]
descriptor.get_children.return_value = [capa_descriptor]
# Make a fake capa module.
capa_module = MagicMock()
......@@ -128,7 +131,7 @@ class CHModuleFactory(object):
responder.compare_answer = compare_answer
capa_module.lcp.responders = {'responder0': responder}
capa_module.displayable_items = lambda: [capa_module]
capa_module.displayable_items.return_value = [capa_module]
system = get_test_system()
# Make the system have a marginally-functional get_module
......@@ -137,7 +140,6 @@ class CHModuleFactory(object):
"""
A fake module-maker.
"""
if descriptor.name == 'capa':
return capa_module
system.get_module = fake_get_module
module = CrowdsourceHinterModule(descriptor, system, DictFieldData(field_data), Mock())
......@@ -205,15 +207,15 @@ class VerticalWithModulesFactory(object):
return module
class FakeChild(object):
class FakeChild(XBlock):
"""
A fake Xmodule.
"""
def __init__(self):
self.runtime = get_test_system()
self.runtime.ajax_url = 'this/is/a/fake/ajax/url'
self.student_view = Mock(return_value=Fragment(self.get_html()))
self.save = Mock()
self.id = 'i4x://this/is/a/fake/id'
def get_html(self):
"""
......@@ -241,9 +243,9 @@ class CrowdsourceHinterTest(unittest.TestCase):
"""
return [FakeChild()]
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/a/fake/ajax/url' in out_html)
self.assertTrue('i4x://this/is/a/fake/id' in out_html)
def test_gethtml_nochild(self):
"""
......@@ -258,7 +260,7 @@ class CrowdsourceHinterTest(unittest.TestCase):
"""
return []
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)
@unittest.skip("Needs to be finished.")
......@@ -269,7 +271,7 @@ class CrowdsourceHinterTest(unittest.TestCase):
NOT WORKING RIGHT NOW
"""
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('Another test numerical problem.' in out_html)
......
......@@ -30,8 +30,8 @@ class TestErrorModule(unittest.TestCase, SetupTestErrorModules):
descriptor = error_module.ErrorDescriptor.from_xml(
self.valid_xml, self.system, self.org, self.course, self.error_msg)
self.assertIsInstance(descriptor, error_module.ErrorDescriptor)
module = descriptor.xmodule(self.system)
context_repr = module.get_html()
descriptor.xmodule_runtime = self.system
context_repr = self.system.render(descriptor, 'student_view').content
self.assertIn(self.error_msg, context_repr)
self.assertIn(repr(self.valid_xml), context_repr)
......@@ -44,8 +44,8 @@ class TestErrorModule(unittest.TestCase, SetupTestErrorModules):
error_descriptor = error_module.ErrorDescriptor.from_descriptor(
descriptor, self.error_msg)
self.assertIsInstance(error_descriptor, error_module.ErrorDescriptor)
module = error_descriptor.xmodule(self.system)
context_repr = module.get_html()
error_descriptor.xmodule_runtime = self.system
context_repr = self.system.render(error_descriptor, 'student_view').content
self.assertIn(self.error_msg, context_repr)
self.assertIn(repr(descriptor), context_repr)
......@@ -65,8 +65,8 @@ class TestNonStaffErrorModule(unittest.TestCase, SetupTestErrorModules):
def test_from_xml_render(self):
descriptor = error_module.NonStaffErrorDescriptor.from_xml(
self.valid_xml, self.system, self.org, self.course)
module = descriptor.xmodule(self.system)
context_repr = module.get_html()
descriptor.xmodule_runtime = self.system
context_repr = self.system.render(descriptor, 'student_view').content
self.assertNotIn(self.error_msg, context_repr)
self.assertNotIn(repr(self.valid_xml), context_repr)
......@@ -79,7 +79,7 @@ class TestNonStaffErrorModule(unittest.TestCase, SetupTestErrorModules):
error_descriptor = error_module.NonStaffErrorDescriptor.from_descriptor(
descriptor, self.error_msg)
self.assertIsInstance(error_descriptor, error_module.ErrorDescriptor)
module = error_descriptor.xmodule(self.system)
context_repr = module.get_html()
error_descriptor.xmodule_runtime = self.system
context_repr = self.system.render(error_descriptor, 'student_view').content
self.assertNotIn(self.error_msg, context_repr)
self.assertNotIn(str(descriptor), context_repr)
......@@ -199,6 +199,7 @@ class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore):
html = peer_grading.peer_grading()
self.assertIn("Peer-Graded", html)
class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore):
"""
Test peer grading that is linked to an open ended module.
......
......@@ -137,6 +137,6 @@ class ModuleProgressTest(unittest.TestCase):
'''
def test_xmodule_default(self):
'''Make sure default get_progress exists, returns None'''
xm = x_module.XModule(None, get_test_system(), DictFieldData({'location': 'a://b/c/d/e'}), Mock())
xm = x_module.XModule(Mock(), get_test_system(), DictFieldData({'location': 'a://b/c/d/e'}), Mock())
p = xm.get_progress()
self.assertEqual(p, None)
......@@ -2,6 +2,8 @@
Tests for the wrapping layer that provides the XBlock API using XModule/Descriptor
functionality
"""
# For tests, ignore access to protected members
# pylint: disable=protected-access
from nose.tools import assert_equal # pylint: disable=E0611
from unittest.case import SkipTest
......@@ -17,6 +19,7 @@ from xmodule.capa_module import CapaDescriptor
from xmodule.course_module import CourseDescriptor
from xmodule.combined_open_ended_module import CombinedOpenEndedDescriptor
from xmodule.discussion_module import DiscussionDescriptor
from xmodule.error_module import ErrorDescriptor
from xmodule.gst_module import GraphicalSliderToolDescriptor
from xmodule.html_module import HtmlDescriptor
from xmodule.peer_grading_module import PeerGradingDescriptor
......@@ -29,7 +32,7 @@ from xmodule.conditional_module import ConditionalDescriptor
from xmodule.randomize_module import RandomizeDescriptor
from xmodule.vertical_module import VerticalDescriptor
from xmodule.wrapper_module import WrapperDescriptor
from xmodule.tests import get_test_descriptor_system
from xmodule.tests import get_test_descriptor_system, mock_render_template
LEAF_XMODULES = (
AnnotatableDescriptor,
......@@ -40,21 +43,20 @@ LEAF_XMODULES = (
HtmlDescriptor,
PeerGradingDescriptor,
PollDescriptor,
WordCloudDescriptor,
# This is being excluded because it has dependencies on django
#VideoDescriptor,
WordCloudDescriptor,
)
CONTAINER_XMODULES = (
CrowdsourceHinterDescriptor,
CourseDescriptor,
SequenceDescriptor,
ConditionalDescriptor,
CourseDescriptor,
CrowdsourceHinterDescriptor,
RandomizeDescriptor,
SequenceDescriptor,
VerticalDescriptor,
WrapperDescriptor,
CourseDescriptor,
)
# These modules are editable in studio yet
......@@ -66,26 +68,24 @@ NOT_STUDIO_EDITABLE = (
class TestXBlockWrapper(object):
@property
def leaf_module_runtime(self):
runtime = ModuleSystem(
render_template=lambda *args, **kwargs: u'{!r}, {!r}'.format(args, kwargs),
render_template=mock_render_template,
anonymous_student_id='dummy_anonymous_student_id',
open_ended_grading_interface={},
static_url='/static',
ajax_url='dummy_ajax_url',
xmodule_field_data=lambda d: d._field_data,
get_module=Mock(),
replace_urls=Mock(),
track_function=Mock(),
error_descriptor_class=ErrorDescriptor,
)
return runtime
def leaf_descriptor(self, descriptor_cls):
location = 'i4x://org/course/category/name'
runtime = get_test_descriptor_system()
runtime.render_template = lambda *args, **kwargs: u'{!r}, {!r}'.format(args, kwargs)
return runtime.construct_xblock_from_class(
descriptor_cls,
ScopeIds(None, descriptor_cls.__name__, location, location),
......@@ -93,7 +93,10 @@ class TestXBlockWrapper(object):
)
def leaf_module(self, descriptor_cls):
return self.leaf_descriptor(descriptor_cls).xmodule(self.leaf_module_runtime)
"""Returns a descriptor that is ready to proxy as an xmodule"""
descriptor = self.leaf_descriptor(descriptor_cls)
descriptor.xmodule_runtime = self.leaf_module_runtime
return descriptor
def container_module_runtime(self, depth):
runtime = self.leaf_module_runtime
......@@ -104,10 +107,16 @@ class TestXBlockWrapper(object):
runtime.position = 2
return runtime
def container_descriptor(self, descriptor_cls):
def container_descriptor(self, descriptor_cls, depth):
"""Return an instance of `descriptor_cls` with `depth` levels of children"""
location = 'i4x://org/course/category/name'
runtime = get_test_descriptor_system()
runtime.render_template = lambda *args, **kwargs: u'{!r}, {!r}'.format(args, kwargs)
if depth == 0:
runtime.load_item.side_effect = lambda x: self.leaf_module(HtmlDescriptor)
else:
runtime.load_item.side_effect = lambda x: self.container_module(VerticalDescriptor, depth - 1)
return runtime.construct_xblock_from_class(
descriptor_cls,
ScopeIds(None, descriptor_cls.__name__, location, location),
......@@ -117,7 +126,10 @@ class TestXBlockWrapper(object):
)
def container_module(self, descriptor_cls, depth):
return self.container_descriptor(descriptor_cls).xmodule(self.container_module_runtime(depth))
"""Returns a descriptor that is ready to proxy as an xmodule"""
descriptor = self.container_descriptor(descriptor_cls, depth)
descriptor.xmodule_runtime = self.container_module_runtime(depth)
return descriptor
class TestStudentView(TestXBlockWrapper):
......@@ -131,9 +143,11 @@ class TestStudentView(TestXBlockWrapper):
# Check that when an xmodule is instantiated from descriptor_cls
# it generates the same thing from student_view that it does from get_html
def check_student_view_leaf_node(self, descriptor_cls):
xmodule = self.leaf_module(descriptor_cls)
assert_equal(xmodule.get_html(), xmodule.runtime.render(xmodule, None, 'student_view').content)
descriptor = self.leaf_module(descriptor_cls)
assert_equal(
descriptor._xmodule.get_html(),
descriptor.render('student_view').content
)
# Test that for all container XModule Descriptors,
# their corresponding XModule renders the same thing using student_view
......@@ -147,13 +161,15 @@ class TestStudentView(TestXBlockWrapper):
yield self.check_student_view_container_node_mixed, descriptor_cls
yield self.check_student_view_container_node_xblocks_only, descriptor_cls
# Check that when an xmodule is generated from descriptor_cls
# with only xmodule children, it generates the same html from student_view
# as it does using get_html
def check_student_view_container_node_xmodules_only(self, descriptor_cls):
xmodule = self.container_module(descriptor_cls, 2)
assert_equal(xmodule.get_html(), xmodule.runtime.render(xmodule, None, 'student_view').content)
descriptor = self.container_module(descriptor_cls, 2)
assert_equal(
descriptor._xmodule.get_html(),
descriptor.render('student_view').content
)
# Check that when an xmodule is generated from descriptor_cls
# with mixed xmodule and xblock children, it generates the same html from student_view
......@@ -184,7 +200,7 @@ class TestStudioView(TestXBlockWrapper):
raise SkipTest(descriptor_cls.__name__ + "is not editable in studio")
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
......@@ -206,8 +222,8 @@ class TestStudioView(TestXBlockWrapper):
if descriptor_cls in NOT_STUDIO_EDITABLE:
raise SkipTest(descriptor_cls.__name__ + "is not editable in studio")
descriptor = self.container_descriptor(descriptor_cls)
assert_equal(descriptor.get_html(), descriptor.runtime.render(descriptor, None, 'studio_view').content)
descriptor = self.container_descriptor(descriptor_cls, 2)
assert_equal(descriptor.get_html(), descriptor.render('studio_view').content)
# Check that when a descriptor is generated from descriptor_cls
# with mixed xmodule and xblock children, it generates the same html from studio_view
......
......@@ -89,7 +89,7 @@ class TimeLimitModule(TimeLimitFields, XModule):
children = self.get_display_items()
if children:
child = children[0]
return self.runtime.render_child(child, None, 'student_view').content
return child.render('student_view').content
else:
return u""
......
......@@ -23,7 +23,7 @@ class VerticalModule(VerticalFields, XModule):
if self.contents is None:
self.contents = [{
'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()]
return self.system.render_template('vert_module.html', {
......
......@@ -157,10 +157,6 @@ class VideoModule(VideoFields, XModule):
log.debug(u"DISPATCH {0}".format(dispatch))
raise Http404()
def get_instance_state(self):
"""Return information about state (position)."""
return json.dumps({'position': self.position})
def get_html(self):
caption_asset_path = "/static/subs/"
......
<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>
......@@ -164,7 +164,7 @@ def get_course_about_section(course, section_key):
html = ''
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
......@@ -213,7 +213,7 @@ def get_course_info_section(request, course, section_key):
html = ''
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
......
......@@ -305,9 +305,9 @@ def progress_summary(student, request, course, field_data_cache):
graded = section_module.graded
scores = []
module_creator = section_module.system.get_module
module_creator = section_module.xmodule_runtime.get_module
for module_descriptor in yield_dynamic_descriptor_descendents(section_module.descriptor, module_creator):
for module_descriptor in yield_dynamic_descriptor_descendents(section_module, module_creator):
course_id = course.id
(correct, total) = get_score(course_id, student, module_descriptor, module_creator, field_data_cache)
......
......@@ -289,7 +289,7 @@ class DjangoKeyValueStore(KeyValueStore):
def get(self, key):
if key.scope not in self._allowed_scopes:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
field_object = self._field_data_cache.find(key)
if field_object is None:
......@@ -320,7 +320,7 @@ class DjangoKeyValueStore(KeyValueStore):
for field in kv_dict:
# Check field for validity
if field.scope not in self._allowed_scopes:
raise InvalidScopeError(field.scope)
raise InvalidScopeError(field)
# If the field is valid and isn't already in the dictionary, add it.
field_object = self._field_data_cache.find_or_create(field)
......@@ -352,7 +352,7 @@ class DjangoKeyValueStore(KeyValueStore):
def delete(self, key):
if key.scope not in self._allowed_scopes:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
field_object = self._field_data_cache.find(key)
if field_object is None:
......@@ -368,7 +368,7 @@ class DjangoKeyValueStore(KeyValueStore):
def has(self, key):
if key.scope not in self._allowed_scopes:
raise InvalidScopeError(key.scope)
raise InvalidScopeError(key)
field_object = self._field_data_cache.find(key)
if field_object is None:
......
......@@ -219,6 +219,9 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
if not has_access(user, descriptor, 'load', course_id):
return None
student_data = DbModel(DjangoKeyValueStore(field_data_cache))
descriptor._field_data = lms_field_data(descriptor._field_data, student_data)
# Setup system context for module instance
ajax_url = reverse(
'modx_dispatch',
......@@ -294,10 +297,6 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
position, wrap_xmodule_display, grade_bucket_type,
static_asset_path)
def xmodule_field_data(descriptor):
student_data = DbModel(DjangoKeyValueStore(field_data_cache))
return lms_field_data(descriptor._field_data, student_data)
def publish(event):
"""A function that allows XModules to publish events. This only supports grade changes right now."""
if event.get('event_name') != 'grade':
......@@ -405,7 +404,6 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': course_id, 'module_id': ''})
),
node_path=settings.NODE_PATH,
xmodule_field_data=xmodule_field_data,
publish=publish,
anonymous_student_id=unique_id_for_user(user),
course_id=course_id,
......@@ -426,27 +424,17 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
make_psychometrics_data_update_handler(course_id, user, descriptor.location.url())
)
try:
module = descriptor.xmodule(system)
except:
log.exception("Error creating module from descriptor {0}".format(descriptor))
system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id))
# make an ErrorDescriptor -- assuming that the descriptor's system is ok
if has_access(user, descriptor.location, 'staff', course_id):
err_descriptor_class = ErrorDescriptor
system.error_descriptor_class = ErrorDescriptor
else:
err_descriptor_class = NonStaffErrorDescriptor
err_descriptor = err_descriptor_class.from_descriptor(
descriptor,
error_msg=exc_info_to_str(sys.exc_info())
)
system.error_descriptor_class = NonStaffErrorDescriptor
# Make an error module
return err_descriptor.xmodule(system)
system.set('user_is_staff', has_access(user, descriptor.location, 'staff', course_id))
return module
descriptor.xmodule_runtime = system
descriptor.scope_ids = descriptor.scope_ids._replace(user_id=user.id)
return descriptor
def find_target_student_module(request, user_id, course_id, mod_id):
......
......@@ -419,6 +419,6 @@ def get_static_tab_contents(request, course, tab):
html = ''
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
......@@ -16,7 +16,7 @@ from student.tests.factories import UserFactory, CourseEnrollmentFactory
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from xblock.field_data import DictFieldData
from xblock.fields import Scope
from xmodule.tests import get_test_system
from xmodule.tests import get_test_system, get_test_descriptor_system
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
......@@ -49,11 +49,26 @@ class BaseTestXmodule(ModuleStoreTestCase):
DATA = ''
MODEL_DATA = {'data': '<some_module></some_module>'}
def xmodule_field_data(self, descriptor):
field_data = {}
field_data.update(self.MODEL_DATA)
student_data = DictFieldData(field_data)
return lms_field_data(descriptor._field_data, student_data)
def new_module_runtime(self):
"""
Generate a new ModuleSystem that is minimally set up for testing
"""
runtime = get_test_system(course_id=self.course.id)
# When asked for a module out of a descriptor, just create a new xmodule runtime,
# and inject it into the descriptor
def get_module(descr):
descr.xmodule_runtime = self.new_module_runtime()
return descr
runtime.get_module = get_module
return runtime
def new_descriptor_runtime(self):
runtime = get_test_descriptor_system()
runtime.get_block = modulestore().get_item
return runtime
def setUp(self):
......@@ -87,16 +102,15 @@ class BaseTestXmodule(ModuleStoreTestCase):
data=self.DATA
)
self.runtime = get_test_system(course_id=self.course.id)
# Allow us to assert that the template was called in the same way from
# different code paths while maintaining the type returned by render_template
self.runtime.render_template = lambda template, context: u'{!r}, {!r}'.format(template, sorted(context.items()))
self.runtime.xmodule_field_data = self.xmodule_field_data
self.runtime = self.new_descriptor_runtime()
self.runtime.get_module = lambda descr: descr.xmodule(self.runtime)
field_data = {}
field_data.update(self.MODEL_DATA)
student_data = DictFieldData(field_data)
self.item_descriptor._field_data = lms_field_data(self.item_descriptor._field_data, student_data)
self.item_module = self.item_descriptor.xmodule(self.runtime)
self.item_descriptor.xmodule_runtime = self.new_module_runtime()
self.item_module = self.item_descriptor
self.item_url = Location(self.item_module.location).url()
......@@ -119,7 +133,11 @@ class BaseTestXmodule(ModuleStoreTestCase):
class XModuleRenderingTestBase(BaseTestXmodule):
def setUp(self):
super(XModuleRenderingTestBase, self).setUp()
self.runtime.render_template = render_to_string
def new_module_runtime(self):
"""
Create a runtime that actually does html rendering
"""
runtime = super(XModuleRenderingTestBase, self).new_module_runtime()
runtime.render_template = render_to_string
return runtime
......@@ -39,7 +39,7 @@ class TestLTI(BaseTestXmodule):
u'oauth_consumer_key': u'',
u'oauth_signature_method': u'HMAC-SHA1',
u'oauth_version': u'1.0',
u'user_id': self.runtime.anonymous_student_id,
u'user_id': self.item_descriptor.xmodule_runtime.anonymous_student_id,
u'role': u'student',
u'oauth_signature': mocked_decoded_signature
}
......@@ -69,12 +69,14 @@ class TestLTI(BaseTestXmodule):
"""
Makes sure that all parameters extracted.
"""
self.runtime.render_template = lambda template, context: context
generated_context = self.item_module.get_html()
generated_context = self.item_module.render('student_view').content
expected_context = {
'input_fields': self.correct_headers,
'element_class': self.item_module.location.category,
'element_id': self.item_module.location.html_id(),
'launch_url': 'http://www.example.com', # default value
}
self.assertDictEqual(generated_context, expected_context)
self.assertEqual(
generated_context,
self.runtime.render_template('lti.html', expected_context),
)
......@@ -74,7 +74,7 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
)
# get the rendered HTML output which should have the rewritten link
html = module.system.render(module, None, 'student_view').content
html = module.render('student_view').content
# See if the url got rewritten to the target link
# note if the URL mapping changes then this assertion will break
......@@ -297,7 +297,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.course.id,
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)
......@@ -310,7 +310,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.course.id,
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)
......@@ -322,7 +322,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.field_data_cache,
self.course.id,
)
result_fragment = module.runtime.render(module, None, 'student_view')
result_fragment = module.render('student_view')
self.assertIn(
'/c4x/{org}/{course}/asset/foo_content'.format(
......@@ -340,7 +340,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.field_data_cache,
self.course.id,
)
result_fragment = module.runtime.render(module, None, 'student_view')
result_fragment = module.render('student_view')
self.assertIn(
'/c4x/{org}/{course}/asset/_file.jpg'.format(
......@@ -364,7 +364,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.course.id,
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)
def test_course_image(self):
......@@ -390,7 +390,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.field_data_cache,
self.course.id,
)
result_fragment = module.runtime.render(module, None, 'student_view')
result_fragment = module.render('student_view')
self.assertIn(
'/courses/{course_id}/bar/content'.format(
......@@ -408,7 +408,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.field_data_cache,
self.course.id,
)
result_fragment = module.runtime.render(module, None, 'student_view')
result_fragment = module.render('student_view')
self.assertIn(
'Staff Debug',
......
......@@ -17,10 +17,13 @@ class TestTimeLimitModuleRendering(XModuleRenderingTestBase):
"""
def test_with_children(self):
block = ItemFactory.create(category='timelimit')
block.xmodule_runtime = self.new_module_runtime()
ItemFactory.create(category='html', data='<html>This is just text</html>', parent=block)
assert_student_view(block, self.runtime.render(block.xmodule(self.runtime), None, 'student_view'))
assert_student_view(block, block.render('student_view'))
def test_without_children(self):
block = ItemFactory.create(category='timelimit')
assert_student_view(block, self.runtime.render(block.xmodule(self.runtime), None, 'student_view'))
block.xmodule_runtime = self.new_module_runtime()
assert_student_view(block, block.render('student_view'))
......@@ -13,14 +13,6 @@ class TestVideo(BaseTestXmodule):
CATEGORY = "video"
DATA = SOURCE_XML
def setUp(self):
# Since the VideoDescriptor changes `self._field_data`,
# we need to instantiate `self.item_module` through
# `self.item_descriptor` rather than directly constructing it
super(TestVideo, self).setUp()
self.item_module = self.item_descriptor.xmodule(self.runtime)
self.item_module.runtime.render_template = lambda template, context: context
def test_handle_ajax_dispatch(self):
responses = {
user.username: self.clients[user.username].post(
......@@ -40,7 +32,7 @@ class TestVideo(BaseTestXmodule):
def test_video_constructor(self):
"""Make sure that all parameters extracted correclty from xml"""
context = self.item_module.get_html()
context = self.item_module.render('student_view').content
sources = {
'main': u'example.mp4',
......@@ -53,7 +45,7 @@ class TestVideo(BaseTestXmodule):
'data_dir': getattr(self, 'data_dir', None),
'caption_asset_path': '/static/subs/',
'show_captions': 'true',
'display_name': 'A Name',
'display_name': u'A Name',
'end': 3610.0,
'id': self.item_module.location.html_id(),
'sources': sources,
......@@ -66,8 +58,10 @@ class TestVideo(BaseTestXmodule):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/'
}
self.maxDiff = None
self.assertEqual(context, expected_context)
self.assertEqual(
context,
self.item_module.xmodule_runtime.render_template('video.html', expected_context)
)
class TestVideoNonYouTube(TestVideo):
......@@ -93,24 +87,24 @@ class TestVideoNonYouTube(TestVideo):
the template generates an empty string for the YouTube streams.
"""
sources = {
u'main': u'example.mp4',
'main': u'example.mp4',
u'mp4': u'example.mp4',
u'webm': u'example.webm',
u'ogv': u'example.ogv'
}
context = self.item_module.get_html()
context = self.item_module.render('student_view').content
expected_context = {
'data_dir': getattr(self, 'data_dir', None),
'caption_asset_path': '/static/subs/',
'show_captions': 'true',
'display_name': 'A Name',
'display_name': u'A Name',
'end': 3610.0,
'id': self.item_module.location.html_id(),
'sources': sources,
'start': 3603.0,
'sub': 'a_sub_file.srt.sjson',
'sub': u'a_sub_file.srt.sjson',
'track': '',
'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.MITX_FEATURES.get('AUTOPLAY_VIDEOS', True),
......@@ -118,4 +112,7 @@ class TestVideoNonYouTube(TestVideo):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/'
}
self.assertEqual(context, expected_context)
self.assertEqual(
context,
self.item_module.xmodule_runtime.render_template('video.html', expected_context)
)
......@@ -22,7 +22,7 @@ from django.conf import settings
from xmodule.video_module import VideoDescriptor, _create_youtube_string
from xmodule.modulestore import Location
from xmodule.tests import get_test_system, LogicTest
from xmodule.tests import get_test_system, LogicTest, get_test_descriptor_system
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
......@@ -57,23 +57,18 @@ class VideoFactory(object):
field_data = {'data': VideoFactory.sample_problem_xml_youtube,
'location': location}
system = get_test_system()
system.render_template = lambda template, context: context
system = get_test_descriptor_system()
descriptor = VideoDescriptor(system, DictFieldData(field_data), ScopeIds(None, None, None, None))
module = descriptor.xmodule(system)
return module
descriptor.xmodule_runtime = get_test_system()
return descriptor
class VideoModuleUnitTest(unittest.TestCase):
"""Unit tests for Video Xmodule."""
def test_video_get_html(self):
"""Make sure that all parameters extracted correclty from xml"""
module = VideoFactory.create()
module.runtime.render_template = lambda template, context: context
sources = {
'main': 'example.mp4',
......@@ -99,14 +94,10 @@ class VideoModuleUnitTest(unittest.TestCase):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/'
}
self.assertEqual(module.get_html(), expected_context)
def test_video_instance_state(self):
module = VideoFactory.create()
self.assertDictEqual(
json.loads(module.get_instance_state()),
{'position': 0})
self.assertEqual(
module.render('student_view').content,
module.runtime.render_template('video.html', expected_context)
)
class VideoModuleLogicTest(LogicTest):
......
"""
Tests courseware views.py
"""
from mock import MagicMock, patch
import datetime
import unittest
from mock import MagicMock, patch
from datetime import datetime
from pytz import UTC
from django.test import TestCase
from django.http import Http404
......@@ -18,12 +19,13 @@ from student.models import CourseEnrollment
from student.tests.factories import AdminFactory
from mitxmako.middleware import MakoMiddleware
from xmodule.modulestore.django import modulestore, clear_existing_modulestores
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore import Location
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
from xmodule.modulestore import Location
from pytz import UTC
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from course_modes.models import CourseMode
import shoppingcart
......@@ -73,7 +75,7 @@ class ViewsTestCase(TestCase):
def setUp(self):
self.user = User.objects.create(username='dummy', password='123456',
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.enrollment = CourseEnrollment.enroll(self.user, self.course_id)
self.enrollment.created = self.date
......@@ -109,7 +111,6 @@ class ViewsTestCase(TestCase):
self.assertEqual(response.status_code, 200)
self.assertIn(in_cart_span, response.content)
def test_user_groups(self):
# depreciated function
mock_user = MagicMock()
......@@ -254,98 +255,115 @@ class ViewsTestCase(TestCase):
response = self.client.get(url)
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")
self.verify_due_date(request, get_accordion)
def test_progress_due_date(self):
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class BaseDueDateTests(ModuleStoreTestCase):
"""
Tests the formatting of due dates in the progress page.
Base class that verifies that due dates are rendered correctly on a page
"""
def get_progress():
""" Returns the HTML for the progress page """
return views.progress(request, "edX/due_date/2013_fall", self.user.id).content
__test__ = False
request = self.request_factory.get("foo")
self.verify_due_date(request, get_progress)
def get_text(self, course): # pylint: disable=unused-argument
"""Return the rendered text for the page to be verified"""
raise NotImplementedError
def verify_due_date(self, request, get_text):
"""
Verifies that due dates are formatted properly in text returned by get_text function.
def set_up_course(self, **course_kwargs):
"""
def set_show_timezone(show_timezone):
"""
Sets the show_timezone property and returns value from get_text function.
Create a stock course with a specific due date.
Note that show_timezone is deprecated and cannot be set by the user.
:param course_kwargs: All kwargs are passed to through to the :class:`CourseFactory`
"""
course.show_timezone = show_timezone
course.save()
return get_text()
course = CourseFactory(**course_kwargs)
chapter = ItemFactory(category='chapter', parent_location=course.location) # pylint: disable=no-member
section = ItemFactory(category='sequential', parent_location=chapter.location, due=datetime(2013, 9, 18, 11, 30, 00))
vertical = ItemFactory(category='vertical', parent_location=section.location)
ItemFactory(category='problem', parent_location=vertical.location)
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()
course = modulestore().get_instance(course.id, course.location) # pylint: disable=no-member
self.assertIsNotNone(course.get_children()[0].get_children()[0].due)
return course
request.user = self.user
# Clear out the modulestores, so we start with the test course in its default state.
clear_existing_modulestores()
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"
time_without_utc = "due Sep 18, 2013 at 11:30"
self.time_with_utc = "due Sep 18, 2013 at 11:30 UTC"
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
# (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
# remove the timezone.
text = get_text()
self.assertIn(time_without_utc, text)
self.assertNotIn(time_with_utc, text)
course = self.set_up_course(due_date_display_format=None, show_timezone=False)
text = self.get_text(course)
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).
self.assertTrue(course.show_timezone)
# Clear out the due date format and verify you get the default (with timezone).
delattr(course, 'due_date_display_format')
course.save()
text = get_text()
self.assertIn(time_with_utc, text)
def test_defaults(self):
course = self.set_up_course()
text = self.get_text(course)
self.assertIn(self.time_with_utc, text)
def test_format_none(self):
# Same for setting the due date to None
text = set_due_date_format(None)
self.assertIn(time_with_utc, text)
course = self.set_up_course(due_date_display_format=None)
text = self.get_text(course)
self.assertIn(self.time_with_utc, text)
def test_format_plain_text(self):
# plain text due date
text = set_due_date_format("foobar")
self.assertNotIn(time_with_utc, text)
course = self.set_up_course(due_date_display_format="foobar")
text = self.get_text(course)
self.assertNotIn(self.time_with_utc, text)
self.assertIn("due foobar", text)
def test_format_date(self):
# due date with no time
text = set_due_date_format(u"%b %d %y")
self.assertNotIn(time_with_utc, text)
course = self.set_up_course(due_date_display_format=u"%b %d %y")
text = self.get_text(course)
self.assertNotIn(self.time_with_utc, text)
self.assertIn("due Sep 18 13", text)
def test_format_hidden(self):
# 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)
def test_format_invalid(self):
# improperly formatted due_date_display_format falls through to default
# (value of show_timezone does not matter-- setting to False to make that clear).
set_show_timezone(False)
text = set_due_date_format(u"%%%")
course = self.set_up_course(due_date_display_format=u"%%%", show_timezone=False)
text = self.get_text(course)
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):
def test_word_cloud_constructor(self):
"""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 = {
'ajax_url': self.item_module.system.ajax_url,
'ajax_url': self.item_module.xmodule_runtime.ajax_url,
'element_class': self.item_module.location.category,
'element_id': self.item_module.location.html_id(),
'num_inputs': 5, # default value
......
......@@ -142,7 +142,7 @@ def redirect_to_course_position(course_module):
the first child.
"""
urlargs = {'course_id': course_module.descriptor.id}
urlargs = {'course_id': course_module.id}
chapter = get_current_child(course_module)
if chapter is None:
# oops. Something bad has happened.
......@@ -407,7 +407,7 @@ def index(request, course_id, chapter=None, section=None,
# add in the appropriate timer information to the rendering context:
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:
# section is none, so display a message
prev_section = get_current_child(chapter_module)
......
......@@ -817,7 +817,7 @@ def instructor_dashboard(request, course_id):
# HTML editor for email
if idash_mode == 'Email' and is_studio_course:
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)
email_editor = fragment.content
......
......@@ -310,6 +310,7 @@ def rescore_problem_module_state(module_descriptor, student_module, xmodule_inst
raise UpdateProblemModuleStateError(msg)
result = instance.rescore_problem()
instance.save()
if 'success' not in result:
# don't consider these fatal, but false means that the individual call didn't complete:
TASK_LOG.warning(u"error processing rescore call for course {course}, problem {loc} and student {student}: "
......
......@@ -13,8 +13,6 @@ from xmodule.x_module import ModuleSystem
from mitxmako.shortcuts import render_to_string
import datetime
from xblock.field_data import DictFieldData
log = logging.getLogger(__name__)
NOTIFICATION_CACHE_TIME = 300
......@@ -70,7 +68,6 @@ def peer_grading_notifications(course, user):
get_module = None,
render_template=render_to_string,
replace_urls=None,
xmodule_field_data=DictFieldData({}),
)
peer_gs = peer_grading_service.PeerGradingService(settings.OPEN_ENDED_GRADING_INTERFACE, system)
pending_grading = False
......@@ -132,7 +129,6 @@ def combined_notifications(course, user):
get_module = None,
render_template=render_to_string,
replace_urls=None,
xmodule_field_data=DictFieldData({})
)
#Initialize controller query service using our mock system
controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE, system)
......
......@@ -9,8 +9,6 @@ from xmodule.open_ended_grading_classes.grading_service_module import GradingSer
from django.conf import settings
from django.http import HttpResponse, Http404
from xblock.field_data import DictFieldData
from courseware.access import has_access
from util.json_request import expect_json
from xmodule.course_module import CourseDescriptor
......@@ -76,7 +74,6 @@ class StaffGradingService(GradingService):
get_module = None,
render_template=render_to_string,
replace_urls=None,
xmodule_field_data=DictFieldData({})
)
super(StaffGradingService, self).__init__(config)
self.url = config['url'] + config['staff_grading']
......
......@@ -16,6 +16,7 @@ from xmodule.open_ended_grading_classes import peer_grading_service, controller_
from xmodule import peer_grading_module
from xmodule.modulestore.django import modulestore
from xmodule.x_module import ModuleSystem
from xmodule.error_module import ErrorDescriptor
from xblock.fields import ScopeIds
from open_ended_grading import staff_grading_service, views, utils
......@@ -251,13 +252,14 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
get_module=None,
render_template=render_to_string,
replace_urls=None,
xmodule_field_data=lambda d: d._field_data,
s3_interface=test_util_open_ended.S3_INTERFACE,
open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
mixins=settings.XBLOCK_MIXINS,
error_descriptor_class=ErrorDescriptor,
)
self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system, field_data, ScopeIds(None, None, None, None))
self.peer_module = self.descriptor.xmodule(self.system)
self.descriptor.xmodule_runtime = self.system
self.peer_module = self.descriptor
self.peer_module.peer_gs = self.mock_service
self.logout()
......
......@@ -36,7 +36,6 @@ system = ModuleSystem(
get_module=None,
render_template=render_to_string,
replace_urls=None,
xmodule_field_data=DictFieldData({}),
)
......
......@@ -15,7 +15,7 @@
-e git+https://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk
# 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/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
......
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