Partial scoring
This commit is contained in:
@@ -154,21 +154,10 @@ class LoncapaProblem(object):
|
||||
def get_max_score(self):
|
||||
'''
|
||||
Return maximum score for this problem.
|
||||
We do this by counting the number of answers available for each question
|
||||
in the problem. If the Response for a question has a get_max_score() method
|
||||
then we call that and add its return value to the count. That can be
|
||||
used to give complex problems (eg programming questions) multiple points.
|
||||
'''
|
||||
maxscore = 0
|
||||
for response, responder in self.responders.iteritems():
|
||||
if hasattr(responder, 'get_max_score'):
|
||||
try:
|
||||
maxscore += responder.get_max_score()
|
||||
except Exception:
|
||||
log.debug('responder %s failed to properly return from get_max_score()' % responder) # FIXME
|
||||
raise
|
||||
else:
|
||||
maxscore += len(self.responder_answers[response])
|
||||
maxscore += responder.get_max_score()
|
||||
return maxscore
|
||||
|
||||
def get_score(self):
|
||||
|
||||
@@ -71,7 +71,6 @@ class LoncapaResponse(object):
|
||||
|
||||
In addition, these methods are optional:
|
||||
|
||||
- get_max_score : if defined, this is called to obtain the maximum score possible for this question
|
||||
- setup_response : find and note the answer input field IDs for the response; called by __init__
|
||||
- check_hint_condition : check to see if the student's answers satisfy a particular condition for a hint to be displayed
|
||||
- render_html : render this Response as HTML (must return XHTML compliant string)
|
||||
@@ -130,6 +129,11 @@ class LoncapaResponse(object):
|
||||
if self.max_inputfields == 1:
|
||||
self.answer_id = self.answer_ids[0] # for convenience
|
||||
|
||||
self.maxpoints = dict()
|
||||
for inputfield in self.inputfields:
|
||||
maxpoints = inputfield.get('points','1') # By default, each answerfield is worth 1 point
|
||||
self.maxpoints.update({inputfield.get('id'): int(maxpoints)})
|
||||
|
||||
self.default_answer_map = {} # dict for default answer map (provided in input elements)
|
||||
for entry in self.inputfields:
|
||||
answer = entry.get('correct_answer')
|
||||
@@ -139,6 +143,12 @@ class LoncapaResponse(object):
|
||||
if hasattr(self, 'setup_response'):
|
||||
self.setup_response()
|
||||
|
||||
def get_max_score(self):
|
||||
'''
|
||||
Return the total maximum points of all answer fields under this Response
|
||||
'''
|
||||
return sum(self.maxpoints.values())
|
||||
|
||||
def render_html(self, renderer):
|
||||
'''
|
||||
Return XHTML Element tree representation of this Response.
|
||||
@@ -877,7 +887,10 @@ class CodeResponse(LoncapaResponse):
|
||||
(err, self.answer_id, convert_files_to_filenames(student_answers)))
|
||||
raise Exception(err)
|
||||
|
||||
self.context.update({'submission': unicode(submission)})
|
||||
if is_file(submission):
|
||||
self.context.update({'submission': submission.name})
|
||||
else:
|
||||
self.context.update({'submission': submission})
|
||||
|
||||
# Prepare xqueue request
|
||||
#------------------------------------------------------------
|
||||
@@ -924,21 +937,24 @@ class CodeResponse(LoncapaResponse):
|
||||
|
||||
def update_score(self, score_msg, oldcmap, queuekey):
|
||||
|
||||
(valid_score_msg, correct, score, msg) = self._parse_score_msg(score_msg)
|
||||
(valid_score_msg, correct, points, msg) = self._parse_score_msg(score_msg)
|
||||
if not valid_score_msg:
|
||||
oldcmap.set(self.answer_id, msg='Error: Invalid grader reply.')
|
||||
return oldcmap
|
||||
|
||||
correctness = 'incorrect'
|
||||
if correct:
|
||||
correctness = 'correct'
|
||||
correctness = 'correct' if correct else 'incorrect'
|
||||
|
||||
self.context['correct'] = correctness # TODO: Find out how this is used elsewhere, if any
|
||||
|
||||
# Replace 'oldcmap' with new grading results if queuekey matches.
|
||||
# If queuekey does not match, we keep waiting for the score_msg whose key actually matches
|
||||
if oldcmap.is_right_queuekey(self.answer_id, queuekey):
|
||||
oldcmap.set(self.answer_id, correctness=correctness, msg=msg.replace(' ', ' '), queuekey=None) # Queuekey is consumed
|
||||
# Sanity check on returned points
|
||||
if points < 0:
|
||||
points = 0
|
||||
elif points > self.maxpoints[self.answer_id]:
|
||||
points = self.maxpoints[self.answer_id]
|
||||
oldcmap.set(self.answer_id, npoints=points, correctness=correctness, msg=msg.replace(' ', ' '), queuekey=None) # Queuekey is consumed
|
||||
else:
|
||||
log.debug('CodeResponse: queuekey %s does not match for answer_id=%s.' % (queuekey, self.answer_id))
|
||||
|
||||
|
||||
@@ -464,7 +464,7 @@ class CapaModule(XModule):
|
||||
return {'success': msg}
|
||||
log.exception("Error in capa_module problem checking")
|
||||
raise Exception("error in capa_module")
|
||||
|
||||
|
||||
self.attempts = self.attempts + 1
|
||||
self.lcp.done = True
|
||||
|
||||
|
||||
@@ -323,7 +323,8 @@ class CodeResponseTest(unittest.TestCase):
|
||||
|
||||
new_cmap = CorrectMap()
|
||||
new_cmap.update(old_cmap)
|
||||
new_cmap.set(answer_id=answer_ids[i], correctness=correctness, msg='MESSAGE', queuekey=None)
|
||||
npoints = 1 if correctness=='correct' else 0
|
||||
new_cmap.set(answer_id=answer_ids[i], npoints=npoints, correctness=correctness, msg='MESSAGE', queuekey=None)
|
||||
|
||||
test_lcp.update_score(xserver_msgs[correctness], queuekey=1000 + i)
|
||||
self.assertEquals(test_lcp.correct_map.get_dict(), new_cmap.get_dict())
|
||||
|
||||
Reference in New Issue
Block a user