Commit 0ec7043e by Victor Shnayder

mostly working version of self-assessment

TODO:
* better css
* green checkmark
* reset button
* tests
parent f88cb685
...@@ -3,44 +3,88 @@ class @SelfAssessment ...@@ -3,44 +3,88 @@ class @SelfAssessment
@el = $(element).find('section.self-assessment') @el = $(element).find('section.self-assessment')
@id = @el.data('id') @id = @el.data('id')
@ajax_url = @el.data('ajax-url') @ajax_url = @el.data('ajax-url')
@state = @el.data('state')
# valid states: 'initial', 'assessing', 'request_hint', 'done'
# Where to put the rubric once we load it # Where to put the rubric once we load it
@errors_area = @$('.error')
@answer_area = @$('textarea.answer')
@rubric_wrapper = @$('.rubric-wrapper') @rubric_wrapper = @$('.rubric-wrapper')
@hint_wrapper = @$('.hint-wrapper')
@message_wrapper = @$('.message-wrapper')
@check_button = @$('.submit-button') @check_button = @$('.submit-button')
@answer_area = @$('textarea.answer')
@errors_area = @$('.error') @find_assessment_elements()
@state = 'prompt' # switches to 'eval' after answer is submitted @find_hint_elements()
@bind() @bind()
# locally scoped jquery. # locally scoped jquery.
$: (selector) -> $: (selector) ->
$(selector, @el) $(selector, @el)
bind: -> bind: () =>
@check_button.click @show_rubric # rebind to the appropriate function for the current state
@check_button.unbind('click')
if @state == 'initial'
@check_button.click @save_answer
else if @state == 'assessing'
@check_button.click @save_assessment
else if @state == 'request_hint'
@check_button.click @save_hint
else if @state == 'done'
@check_button.hide()
find_eval_elements: -> find_assessment_elements: ->
# find the elements we'll need from the newly loaded rubric data
@assessment = @$('select.assessment') @assessment = @$('select.assessment')
@hint = @$('textarea.hint')
@save_message = @$('.save_message')
show_rubric: (event) => find_hint_elements: ->
@hint_area = @$('textarea.hint')
save_answer: (event) =>
event.preventDefault() event.preventDefault()
if @state == 'prompt' if @state == 'initial'
data = {'student_answer' : @answer_area.val()} data = {'student_answer' : @answer_area.val()}
$.postWithPrefix "#{@ajax_url}/show", data, (response) => $.postWithPrefix "#{@ajax_url}/save_answer", data, (response) =>
if response.success if response.success
@rubric_wrapper.html(response.rubric) @rubric_wrapper.html(response.rubric_html)
@state = 'eval' @state = 'assessing'
@find_eval_elements() @find_assessment_elements()
@bind()
else else
@errors_area.html(response.message) @errors_area.html(response.message)
else else
data = {'assessment' : @assessment.find(':selected').text(), 'hint' : @hint.val()} @errors_area.html('Problem state got out of sync. Try reloading the page.')
$.postWithPrefix "#{@ajax_url}/save", data, (response) => save_assessment: (event) =>
event.preventDefault()
if @state == 'assessing'
data = {'assessment' : @assessment.find(':selected').text()}
$.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) =>
if response.success if response.success
@rubric_wrapper.html(response.message) @hint_wrapper.html(response.hint_html)
@state = 'request_hint'
@find_hint_elements()
@bind()
else else
@errors_area.html('There was an error saving your response.') @errors_area.html(response.message)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
save_hint: (event) =>
event.preventDefault()
if @state == 'request_hint'
data = {'hint' : @hint_area.val()}
$.postWithPrefix "#{@ajax_url}/save_hint", data, (response) =>
if response.success
@message_wrapper.html(response.message_html)
@state = 'done'
@bind()
else
@errors_area.html(response.message)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
...@@ -61,10 +61,6 @@ class SelfAssessmentModule(XModule): ...@@ -61,10 +61,6 @@ class SelfAssessmentModule(XModule):
js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]} js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]}
js_module_name = "SelfAssessment" js_module_name = "SelfAssessment"
def get_html(self):
# cdodge: perform link substitutions for any references to course static content (e.g. images)
return rewrite_links(self.html, self.rewrite_content_links)
def __init__(self, system, location, definition, descriptor, def __init__(self, system, location, definition, descriptor,
instance_state=None, shared_state=None, **kwargs): instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, descriptor, XModule.__init__(self, system, location, definition, descriptor,
...@@ -100,9 +96,8 @@ class SelfAssessmentModule(XModule): ...@@ -100,9 +96,8 @@ class SelfAssessmentModule(XModule):
else: else:
instance_state = {} instance_state = {}
# Note: assessment responses are 'incorrect'/'correct'
self.student_answers = instance_state.get('student_answers', []) self.student_answers = instance_state.get('student_answers', [])
# assessment responses are 'incorrect'/'correct'
self.assessment = instance_state.get('assessment', []) self.assessment = instance_state.get('assessment', [])
self.hints = instance_state.get('hints', []) self.hints = instance_state.get('hints', [])
...@@ -111,7 +106,7 @@ class SelfAssessmentModule(XModule): ...@@ -111,7 +106,7 @@ class SelfAssessmentModule(XModule):
# 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.score = instance_state.get('score', 0)
self.max_score = instance_state.get('max_score', MAX_SCORE) self._max_score = instance_state.get('max_score', MAX_SCORE)
self.attempts = instance_state.get('attempts', 0) self.attempts = instance_state.get('attempts', 0)
...@@ -122,18 +117,23 @@ class SelfAssessmentModule(XModule): ...@@ -122,18 +117,23 @@ class SelfAssessmentModule(XModule):
self.submit_message = definition['submitmessage'] self.submit_message = definition['submitmessage']
self.hint_prompt = definition['hintprompt'] self.hint_prompt = definition['hintprompt']
def get_html(self):
#set context variables and render template
previous_answer = self.student_answers[-1] if self.student_answers else '' previous_answer = self.student_answers[-1] if self.student_answers else ''
#set context variables and render template
context = { context = {
'prompt' : self.prompt, 'prompt': self.prompt,
'previous_answer' : previous_answer, 'previous_answer': previous_answer,
'ajax_url' : system.ajax_url, 'ajax_url': self.system.ajax_url,
'initial_rubric' : self.get_rubric_html(), 'initial_rubric': self.get_rubric_html(),
'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,
} }
self.html = self.system.render_template('self_assessment_prompt.html', context) html = self.system.render_template('self_assessment_prompt.html', context)
# cdodge: perform link substitutions for any references to course static content (e.g. images)
return rewrite_links(html, self.rewrite_content_links)
def get_score(self): def get_score(self):
""" """
...@@ -145,15 +145,15 @@ class SelfAssessmentModule(XModule): ...@@ -145,15 +145,15 @@ class SelfAssessmentModule(XModule):
""" """
Return max_score Return max_score
""" """
return self.max_score return self._max_score
def get_progress(self): def get_progress(self):
''' '''
For now, just return score / max_score For now, just return 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.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
...@@ -167,7 +167,7 @@ class SelfAssessmentModule(XModule): ...@@ -167,7 +167,7 @@ class SelfAssessmentModule(XModule):
Returns a json dictionary: Returns a json dictionary:
{ 'progress_changed' : True/False, { 'progress_changed' : True/False,
'progress' : 'none'/'in_progress'/'done', 'progress': 'none'/'in_progress'/'done',
<other request-specific values here > } <other request-specific values here > }
""" """
...@@ -207,7 +207,7 @@ class SelfAssessmentModule(XModule): ...@@ -207,7 +207,7 @@ class SelfAssessmentModule(XModule):
return '' return ''
# we'll render it # we'll render it
context = {'rubric' : self.rubric} context = {'rubric': self.rubric}
if self.state == self.ASSESSING: if self.state == self.ASSESSING:
context['read_only'] = False context['read_only'] = False
...@@ -226,8 +226,9 @@ class SelfAssessmentModule(XModule): ...@@ -226,8 +226,9 @@ class SelfAssessmentModule(XModule):
return '' return ''
# else we'll render it # else we'll render it
hint = self.hints[-1] if len(self.hints) > 0 else ''
context = {'hint_prompt': self.hint_prompt, context = {'hint_prompt': self.hint_prompt,
'hint': self.hint} 'hint': hint}
if self.state == self.REQUEST_HINT: if self.state == self.REQUEST_HINT:
context['read_only'] = False context['read_only'] = False
...@@ -245,7 +246,7 @@ class SelfAssessmentModule(XModule): ...@@ -245,7 +246,7 @@ class SelfAssessmentModule(XModule):
if self.state != self.DONE: if self.state != self.DONE:
return "" return ""
return """<div class="save_message">{0}</div>""".format(self.message) return """<div class="save_message">{0}</div>""".format(self.submit_message)
def save_answer(self, get): def save_answer(self, get):
...@@ -253,7 +254,7 @@ class SelfAssessmentModule(XModule): ...@@ -253,7 +254,7 @@ class SelfAssessmentModule(XModule):
After the answer is submitted, show the rubric. After the answer is submitted, show the rubric.
""" """
# Check to see if attempts are less than max # Check to see if attempts are less than max
if self.attempts < self.max_attempts: if self.attempts > self.max_attempts:
# If too many attempts, prevent student from saving answer and # If too many attempts, prevent student from saving answer and
# seeing rubric. In normal use, students shouldn't see this because # seeing rubric. In normal use, students shouldn't see this because
# 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.
...@@ -265,23 +266,23 @@ class SelfAssessmentModule(XModule): ...@@ -265,23 +266,23 @@ class SelfAssessmentModule(XModule):
if self.state != self.INITIAL: if self.state != self.INITIAL:
return self.out_of_sync_error(get) return self.out_of_sync_error(get)
self.student_answers.append = get['student_answer'] self.student_answers.append(get['student_answer'])
self.state = self.ASSESSING self.state = self.ASSESSING
return { return {
'success': True, 'success': True,
'rubric': self.get_rubric_html() 'rubric_html': self.get_rubric_html()
} }
def save_assessment(self, get): def save_assessment(self, get):
""" """
Save the assessment. Save the assessment.
Returns a dict { 'success' : bool, 'hint_html': hint_html 'error' : error-msg}, Returns a dict { 'success': bool, 'hint_html': hint_html 'error': error-msg},
with 'error' only present if 'success' is False, and 'hint_html' only if success is true with 'error' only present if 'success' is False, and 'hint_html' only if success is true
""" """
if (self.state != self.ASSESSMENT or if (self.state != self.ASSESSING or
len(self.student_answers) != len(self.assessment) + 1): len(self.student_answers) != len(self.assessment) + 1):
return self.out_of_sync_error(get) return self.out_of_sync_error(get)
...@@ -294,9 +295,9 @@ class SelfAssessmentModule(XModule): ...@@ -294,9 +295,9 @@ class SelfAssessmentModule(XModule):
def save_hint(self, get): def save_hint(self, get):
''' '''
Save the hint. Save the hint.
Returns a dict { 'success' : bool, Returns a dict { 'success': bool,
'message_html': message_html, 'message_html': message_html,
'error' : error-msg}, 'error': error-msg},
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.
''' '''
...@@ -313,13 +314,14 @@ class SelfAssessmentModule(XModule): ...@@ -313,13 +314,14 @@ class SelfAssessmentModule(XModule):
# To the tracking logs! # To the tracking logs!
event_info = { event_info = {
'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, 'assessment': self.assessment,
'hints' : self.hints, 'hints': self.hints,
'score': points, 'score': points,
'done': self.done,}} }
}
self.system.track_function('save_hint', event_info) self.system.track_function('save_hint', event_info)
return {'success': True, return {'success': True,
...@@ -335,7 +337,7 @@ class SelfAssessmentModule(XModule): ...@@ -335,7 +337,7 @@ class SelfAssessmentModule(XModule):
""" """
if self.state != DONE: if self.state != DONE:
return self.out_of_sync_error(get) return self.out_of_sync_error(get)
if self.attempts < self.max_attempts: if self.attempts > self.max_attempts:
return { return {
'success': False, 'success': False,
'error': 'Too many attempts.' 'error': 'Too many attempts.'
...@@ -353,12 +355,12 @@ class SelfAssessmentModule(XModule): ...@@ -353,12 +355,12 @@ class SelfAssessmentModule(XModule):
state = { state = {
'student_answers': self.student_answers, 'student_answers': self.student_answers,
'temp_answer': self.temp_answer,
'hints' : self.hints,
'assessment': self.assessment, 'assessment': self.assessment,
'hints': self.hints,
'state': self.state,
'score': points, 'score': points,
'max_score' : MAX_SCORE, 'max_score': self._max_score,
'attempts' : self.attempts 'attempts': self.attempts
} }
return json.dumps(state) return json.dumps(state)
...@@ -385,10 +387,10 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): ...@@ -385,10 +387,10 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
Returns: Returns:
{ {
'rubric' : 'some-html', 'rubric': 'some-html',
'prompt' : 'some-html', 'prompt': 'some-html',
'submitmessage' : 'some-html' 'submitmessage': 'some-html'
'hintprompt' : 'some-html' 'hintprompt': 'some-html'
} }
""" """
expected_children = ['rubric', 'prompt', 'submitmessage', 'hintprompt'] expected_children = ['rubric', 'prompt', 'submitmessage', 'hintprompt']
...@@ -400,10 +402,10 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): ...@@ -400,10 +402,10 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
"""Assumes that xml_object has child k""" """Assumes that xml_object has child k"""
return stringify_children(xml_object.xpath(k)[0]) return stringify_children(xml_object.xpath(k)[0])
return {'rubric' : parse('rubric'), return {'rubric': parse('rubric'),
'prompt' : parse('prompt'), 'prompt': parse('prompt'),
'submitmessage' : parse('submitmessage'), 'submitmessage': parse('submitmessage'),
'hintprompt' : parse('hintprompt'), 'hintprompt': parse('hintprompt'),
} }
......
<section id="self_assessment_${id}" class="self-assessment" data-ajax-url="${ajax_url}" data-id="${id}"> <section id="self_assessment_${id}" class="self-assessment" data-ajax-url="${ajax_url}"
data-id="${id}" data-state="${state}">
<div class="error"></div> <div class="error"></div>
<div class="prompt"> <div class="prompt">
${prompt} ${prompt}
......
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