")
+ if response.actual_feedback!=undefined
+ calibration_wrapper.append("
Instructor Feedback: #{response.actual_feedback}
")
+
# disable score selection and submission from the grading interface
$("input[name='score-selection']").attr('disabled', true)
@submit_button.hide()
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
index 59df481954..12f90ed1b3 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
@@ -131,6 +131,7 @@ class CombinedOpenEndedV1Module():
self.state = instance_state.get('state', self.INITIAL)
self.student_attempts = instance_state.get('student_attempts', 0)
+ self.weight = instance_state.get('weight', 1)
#Allow reset is true if student has failed the criteria to move to the next child task
self.ready_to_reset = instance_state.get('ready_to_reset', False)
@@ -144,7 +145,7 @@ class CombinedOpenEndedV1Module():
grace_period_string = self.instance_state.get('graceperiod', None)
try:
self.timeinfo = TimeInfo(due_date, grace_period_string)
- except:
+ except Exception:
log.error("Error parsing due date information in location {0}".format(location))
raise
self.display_due_date = self.timeinfo.display_due_date
@@ -362,7 +363,7 @@ class CombinedOpenEndedV1Module():
# if link.startswith(XASSET_SRCREF_PREFIX):
# Placing try except so that if the error is fixed, this code will start working again.
return_html = rewrite_links(html, self.rewrite_content_links)
- except:
+ except Exception:
pass
return return_html
@@ -402,6 +403,7 @@ class CombinedOpenEndedV1Module():
self.static_data, instance_state=task_state)
last_response = task.latest_answer()
last_score = task.latest_score()
+ all_scores = task.all_scores()
last_post_assessment = task.latest_post_assessment(self.system)
last_post_feedback = ""
feedback_dicts = [{}]
@@ -417,13 +419,18 @@ class CombinedOpenEndedV1Module():
else:
last_post_evaluation = task.format_feedback_with_evaluation(self.system, last_post_assessment)
last_post_assessment = last_post_evaluation
- rubric_data = task._parse_score_msg(task.child_history[-1].get('post_assessment', ""), self.system)
- rubric_scores = rubric_data['rubric_scores']
- grader_types = rubric_data['grader_types']
- feedback_items = rubric_data['feedback_items']
- feedback_dicts = rubric_data['feedback_dicts']
- grader_ids = rubric_data['grader_ids']
- submission_ids = rubric_data['submission_ids']
+ try:
+ rubric_data = task._parse_score_msg(task.child_history[-1].get('post_assessment', ""), self.system)
+ except Exception:
+ log.debug("Could not parse rubric data from child history. "
+ "Likely we have not yet initialized a previous step, so this is perfectly fine.")
+ rubric_data = {}
+ rubric_scores = rubric_data.get('rubric_scores')
+ grader_types = rubric_data.get('grader_types')
+ feedback_items = rubric_data.get('feedback_items')
+ feedback_dicts = rubric_data.get('feedback_dicts')
+ grader_ids = rubric_data.get('grader_ids')
+ submission_ids = rubric_data.get('submission_ids')
elif task_type == "selfassessment":
rubric_scores = last_post_assessment
grader_types = ['SA']
@@ -441,7 +448,7 @@ class CombinedOpenEndedV1Module():
human_state = task.HUMAN_NAMES[state]
else:
human_state = state
- if len(grader_types) > 0:
+ if grader_types is not None and len(grader_types) > 0:
grader_type = grader_types[0]
else:
grader_type = "IN"
@@ -454,6 +461,7 @@ class CombinedOpenEndedV1Module():
last_response_dict = {
'response': last_response,
'score': last_score,
+ 'all_scores': all_scores,
'post_assessment': last_post_assessment,
'type': task_type,
'max_score': max_score,
@@ -732,10 +740,37 @@ class CombinedOpenEndedV1Module():
"""
max_score = None
score = None
- if self.check_if_done_and_scored():
- last_response = self.get_last_response(self.current_task_number)
- max_score = last_response['max_score']
- score = last_response['score']
+ if self.is_scored and self.weight is not None:
+ #Finds the maximum score of all student attempts and keeps it.
+ score_mat = []
+ for i in xrange(0, len(self.task_states)):
+ #For each task, extract all student scores on that task (each attempt for each task)
+ last_response = self.get_last_response(i)
+ max_score = last_response.get('max_score', None)
+ score = last_response.get('all_scores', None)
+ if score is not None:
+ #Convert none scores and weight scores properly
+ for z in xrange(0, len(score)):
+ if score[z] is None:
+ score[z] = 0
+ score[z] *= float(self.weight)
+ score_mat.append(score)
+
+ if len(score_mat) > 0:
+ #Currently, assume that the final step is the correct one, and that those are the final scores.
+ #This will change in the future, which is why the machinery above exists to extract all scores on all steps
+ #TODO: better final score handling.
+ scores = score_mat[-1]
+ score = max(scores)
+ else:
+ score = 0
+
+ if max_score is not None:
+ #Weight the max score if it is not None
+ max_score *= float(self.weight)
+ else:
+ #Without a max_score, we cannot have a score!
+ score = None
score_dict = {
'score': score,
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
index b9341f0cbe..d5889636ed 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
@@ -72,7 +72,8 @@ class OpenEndedChild(object):
try:
instance_state = json.loads(instance_state)
except:
- log.error("Could not load instance state for open ended. Setting it to nothing.: {0}".format(instance_state))
+ log.error(
+ "Could not load instance state for open ended. Setting it to nothing.: {0}".format(instance_state))
else:
instance_state = {}
@@ -81,8 +82,8 @@ class OpenEndedChild(object):
# element.
# Scores are on scale from 0 to max_score
- self.child_history=instance_state.get('child_history',[])
- self.child_state=instance_state.get('child_state', self.INITIAL)
+ self.child_history = instance_state.get('child_history', [])
+ self.child_state = instance_state.get('child_state', self.INITIAL)
self.child_created = instance_state.get('child_created', False)
self.child_attempts = instance_state.get('child_attempts', 0)
@@ -162,6 +163,12 @@ class OpenEndedChild(object):
return None
return self.child_history[-1].get('score')
+ def all_scores(self):
+ """None if not available"""
+ if not self.child_history:
+ return None
+ return [self.child_history[i].get('score') for i in xrange(0, len(self.child_history))]
+
def latest_post_assessment(self, system):
"""Empty string if not available"""
if not self.child_history:
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py
index 5fb901d49c..497d2f6eed 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py
@@ -291,7 +291,7 @@ class SelfAssessmentDescriptor():
template_dir_name = "selfassessment"
def __init__(self, system):
- self.system =system
+ self.system = system
@classmethod
def definition_from_xml(cls, xml_object, system):
diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py
index 5075507bce..db4514d0e0 100644
--- a/common/lib/xmodule/xmodule/peer_grading_module.py
+++ b/common/lib/xmodule/xmodule/peer_grading_module.py
@@ -15,6 +15,7 @@ from xmodule.open_ended_grading_classes.xblock_field_types import StringyFloat
from xmodule.fields import Date
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService
+from open_ended_grading_classes import combined_open_ended_rubric
log = logging.getLogger(__name__)
@@ -178,8 +179,14 @@ class PeerGradingModule(PeerGradingFields, XModule):
pass
def get_score(self):
+ max_score = None
+ score = None
+ score_dict = {
+ 'score': score,
+ 'total': max_score,
+ }
if self.use_for_single_location not in TRUE_DICT or self.is_graded not in TRUE_DICT:
- return None
+ return score_dict
try:
count_graded = self.student_data_for_location['count_graded']
@@ -198,10 +205,11 @@ class PeerGradingModule(PeerGradingFields, XModule):
#Ensures that once a student receives a final score for peer grading, that it does not change.
self.student_data_for_location = response
- score_dict = {
- 'score': int(count_graded >= count_required and count_graded>0) * int(self.weight),
- 'total': self.max_grade * int(self.weight),
- }
+ if self.weight is not None:
+ score = int(count_graded >= count_required and count_graded > 0) * float(self.weight)
+ total = self.max_grade * float(self.weight)
+ score_dict['score'] = score
+ score_dict['total'] = total
return score_dict
@@ -384,8 +392,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
# if we can't parse the rubric into HTML,
except etree.XMLSyntaxError:
#This is a dev_facing_error
- log.exception("Cannot parse rubric string. Raw string: {0}"
- .format(rubric))
+ log.exception("Cannot parse rubric string.")
#This is a student_facing_error
return {'success': False,
'error': 'Error displaying submission. Please notify course staff.'}
@@ -425,12 +432,15 @@ class PeerGradingModule(PeerGradingFields, XModule):
try:
response = self.peer_gs.save_calibration_essay(location, grader_id, calibration_essay_id,
submission_key, score, feedback, rubric_scores)
+ if 'actual_rubric' in response:
+ rubric_renderer = combined_open_ended_rubric.CombinedOpenEndedRubric(self.system, True)
+ response['actual_rubric'] = rubric_renderer.render_rubric(response['actual_rubric'])['html']
return response
except GradingServiceError:
#This is a dev_facing_error
log.exception(
- "Error saving calibration grade, location: {0}, submission_id: {1}, submission_key: {2}, grader_id: {3}".format(
- location, submission_id, submission_key, grader_id))
+ "Error saving calibration grade, location: {0}, submission_key: {1}, grader_id: {2}".format(
+ location, submission_key, grader_id))
#This is a student_facing_error
return self._err_response('There was an error saving your score. Please notify course staff.')
@@ -577,5 +587,5 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
stores_state = True
has_score = True
- always_recalculate_grades=True
+ always_recalculate_grades = True
template_dir_name = "peer_grading"
diff --git a/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml b/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml
index 515d9071b1..a11367b46f 100644
--- a/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml
+++ b/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml
@@ -1,10 +1,9 @@
---
metadata:
display_name: Open Ended Response
- max_attempts: 1
+ attempts: 1
is_graded: False
version: 1
- display_name: Open Ended Response
skip_spelling_checks: False
accept_file_upload: False
weight: ""
diff --git a/common/lib/xmodule/xmodule/templates/peer_grading/default.yaml b/common/lib/xmodule/xmodule/templates/peer_grading/default.yaml
index 1ba8f978d6..23d41d616f 100644
--- a/common/lib/xmodule/xmodule/templates/peer_grading/default.yaml
+++ b/common/lib/xmodule/xmodule/templates/peer_grading/default.yaml
@@ -1,7 +1,6 @@
---
metadata:
display_name: Peer Grading Interface
- attempts: 1
use_for_single_location: False
link_to_location: None
is_graded: False
diff --git a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
index 1950389399..59f0e222ee 100644
--- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
+++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
@@ -323,6 +323,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
's3_interface': test_util_open_ended.S3_INTERFACE,
'open_ended_grading_interface': test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
'skip_basic_checks': False,
+ 'is_graded': True,
}
oeparam = etree.XML('''
@@ -358,7 +359,8 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
combinedoe_container = CombinedOpenEndedModule(test_system,
location,
descriptor,
- model_data={'data': full_definition, 'weight' : '1'})
+ model_data={'data': full_definition, 'weight': '1'})
+
def setUp(self):
# TODO: this constructor call is definitely wrong, but neither branch
@@ -369,7 +371,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
self.descriptor,
static_data=self.static_data,
metadata=self.metadata,
- instance_state={})
+ instance_state=self.static_data)
def test_get_tag_name(self):
name = self.combinedoe.get_tag_name("Tag")
@@ -405,4 +407,65 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
def test_container_weight(self):
weight = self.combinedoe_container.weight
- self.assertEqual(weight,1)
+ self.assertEqual(weight, 1)
+
+ def test_container_child_weight(self):
+ weight = self.combinedoe_container.child_module.weight
+ self.assertEqual(weight, 1)
+
+ def test_get_score(self):
+ score_dict = self.combinedoe.get_score()
+ self.assertEqual(score_dict['score'], 0)
+ self.assertEqual(score_dict['total'], 1)
+
+ def test_alternate_orderings(self):
+ t1 = self.task_xml1
+ t2 = self.task_xml2
+ xml_to_test = [[t1], [t2], [t1, t1], [t1, t2], [t2, t2], [t2, t1], [t1, t2, t1]]
+ for xml in xml_to_test:
+ definition = {'prompt': etree.XML(self.prompt), 'rubric': etree.XML(self.rubric), 'task_xml': xml}
+ descriptor = Mock(data=definition)
+ combinedoe = CombinedOpenEndedV1Module(self.test_system,
+ self.location,
+ definition,
+ descriptor,
+ static_data=self.static_data,
+ metadata=self.metadata,
+ instance_state=self.static_data)
+
+ changed = combinedoe.update_task_states()
+ self.assertFalse(changed)
+
+ def test_get_score_realistic(self):
+ instance_state = r"""{"ready_to_reset": false, "skip_spelling_checks": true, "current_task_number": 1, "weight": 5.0, "graceperiod": "1 day 12 hours 59 minutes 59 seconds", "is_graded": "True", "task_states": ["{\"child_created\": false, \"child_attempts\": 4, \"version\": 1, \"child_history\": [{\"answer\": \"The students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the group\\u2019s procedure, describe what additional information you would need in order to replicate the expe\", \"post_assessment\": \"{\\\"submission_id\\\": 3097, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: More grammar errors than average.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"the students data are recorded in the table below . starting mass g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the groups procedure , describe what additional information you would need in order to replicate the expe\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3233, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"After 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"To replicate the experiment, the procedure would require more detail. One piece of information that is omitted is the amount of vinegar used in the experiment. It is also important to know what temperature the experiment was kept at during the 24 hours. Finally, the procedure needs to include details about the experiment, for example if the whole sample must be submerged.\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"e the mass of four different samples.\\r\\nPour vinegar in each of four separate, but identical, containers.\\r\\nPlace a sample of one material into one container and label. Repeat with remaining samples, placing a single sample into a single container.\\r\\nAfter 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"\", \"post_assessment\": \"[3]\", \"score\": 3}], \"max_score\": 3, \"child_state\": \"done\"}", "{\"child_created\": false, \"child_attempts\": 0, \"version\": 1, \"child_history\": [{\"answer\": \"The students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the group\\u2019s procedure, describe what additional information you would need in order to replicate the expe\", \"post_assessment\": \"{\\\"submission_id\\\": 3097, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: More grammar errors than average.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"the students data are recorded in the table below . starting mass g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the groups procedure , describe what additional information you would need in order to replicate the expe\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3233, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"After 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the\", \"post_assessment\": \"{\\\"submission_id\\\": 3098, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"after hours , remove the samples from the containers and rinse each sample with distilled water . allow the samples to sit and dry for minutes . determine the mass of each sample . the students data are recorded in the table below . starting mass g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3235, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"To replicate the experiment, the procedure would require more detail. One piece of information that is omitted is the amount of vinegar used in the experiment. It is also important to know what temperature the experiment was kept at during the 24 hours. Finally, the procedure needs to include details about the experiment, for example if the whole sample must be submerged.\", \"post_assessment\": \"{\\\"submission_id\\\": 3099, \\\"score\\\": 3, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"to replicate the experiment , the procedure would require more detail . one piece of information that is omitted is the amount of vinegar used in the experiment . it is also important to know what temperature the experiment was kept at during the hours . finally , the procedure needs to include details about the experiment , for example if the whole sample must be submerged .\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3237, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality3\\\"}\", \"score\": 3}, {\"answer\": \"e the mass of four different samples.\\r\\nPour vinegar in each of four separate, but identical, containers.\\r\\nPlace a sample of one material into one container and label. Repeat with remaining samples, placing a single sample into a single container.\\r\\nAfter 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\", \"post_assessment\": \"{\\\"submission_id\\\": 3100, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"e the mass of four different samples . pour vinegar in each of four separate , but identical , containers . place a sample of one material into one container and label . repeat with remaining samples , placing a single sample into a single container . after hours , remove the samples from the containers and rinse each sample with distilled water . allow the samples to sit and dry for minutes . determine the mass of each sample . the students data are recorded in the table below . \\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3239, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"\", \"post_assessment\": \"{\\\"submission_id\\\": 3101, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"invalid essay .\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3241, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}], \"max_score\": 3, \"child_state\": \"done\"}"], "attempts": "10000", "student_attempts": 0, "due": null, "state": "done", "accept_file_upload": false, "display_name": "Science Question -- Machine Assessed"}"""
+ instance_state = json.loads(instance_state)
+ rubric = """
+
+
+
+ Response Quality
+
+
+
+
+
+
+
+ """
+ definition = {'prompt': etree.XML(self.prompt), 'rubric': etree.XML(rubric),
+ 'task_xml': [self.task_xml1, self.task_xml2]}
+ descriptor = Mock(data=definition)
+ combinedoe = CombinedOpenEndedV1Module(self.test_system,
+ self.location,
+ definition,
+ descriptor,
+ static_data=self.static_data,
+ metadata=self.metadata,
+ instance_state=instance_state)
+ score_dict = combinedoe.get_score()
+ self.assertEqual(score_dict['score'], 15.0)
+ self.assertEqual(score_dict['total'], 15.0)
+
+
+
+
diff --git a/lms/djangoapps/open_ended_grading/staff_grading_service.py b/lms/djangoapps/open_ended_grading/staff_grading_service.py
index 57bfd7df42..2c611b4481 100644
--- a/lms/djangoapps/open_ended_grading/staff_grading_service.py
+++ b/lms/djangoapps/open_ended_grading/staff_grading_service.py
@@ -310,19 +310,24 @@ def save_grade(request, course_id):
if request.method != 'POST':
raise Http404
-
- required = set(['score', 'feedback', 'submission_id', 'location', 'submission_flagged', 'rubric_scores[]'])
- actual = set(request.POST.keys())
+ p = request.POST
+ required = set(['score', 'feedback', 'submission_id', 'location', 'submission_flagged'])
+ skipped = 'skipped' in p
+ #If the instructor has skipped grading the submission, then there will not be any rubric scores.
+ #Only add in the rubric scores if the instructor has not skipped.
+ if not skipped:
+ required|=set(['rubric_scores[]'])
+ actual = set(p.keys())
missing = required - actual
if len(missing) > 0:
return _err_response('Missing required keys {0}'.format(
', '.join(missing)))
grader_id = unique_id_for_user(request.user)
- p = request.POST
+
location = p['location']
- skipped = 'skipped' in p
+
try:
result_json = staff_grading_service().save_grade(course_id,
diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py
index e554fdf0e1..93d27d8e24 100644
--- a/lms/djangoapps/open_ended_grading/tests.py
+++ b/lms/djangoapps/open_ended_grading/tests.py
@@ -97,7 +97,7 @@ class TestStaffGradingService(LoginEnrollmentTestCase):
self.assertIsNotNone(d['rubric'])
- def test_save_grade(self):
+ def save_grade_base(self,skip=False):
self.login(self.instructor, self.password)
url = reverse('staff_grading_save_grade', kwargs={'course_id': self.course_id})
@@ -108,12 +108,20 @@ class TestStaffGradingService(LoginEnrollmentTestCase):
'location': self.location,
'submission_flagged': "true",
'rubric_scores[]': ['1', '2']}
+ if skip:
+ data.update({'skipped' : True})
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertTrue(d['success'], str(d))
self.assertEquals(d['submission_id'], self.mock_service.cnt)
+ def test_save_grade(self):
+ self.save_grade_base(skip=False)
+
+ def test_save_grade_skip(self):
+ self.save_grade_base(skip=True)
+
def test_get_problem_list(self):
self.login(self.instructor, self.password)
diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee
index 6af9ecf5d1..f4a3360d1e 100644
--- a/lms/static/coffee/src/staff_grading/staff_grading.coffee
+++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee
@@ -185,6 +185,7 @@ class @StaffGrading
$(window).keydown @keydown_handler
+ $(window).keyup @keyup_handler
@question_header = $('.question-header')
@question_header.click @collapse_question
@collapse_question()
@@ -206,6 +207,7 @@ class @StaffGrading
@num_pending = 0
@score_lst = []
@grade = null
+ @is_ctrl = false
@problems = null
@@ -231,10 +233,18 @@ class @StaffGrading
@state = state_graded
@submit_button.show()
- keydown_handler: (e) =>
- if e.which == 13 && !@list_view && Rubric.check_complete()
+ keydown_handler: (event) =>
+ #Previously, responses were submitted when hitting enter. Add in a modifier that ensures that ctrl+enter is needed.
+ if event.which == 17 && @is_ctrl==false
+ @is_ctrl=true
+ else if @is_ctrl==true && event.which == 13 && !@list_view && Rubric.check_complete()
@submit_and_get_next()
+ keyup_handler: (event) =>
+ #Handle keyup event when ctrl key is released
+ if event.which == 17 && @is_ctrl==true
+ @is_ctrl=false
+
set_button_text: (text) =>
@action_button.attr('value', text)
diff --git a/lms/templates/peer_grading/peer_grading_problem.html b/lms/templates/peer_grading/peer_grading_problem.html
index 87559ec877..5ad3136815 100644
--- a/lms/templates/peer_grading/peer_grading_problem.html
+++ b/lms/templates/peer_grading/peer_grading_problem.html
@@ -43,8 +43,8 @@
Please include some written feedback as well.
-
Flag this submission for review by course staff (use if the submission contains inappropriate content)
-
I do not know how to grade this question
+
This submission has explicit or pornographic content :
+
I do not know how to grade this question :
@@ -82,6 +82,19 @@
-
+
+
+
Are you sure that you want to flag this submission?
+
+ You are about to flag a submission. You should only flag a submission that contains explicit or offensive content. If the submission is not addressed to the question or is incorrect, you should give it a score of zero and accompanying feedback instead of flagging it.
+