Commit ae65bb03 by Diana Huang

Merge branch 'master' into feature/diana/rubric-input

Conflicts:
	common/lib/capa/capa/inputtypes.py
parents cc409580 b067b0bf
...@@ -186,24 +186,6 @@ class LoncapaProblem(object): ...@@ -186,24 +186,6 @@ class LoncapaProblem(object):
maxscore += responder.get_max_score() maxscore += responder.get_max_score()
return maxscore return maxscore
def message_post(self,event_info):
"""
Handle an ajax post that contains feedback on feedback
Returns a boolean success variable
Note: This only allows for feedback to be posted back to the grading controller for the first
open ended response problem on each page. Multiple problems will cause some sync issues.
TODO: Handle multiple problems on one page sync issues.
"""
success=False
message = "Could not find a valid responder."
log.debug("in lcp")
for responder in self.responders.values():
if hasattr(responder, 'handle_message_post'):
success, message = responder.handle_message_post(event_info)
if success:
break
return success, message
def get_score(self): def get_score(self):
""" """
Compute score for this problem. The score is the number of points awarded. Compute score for this problem. The score is the number of points awarded.
......
...@@ -736,54 +736,6 @@ registry.register(ChemicalEquationInput) ...@@ -736,54 +736,6 @@ registry.register(ChemicalEquationInput)
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
class OpenEndedInput(InputTypeBase):
"""
A text area input for code--uses codemirror, does syntax highlighting, special tab handling,
etc.
"""
template = "openendedinput.html"
tags = ['openendedinput']
# pulled out for testing
submitted_msg = ("Feedback not yet available. Reload to check again. "
"Once the problem is graded, this message will be "
"replaced with the grader's feedback.")
@classmethod
def get_attributes(cls):
"""
Convert options to a convenient format.
"""
return [Attribute('rows', '30'),
Attribute('cols', '80'),
Attribute('hidden', ''),
]
def setup(self):
"""
Implement special logic: handle queueing state, and default input.
"""
# if no student input yet, then use the default input given by the problem
if not self.value:
self.value = self.xml.text
# Check if problem has been queued
self.queue_len = 0
# Flag indicating that the problem has been queued, 'msg' is length of queue
if self.status == 'incomplete':
self.status = 'queued'
self.queue_len = self.msg
self.msg = self.submitted_msg
def _extra_context(self):
"""Defined queue_len, add it """
return {'queue_len': self.queue_len,}
registry.register(OpenEndedInput)
#-----------------------------------------------------------------------------
class RubricInput(InputTypeBase): class RubricInput(InputTypeBase):
""" """
This is the logic for parsing and displaying a rubric of type This is the logic for parsing and displaying a rubric of type
......
...@@ -19,6 +19,7 @@ setup( ...@@ -19,6 +19,7 @@ setup(
"abtest = xmodule.abtest_module:ABTestDescriptor", "abtest = xmodule.abtest_module:ABTestDescriptor",
"book = xmodule.backcompat_module:TranslateCustomTagDescriptor", "book = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"chapter = xmodule.seq_module:SequenceDescriptor", "chapter = xmodule.seq_module:SequenceDescriptor",
"combinedopenended = xmodule.combined_open_ended_module:CombinedOpenEndedDescriptor",
"course = xmodule.course_module:CourseDescriptor", "course = xmodule.course_module:CourseDescriptor",
"customtag = xmodule.template_module:CustomTagDescriptor", "customtag = xmodule.template_module:CustomTagDescriptor",
"discuss = xmodule.backcompat_module:TranslateCustomTagDescriptor", "discuss = xmodule.backcompat_module:TranslateCustomTagDescriptor",
...@@ -28,7 +29,6 @@ setup( ...@@ -28,7 +29,6 @@ setup(
"problem = xmodule.capa_module:CapaDescriptor", "problem = xmodule.capa_module:CapaDescriptor",
"problemset = xmodule.seq_module:SequenceDescriptor", "problemset = xmodule.seq_module:SequenceDescriptor",
"section = xmodule.backcompat_module:SemanticSectionDescriptor", "section = xmodule.backcompat_module:SemanticSectionDescriptor",
"selfassessment = xmodule.self_assessment_module:SelfAssessmentDescriptor",
"sequential = xmodule.seq_module:SequenceDescriptor", "sequential = xmodule.seq_module:SequenceDescriptor",
"slides = xmodule.backcompat_module:TranslateCustomTagDescriptor", "slides = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"vertical = xmodule.vertical_module:VerticalDescriptor", "vertical = xmodule.vertical_module:VerticalDescriptor",
......
...@@ -380,7 +380,6 @@ class CapaModule(XModule): ...@@ -380,7 +380,6 @@ class CapaModule(XModule):
'problem_save': self.save_problem, 'problem_save': self.save_problem,
'problem_show': self.get_answer, 'problem_show': self.get_answer,
'score_update': self.update_score, 'score_update': self.update_score,
'message_post' : self.message_post,
} }
if dispatch not in handlers: if dispatch not in handlers:
...@@ -395,20 +394,6 @@ class CapaModule(XModule): ...@@ -395,20 +394,6 @@ class CapaModule(XModule):
}) })
return json.dumps(d, cls=ComplexEncoder) return json.dumps(d, cls=ComplexEncoder)
def message_post(self, get):
"""
Posts a message from a form to an appropriate location
"""
event_info = dict()
event_info['state'] = self.lcp.get_state()
event_info['problem_id'] = self.location.url()
event_info['student_id'] = self.system.anonymous_student_id
event_info['survey_responses']= get
success, message = self.lcp.message_post(event_info)
return {'success' : success, 'message' : message}
def closed(self): def closed(self):
''' Is the student still allowed to submit answers? ''' ''' Is the student still allowed to submit answers? '''
if self.attempts == self.max_attempts: if self.attempts == self.max_attempts:
...@@ -445,6 +430,7 @@ class CapaModule(XModule): ...@@ -445,6 +430,7 @@ class CapaModule(XModule):
return False return False
def update_score(self, get): def update_score(self, get):
""" """
Delivers grading response (e.g. from asynchronous code checking) to Delivers grading response (e.g. from asynchronous code checking) to
......
from mitxmako.shortcuts import render_to_string
import logging
from lxml import etree
log=logging.getLogger(__name__)
class CombinedOpenEndedRubric:
@staticmethod
def render_rubric(rubric_xml):
try:
rubric_categories = CombinedOpenEndedRubric.extract_rubric_categories(rubric_xml)
html = render_to_string('open_ended_rubric.html', {'rubric_categories' : rubric_categories})
except:
log.exception("Could not parse the rubric.")
html = rubric_xml
return html
@staticmethod
def extract_rubric_categories(element):
'''
Contstruct a list of categories such that the structure looks like:
[ { category: "Category 1 Name",
options: [{text: "Option 1 Name", points: 0}, {text:"Option 2 Name", points: 5}]
},
{ category: "Category 2 Name",
options: [{text: "Option 1 Name", points: 0},
{text: "Option 2 Name", points: 1},
{text: "Option 3 Name", points: 2]}]
'''
element = etree.fromstring(element)
categories = []
for category in element:
if category.tag != 'category':
raise Exception("[capa.inputtypes.extract_categories] Expected a <category> tag: got {0} instead".format(category.tag))
else:
categories.append(CombinedOpenEndedRubric.extract_category(category))
return categories
@staticmethod
def extract_category(category):
'''
construct an individual category
{category: "Category 1 Name",
options: [{text: "Option 1 text", points: 1},
{text: "Option 2 text", points: 2}]}
all sorting and auto-point generation occurs in this function
'''
has_score=False
descriptionxml = category[0]
scorexml = category[1]
if scorexml.tag == "option":
optionsxml = category[1:]
else:
optionsxml = category[2:]
has_score=True
# parse description
if descriptionxml.tag != 'description':
raise Exception("[extract_category]: expected description tag, got {0} instead".format(descriptionxml.tag))
if has_score:
if scorexml.tag != 'score':
raise Exception("[extract_category]: expected score tag, got {0} instead".format(scorexml.tag))
for option in optionsxml:
if option.tag != "option":
raise Exception("[extract_category]: expected option tag, got {0} instead".format(option.tag))
description = descriptionxml.text
if has_score:
score = int(scorexml.text)
else:
score = 0
cur_points = 0
options = []
autonumbering = True
# parse options
for option in optionsxml:
if option.tag != 'option':
raise Exception("[extract_category]: expected option tag, got {0} instead".format(option.tag))
else:
pointstr = option.get("points")
if pointstr:
autonumbering = False
# try to parse this into an int
try:
points = int(pointstr)
except ValueError:
raise Exception("[extract_category]: expected points to have int, got {0} instead".format(pointstr))
elif autonumbering:
# use the generated one if we're in the right mode
points = cur_points
cur_points = cur_points + 1
else:
raise Exception("[extract_category]: missing points attribute. Cannot continue to auto-create points values after a points value is explicitly dfined.")
optiontext = option.text
selected = False
if has_score:
if points == score:
selected = True
options.append({'text': option.text, 'points': points, 'selected' : selected})
# sort and check for duplicates
options = sorted(options, key=lambda option: option['points'])
CombinedOpenEndedRubric.validate_options(options)
return {'description': description, 'options': options, 'score' : score, 'has_score' : has_score}
@staticmethod
def validate_options(options):
'''
Validates a set of options. This can and should be extended to filter out other bad edge cases
'''
if len(options) == 0:
raise Exception("[extract_category]: no options associated with this category")
if len(options) == 1:
return
prev = options[0]['points']
for option in options[1:]:
if prev == option['points']:
raise Exception("[extract_category]: found duplicate point values between two different options")
else:
prev = option['points']
\ No newline at end of file
...@@ -25,7 +25,6 @@ class @Problem ...@@ -25,7 +25,6 @@ class @Problem
@$('section.action input.reset').click @reset @$('section.action input.reset').click @reset
@$('section.action input.show').click @show @$('section.action input.show').click @show
@$('section.action input.save').click @save @$('section.action input.save').click @save
@$('section.evaluation input.submit-message').click @message_post
# Collapsibles # Collapsibles
Collapsible.setCollapsibles(@el) Collapsible.setCollapsibles(@el)
...@@ -198,35 +197,6 @@ class @Problem ...@@ -198,35 +197,6 @@ class @Problem
else else
@gentle_alert response.success @gentle_alert response.success
message_post: =>
Logger.log 'message_post', @answers
fd = new FormData()
feedback = @$('section.evaluation textarea.feedback-on-feedback')[0].value
submission_id = $('div.external-grader-message div.submission_id')[0].innerHTML
grader_id = $('div.external-grader-message div.grader_id')[0].innerHTML
score = $(".evaluation-scoring input:radio[name='evaluation-score']:checked").val()
fd.append('feedback', feedback)
fd.append('submission_id', submission_id)
fd.append('grader_id', grader_id)
if(!score)
@gentle_alert "You need to pick a rating before you can submit."
return
else
fd.append('score', score)
settings =
type: "POST"
data: fd
processData: false
contentType: false
success: (response) =>
@gentle_alert response.message
@$('section.evaluation').slideToggle()
$.ajaxWithPrefix("#{@url}/message_post", settings)
reset: => reset: =>
Logger.log 'problem_reset', @answers Logger.log 'problem_reset', @answers
$.postWithPrefix "#{@url}/problem_reset", id: @id, (response) => $.postWithPrefix "#{@url}/problem_reset", id: @id, (response) =>
......
...@@ -22,7 +22,7 @@ class @Collapsible ...@@ -22,7 +22,7 @@ class @Collapsible
if $(event.target).text() == 'See full output' if $(event.target).text() == 'See full output'
new_text = 'Hide output' new_text = 'Hide output'
else else
new_text = 'See full ouput' new_text = 'See full output'
$(event.target).text(new_text) $(event.target).text(new_text)
@toggleHint: (event) => @toggleHint: (event) =>
......
class @CombinedOpenEnded
constructor: (element) ->
@element=element
@reinitialize(element)
reinitialize: (element) ->
@wrapper=$(element).find('section.xmodule_CombinedOpenEndedModule')
@el = $(element).find('section.combined-open-ended')
@combined_open_ended=$(element).find('section.combined-open-ended')
@id = @el.data('id')
@ajax_url = @el.data('ajax-url')
@state = @el.data('state')
@task_count = @el.data('task-count')
@task_number = @el.data('task-number')
@allow_reset = @el.data('allow_reset')
@reset_button = @$('.reset-button')
@reset_button.click @reset
@next_problem_button = @$('.next-step-button')
@next_problem_button.click @next_problem
@show_results_button=@$('.show-results-button')
@show_results_button.click @show_results
# valid states: 'initial', 'assessing', 'post_assessment', 'done'
Collapsible.setCollapsibles(@el)
@submit_evaluation_button = $('.submit-evaluation-button')
@submit_evaluation_button.click @message_post
@results_container = $('.result-container')
# Where to put the rubric once we load it
@el = $(element).find('section.open-ended-child')
@errors_area = @$('.error')
@answer_area = @$('textarea.answer')
@rubric_wrapper = @$('.rubric-wrapper')
@hint_wrapper = @$('.hint-wrapper')
@message_wrapper = @$('.message-wrapper')
@submit_button = @$('.submit-button')
@child_state = @el.data('state')
@child_type = @el.data('child-type')
if @child_type=="openended"
@skip_button = @$('.skip-button')
@skip_button.click @skip_post_assessment
@open_ended_child= @$('.open-ended-child')
@find_assessment_elements()
@find_hint_elements()
@rebind()
# locally scoped jquery.
$: (selector) ->
$(selector, @el)
show_results: (event) =>
status_item = $(event.target).parent().parent()
status_number = status_item.data('status-number')
data = {'task_number' : status_number}
$.postWithPrefix "#{@ajax_url}/get_results", data, (response) =>
if response.success
@results_container.after(response.html).remove()
@results_container = $('div.result-container')
@submit_evaluation_button = $('.submit-evaluation-button')
@submit_evaluation_button.click @message_post
Collapsible.setCollapsibles(@results_container)
else
@errors_area.html(response.error)
message_post: (event)=>
Logger.log 'message_post', @answers
external_grader_message=$(event.target).parent().parent().parent()
evaluation_scoring = $(event.target).parent()
fd = new FormData()
feedback = evaluation_scoring.find('textarea.feedback-on-feedback')[0].value
submission_id = external_grader_message.find('input.submission_id')[0].value
grader_id = external_grader_message.find('input.grader_id')[0].value
score = evaluation_scoring.find("input:radio[name='evaluation-score']:checked").val()
fd.append('feedback', feedback)
fd.append('submission_id', submission_id)
fd.append('grader_id', grader_id)
if(!score)
@gentle_alert "You need to pick a rating before you can submit."
return
else
fd.append('score', score)
settings =
type: "POST"
data: fd
processData: false
contentType: false
success: (response) =>
@gentle_alert response.msg
$('section.evaluation').slideToggle()
@message_wrapper.html(response.message_html)
$.ajaxWithPrefix("#{@ajax_url}/save_post_assessment", settings)
rebind: () =>
# rebind to the appropriate function for the current state
@submit_button.unbind('click')
@submit_button.show()
@reset_button.hide()
@next_problem_button.hide()
@hint_area.attr('disabled', false)
if @child_type=="openended"
@skip_button.hide()
if @allow_reset=="True"
@reset_button.show()
@submit_button.hide()
@answer_area.attr("disabled", true)
@hint_area.attr('disabled', true)
else if @child_state == 'initial'
@answer_area.attr("disabled", false)
@submit_button.prop('value', 'Submit')
@submit_button.click @save_answer
else if @child_state == 'assessing'
@answer_area.attr("disabled", true)
@submit_button.prop('value', 'Submit assessment')
@submit_button.click @save_assessment
if @child_type == "openended"
@submit_button.hide()
@queueing()
else if @child_state == 'post_assessment'
if @child_type=="openended"
@skip_button.show()
@skip_post_assessment()
@answer_area.attr("disabled", true)
@submit_button.prop('value', 'Submit post-assessment')
if @child_type=="selfassessment"
@submit_button.click @save_hint
else
@submit_button.click @message_post
else if @child_state == 'done'
@answer_area.attr("disabled", true)
@hint_area.attr('disabled', true)
@submit_button.hide()
if @child_type=="openended"
@skip_button.hide()
if @task_number<@task_count
@next_problem()
else
@reset_button.show()
find_assessment_elements: ->
@assessment = @$('select.assessment')
find_hint_elements: ->
@hint_area = @$('textarea.post_assessment')
save_answer: (event) =>
event.preventDefault()
if @child_state == 'initial'
data = {'student_answer' : @answer_area.val()}
$.postWithPrefix "#{@ajax_url}/save_answer", data, (response) =>
if response.success
@rubric_wrapper.html(response.rubric_html)
@child_state = 'assessing'
@find_assessment_elements()
@rebind()
else
@errors_area.html(response.error)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
save_assessment: (event) =>
event.preventDefault()
if @child_state == 'assessing'
data = {'assessment' : @assessment.find(':selected').text()}
$.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) =>
if response.success
@child_state = response.state
if @child_state == 'post_assessment'
@hint_wrapper.html(response.hint_html)
@find_hint_elements()
else if @child_state == 'done'
@message_wrapper.html(response.message_html)
@rebind()
else
@errors_area.html(response.error)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
save_hint: (event) =>
event.preventDefault()
if @child_state == 'post_assessment'
data = {'hint' : @hint_area.val()}
$.postWithPrefix "#{@ajax_url}/save_post_assessment", data, (response) =>
if response.success
@message_wrapper.html(response.message_html)
@child_state = 'done'
@rebind()
else
@errors_area.html(response.error)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
skip_post_assessment: =>
if @child_state == 'post_assessment'
$.postWithPrefix "#{@ajax_url}/skip_post_assessment", {}, (response) =>
if response.success
@child_state = 'done'
@rebind()
else
@errors_area.html(response.error)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
reset: (event) =>
event.preventDefault()
if @child_state == 'done' or @allow_reset=="True"
$.postWithPrefix "#{@ajax_url}/reset", {}, (response) =>
if response.success
@answer_area.val('')
@rubric_wrapper.html('')
@hint_wrapper.html('')
@message_wrapper.html('')
@child_state = 'initial'
@combined_open_ended.after(response.html).remove()
@allow_reset="False"
@reinitialize(@element)
@rebind()
@reset_button.hide()
else
@errors_area.html(response.error)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
next_problem: =>
if @child_state == 'done'
$.postWithPrefix "#{@ajax_url}/next_problem", {}, (response) =>
if response.success
@answer_area.val('')
@rubric_wrapper.html('')
@hint_wrapper.html('')
@message_wrapper.html('')
@child_state = 'initial'
@combined_open_ended.after(response.html).remove()
@reinitialize(@element)
@rebind()
@next_problem_button.hide()
if !response.allow_reset
@gentle_alert "Moved to next step."
else
@gentle_alert "Your score did not meet the criteria to move to the next step."
else
@errors_area.html(response.error)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
gentle_alert: (msg) =>
if @el.find('.open-ended-alert').length
@el.find('.open-ended-alert').remove()
alert_elem = "<div class='open-ended-alert'>" + msg + "</div>"
@el.find('.open-ended-action').after(alert_elem)
@el.find('.open-ended-alert').css(opacity: 0).animate(opacity: 1, 700)
queueing: =>
if @child_state=="assessing" and @child_type=="openended"
if window.queuePollerID # Only one poller 'thread' per Problem
window.clearTimeout(window.queuePollerID)
window.queuePollerID = window.setTimeout(@poll, 10000)
poll: =>
$.postWithPrefix "#{@ajax_url}/check_for_score", (response) =>
if response.state == "done" or response.state=="post_assessment"
delete window.queuePollerID
location.reload()
else
window.queuePollerID = window.setTimeout(@poll, 10000)
\ No newline at end of file
class @SelfAssessment
constructor: (element) ->
@el = $(element).find('section.self-assessment')
@id = @el.data('id')
@ajax_url = @el.data('ajax-url')
@state = @el.data('state')
@allow_reset = @el.data('allow_reset')
# valid states: 'initial', 'assessing', 'request_hint', 'done'
# Where to put the rubric once we load it
@errors_area = @$('.error')
@answer_area = @$('textarea.answer')
@rubric_wrapper = @$('.rubric-wrapper')
@hint_wrapper = @$('.hint-wrapper')
@message_wrapper = @$('.message-wrapper')
@submit_button = @$('.submit-button')
@reset_button = @$('.reset-button')
@reset_button.click @reset
@find_assessment_elements()
@find_hint_elements()
@rebind()
# locally scoped jquery.
$: (selector) ->
$(selector, @el)
rebind: () =>
# rebind to the appropriate function for the current state
@submit_button.unbind('click')
@submit_button.show()
@reset_button.hide()
@hint_area.attr('disabled', false)
if @state == 'initial'
@answer_area.attr("disabled", false)
@submit_button.prop('value', 'Submit')
@submit_button.click @save_answer
else if @state == 'assessing'
@answer_area.attr("disabled", true)
@submit_button.prop('value', 'Submit assessment')
@submit_button.click @save_assessment
else if @state == 'request_hint'
@answer_area.attr("disabled", true)
@submit_button.prop('value', 'Submit hint')
@submit_button.click @save_hint
else if @state == 'done'
@answer_area.attr("disabled", true)
@hint_area.attr('disabled', true)
@submit_button.hide()
if @allow_reset
@reset_button.show()
else
@reset_button.hide()
find_assessment_elements: ->
@assessment = @$('select.assessment')
find_hint_elements: ->
@hint_area = @$('textarea.hint')
save_answer: (event) =>
event.preventDefault()
if @state == 'initial'
data = {'student_answer' : @answer_area.val()}
$.postWithPrefix "#{@ajax_url}/save_answer", data, (response) =>
if response.success
@rubric_wrapper.html(response.rubric_html)
@state = 'assessing'
@find_assessment_elements()
@rebind()
else
@errors_area.html(response.error)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
save_assessment: (event) =>
event.preventDefault()
if @state == 'assessing'
data = {'assessment' : @assessment.find(':selected').text()}
$.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) =>
if response.success
@state = response.state
if @state == 'request_hint'
@hint_wrapper.html(response.hint_html)
@find_hint_elements()
else if @state == 'done'
@message_wrapper.html(response.message_html)
@allow_reset = response.allow_reset
@rebind()
else
@errors_area.html(response.error)
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'
@allow_reset = response.allow_reset
@rebind()
else
@errors_area.html(response.error)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
reset: (event) =>
event.preventDefault()
if @state == 'done'
$.postWithPrefix "#{@ajax_url}/reset", {}, (response) =>
if response.success
@answer_area.val('')
@rubric_wrapper.html('')
@hint_wrapper.html('')
@message_wrapper.html('')
@state = 'initial'
@rebind()
@reset_button.hide()
else
@errors_area.html(response.error)
else
@errors_area.html('Problem state got out of sync. Try reloading the page.')
import copy
from fs.errors import ResourceNotFoundError
import itertools
import json
import logging
from lxml import etree
from lxml.html import rewrite_links
from path import path
import os
import sys
import hashlib
import capa.xqueue_interface as xqueue_interface
from pkg_resources import resource_string
from .capa_module import only_one, ComplexEncoder
from .editing_module import EditingDescriptor
from .html_checker import check_html
from progress import Progress
from .stringify import stringify_children
from .xml_module import XmlDescriptor
from xmodule.modulestore import Location
from capa.util import *
from datetime import datetime
log = logging.getLogger("mitx.courseware")
# Set the default number of max attempts. Should be 1 for production
# Set higher for debugging/testing
# attempts specified in xml definition overrides this.
MAX_ATTEMPTS = 1
# Set maximum available number of points.
# Overriden by max_score specified in xml.
MAX_SCORE = 1
class OpenEndedChild():
"""
States:
initial (prompt, textbox shown)
|
assessing (read-only textbox, rubric + assessment input shown for self assessment, response queued for open ended)
|
post_assessment (read-only textbox, read-only rubric and assessment, hint input box shown)
|
done (submitted msg, green checkmark, everything else read-only. If attempts < max, shows
a reset button that goes back to initial state. Saves previous
submissions too.)
"""
DEFAULT_QUEUE = 'open-ended'
DEFAULT_MESSAGE_QUEUE = 'open-ended-message'
max_inputfields = 1
STATE_VERSION = 1
# states
INITIAL = 'initial'
ASSESSING = 'assessing'
POST_ASSESSMENT = 'post_assessment'
DONE = 'done'
#This is used to tell students where they are at in the module
HUMAN_NAMES = {
'initial': 'Started',
'assessing': 'Being scored',
'post_assessment': 'Scoring finished',
'done': 'Problem complete',
}
def __init__(self, system, location, definition, descriptor, static_data,
instance_state=None, shared_state=None, **kwargs):
# Load instance state
if instance_state is not None:
instance_state = json.loads(instance_state)
else:
instance_state = {}
# History is a list of tuples of (answer, score, hint), where hint may be
# None for any element, and score and hint can be None for the last (current)
# element.
# Scores are on scale from 0 to max_score
self.history = instance_state.get('history', [])
self.state = instance_state.get('state', self.INITIAL)
self.created = instance_state.get('created', False)
self.attempts = instance_state.get('attempts', 0)
self.max_attempts = static_data['max_attempts']
self.prompt = static_data['prompt']
self.rubric = static_data['rubric']
# Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect).
self._max_score = static_data['max_score']
self.setup_response(system, location, definition, descriptor)
def setup_response(self, system, location, definition, descriptor):
"""
Needs to be implemented by the inheritors of this module. Sets up additional fields used by the child modules.
@param system: Modulesystem
@param location: Module location
@param definition: XML definition
@param descriptor: Descriptor of the module
@return: None
"""
pass
def latest_answer(self):
"""None if not available"""
if not self.history:
return ""
return self.history[-1].get('answer', "")
def latest_score(self):
"""None if not available"""
if not self.history:
return None
return self.history[-1].get('score')
def latest_post_assessment(self):
"""None if not available"""
if not self.history:
return ""
return self.history[-1].get('post_assessment', "")
def new_history_entry(self, answer):
"""
Adds a new entry to the history dictionary
@param answer: The student supplied answer
@return: None
"""
self.history.append({'answer': answer})
def record_latest_score(self, score):
"""Assumes that state is right, so we're adding a score to the latest
history element"""
self.history[-1]['score'] = score
def record_latest_post_assessment(self, post_assessment):
"""Assumes that state is right, so we're adding a score to the latest
history element"""
self.history[-1]['post_assessment'] = post_assessment
def change_state(self, new_state):
"""
A centralized place for state changes--allows for hooks. If the
current state matches the old state, don't run any hooks.
"""
if self.state == new_state:
return
self.state = new_state
if self.state == self.DONE:
self.attempts += 1
def get_instance_state(self):
"""
Get the current score and state
"""
state = {
'version': self.STATE_VERSION,
'history': self.history,
'state': self.state,
'max_score': self._max_score,
'attempts': self.attempts,
'created': False,
}
return json.dumps(state)
def _allow_reset(self):
"""Can the module be reset?"""
return (self.state == self.DONE and self.attempts < self.max_attempts)
def max_score(self):
"""
Return max_score
"""
return self._max_score
def get_score(self):
"""
Returns the last score in the list
"""
score = self.latest_score()
return {'score': score if score is not None else 0,
'total': self._max_score}
def reset(self, system):
"""
If resetting is allowed, reset the state.
Returns {'success': bool, 'error': msg}
(error only present if not success)
"""
self.change_state(self.INITIAL)
return {'success': True}
def get_progress(self):
'''
For now, just return last score / max_score
'''
if self._max_score > 0:
try:
return Progress(self.get_score()['score'], self._max_score)
except Exception as err:
log.exception("Got bad progress")
return None
return None
def out_of_sync_error(self, get, msg=''):
"""
return dict out-of-sync error message, and also log.
"""
log.warning("Assessment module state out sync. state: %r, get: %r. %s",
self.state, get, msg)
return {'success': False,
'error': 'The problem state got out-of-sync'}
def get_html(self):
"""
Needs to be implemented by inheritors. Renders the HTML that students see.
@return:
"""
pass
def handle_ajax(self):
"""
Needs to be implemented by child modules. Handles AJAX events.
@return:
"""
pass
def is_submission_correct(self, score):
"""
Checks to see if a given score makes the answer correct. Very naive right now (>66% is correct)
@param score: Numeric score.
@return: Boolean correct.
"""
correct = False
if(isinstance(score, (int, long, float, complex))):
score_ratio = int(score) / float(self.max_score())
correct = (score_ratio >= 0.66)
return correct
def is_last_response_correct(self):
"""
Checks to see if the last response in the module is correct.
@return: 'correct' if correct, otherwise 'incorrect'
"""
score = self.get_score()['score']
correctness = 'correct' if self.is_submission_correct(score) else 'incorrect'
return correctness
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
set -e set -e
set -x set -x
git remote prune origin
# Reset the submodule, in case it changed # Reset the submodule, in case it changed
git submodule foreach 'git reset --hard HEAD' git submodule foreach 'git reset --hard HEAD'
......
...@@ -15,6 +15,8 @@ function github_mark_failed_on_exit { ...@@ -15,6 +15,8 @@ function github_mark_failed_on_exit {
trap '[ $? == "0" ] || github_status state:failure "failed"' EXIT trap '[ $? == "0" ] || github_status state:failure "failed"' EXIT
} }
git remote prune origin
github_mark_failed_on_exit github_mark_failed_on_exit
github_status state:pending "is running" github_status state:pending "is running"
......
...@@ -62,6 +62,7 @@ class GradingService(object): ...@@ -62,6 +62,7 @@ class GradingService(object):
""" """
Make a get request to the grading controller Make a get request to the grading controller
""" """
log.debug(params)
op = lambda: self.session.get(url, op = lambda: self.session.get(url,
allow_redirects=allow_redirects, allow_redirects=allow_redirects,
params=params) params=params)
......
...@@ -81,7 +81,7 @@ class PeerGradingService(GradingService): ...@@ -81,7 +81,7 @@ class PeerGradingService(GradingService):
self.get_problem_list_url = self.url + '/get_problem_list/' self.get_problem_list_url = self.url + '/get_problem_list/'
def get_next_submission(self, problem_location, grader_id): def get_next_submission(self, problem_location, grader_id):
response = self.get(self.get_next_submission_url, False, response = self.get(self.get_next_submission_url,
{'location': problem_location, 'grader_id': grader_id}) {'location': problem_location, 'grader_id': grader_id})
return response return response
......
...@@ -76,8 +76,8 @@ DATABASES = AUTH_TOKENS['DATABASES'] ...@@ -76,8 +76,8 @@ DATABASES = AUTH_TOKENS['DATABASES']
XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE'] XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE']
STAFF_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE') STAFF_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE', STAFF_GRADING_INTERFACE)
PEER_GRADING_INTERFACE = AUTH_TOKENS.get('PEER_GRADING_INTERFACE', PEER_GRADING_INTERFACE)
PEARSON_TEST_USER = "pearsontest" PEARSON_TEST_USER = "pearsontest"
PEARSON_TEST_PASSWORD = AUTH_TOKENS.get("PEARSON_TEST_PASSWORD") PEARSON_TEST_PASSWORD = AUTH_TOKENS.get("PEARSON_TEST_PASSWORD")
...@@ -329,12 +329,28 @@ WIKI_LINK_DEFAULT_LEVEL = 2 ...@@ -329,12 +329,28 @@ WIKI_LINK_DEFAULT_LEVEL = 2
################################# Staff grading config ##################### ################################# Staff grading config #####################
STAFF_GRADING_INTERFACE = None #By setting up the default settings with an incorrect user name and password,
# will get an error when attempting to connect
STAFF_GRADING_INTERFACE = {
'url': 'http://sandbox-grader-001.m.edx.org/staff_grading',
'username': 'incorrect_user',
'password': 'incorrect_pass',
}
# Used for testing, debugging # Used for testing, debugging
MOCK_STAFF_GRADING = False MOCK_STAFF_GRADING = False
################################# Peer grading config ##################### ################################# Peer grading config #####################
PEER_GRADING_INTERFACE = None
#By setting up the default settings with an incorrect user name and password,
# will get an error when attempting to connect
PEER_GRADING_INTERFACE = {
'url': 'http://sandbox-grader-001.m.edx.org/peer_grading',
'username': 'incorrect_user',
'password': 'incorrect_pass',
}
# Used for testing, debugging
MOCK_PEER_GRADING = False MOCK_PEER_GRADING = False
################################# Jasmine ################################### ################################# Jasmine ###################################
......
<section id="combined-open-ended" class="combined-open-ended" data-ajax-url="${ajax_url}" data-allow_reset="${allow_reset}" data-state="${state}" data-task-count="${task_count}" data-task-number="${task_number}">
<div class="status-container">
<h4>Status</h4><br/>
${status | n}
</div>
<div class="item-container">
<h4>Problem</h4><br/>
% for item in items:
<div class="item">${item['content'] | n}</div>
% endfor
<input type="button" value="Reset" class="reset-button" name="reset"/>
<input type="button" value="Next Step" class="next-step-button" name="reset"/>
</div>
<div class="result-container">
</div>
</section>
<div class="result-container">
<h4>Results from Step ${task_number}</h4><br/>
${results | n}
</div>
\ No newline at end of file
<section id="combined-open-ended-status" class="combined-open-ended-status">
%for i in xrange(0,len(status_list)):
<%status=status_list[i]%>
%if i==len(status_list)-1:
<div class="statusitem-current" data-status-number="${i}">
%else:
<div class="statusitem" data-status-number="${i}">
%endif
Step ${status['task_number']} (${status['human_state']}) : ${status['score']} / ${status['max_score']}
% if status['state'] == 'initial':
<span class="unanswered" id="status"></span>
% elif status['state'] in ['done', 'post_assessment'] and status['correct'] == 'correct':
<span class="correct" id="status"></span>
% elif status['state'] in ['done', 'post_assessment'] and status['correct'] == 'incorrect':
<span class="incorrect" id="status"></span>
% elif status['state'] == 'assessing':
<span class="grading" id="status"></span>
% endif
%if status['type']=="openended" and status['state'] in ['done', 'post_assessment']:
<div class="show-results">
<a href="#" class="show-results-button">Show results from step ${status['task_number']}</a>
</div>
%endif
</div>
%endfor
</section>
\ No newline at end of file
<section id="openended_${id}" class="open-ended-child" data-state="${state}" data-child-type="${child_type}">
<div class="error"></div>
<div class="prompt">
${prompt|n}
</div>
<textarea rows="${rows}" cols="${cols}" name="answer" class="answer short-form-response" id="input_${id}">${previous_answer|h}</textarea>
<div class="message-wrapper"></div>
<div class="grader-status">
% if state == 'initial':
<span class="unanswered" style="display:inline-block;" id="status_${id}">Unanswered</span>
% elif state in ['done', 'post_assessment'] and correct == 'correct':
<span class="correct" id="status_${id}">Correct</span>
% elif state in ['done', 'post_assessment'] and correct == 'incorrect':
<span class="incorrect" id="status_${id}">Incorrect</span>
% elif state == 'assessing':
<span class="grading" id="status_${id}">Submitted for grading</span>
% endif
% if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif
</div>
<input type="button" value="Submit" class="submit-button" name="show"/>
<input name="skip" class="skip-button" type="button" value="Skip Post-Assessment"/>
<div class="open-ended-action"></div>
<span id="answer_${id}"></span>
</section>
<section id="openended_${id}" class="openended"> <div class="external-grader-message">
<textarea rows="${rows}" cols="${cols}" name="input_${id}" class="short-form-response" id="input_${id}" ${msg|n}
% if hidden:
style="display:none;"
% endif
>${value|h}</textarea>
<div class="grader-status">
% if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}">Unanswered</span>
% elif status == 'correct':
<span class="correct" id="status_${id}">Correct</span>
% elif status == 'incorrect':
<span class="incorrect" id="status_${id}">Incorrect</span>
% elif status == 'queued':
<span class="grading" id="status_${id}">Submitted for grading</span>
% endif
% if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif
</div>
<span id="answer_${id}"></span>
% if status == 'queued':
<input name="reload" class="reload" type="button" value="Recheck for Feedback" onclick="document.location.reload(true);" />
% endif
<div class="external-grader-message">
${msg|n}
% if status in ['correct','incorrect']:
<div class="collapsible evaluation-response"> <div class="collapsible evaluation-response">
<header> <header>
<a href="#">Respond to Feedback</a> <a href="#">Respond to Feedback</a>
</header> </header>
<section id="evaluation_${id}" class="evaluation"> <section id="evaluation_${id}" class="evaluation">
<p>How accurate do you find this feedback?</p> <p>How accurate do you find this feedback?</p>
<div class="evaluation-scoring"> <div class="evaluation-scoring">
<ul class="scoring-list"> <ul class="scoring-list">
<li><input type="radio" name="evaluation-score" id="evaluation-score-5" value="5" /> <label for="evaluation-score-5"> Correct</label></li> <li><input type="radio" name="evaluation-score" id="evaluation-score-5" value="5" /> <label for="evaluation-score-5"> Correct</label></li>
<li><input type="radio" name="evaluation-score" id="evaluation-score-4" value="4" /> <label for="evaluation-score-4"> Partially Correct</label></li> <li><input type="radio" name="evaluation-score" id="evaluation-score-4" value="4" /> <label for="evaluation-score-4"> Partially Correct</label></li>
<li><input type="radio" name="evaluation-score" id="evaluation-score-3" value="3" /> <label for="evaluation-score-3"> No Opinion</label></li> <li><input type="radio" name="evaluation-score" id="evaluation-score-3" value="3" /> <label for="evaluation-score-3"> No Opinion</label></li>
<li><input type="radio" name="evaluation-score" id="evaluation-score-2" value="2" /> <label for="evaluation-score-2"> Partially Incorrect</label></li> <li><input type="radio" name="evaluation-score" id="evaluation-score-2" value="2" /> <label for="evaluation-score-2"> Partially Incorrect</label></li>
<li><input type="radio" name="evaluation-score" id="evaluation-score-1" value="1" /> <label for="evaluation-score-1"> Incorrect</label></li> <li><input type="radio" name="evaluation-score" id="evaluation-score-1" value="1" /> <label for="evaluation-score-1"> Incorrect</label></li>
</ul> </ul>
</div> </div>
<p>Additional comments:</p> <p>Additional comments:</p>
<textarea rows="${rows}" cols="${cols}" name="feedback_${id}" class="feedback-on-feedback" id="feedback_${id}"></textarea> <textarea rows="${rows}" cols="${cols}" name="feedback_${id}" class="feedback-on-feedback" id="feedback_${id}"></textarea>
<div class="submit-message-container"> <input type="button" value="Submit Feedback" class="submit-evaluation-button" name="reset"/>
<input name="submit-message" class="submit-message" type="button" value="Submit your message"/> </section>
</div>
</section>
</div> </div>
% endif </div>
</div> \ No newline at end of file
</section>
...@@ -12,5 +12,6 @@ ...@@ -12,5 +12,6 @@
<div class="result-output"> <div class="result-output">
${ feedback | n} ${ feedback | n}
</div> </div>
${rubric_feedback | n}
</div> </div>
</section> </section>
\ No newline at end of file
<table class="rubric">
% for i in range(len(rubric_categories)):
<% category = rubric_categories[i] %>
<tr>
<th>
${category['description']}
% if category['has_score'] == True:
(Your score: ${category['score']})
% endif
</th>
% for j in range(len(category['options'])):
<% option = category['options'][j] %>
<td>
<div class="view-only">
${option['text']}
% if option.has_key('selected'):
% if option['selected'] == True:
<div class="selected-grade">[${option['points']} points]</div>
%else:
<div class="grade">[${option['points']} points]</div>
% endif
% else:
<div class="grade">[${option['points']} points]</div>
%endif
</div>
</td>
% endfor
</tr>
% endfor
</table>
\ No newline at end of file
...@@ -2,6 +2,6 @@ ...@@ -2,6 +2,6 @@
<div class="hint-prompt"> <div class="hint-prompt">
${hint_prompt} ${hint_prompt}
</div> </div>
<textarea name="hint" class="hint" cols="70" rows="5" <textarea name="post_assessment" class="post_assessment" cols="70" rows="5"
${'readonly="true"' if read_only else ''}>${hint}</textarea> ${'readonly="true"' if read_only else ''}>${hint}</textarea>
</div> </div>
<section id="self_assessment_${id}" class="self-assessment" data-ajax-url="${ajax_url}" <section id="self_assessment_${id}" class="open-ended-child" data-ajax-url="${ajax_url}"
data-id="${id}" data-state="${state}" data-allow_reset="${allow_reset}"> data-id="${id}" data-state="${state}" data-allow_reset="${allow_reset}" data-child-type="${child_type}">
<div class="error"></div> <div class="error"></div>
<div class="prompt"> <div class="prompt">
${prompt} ${prompt}
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
<textarea name="answer" class="answer short-form-response" cols="70" rows="20">${previous_answer|h}</textarea> <textarea name="answer" class="answer short-form-response" cols="70" rows="20">${previous_answer|h}</textarea>
</div> </div>
<div class="open-ended-action"></div>
<div class="rubric-wrapper">${initial_rubric}</div> <div class="rubric-wrapper">${initial_rubric}</div>
<div class="hint-wrapper">${initial_hint}</div> <div class="hint-wrapper">${initial_hint}</div>
...@@ -16,5 +18,4 @@ ...@@ -16,5 +18,4 @@
<div class="message-wrapper">${initial_message}</div> <div class="message-wrapper">${initial_message}</div>
<input type="button" value="Submit" class="submit-button" name="show"/> <input type="button" value="Submit" class="submit-button" name="show"/>
<input type="button" value="Reset" class="reset-button" name="reset"/>
</section> </section>
<div class="assessment"> <div class="assessment">
<div class="rubric"> <div class="rubric">
<h3>Self-assess your answer with this rubric:</h3> <h3>Self-assess your answer with this rubric:</h3>
${rubric} ${rubric | n }
</div> </div>
% if not read_only: % if not read_only:
......
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