Commit d8533dc9 by Victor Shnayder

Merge pull request #1038 from MITx/vik/add_self_assessment

Vik/add self assessment
parents a8688fb2 e65cb908
...@@ -21,7 +21,6 @@ class @SelfAssessment ...@@ -21,7 +21,6 @@ class @SelfAssessment
@find_assessment_elements() @find_assessment_elements()
@find_hint_elements() @find_hint_elements()
@rebind() @rebind()
# locally scoped jquery. # locally scoped jquery.
...@@ -33,16 +32,22 @@ class @SelfAssessment ...@@ -33,16 +32,22 @@ class @SelfAssessment
@submit_button.unbind('click') @submit_button.unbind('click')
@submit_button.show() @submit_button.show()
@reset_button.hide() @reset_button.hide()
@hint_area.attr('disabled', false)
if @state == 'initial' if @state == 'initial'
@answer_area.attr("disabled", false)
@submit_button.prop('value', 'Submit') @submit_button.prop('value', 'Submit')
@submit_button.click @save_answer @submit_button.click @save_answer
else if @state == 'assessing' else if @state == 'assessing'
@answer_area.attr("disabled", true)
@submit_button.prop('value', 'Submit assessment') @submit_button.prop('value', 'Submit assessment')
@submit_button.click @save_assessment @submit_button.click @save_assessment
else if @state == 'request_hint' else if @state == 'request_hint'
@answer_area.attr("disabled", true)
@submit_button.prop('value', 'Submit hint') @submit_button.prop('value', 'Submit hint')
@submit_button.click @save_hint @submit_button.click @save_hint
else if @state == 'done' else if @state == 'done'
@answer_area.attr("disabled", true)
@hint_area.attr('disabled', true)
@submit_button.hide() @submit_button.hide()
if @allow_reset if @allow_reset
@reset_button.show() @reset_button.show()
...@@ -67,7 +72,7 @@ class @SelfAssessment ...@@ -67,7 +72,7 @@ class @SelfAssessment
@find_assessment_elements() @find_assessment_elements()
@rebind() @rebind()
else else
@errors_area.html(response.message) @errors_area.html(response.error)
else else
@errors_area.html('Problem state got out of sync. Try reloading the page.') @errors_area.html('Problem state got out of sync. Try reloading the page.')
...@@ -77,12 +82,18 @@ class @SelfAssessment ...@@ -77,12 +82,18 @@ class @SelfAssessment
data = {'assessment' : @assessment.find(':selected').text()} data = {'assessment' : @assessment.find(':selected').text()}
$.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) => $.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) =>
if response.success if response.success
@state = response.state
if @state == 'request_hint'
@hint_wrapper.html(response.hint_html) @hint_wrapper.html(response.hint_html)
@state = 'request_hint'
@find_hint_elements() @find_hint_elements()
else if @state == 'done'
@message_wrapper.html(response.message_html)
@allow_reset = response.allow_reset
@rebind() @rebind()
else else
@errors_area.html(response.message) @errors_area.html(response.error)
else else
@errors_area.html('Problem state got out of sync. Try reloading the page.') @errors_area.html('Problem state got out of sync. Try reloading the page.')
...@@ -99,7 +110,7 @@ class @SelfAssessment ...@@ -99,7 +110,7 @@ class @SelfAssessment
@allow_reset = response.allow_reset @allow_reset = response.allow_reset
@rebind() @rebind()
else else
@errors_area.html(response.message) @errors_area.html(response.error)
else else
@errors_area.html('Problem state got out of sync. Try reloading the page.') @errors_area.html('Problem state got out of sync. Try reloading the page.')
...@@ -109,6 +120,7 @@ class @SelfAssessment ...@@ -109,6 +120,7 @@ class @SelfAssessment
if @state == 'done' if @state == 'done'
$.postWithPrefix "#{@ajax_url}/reset", {}, (response) => $.postWithPrefix "#{@ajax_url}/reset", {}, (response) =>
if response.success if response.success
@answer_area.html('')
@rubric_wrapper.html('') @rubric_wrapper.html('')
@hint_wrapper.html('') @hint_wrapper.html('')
@message_wrapper.html('') @message_wrapper.html('')
...@@ -116,6 +128,6 @@ class @SelfAssessment ...@@ -116,6 +128,6 @@ class @SelfAssessment
@rebind() @rebind()
@reset_button.hide() @reset_button.hide()
else else
@errors_area.html(response.message) @errors_area.html(response.error)
else else
@errors_area.html('Problem state got out of sync. Try reloading the page.') @errors_area.html('Problem state got out of sync. Try reloading the page.')
...@@ -33,8 +33,8 @@ log = logging.getLogger("mitx.courseware") ...@@ -33,8 +33,8 @@ log = logging.getLogger("mitx.courseware")
# attempts specified in xml definition overrides this. # attempts specified in xml definition overrides this.
MAX_ATTEMPTS = 1 MAX_ATTEMPTS = 1
# Set maximum available number of points. Should be set to 1 for now due to assessment handling, # Set maximum available number of points.
# which only allows for correct/incorrect. # Overriden by max_score specified in xml.
MAX_SCORE = 1 MAX_SCORE = 1
class SelfAssessmentModule(XModule): class SelfAssessmentModule(XModule):
...@@ -68,13 +68,19 @@ class SelfAssessmentModule(XModule): ...@@ -68,13 +68,19 @@ class SelfAssessmentModule(XModule):
""" """
Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt, Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt,
and one optional attribute, attempts, which should be an integer that defaults to 1. and two optional attributes:
attempts, which should be an integer that defaults to 1.
If it's > 1, the student will be able to re-submit after they see If it's > 1, the student will be able to re-submit after they see
the rubric. Note: all the submissions are stored. the rubric.
max_score, which should be an integer that defaults to 1.
It defines the maximum number of points a student can get. Assumed to be integer scale
from 0 to max_score, with an interval of 1.
Note: all the submissions are stored.
Sample file: Sample file:
<selfassessment attempts="1"> <selfassessment attempts="1" max_score="1">
<prompt> <prompt>
Insert prompt text here. (arbitrary html) Insert prompt text here. (arbitrary html)
</prompt> </prompt>
...@@ -96,17 +102,17 @@ class SelfAssessmentModule(XModule): ...@@ -96,17 +102,17 @@ class SelfAssessmentModule(XModule):
else: else:
instance_state = {} instance_state = {}
# Note: assessment responses are 'incorrect'/'correct' # Note: score responses are on scale from 0 to max_score
self.student_answers = instance_state.get('student_answers', []) self.student_answers = instance_state.get('student_answers', [])
self.assessment = instance_state.get('assessment', []) self.scores = instance_state.get('scores', [])
self.hints = instance_state.get('hints', []) self.hints = instance_state.get('hints', [])
self.state = instance_state.get('state', 'initial') self.state = instance_state.get('state', 'initial')
# Used for progress / grading. Currently get credit just for # Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect). # completion (doesn't matter if you self-assessed correct/incorrect).
self.score = instance_state.get('score', 0)
self._max_score = instance_state.get('max_score', MAX_SCORE) self._max_score = int(self.metadata.get('max_score', MAX_SCORE))
self.attempts = instance_state.get('attempts', 0) self.attempts = instance_state.get('attempts', 0)
...@@ -117,11 +123,17 @@ class SelfAssessmentModule(XModule): ...@@ -117,11 +123,17 @@ class SelfAssessmentModule(XModule):
self.submit_message = definition['submitmessage'] self.submit_message = definition['submitmessage']
self.hint_prompt = definition['hintprompt'] self.hint_prompt = definition['hintprompt']
def _allow_reset(self):
"""Can the module be reset?"""
return self.state == self.DONE and self.attempts < self.max_attempts
def get_html(self): def get_html(self):
#set context variables and render template #set context variables and render template
previous_answer = self.student_answers[-1] if self.student_answers else '' if self.state != self.INITIAL and self.student_answers:
previous_answer = self.student_answers[-1]
else:
previous_answer = ''
allow_reset = self.state == self.DONE and self.attempts < self.max_attempts
context = { context = {
'prompt': self.prompt, 'prompt': self.prompt,
'previous_answer': previous_answer, 'previous_answer': previous_answer,
...@@ -130,7 +142,7 @@ class SelfAssessmentModule(XModule): ...@@ -130,7 +142,7 @@ class SelfAssessmentModule(XModule):
'initial_hint': self.get_hint_html(), 'initial_hint': self.get_hint_html(),
'initial_message': self.get_message_html(), 'initial_message': self.get_message_html(),
'state': self.state, 'state': self.state,
'allow_reset': allow_reset, 'allow_reset': self._allow_reset(),
} }
html = self.system.render_template('self_assessment_prompt.html', context) html = self.system.render_template('self_assessment_prompt.html', context)
...@@ -141,7 +153,7 @@ class SelfAssessmentModule(XModule): ...@@ -141,7 +153,7 @@ class SelfAssessmentModule(XModule):
""" """
Returns dict with 'score' key Returns dict with 'score' key
""" """
return {'score': self.score} return {'score': self.get_last_score()}
def max_score(self): def max_score(self):
""" """
...@@ -149,13 +161,22 @@ class SelfAssessmentModule(XModule): ...@@ -149,13 +161,22 @@ class SelfAssessmentModule(XModule):
""" """
return self._max_score return self._max_score
def get_last_score(self):
"""
Returns the last score in the list
"""
last_score=0
if(len(self.scores)>0):
last_score=self.scores[len(self.scores)-1]
return last_score
def get_progress(self): def get_progress(self):
''' '''
For now, just return score / max_score For now, just return last score / max_score
''' '''
if self._max_score > 0: if self._max_score > 0:
try: try:
return Progress(self.score, self._max_score) return Progress(self.get_last_score(), self._max_score)
except Exception as err: except Exception as err:
log.exception("Got bad progress") log.exception("Got bad progress")
return None return None
...@@ -192,12 +213,12 @@ class SelfAssessmentModule(XModule): ...@@ -192,12 +213,12 @@ class SelfAssessmentModule(XModule):
}) })
return json.dumps(d, cls=ComplexEncoder) return json.dumps(d, cls=ComplexEncoder)
def out_of_sync_error(self, get): def out_of_sync_error(self, get, msg=''):
""" """
return dict out-of-sync error message, and also log. return dict out-of-sync error message, and also log.
""" """
log.warning("Assessment module state out sync. state: %r, get: %r", log.warning("Assessment module state out sync. state: %r, get: %r. %s",
self.state, get) self.state, get, msg)
return {'success': False, return {'success': False,
'error': 'The problem state got out-of-sync'} 'error': 'The problem state got out-of-sync'}
...@@ -209,7 +230,9 @@ class SelfAssessmentModule(XModule): ...@@ -209,7 +230,9 @@ class SelfAssessmentModule(XModule):
return '' return ''
# we'll render it # we'll render it
context = {'rubric': self.rubric} context = {'rubric': self.rubric,
'max_score' : self._max_score,
}
if self.state == self.ASSESSING: if self.state == self.ASSESSING:
context['read_only'] = False context['read_only'] = False
...@@ -227,8 +250,12 @@ class SelfAssessmentModule(XModule): ...@@ -227,8 +250,12 @@ class SelfAssessmentModule(XModule):
if self.state in (self.INITIAL, self.ASSESSING): if self.state in (self.INITIAL, self.ASSESSING):
return '' return ''
# else we'll render it if self.state == self.DONE and len(self.hints) > 0:
hint = self.hints[-1] if len(self.hints) > 0 else '' # display the previous hint
hint = self.hints[-1]
else:
hint = ''
context = {'hint_prompt': self.hint_prompt, context = {'hint_prompt': self.hint_prompt,
'hint': hint} 'hint': hint}
...@@ -262,7 +289,7 @@ class SelfAssessmentModule(XModule): ...@@ -262,7 +289,7 @@ class SelfAssessmentModule(XModule):
# they won't see the reset button once they're out of attempts. # they won't see the reset button once they're out of attempts.
return { return {
'success': False, 'success': False,
'message': 'Too many attempts.' 'error': 'Too many attempts.'
} }
if self.state != self.INITIAL: if self.state != self.INITIAL:
...@@ -278,21 +305,45 @@ class SelfAssessmentModule(XModule): ...@@ -278,21 +305,45 @@ class SelfAssessmentModule(XModule):
def save_assessment(self, get): def save_assessment(self, get):
""" """
Save the assessment. Save the assessment. If the student said they're right, don't ask for a
hint, and go straight to the done state. Otherwise, do ask for a hint.
Returns a dict { 'success': bool, 'hint_html': hint_html 'error': error-msg}, Returns a dict { 'success': bool, 'state': state,
with 'error' only present if 'success' is False, and 'hint_html' only if success is true
'hint_html': hint_html OR 'message_html': html and 'allow_reset',
'error': error-msg},
with 'error' only present if 'success' is False, and 'hint_html' or
'message_html' only if success is true
""" """
if (self.state != self.ASSESSING or n_answers = len(self.student_answers)
len(self.student_answers) != len(self.assessment) + 1): n_scores = len(self.scores)
return self.out_of_sync_error(get) if (self.state != self.ASSESSING or n_answers != n_scores + 1):
msg = "%d answers, %d scores" % (n_answers, n_scores)
return self.out_of_sync_error(get, msg)
try:
score = int(get['assessment'])
except:
return {'success': False, 'error': "Non-integer score value"}
self.assessment.append(get['assessment'].lower()) self.scores.append(score)
d = {'success': True,}
if score == self.max_score():
self.state = self.DONE
d['message_html'] = self.get_message_html()
d['allow_reset'] = self._allow_reset()
else:
self.state = self.REQUEST_HINT self.state = self.REQUEST_HINT
d['hint_html'] = self.get_hint_html()
d['state'] = self.state
return d
# TODO: return different hint based on assessment value...
return {'success': True, 'hint_html': self.get_hint_html()}
def save_hint(self, get): def save_hint(self, get):
''' '''
...@@ -304,14 +355,14 @@ class SelfAssessmentModule(XModule): ...@@ -304,14 +355,14 @@ class SelfAssessmentModule(XModule):
with the error key only present if success is False and message_html with the error key only present if success is False and message_html
only if True. only if True.
''' '''
if self.state != self.REQUEST_HINT or len(self.assessment) != len(self.hints) + 1: if self.state != self.REQUEST_HINT:
# Note: because we only ask for hints on wrong answers, may not have
# the same number of hints and answers.
return self.out_of_sync_error(get) return self.out_of_sync_error(get)
self.hints.append(get['hint'].lower()) self.hints.append(get['hint'].lower())
self.state = self.DONE self.state = self.DONE
# Points are assigned for completion, so always set to 1
points = 1
# increment attempts # increment attempts
self.attempts = self.attempts + 1 self.attempts = self.attempts + 1
...@@ -320,16 +371,15 @@ class SelfAssessmentModule(XModule): ...@@ -320,16 +371,15 @@ class SelfAssessmentModule(XModule):
'selfassessment_id': self.location.url(), 'selfassessment_id': self.location.url(),
'state': { 'state': {
'student_answers': self.student_answers, 'student_answers': self.student_answers,
'assessment': self.assessment, 'score': self.scores,
'hints': self.hints, 'hints': self.hints,
'score': points,
} }
} }
self.system.track_function('save_hint', event_info) self.system.track_function('save_hint', event_info)
return {'success': True, return {'success': True,
'message_html': self.get_message_html(), 'message_html': self.get_message_html(),
'allow_reset': self.attempts < self.max_attempts} 'allow_reset': self._allow_reset()}
def reset(self, get): def reset(self, get):
...@@ -353,17 +403,14 @@ class SelfAssessmentModule(XModule): ...@@ -353,17 +403,14 @@ class SelfAssessmentModule(XModule):
def get_instance_state(self): def get_instance_state(self):
""" """
Get the current assessment, points, and state Get the current score and state
""" """
#Assign points based on completion. May want to change to assessment-based down the road.
points = 1
state = { state = {
'student_answers': self.student_answers, 'student_answers': self.student_answers,
'assessment': self.assessment,
'hints': self.hints, 'hints': self.hints,
'state': self.state, 'state': self.state,
'score': points, 'scores': self.scores,
'max_score': self._max_score, 'max_score': self._max_score,
'attempts': self.attempts 'attempts': self.attempts
} }
......
...@@ -262,5 +262,9 @@ section.self-assessment { ...@@ -262,5 +262,9 @@ section.self-assessment {
margin-bottom: 5px; margin-bottom: 5px;
} }
.error {
font-size: 14px;
font-weight: bold;
}
} }
...@@ -6,8 +6,9 @@ ...@@ -6,8 +6,9 @@
% if not read_only: % if not read_only:
<select name="assessment" class="assessment"> <select name="assessment" class="assessment">
<option value="incorrect">Incorrect</option> %for i in xrange(0,max_score+1):
<option value="correct">Correct</option> <option value="${i}">${i}</option>
%endfor
</select> </select>
% endif % endif
......
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