Skip to content
Snippets Groups Projects
Commit fd72005a authored by Vik Paruchuri's avatar Vik Paruchuri Committed by Brian Wilson
Browse files

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

parent d8447436
Branches
Tags release-2020-11-23-14.49
No related merge requests found
......@@ -104,30 +104,28 @@ class PeerGradingModule(PeerGradingFields, XModule):
def __init__(self, *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)
if (self.runtime.open_ended_grading_interface):
self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.system)
else:
self.peer_gs = MockPeerGradingService()
if self.use_for_single_location:
try:
linked_descriptors = self.descriptor.get_required_module_descriptors()
if len(linked_descriptors) == 0:
error_msg = "Peer grading module {0} is trying to use single problem mode without "
"a location specified.".format(self.location)
log.error(error_msg)
raise InvalidLinkLocation(error_msg)
if self.use_for_single_location_local:
linked_descriptors = self.descriptor.get_required_module_descriptors()
if len(linked_descriptors) == 0:
error_msg = "Peer grading module {0} is trying to use single problem mode without "
"a location specified.".format(self.location)
log.error(error_msg)
# Change module over to panel mode from single problem mode.
self.use_for_single_location_local = False
else:
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:
self.timeinfo = TimeInfo(self.due, self.graceperiod)
......@@ -175,7 +173,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
"""
if self.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()
else:
return self.peer_grading_problem({'location': self.link_to_location})['html']
......@@ -236,7 +234,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
'score': 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
try:
......@@ -270,7 +268,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
randomization, and 5/7 on another
'''
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
return max_grade
......@@ -492,7 +490,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
Show the Peer grading closed template
'''
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
......@@ -578,7 +576,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
'error_text': error_text,
# Checked above
'staff_access': False,
'use_single_location': self.use_for_single_location,
'use_single_location': self.use_for_single_location_local,
})
return html
......@@ -588,7 +586,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
Show individual problem interface
'''
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 a dev_facing_error
log.error(
......@@ -610,7 +608,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
# Checked above
'staff_access': 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}
......@@ -656,10 +654,24 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
return non_editable_fields
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:
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:
return []
......
import unittest
from xmodule.modulestore import Location
from .import get_test_system
from test_util_open_ended import MockQueryDict, DummyModulestore
from xmodule.open_ended_grading_classes.peer_grading_service import MockPeerGradingService
import json
import logging
from mock import Mock
from xmodule.peer_grading_module import PeerGradingModule, InvalidLinkLocation
from xblock.field_data import DictFieldData
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__)
......@@ -37,7 +38,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
'feedback': "",
'rubric_scores[]': [0, 1],
'submission_flagged': False,
'answer_unknown' : False,
'answer_unknown': False,
})
def setUp(self):
......@@ -57,22 +58,22 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return:
"""
closed = self.peer_grading.closed()
self.assertEqual(closed, False)
self.assertFalse(closed)
def test_get_html(self):
"""
Test to see if the module can be rendered
@return:
"""
html = self.peer_grading.get_html()
_html = self.peer_grading.get_html()
def test_get_data(self):
"""
Try getting data from the external grading service
@return:
"""
success, data = self.peer_grading.query_data_for_location(self.problem_location.url())
self.assertEqual(success, True)
success, _data = self.peer_grading.query_data_for_location(self.problem_location.url())
self.assertTrue(success)
def test_get_score(self):
"""
......@@ -80,7 +81,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return:
"""
score = self.peer_grading.get_score()
self.assertEquals(score['score'], None)
self.assertIsNone(score['score'])
def test_get_max_score(self):
"""
......@@ -95,7 +96,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
Test to see if we can get the next mock submission
@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)
def test_save_grade(self):
......@@ -111,9 +112,8 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
Check to see if the student has calibrated yet
@return:
"""
calibrated_dict = {'location': "blah"}
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):
"""
......@@ -121,7 +121,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return:
"""
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):
"""
......@@ -129,7 +129,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return:
"""
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):
"""
......@@ -137,7 +137,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return:
"""
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):
"""
......@@ -146,7 +146,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
@return:
"""
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):
"""
......@@ -155,6 +155,7 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
"""
self.peer_grading.get_instance_state()
class MockPeerGradingServiceProblemList(MockPeerGradingService):
def get_problem_list(self, course_id, grader_id):
return {'success': True,
......@@ -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"},
]}
class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore):
"""
Test peer grading xmodule at the unit level. More detailed tests are difficult, as the module relies on an
external grading service.
"""
problem_location = Location(["i4x", "edX", "open_ended", "peergrading",
"PeerGradingScored"])
problem_location = Location(
["i4x", "edX", "open_ended", "peergrading", "PeerGradingScored"]
)
def setUp(self):
"""
Create a peer grading module from a test system
......@@ -180,7 +184,7 @@ class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore):
def test_metadata_load(self):
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):
"""
......@@ -217,6 +221,37 @@ class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore):
self.test_system.open_ended_grading_interface = None
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):
"""
Create a peer grading problem with a linked location.
......@@ -235,34 +270,56 @@ class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore):
else:
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.
peer_grading = PeerGradingModule(
pg_descriptor,
self.test_system,
field_data,
ScopeIds(None, None, self.problem_location, self.problem_location)
self.field_data,
self.scope_ids,
)
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):
"""
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.
with self.assertRaises(InvalidLinkLocation):
self._create_peer_grading_with_linked_problem(self.coe_location, valid_linked_descriptor=False)
peer_grading = 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):
"""
......@@ -284,7 +341,7 @@ class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore):
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.
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'])
# If we don't specify a location, it should use the linked location.
......@@ -344,5 +401,5 @@ class PeerGradingModuleTrackChangesTest(unittest.TestCase, DummyModulestore):
"""
self.peer_grading._find_corresponding_module_for_location = self.mock_track_changes_problem
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'])
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