Commit fd72005a by Vik Paruchuri Committed by Brian Wilson

Ensure that peer grading modules whose linked problem is removed change over to panel mode

parent d8447436
...@@ -104,30 +104,28 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -104,30 +104,28 @@ class PeerGradingModule(PeerGradingFields, XModule):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(PeerGradingModule, self).__init__(*args, **kwargs) super(PeerGradingModule, self).__init__(*args, **kwargs)
#We need to set the location here so the child modules can use it # Copy this to a new variable so that we can edit it if needed.
# We need to edit it if the linked module cannot be found, so
# we can revert to panel model.
self.use_for_single_location_local = self.use_for_single_location
# 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.runtime.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()
if self.use_for_single_location: if self.use_for_single_location_local:
try: linked_descriptors = self.descriptor.get_required_module_descriptors()
linked_descriptors = self.descriptor.get_required_module_descriptors() if len(linked_descriptors) == 0:
if len(linked_descriptors) == 0: error_msg = "Peer grading module {0} is trying to use single problem mode without "
error_msg = "Peer grading module {0} is trying to use single problem mode without " "a location specified.".format(self.location)
"a location specified.".format(self.location) log.error(error_msg)
log.error(error_msg) # Change module over to panel mode from single problem mode.
raise InvalidLinkLocation(error_msg) self.use_for_single_location_local = False
else:
self.linked_problem = self.system.get_module(linked_descriptors[0]) self.linked_problem = self.system.get_module(linked_descriptors[0])
except ItemNotFoundError:
log.error("Linked location {0} for peer grading module {1} does not exist".format(
self.link_to_location, self.location))
raise
except NoPathToItem:
log.error("Linked location {0} for peer grading module {1} cannot be linked to.".format(
self.link_to_location, self.location))
raise
try: try:
self.timeinfo = TimeInfo(self.due, self.graceperiod) self.timeinfo = TimeInfo(self.due, self.graceperiod)
...@@ -175,7 +173,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -175,7 +173,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
""" """
if self.closed(): if self.closed():
return self.peer_grading_closed() return self.peer_grading_closed()
if not self.use_for_single_location: if not self.use_for_single_location_local:
return self.peer_grading() return self.peer_grading()
else: else:
return self.peer_grading_problem({'location': self.link_to_location})['html'] return self.peer_grading_problem({'location': self.link_to_location})['html']
...@@ -236,7 +234,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -236,7 +234,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
'score': score, 'score': score,
'total': max_score, 'total': max_score,
} }
if not self.use_for_single_location or not self.graded: if not self.use_for_single_location_local or not self.graded:
return score_dict return score_dict
try: try:
...@@ -270,7 +268,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -270,7 +268,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
randomization, and 5/7 on another randomization, and 5/7 on another
''' '''
max_grade = None max_grade = None
if self.use_for_single_location and self.graded: if self.use_for_single_location_local and self.graded:
max_grade = self.weight max_grade = self.weight
return max_grade return max_grade
...@@ -492,7 +490,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -492,7 +490,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
Show the Peer grading closed template Show the Peer grading closed template
''' '''
html = self.system.render_template('peer_grading/peer_grading_closed.html', { html = self.system.render_template('peer_grading/peer_grading_closed.html', {
'use_for_single_location': self.use_for_single_location 'use_for_single_location': self.use_for_single_location_local
}) })
return html return html
...@@ -578,7 +576,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -578,7 +576,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
'error_text': error_text, 'error_text': error_text,
# Checked above # Checked above
'staff_access': False, 'staff_access': False,
'use_single_location': self.use_for_single_location, 'use_single_location': self.use_for_single_location_local,
}) })
return html return html
...@@ -588,7 +586,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -588,7 +586,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
Show individual problem interface Show individual problem interface
''' '''
if data is None or data.get('location') is None: if data is None or data.get('location') is None:
if not self.use_for_single_location: if not self.use_for_single_location_local:
# This is an error case, because it must be set to use a single location to be called without get parameters # This is an error case, because it must be set to use a single location to be called without get parameters
# This is a dev_facing_error # This is a dev_facing_error
log.error( log.error(
...@@ -610,7 +608,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -610,7 +608,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
# Checked above # Checked above
'staff_access': False, 'staff_access': False,
'track_changes': getattr(module, 'track_changes', False), 'track_changes': getattr(module, 'track_changes', False),
'use_single_location': self.use_for_single_location, 'use_single_location': self.use_for_single_location_local,
}) })
return {'html': html, 'success': True} return {'html': html, 'success': True}
...@@ -656,10 +654,24 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor): ...@@ -656,10 +654,24 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
return non_editable_fields return non_editable_fields
def get_required_module_descriptors(self): def get_required_module_descriptors(self):
"""Returns a list of XModuleDescritpor instances upon which this module depends, but are """
not children of this module""" Returns a list of XModuleDescriptor instances upon which this module depends, but are
not children of this module.
"""
# If use_for_single_location is True, this is linked to an open ended problem.
if self.use_for_single_location: if self.use_for_single_location:
return [self.system.load_item(self.link_to_location)] # Try to load the linked module.
# If we can't load it, return empty list to avoid exceptions on progress page.
try:
linked_module = self.system.load_item(self.link_to_location)
return [linked_module]
except (NoPathToItem, ItemNotFoundError):
error_message = ("Cannot find the combined open ended module "
"at location {0} being linked to from peer "
"grading module {1}").format(self.link_to_location, self.location)
log.error(error_message)
return []
else: else:
return [] return []
......
import unittest import unittest
from xmodule.modulestore import Location import json
from .import get_test_system import logging
from test_util_open_ended import MockQueryDict, DummyModulestore
from xmodule.open_ended_grading_classes.peer_grading_service import MockPeerGradingService
from mock import Mock from mock import Mock
from xmodule.peer_grading_module import PeerGradingModule, InvalidLinkLocation
from xblock.field_data import DictFieldData from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds from xblock.fields import ScopeIds
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
import json
import logging from xmodule.modulestore import Location
from xmodule.tests import get_test_system, get_test_descriptor_system
from xmodule.tests.test_util_open_ended import MockQueryDict, DummyModulestore
from xmodule.open_ended_grading_classes.peer_grading_service import MockPeerGradingService
from xmodule.peer_grading_module import PeerGradingModule, PeerGradingDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -37,7 +38,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore): ...@@ -37,7 +38,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
'feedback': "", 'feedback': "",
'rubric_scores[]': [0, 1], 'rubric_scores[]': [0, 1],
'submission_flagged': False, 'submission_flagged': False,
'answer_unknown' : False, 'answer_unknown': False,
}) })
def setUp(self): def setUp(self):
...@@ -57,22 +58,22 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore): ...@@ -57,22 +58,22 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return: @return:
""" """
closed = self.peer_grading.closed() closed = self.peer_grading.closed()
self.assertEqual(closed, False) self.assertFalse(closed)
def test_get_html(self): def test_get_html(self):
""" """
Test to see if the module can be rendered Test to see if the module can be rendered
@return: @return:
""" """
html = self.peer_grading.get_html() _html = self.peer_grading.get_html()
def test_get_data(self): def test_get_data(self):
""" """
Try getting data from the external grading service Try getting data from the external grading service
@return: @return:
""" """
success, data = self.peer_grading.query_data_for_location(self.problem_location.url()) success, _data = self.peer_grading.query_data_for_location(self.problem_location.url())
self.assertEqual(success, True) self.assertTrue(success)
def test_get_score(self): def test_get_score(self):
""" """
...@@ -80,7 +81,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore): ...@@ -80,7 +81,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return: @return:
""" """
score = self.peer_grading.get_score() score = self.peer_grading.get_score()
self.assertEquals(score['score'], None) self.assertIsNone(score['score'])
def test_get_max_score(self): def test_get_max_score(self):
""" """
...@@ -95,7 +96,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore): ...@@ -95,7 +96,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
Test to see if we can get the next mock submission Test to see if we can get the next mock submission
@return: @return:
""" """
success, next_submission = self.peer_grading.get_next_submission({'location': 'blah'}) success, _next_submission = self.peer_grading.get_next_submission({'location': 'blah'})
self.assertEqual(success, True) self.assertEqual(success, True)
def test_save_grade(self): def test_save_grade(self):
...@@ -111,9 +112,8 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore): ...@@ -111,9 +112,8 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
Check to see if the student has calibrated yet Check to see if the student has calibrated yet
@return: @return:
""" """
calibrated_dict = {'location': "blah"}
response = self.peer_grading.is_student_calibrated(self.calibrated_dict) response = self.peer_grading.is_student_calibrated(self.calibrated_dict)
self.assertEqual(response['success'], True) self.assertTrue(response['success'])
def test_show_calibration_essay(self): def test_show_calibration_essay(self):
""" """
...@@ -121,7 +121,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore): ...@@ -121,7 +121,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return: @return:
""" """
response = self.peer_grading.show_calibration_essay(self.calibrated_dict) response = self.peer_grading.show_calibration_essay(self.calibrated_dict)
self.assertEqual(response['success'], True) self.assertTrue(response['success'])
def test_save_calibration_essay(self): def test_save_calibration_essay(self):
""" """
...@@ -129,7 +129,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore): ...@@ -129,7 +129,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return: @return:
""" """
response = self.peer_grading.save_calibration_essay(self.save_dict) response = self.peer_grading.save_calibration_essay(self.save_dict)
self.assertEqual(response['success'], True) self.assertTrue(response['success'])
def test_peer_grading_problem(self): def test_peer_grading_problem(self):
""" """
...@@ -137,7 +137,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore): ...@@ -137,7 +137,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return: @return:
""" """
response = self.peer_grading.peer_grading_problem(self.coe_dict) response = self.peer_grading.peer_grading_problem(self.coe_dict)
self.assertEqual(response['success'], True) self.assertTrue(response['success'])
def test___find_corresponding_module_for_location_exceptions(self): def test___find_corresponding_module_for_location_exceptions(self):
""" """
...@@ -146,7 +146,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore): ...@@ -146,7 +146,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return: @return:
""" """
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
self.peer_grading._find_corresponding_module_for_location(Location('i4x','a','b','c','d')) self.peer_grading._find_corresponding_module_for_location(Location('i4x', 'a', 'b', 'c', 'd'))
def test_get_instance_state(self): def test_get_instance_state(self):
""" """
...@@ -155,6 +155,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore): ...@@ -155,6 +155,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
""" """
self.peer_grading.get_instance_state() self.peer_grading.get_instance_state()
class MockPeerGradingServiceProblemList(MockPeerGradingService): class MockPeerGradingServiceProblemList(MockPeerGradingService):
def get_problem_list(self, course_id, grader_id): def get_problem_list(self, course_id, grader_id):
return {'success': True, return {'success': True,
...@@ -162,13 +163,16 @@ class MockPeerGradingServiceProblemList(MockPeerGradingService): ...@@ -162,13 +163,16 @@ class MockPeerGradingServiceProblemList(MockPeerGradingService):
{"num_graded": 3, "num_pending": 681, "num_required": 3, "location": "i4x://edX/open_ended/combinedopenended/SampleQuestion", "problem_name": "Peer-Graded Essay"}, {"num_graded": 3, "num_pending": 681, "num_required": 3, "location": "i4x://edX/open_ended/combinedopenended/SampleQuestion", "problem_name": "Peer-Graded Essay"},
]} ]}
class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore): class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore):
""" """
Test peer grading xmodule at the unit level. More detailed tests are difficult, as the module relies on an Test peer grading xmodule at the unit level. More detailed tests are difficult, as the module relies on an
external grading service. external grading service.
""" """
problem_location = Location(["i4x", "edX", "open_ended", "peergrading", problem_location = Location(
"PeerGradingScored"]) ["i4x", "edX", "open_ended", "peergrading", "PeerGradingScored"]
)
def setUp(self): def setUp(self):
""" """
Create a peer grading module from a test system Create a peer grading module from a test system
...@@ -180,7 +184,7 @@ class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore): ...@@ -180,7 +184,7 @@ class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore):
def test_metadata_load(self): def test_metadata_load(self):
peer_grading = self.get_module_from_location(self.problem_location, COURSE) peer_grading = self.get_module_from_location(self.problem_location, COURSE)
self.assertEqual(peer_grading.closed(), False) self.assertFalse(peer_grading.closed())
def test_problem_list(self): def test_problem_list(self):
""" """
...@@ -217,6 +221,37 @@ class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore): ...@@ -217,6 +221,37 @@ class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore):
self.test_system.open_ended_grading_interface = None self.test_system.open_ended_grading_interface = None
self.setup_modulestore(COURSE) self.setup_modulestore(COURSE)
@property
def field_data(self):
"""
Setup the proper field data for a peer grading module.
"""
return DictFieldData({
'data': '<peergrading/>',
'location': self.problem_location,
'use_for_single_location': True,
'link_to_location': self.coe_location.url(),
'graded': True,
})
@property
def scope_ids(self):
"""
Return the proper scope ids for the peer grading module.
"""
return ScopeIds(None, None, self.problem_location, self.problem_location)
def _create_peer_grading_descriptor_with_linked_problem(self):
# Initialize the peer grading module.
system = get_test_descriptor_system()
return system.construct_xblock_from_class(
PeerGradingDescriptor,
field_data=self.field_data,
scope_ids=self.scope_ids
)
def _create_peer_grading_with_linked_problem(self, location, valid_linked_descriptor=True): def _create_peer_grading_with_linked_problem(self, location, valid_linked_descriptor=True):
""" """
Create a peer grading problem with a linked location. Create a peer grading problem with a linked location.
...@@ -235,34 +270,56 @@ class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore): ...@@ -235,34 +270,56 @@ class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore):
else: else:
pg_descriptor.get_required_module_descriptors = lambda: [] pg_descriptor.get_required_module_descriptors = lambda: []
# Setup the proper field data for the peer grading module.
field_data = DictFieldData({
'data': '<peergrading/>',
'location': self.problem_location,
'use_for_single_location': True,
'link_to_location': self.coe_location.url(),
'graded': True,
})
# Initialize the peer grading module. # Initialize the peer grading module.
peer_grading = PeerGradingModule( peer_grading = PeerGradingModule(
pg_descriptor, pg_descriptor,
self.test_system, self.test_system,
field_data, self.field_data,
ScopeIds(None, None, self.problem_location, self.problem_location) self.scope_ids,
) )
return peer_grading return peer_grading
def _get_descriptor_with_invalid_link(self, exception_to_raise):
"""
Ensure that a peer grading descriptor with an invalid link will return an empty list.
"""
# Create a descriptor, and make loading an item throw an error.
descriptor = self._create_peer_grading_descriptor_with_linked_problem()
descriptor.system.load_item = Mock(side_effect=exception_to_raise)
# Ensure that modules is a list of length 0.
modules = descriptor.get_required_module_descriptors()
self.assertIsInstance(modules, list)
self.assertEqual(len(modules), 0)
def test_descriptor_with_nopath(self):
"""
Test to see if a descriptor with a NoPathToItem error when trying to get
its linked module behaves properly.
"""
self._get_descriptor_with_invalid_link(NoPathToItem)
def test_descriptor_with_item_not_found(self):
"""
Test to see if a descriptor with an ItemNotFound error when trying to get
its linked module behaves properly.
"""
self._get_descriptor_with_invalid_link(ItemNotFoundError)
def test_invalid_link(self): def test_invalid_link(self):
""" """
Ensure that a peer grading problem with no linked locations raises an error. Ensure that a peer grading problem with no linked locations stays in panel mode.
""" """
# Setup the peer grading module with no linked locations. # Setup the peer grading module with no linked locations.
with self.assertRaises(InvalidLinkLocation): peer_grading = self._create_peer_grading_with_linked_problem(self.coe_location, valid_linked_descriptor=False)
self._create_peer_grading_with_linked_problem(self.coe_location, valid_linked_descriptor=False)
self.assertFalse(peer_grading.use_for_single_location_local)
self.assertTrue(peer_grading.use_for_single_location)
def test_linked_problem(self): def test_linked_problem(self):
""" """
...@@ -284,7 +341,7 @@ class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore): ...@@ -284,7 +341,7 @@ class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore):
peer_grading = self._create_peer_grading_with_linked_problem(self.coe_location) peer_grading = self._create_peer_grading_with_linked_problem(self.coe_location)
# If we specify a location, it will render the problem for that location. # If we specify a location, it will render the problem for that location.
data = peer_grading.handle_ajax('problem', {'location' : self.coe_location}) data = peer_grading.handle_ajax('problem', {'location': self.coe_location})
self.assertTrue(json.loads(data)['success']) self.assertTrue(json.loads(data)['success'])
# If we don't specify a location, it should use the linked location. # If we don't specify a location, it should use the linked location.
...@@ -344,5 +401,5 @@ class PeerGradingModuleTrackChangesTest(unittest.TestCase, DummyModulestore): ...@@ -344,5 +401,5 @@ class PeerGradingModuleTrackChangesTest(unittest.TestCase, DummyModulestore):
""" """
self.peer_grading._find_corresponding_module_for_location = self.mock_track_changes_problem self.peer_grading._find_corresponding_module_for_location = self.mock_track_changes_problem
response = self.peer_grading.peer_grading_problem({'location': 'mocked'}) response = self.peer_grading.peer_grading_problem({'location': 'mocked'})
self.assertEqual(response['success'], True) self.assertTrue(response['success'])
self.assertIn("'track_changes': True", response['html']) self.assertIn("'track_changes': True", response['html'])
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