Commit 318341cb by Will Daly

Merge pull request #479 from edx/will/authoring-schema-validation

JavaScript client data validation
parents aac44d75 31b4b455
...@@ -133,18 +133,10 @@ ...@@ -133,18 +133,10 @@
<div id="openassessment_rubric_feedback_wrapper" class="wrapper-comp-settings"> <div id="openassessment_rubric_feedback_wrapper" class="wrapper-comp-settings">
<div id="openassessment_rubric_feedback_header_open"> <div id="openassessment_rubric_feedback_header">
<span> <span>
{% trans "Rubric Feedback" %} {% trans "Rubric Feedback" %}
</span> </span>
<div class="openassessment_rubric_remove_button" id="openassessment_rubric_feedback_remove">
<h2>{% trans "Remove" %}</h2>
</div>
<div id="openassessment_rubric_feedback_header_closed">
<h2>
{% trans "Add Rubric Feedback" %}
</h2>
</div>
</div> </div>
<ul class="list-input settings-list"> <ul class="list-input settings-list">
<li class="field comp-setting-entry"> <li class="field comp-setting-entry">
...@@ -154,7 +146,7 @@ ...@@ -154,7 +146,7 @@
</div> </div>
</li> </li>
<p class="setting-help"> <p class="setting-help">
{% trans "If you would like your students to be able to provide feedback on the rubric, add a prompt to ask them for it." %} {% trans "Directions shown to students when they give feedback." %}
</p> </p>
</ul> </ul>
</div> </div>
...@@ -173,14 +165,14 @@ ...@@ -173,14 +165,14 @@
<li class="openassessment_date_editor field comp-setting-entry"> <li class="openassessment_date_editor field comp-setting-entry">
<div class="wrapper-comp-setting"> <div class="wrapper-comp-setting">
<label for="openassessment_submission_start_editor" class="setting-label">{% trans "Response Submission Start Date"%} </label> <label for="openassessment_submission_start_editor" class="setting-label">{% trans "Response Submission Start Date"%} </label>
<input type="datetime-local" class="input setting-input" id="openassessment_submission_start_editor"> <input type="text" class="input setting-input" id="openassessment_submission_start_editor">
</div> </div>
<p class="setting-help">{% trans "The date at which submissions will first be accepted." %}</p> <p class="setting-help">{% trans "The date at which submissions will first be accepted." %}</p>
</li> </li>
<li class="openassessment_date_editor field comp-setting-entry"> <li class="openassessment_date_editor field comp-setting-entry">
<div class="wrapper-comp-setting"> <div class="wrapper-comp-setting">
<label for="openassessment_submission_due_editor" class="setting-label">{% trans "Response Submission Due Date" %}</label> <label for="openassessment_submission_due_editor" class="setting-label">{% trans "Response Submission Due Date" %}</label>
<input type="datetime-local" class="input setting-input" id="openassessment_submission_due_editor"> <input type="text" class="input setting-input" id="openassessment_submission_due_editor">
</div> </div>
<p class="setting-help">{% trans "The date at which submissions will stop being accepted." %}</p> <p class="setting-help">{% trans "The date at which submissions will stop being accepted." %}</p>
</li> </li>
...@@ -249,14 +241,14 @@ ...@@ -249,14 +241,14 @@
<li class="field comp-setting-entry"> <li class="field comp-setting-entry">
<div class="wrapper-comp-setting"> <div class="wrapper-comp-setting">
<label for="peer_assessment_start_date" class="setting-label">{% trans "Start Date" %}</label> <label for="peer_assessment_start_date" class="setting-label">{% trans "Start Date" %}</label>
<input id="peer_assessment_start_date" type="datetime-local" class="input setting-input"> <input id="peer_assessment_start_date" type="text" class="input setting-input">
</div> </div>
<p class="setting-help">{% trans "If desired, specify a start date for the peer assessment period. If no date is specified, peer assessment can begin when submissions begin."%}</p> <p class="setting-help">{% trans "If desired, specify a start date for the peer assessment period. If no date is specified, peer assessment can begin when submissions begin."%}</p>
</li> </li>
<li class="field comp-setting-entry"> <li class="field comp-setting-entry">
<div class="wrapper-comp-setting"> <div class="wrapper-comp-setting">
<label for="peer_assessment_due_date" class="setting-label">{% trans "Due Date" %}</label> <label for="peer_assessment_due_date" class="setting-label">{% trans "Due Date" %}</label>
<input id="peer_assessment_due_date" type="datetime-local" class="input setting-input"> <input id="peer_assessment_due_date" type="text" class="input setting-input">
</div> </div>
<p class="setting-help">{% trans "If desired, specify a due date for the peer assessment period. If no date is specified, peer assessment can run as long as the problem is open."%}</p> <p class="setting-help">{% trans "If desired, specify a due date for the peer assessment period. If no date is specified, peer assessment can run as long as the problem is open."%}</p>
</li> </li>
...@@ -282,14 +274,14 @@ ...@@ -282,14 +274,14 @@
<li class="field comp-setting-entry"> <li class="field comp-setting-entry">
<div class="wrapper-comp-setting"> <div class="wrapper-comp-setting">
<label for="self_assessment_start_date" class="setting-label">{% trans "Start Date" %}</label> <label for="self_assessment_start_date" class="setting-label">{% trans "Start Date" %}</label>
<input id="self_assessment_start_date" type="datetime-local" class="input setting-input"> <input id="self_assessment_start_date" type="text" class="input setting-input">
</div> </div>
<p class="setting-help">{% trans "If desired, specify a start date for the self assessment period. If no date is specified, self assessment can begin when submissions begin."%}</p> <p class="setting-help">{% trans "If desired, specify a start date for the self assessment period. If no date is specified, self assessment can begin when submissions begin."%}</p>
</li> </li>
<li class="field comp-setting-entry"> <li class="field comp-setting-entry">
<div class="wrapper-comp-setting"> <div class="wrapper-comp-setting">
<label for="self_assessment_due_date" class="setting-label">{% trans "Due Date" %}</label> <label for="self_assessment_due_date" class="setting-label">{% trans "Due Date" %}</label>
<input id="self_assessment_due_date" type="datetime-local" class="input setting-input"> <input id="self_assessment_due_date" type="text" class="input setting-input">
</div> </div>
<p class="setting-help">{% trans "If desired, specify a due date for the self assessment period. If no date is specified, self assessment can run as long as the problem is open."%}</p> <p class="setting-help">{% trans "If desired, specify a due date for the self assessment period. If no date is specified, self assessment can run as long as the problem is open."%}</p>
</li> </li>
......
...@@ -25,11 +25,11 @@ from openassessment.xblock.studio_mixin import StudioMixin ...@@ -25,11 +25,11 @@ from openassessment.xblock.studio_mixin import StudioMixin
from openassessment.xblock.xml import parse_from_xml, serialize_content_to_xml from openassessment.xblock.xml import parse_from_xml, serialize_content_to_xml
from openassessment.xblock.staff_info_mixin import StaffInfoMixin from openassessment.xblock.staff_info_mixin import StaffInfoMixin
from openassessment.xblock.workflow_mixin import WorkflowMixin from openassessment.xblock.workflow_mixin import WorkflowMixin
from openassessment.workflow import api as workflow_api
from openassessment.workflow.errors import AssessmentWorkflowError from openassessment.workflow.errors import AssessmentWorkflowError
from openassessment.xblock.student_training_mixin import StudentTrainingMixin from openassessment.xblock.student_training_mixin import StudentTrainingMixin
from openassessment.xblock.validation import validator from openassessment.xblock.validation import validator
from openassessment.xblock.resolve_dates import resolve_dates, DISTANT_PAST, DISTANT_FUTURE from openassessment.xblock.resolve_dates import resolve_dates, DISTANT_PAST, DISTANT_FUTURE
from openassessment.xblock.data_conversion import create_rubric_dict
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -345,60 +345,26 @@ class OpenAssessmentBlock( ...@@ -345,60 +345,26 @@ class OpenAssessmentBlock(
Inherited by XBlock core. Inherited by XBlock core.
""" """
block = runtime.construct_xblock_from_class(cls, keys)
config = parse_from_xml(node) config = parse_from_xml(node)
rubric = { block = runtime.construct_xblock_from_class(cls, keys)
"prompt": config["prompt"],
"feedbackprompt": config["rubric_feedback_prompt"],
"criteria": config["rubric_criteria"],
}
xblock_validator = validator(block, strict_post_release=False) xblock_validator = validator(block, strict_post_release=False)
xblock_validator( xblock_validator(
rubric, create_rubric_dict(config['prompt'], config['rubric_criteria']),
{ 'due': config['submission_due'], 'start': config['submission_start']},
config['rubric_assessments']
)
block.update(
config['rubric_criteria'],
config['rubric_feedback_prompt'],
config['rubric_assessments'], config['rubric_assessments'],
config['submission_due'], submission_start=config['submission_start'],
config['submission_start'], submission_due=config['submission_due']
config['title'],
config['prompt']
) )
return block
def update(self, criteria, feedback_prompt, assessments, submission_due, block.rubric_criteria = config['rubric_criteria']
submission_start, title, prompt): block.rubric_feedback_prompt = config['rubric_feedback_prompt']
""" block.rubric_assessments = config['rubric_assessments']
Given a dictionary of properties, update the XBlock block.submission_start = config['submission_start']
block.submission_due = config['submission_due']
block.title = config['title']
block.prompt = config['prompt']
Args: return block
criteria (list): A list of rubric criteria for this XBlock.
feedback_prompt (str):
assessments (list): A list of assessment module configurations for
this XBlock.
submission_due (str): ISO formatted submission due date.
submission_start (str): ISO formatted submission start date.
title (str): The title of this XBlock
prompt (str): The prompt for this XBlock.
Returns:
None
"""
# If we've gotten this far, then we've successfully parsed the XML
# and validated the contents. At long last, we can safely update the XBlock.
self.title = title
self.prompt = prompt
self.rubric_criteria = criteria
self.rubric_assessments = assessments
self.rubric_feedback_prompt = feedback_prompt
self.submission_start = submission_start
self.submission_due = submission_due
@property @property
def valid_assessments(self): def valid_assessments(self):
......
"""
Schema for validating and sanitizing data received from the JavaScript client.
"""
import dateutil
from pytz import utc
from voluptuous import Schema, Required, All, Any, Range, In, Invalid
from openassessment.xblock.xml import parse_examples_xml_str, UpdateFromXmlError
def utf8_validator(value):
"""Validate and sanitize unicode strings.
If we're given a bytestring, assume that the encoding is UTF-8
Args:
value: The value to validate
Returns:
unicode
Raises:
Invalid
"""
try:
if isinstance(value, str):
return value.decode('utf-8')
else:
return unicode(value)
except (ValueError, TypeError):
raise Invalid(u"Could not load unicode from value \"{val}\"".format(val=value))
def datetime_validator(value):
"""Validate and sanitize a datetime string in ISO format.
Args:
value: The value to validate
Returns:
unicode: ISO-formatted datetime string
Raises:
Invalid
"""
try:
# The dateutil parser defaults empty values to the current day,
# which is NOT what we want.
if value is None or value == '':
raise Invalid(u"Datetime value cannot be \"{val}\"".format(val=value))
# Parse the date and interpret it as UTC
value = dateutil.parser.parse(value).replace(tzinfo=utc)
return unicode(value.isoformat())
except (ValueError, TypeError):
raise Invalid(u"Could not parse datetime from value \"{val}\"".format(val=value))
def examples_xml_validator(value):
"""Parse and validate student training examples XML.
Args:
value: The value to parse.
Returns:
list of training examples, serialized as dictionaries.
Raises:
Invalid
"""
try:
return parse_examples_xml_str(value)
except UpdateFromXmlError:
raise Invalid(u"Could not parse examples from XML")
# Schema definition for an update from the Studio JavaScript editor.
EDITOR_UPDATE_SCHEMA = Schema({
Required('prompt'): utf8_validator,
Required('title'): utf8_validator,
Required('feedback_prompt'): utf8_validator,
Required('submission_start'): Any(datetime_validator, None),
Required('submission_due'): Any(datetime_validator, None),
Required('assessments'): [
Schema({
Required('name'): All(
utf8_validator,
In([
u'peer-assessment',
u'self-assessment',
u'example-based-assessment',
u'student-training'
])
),
Required('start', default=None): Any(datetime_validator, None),
Required('due', default=None): Any(datetime_validator, None),
'must_grade': All(int, Range(min=0)),
'must_be_graded_by': All(int, Range(min=0)),
'examples': All(utf8_validator, examples_xml_validator)
})
],
Required('feedbackprompt', default=u""): utf8_validator,
Required('criteria'): [
Schema({
Required('order_num'): All(int, Range(min=0)),
Required('name'): utf8_validator,
Required('prompt'): utf8_validator,
Required('feedback'): All(
utf8_validator,
In([
'disabled',
'optional',
'required',
])
),
Required('options'): [
Schema({
Required('order_num'): All(int, Range(min=0)),
Required('name'): utf8_validator,
Required('explanation'): utf8_validator,
Required('points'): All(int, Range(min=0)),
})
]
})
]
})
\ No newline at end of file
if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}OpenAssessment.BaseView=function(runtime,element,server){this.runtime=runtime;this.element=element;this.server=server;this.responseView=new OpenAssessment.ResponseView(this.element,this.server,this);this.trainingView=new OpenAssessment.StudentTrainingView(this.element,this.server,this);this.selfView=new OpenAssessment.SelfView(this.element,this.server,this);this.peerView=new OpenAssessment.PeerView(this.element,this.server,this);this.gradeView=new OpenAssessment.GradeView(this.element,this.server,this);this.messageView=new OpenAssessment.MessageView(this.element,this.server,this);this.staffInfoView=new OpenAssessment.StaffInfoView(this.element,this.server,this)};OpenAssessment.BaseView.prototype={scrollToTop:function(){if($.scrollTo instanceof Function){$(window).scrollTo($("#openassessment__steps"),800,{offset:-50})}},setUpCollapseExpand:function(parentSel,onExpand){parentSel.find(".ui-toggle-visibility__control").click(function(eventData){var sel=$(eventData.target).closest(".ui-toggle-visibility");if(sel.hasClass("is--collapsed")&&onExpand!==undefined){onExpand()}sel.toggleClass("is--collapsed")})},load:function(){this.responseView.load();this.loadAssessmentModules();this.staffInfoView.load()},loadAssessmentModules:function(){this.trainingView.load();this.peerView.load();this.selfView.load();this.gradeView.load()},loadMessageView:function(){this.messageView.load()},toggleActionError:function(type,msg){var element=this.element;var container=null;if(type=="save"){container=".response__submission__actions"}else if(type=="submit"||type=="peer"||type=="self"||type=="student-training"){container=".step__actions"}else if(type=="feedback_assess"){container=".submission__feedback__actions"}if(container===null){if(msg!==null){console.log(msg)}}else{var msgHtml=msg===null?"":msg;$(container+" .message__content",element).html("<p>"+msgHtml+"</p>");$(container,element).toggleClass("has--error",msg!==null)}},showLoadError:function(step){var container="#openassessment__"+step;$(container).toggleClass("has--error",true);$(container+" .step__status__value i").removeClass().addClass("ico icon-warning-sign");$(container+" .step__status__value .copy").html(gettext("Unable to Load"))}};function OpenAssessmentBlock(runtime,element){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.BaseView(runtime,element,server);view.load()}OpenAssessment.StudioView=function(runtime,element,server){this.runtime=runtime;this.server=server;this.liveElement=$(element);var liveElement=this.liveElement;this.settingsFieldSelectors={promptBox:$("#openassessment_prompt_editor",liveElement),titleField:$("#openassessment_title_editor",liveElement),submissionStartField:$("#openassessment_submission_start_editor",liveElement),submissionDueField:$("#openassessment_submission_due_editor",liveElement),hasPeer:$("#include_peer_assessment",liveElement),hasSelf:$("#include_self_assessment",liveElement),hasAI:$("#include_ai_assessment",liveElement),hasTraining:$("#include_student_training",liveElement),peerMustGrade:$("#peer_assessment_must_grade",liveElement),peerGradedBy:$("#peer_assessment_graded_by",liveElement),peerStart:$("#peer_assessment_start_date",liveElement),peerDue:$("#peer_assessment_due_date",liveElement),selfStart:$("#self_assessment_start_date",liveElement),selfDue:$("#self_assessment_due_date",liveElement)};this.aiTrainingExamplesCodeBox=CodeMirror.fromTextArea($("#ai_training_examples",liveElement).first().get(0),{mode:"xml",lineNumbers:true,lineWrapping:true});this.studentTrainingExamplesCodeBox=CodeMirror.fromTextArea($("#student_training_examples",liveElement).first().get(0),{mode:"xml",lineNumbers:true,lineWrapping:true});var criterionHtml=$("#openassessment_criterion_1",liveElement).parent().html();this.criterionHtmlTemplate=criterionHtml.replace(new RegExp("1","g"),"C-C-C");var optionHtml=$("#openassessment_criterion_1_option_1",liveElement).parent().html();var criteriaReplaced=optionHtml.replace(new RegExp("criterion_1","g"),"criterion_C-C-C");this.optionHtmlTemplate=criteriaReplaced.replace(new RegExp("option_1","g"),"option_O-O-O");this.numberOfCriteria=0;this.numberOfOptions=[];this.rubricCriteriaSelectors=[];this.rubricFeedbackPrompt=$("#openassessment_rubric_feedback",liveElement);this.hasRubricFeedbackPrompt=true;$("#openassessment_criterion_list",liveElement).empty();this.addNewCriterionToRubric();var view=this;$(".openassessment_save_button",liveElement).click(function(eventData){view.save()});$(".openassessment_cancel_button",liveElement).click(function(eventData){view.cancel()});$(".openassessment_editor_content_and_tabs",liveElement).tabs();view.addSettingsAssessmentCheckboxListener("ai_assessment",liveElement);view.addSettingsAssessmentCheckboxListener("self_assessment",liveElement);view.addSettingsAssessmentCheckboxListener("peer_assessment",liveElement);view.addSettingsAssessmentCheckboxListener("student_training",liveElement);$("#openassessment_rubric_add_criterion",liveElement).click(function(eventData){view.addNewCriterionToRubric(liveElement)});$("#openassessment_rubric_feedback_remove",liveElement).click(function(eventData){$("#openassessment_rubric_feedback_header_open",liveElement).fadeOut();$("#openassessment_rubric_feedback_input_wrapper",liveElement).fadeOut();$("#openassessment_rubric_feedback_header_closed",liveElement).fadeIn();view.hasRubricFeedbackPrompt=false});$("#openassessment_rubric_feedback_header_closed",liveElement).click(function(eventData){$("#openassessment_rubric_feedback_header_closed",liveElement).fadeOut();$("#openassessment_rubric_feedback_header_open",liveElement).fadeIn();$("#openassessment_rubric_feedback_input_wrapper",liveElement).fadeIn();view.hasRubricFeedbackPrompt=true});$("#openassessment_rubric_feedback_header_closed",liveElement).hide()};OpenAssessment.StudioView.prototype={load:function(){var view=this;this.server.loadEditorContext().done(function(prompt,rubric,title,subStart,subDue,assessments){view.settingsFieldSelectors.submissionStartField.prop("value",subStart);view.settingsFieldSelectors.submissionDueField.prop("value",subDue);view.settingsFieldSelectors.promptBox.prop("value",prompt);view.settingsFieldSelectors.titleField.prop("value",title);view.settingsFieldSelectors.hasTraining.prop("checked",false).change();view.settingsFieldSelectors.hasPeer.prop("checked",false).change();view.settingsFieldSelectors.hasSelf.prop("checked",false).change();view.settingsFieldSelectors.hasAI.prop("checked",false).change();for(var i=0;i<assessments.length;i++){var assessment=assessments[i];if(assessment.name=="peer-assessment"){view.settingsFieldSelectors.peerMustGrade.prop("value",assessment.must_grade);view.settingsFieldSelectors.peerGradedBy.prop("value",assessment.must_be_graded_by);view.settingsFieldSelectors.peerStart.prop("value",assessment.start);view.settingsFieldSelectors.peerDue.prop("value",assessment.due);view.settingsFieldSelectors.hasPeer.prop("checked",true).change()}else if(assessment.name=="self-assessment"){view.settingsFieldSelectors.selfStart.prop("value",assessment.start);view.settingsFieldSelectors.selfDue.prop("value",assessment.due);view.settingsFieldSelectors.hasSelf.prop("checked",true).change()}else if(assessment.name=="example-based-assessment"){view.settingsFieldSelectors.aiTrainingExamplesCodeBox.setValue(assessment.examples);view.settingsFieldSelectors.hasAI.prop("checked",true).change()}else if(assessment.name=="student-training"){view.studentTrainingExamplesCodeBox.setValue(assessment.examples);view.settingsFieldSelectors.hasTraining.prop("checked",true).change()}}while(view.numberOfCriteria<rubric.criteria.length){view.addNewCriterionToRubric()}while(view.numberOfCriteria>rubric.criteria.length){view.removeCriterionFromRubric(1)}for(i=0;i<rubric.criteria.length;i++){while(view.numberOfOptions[i+1]<rubric.criteria[i].options.length){view.addNewOptionToCriterion(view.liveElement,i+1)}while(view.numberOfOptions[i+1]>rubric.criteria[i].options.length){view.removeOptionFromCriterion(view.liveElement,i+1,1)}}for(i=0;i<rubric.criteria.length;i++){var criterion=rubric.criteria[i];var selectors=view.rubricCriteriaSelectors[i+1];selectors.name.prop("value",criterion.name);selectors.prompt.prop("value",criterion.prompt);selectors.feedback=criterion.feedback;for(var j=0;j<criterion.options.length;j++){var option=criterion.options[j];var optionSelectors=selectors.options[j+1];optionSelectors.name.prop("value",option.name);optionSelectors.points.prop("value",option.points);optionSelectors.explanation.prop("value",option.explanation)}}if(rubric.feedbackprompt){view.rubricFeedbackPrompt.prop("value",rubric.feedbackprompt);view.hasRubricFeedbackPrompt=true}else{view.rubricFeedbackPrompt.prop("value","");view.hasRubricFeedbackPrompt=false}}).fail(function(msg){view.showError(msg)})},save:function(){var view=this;this.server.checkReleased().done(function(isReleased){if(isReleased){view.confirmPostReleaseUpdate($.proxy(view.updateEditorContext,view))}else{view.updateEditorContext()}}).fail(function(errMsg){view.showError(errMsg)})},addSettingsAssessmentCheckboxListener:function(name,liveElement){$("#include_"+name,liveElement).change(function(){if(this.checked){$("#"+name+"_description_closed",liveElement).fadeOut("fast");$("#"+name+"_settings_editor",liveElement).fadeIn()}else{$("#"+name+"_settings_editor",liveElement).fadeOut("fast");$("#"+name+"_description_closed",liveElement).fadeIn()}})},confirmPostReleaseUpdate:function(onConfirm){var msg=gettext("This problem has already been released. Any changes will apply only to future assessments.");if(confirm(msg)){onConfirm()}},addNewCriterionToRubric:function(){var view=this;var liveElement=this.liveElement;var newCriterionID=this.numberOfCriteria+1;this.numberOfCriteria+=1;this.numberOfOptions[newCriterionID]=0;var criterionHtml=this.criterionHtmlTemplate.replace(new RegExp("C-C-C","g"),""+newCriterionID);$("#openassessment_criterion_list",liveElement).append(criterionHtml);liveElement=$("#openassessment_criterion_"+newCriterionID);$(".openassessment_criterion_option_list",liveElement).empty();view.rubricCriteriaSelectors[newCriterionID]={criterion:liveElement,name:$(".openassessment_criterion_name",liveElement).first(),prompt:$(".openassessment_criterion_prompt",liveElement).first(),options:[],feedback:"disabled"};$("input:radio[value=disabled]",liveElement).prop("checked",true);view.addNewOptionToCriterion(liveElement,newCriterionID);$("#openassessment_display_criterion_"+newCriterionID,liveElement).change(function(){if(this.checked){$("#openassessment_criterion_body_"+newCriterionID,liveElement).fadeIn()}else{$("#openassessment_criterion_body_"+newCriterionID,liveElement).fadeOut()}});$("#openassessment_criterion_"+newCriterionID+"_remove",liveElement).click(function(eventData){view.removeCriterionFromRubric(newCriterionID)});$("#openassessment_criterion_"+newCriterionID+"_add_option",liveElement).click(function(eventData){view.addNewOptionToCriterion(liveElement,newCriterionID)});$(".openassessment_feedback_remove_button",liveElement).click(function(eventData){$(".openassessment_criterion_feedback_direction",liveElement).fadeOut();$(".openassessment_criterion_feedback_header_open",liveElement).fadeOut();$(".openassessment_criterion_feedback_header_closed",liveElement).fadeIn();$(".openassessment_feedback_remove_button",liveElement).fadeOut();view.rubricCriteriaSelectors[newCriterionID].hasFeedback=false});$(".openassessment_criterion_feedback_header_closed",liveElement).click(function(eventData){$(".openassessment_criterion_feedback_direction",liveElement).fadeIn();$(".openassessment_criterion_feedback_header_open",liveElement).fadeIn();$(".openassessment_criterion_feedback_header_closed",liveElement).fadeOut();$(".openassessment_feedback_remove_button",liveElement).fadeIn();view.rubricCriteriaSelectors[newCriterionID].hasFeedback=true});$(".openassessment_criterion_feedback_header_closed",liveElement).hide()},removeCriterionFromRubric:function(criterionToRemove){var view=this;var numCriteria=view.numberOfCriteria;var selectors=view.rubricCriteriaSelectors;for(var i=criterionToRemove;i<numCriteria;i++){selectors[i].name.prop("value",selectors[i+1].name.prop("value"));selectors[i].prompt.prop("value",selectors[i+1].prompt.prop("value"));selectors[i].feedback=selectors[i+1].feedback;$('input:radio[value="disabled"]',selectors[i].criterion).prop("checked",true);while(view.numberOfOptions[i]<view.numberOfOptions[i+1]){view.addNewOptionToCriterion(selectors[i].criteria,i)}while(view.numberOfOptions[i]>view.numberOfOptions[i+1]){view.removeOptionFromCriterion(selectors[i].criteria,i,1)}var options1=selectors[i].options;var options2=selectors[i+1].options;var numOptions2=view.numberOfOptions[i+1];for(var j=1;j<numOptions2;j++){options1[j].points.prop("value",options2[j].points.prop("value"));options1[j].name.prop("value",options2[j].name.prop("value"));options1[j].explanation.prop("value",options2[j].explanation.prop("value"))}}view.rubricCriteriaSelectors[view.rubricCriteriaSelectors.length-1].criterion.remove();view.rubricCriteriaSelectors=view.rubricCriteriaSelectors.slice(0,numCriteria);view.numberOfOptions=view.numberOfOptions.slice(0,numCriteria);view.numberOfCriteria-=1},addNewOptionToCriterion:function(liveElement,criterionID){var view=this;var newOptionID=this.numberOfOptions[criterionID]+1;this.numberOfOptions[criterionID]+=1;var optionHtml=this.optionHtmlTemplate;optionHtml=optionHtml.replace(new RegExp("C-C-C","g"),""+criterionID);optionHtml=optionHtml.replace(new RegExp("O-O-O","g"),""+newOptionID);$("#openassessment_criterion_"+criterionID+"_options",liveElement).append(optionHtml);liveElement=$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID);view.rubricCriteriaSelectors[criterionID].options[newOptionID]={option:liveElement,points:$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_points",liveElement),name:$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_name",liveElement),explanation:$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_explanation",liveElement)};$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_remove",liveElement).click(function(eventData){view.removeOptionFromCriterion(liveElement,criterionID,newOptionID)})},removeOptionFromCriterion:function(liveElement,criterionID,optionToRemove){var view=this;var numberOfOptions=view.numberOfOptions[criterionID];var optionSelectors=view.rubricCriteriaSelectors[criterionID].options;for(var i=optionToRemove;i<numberOfOptions;i++){optionSelectors[i].points.prop("value",optionSelectors[i+1].points.prop("value"));optionSelectors[i].name.prop("value",optionSelectors[i+1].name.prop("value"));optionSelectors[i].explanation.prop("value",optionSelectors[i+1].explanation.prop("value"))}optionSelectors[optionSelectors.length-1].option.remove();view.rubricCriteriaSelectors[criterionID].options=view.rubricCriteriaSelectors[criterionID].options.slice(0,optionSelectors.length-1);view.numberOfOptions[criterionID]-=1},updateEditorContext:function(){this.runtime.notify("save",{state:"start"});var prompt=this.settingsFieldSelectors.promptBox.prop("value");var title=this.settingsFieldSelectors.titleField.prop("value");var subStart=this.settingsFieldSelectors.submissionStartField.prop("value");var subDue=this.settingsFieldSelectors.submissionDueField.prop("value");var rubricCriteria=[];for(var i=1;i<=this.numberOfCriteria;i++){var selectorDict=this.rubricCriteriaSelectors[i];var criterionValueDict={order_num:i-1,name:selectorDict.name.prop("value"),prompt:selectorDict.prompt.prop("value"),feedback:$("#openassessment_criterion_"+i+"_feedback").val()};var optionSelectorList=selectorDict.options;var optionValueList=[];for(var j=1;j<=this.numberOfOptions[i];j++){var optionSelectors=optionSelectorList[j];optionValueList=optionValueList.concat([{order_num:j-1,points:optionSelectors.points.prop("value"),name:optionSelectors.name.prop("value"),explanation:optionSelectors.explanation.prop("value")}])}criterionValueDict.options=optionValueList;rubricCriteria=rubricCriteria.concat([criterionValueDict])}var rubric={criteria:rubricCriteria};if(this.hasRubricFeedbackPrompt){rubric.feedbackprompt=this.rubricFeedbackPrompt.prop("value")}var assessments=[];if(this.settingsFieldSelectors.hasTraining.prop("checked")){assessments[assessments.length]={name:"student-training",examples:this.studentTrainingExamplesCodeBox.getValue()}}if(this.settingsFieldSelectors.hasPeer.prop("checked")){var assessment={name:"peer-assessment",must_grade:parseInt(this.settingsFieldSelectors.peerMustGrade.prop("value")),must_be_graded_by:parseInt(this.settingsFieldSelectors.peerGradedBy.prop("value"))};var startStr=this.settingsFieldSelectors.peerStart.prop("value");var dueStr=this.settingsFieldSelectors.peerDue.prop("value");if(startStr){assessment=$.extend(assessment,{start:startStr})}if(dueStr){assessment=$.extend(assessment,{due:dueStr})}assessments[assessments.length]=assessment}if(this.settingsFieldSelectors.hasSelf.prop("checked")){var assessment={name:"self-assessment"};var startStr=this.settingsFieldSelectors.selfStart.prop("value");var dueStr=this.settingsFieldSelectors.selfDue.prop("value");if(startStr){assessment=$.extend(assessment,{start:startStr})}if(dueStr){assessment=$.extend(assessment,{due:dueStr})}assessments[assessments.length]=assessment}if(this.settingsFieldSelectors.hasAI.prop("checked")){assessments[assessments.length]={name:"example-based-assessment",examples:this.aiTrainingExamplesCodeBox.getValue()}}var view=this;this.server.updateEditorContext(prompt,rubric,title,subStart,subDue,assessments).done(function(){view.runtime.notify("save",{state:"end"});view.load()}).fail(function(msg){view.showError(msg)})},cancel:function(){this.runtime.notify("cancel",{})},showError:function(errorMsg){this.runtime.notify("error",{msg:errorMsg})}};function OpenAssessmentEditor(runtime,element){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.StudioView(runtime,element,server);view.load()}OpenAssessment.GradeView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.GradeView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("grade").done(function(html){$("#openassessment__grade",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){baseView.showLoadError("grade",errMsg)})},installHandlers:function(){var sel=$("#openassessment__grade",this.element);this.baseView.setUpCollapseExpand(sel);var view=this;sel.find("#feedback__submit").click(function(eventObject){eventObject.preventDefault();view.submitFeedbackOnAssessment()})},feedbackText:function(text){if(typeof text==="undefined"){return $("#feedback__remarks__value",this.element).val()}else{$("#feedback__remarks__value",this.element).val(text)}},feedbackOptions:function(options){var view=this;if(typeof options==="undefined"){return $.map($(".feedback__overall__value:checked",view.element),function(element,index){return $(element).val()})}else{$(".feedback__overall__value",this.element).prop("checked",false);$.each(options,function(index,opt){$("#feedback__overall__value--"+opt,view.element).prop("checked",true)})}},setHidden:function(sel,hidden){sel.toggleClass("is--hidden",hidden);sel.attr("aria-hidden",hidden?"true":"false")},isHidden:function(sel){return sel.hasClass("is--hidden")&&sel.attr("aria-hidden")=="true"},feedbackState:function(newState){var containerSel=$(".submission__feedback__content",this.element);var instructionsSel=containerSel.find(".submission__feedback__instructions");var fieldsSel=containerSel.find(".submission__feedback__fields");var actionsSel=containerSel.find(".submission__feedback__actions");var transitionSel=containerSel.find(".transition__status");var messageSel=containerSel.find(".message--complete");if(typeof newState==="undefined"){var isSubmitting=containerSel.hasClass("is--transitioning")&&containerSel.hasClass("is--submitting")&&!this.isHidden(transitionSel)&&this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var hasSubmitted=containerSel.hasClass("is--submitted")&&this.isHidden(transitionSel)&&!this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var isOpen=!containerSel.hasClass("is--submitted")&&!containerSel.hasClass("is--transitioning")&&!containerSel.hasClass("is--submitting")&&this.isHidden(transitionSel)&&this.isHidden(messageSel)&&!this.isHidden(instructionsSel)&&!this.isHidden(fieldsSel)&&!this.isHidden(actionsSel);if(isOpen){return"open"}else if(isSubmitting){return"submitting"}else if(hasSubmitted){return"submitted"}else{throw"Invalid feedback state"}}else{if(newState=="open"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,false);this.setHidden(fieldsSel,false);this.setHidden(actionsSel,false);this.setHidden(transitionSel,true);this.setHidden(messageSel,true)}else if(newState=="submitting"){containerSel.toggleClass("is--transitioning",true);containerSel.toggleClass("is--submitting",true);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,false);this.setHidden(messageSel,true)}else if(newState=="submitted"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",true);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,true);this.setHidden(messageSel,false)}}},submitFeedbackOnAssessment:function(){var view=this;var baseView=this.baseView;$("#feedback__submit",this.element).toggleClass("is--disabled",true);view.feedbackState("submitting");this.server.submitFeedbackOnAssessment(this.feedbackText(),this.feedbackOptions()).done(function(){view.feedbackState("submitted")}).fail(function(errMsg){baseView.toggleActionError("feedback_assess",errMsg)})}};OpenAssessment.MessageView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.MessageView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("message").done(function(html){$("#openassessment__message",view.element).replaceWith(html)}).fail(function(errMsg){baseView.showLoadError("message",errMsg)})}};OpenAssessment.PeerView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.PeerView.prototype={load:function(){var view=this;this.server.render("peer_assessment").done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.installHandlers(false)}).fail(function(errMsg){view.baseView.showLoadError("peer-assessment")});view.baseView.loadMessageView()},loadContinuedAssessment:function(){var view=this;this.server.renderContinuedPeer().done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.installHandlers(true)}).fail(function(errMsg){view.baseView.showLoadError("peer-assessment")})},installHandlers:function(isContinuedAssessment){var sel=$("#openassessment__peer-assessment",this.element);var view=this;this.baseView.setUpCollapseExpand(sel,$.proxy(view.loadContinuedAssessment,view));var rubricSelector=$("#peer-assessment--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(view.peerSubmitEnabled,view))}sel.find("#peer-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();if(!isContinuedAssessment){view.peerAssess()}else{view.continuedPeerAssess()}})},peerSubmitEnabled:function(enabled){var button=$("#peer-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled)}},peerAssess:function(){var view=this;var baseView=view.baseView;this.peerAssessRequest(function(){view.load();baseView.loadAssessmentModules();baseView.scrollToTop()})},continuedPeerAssess:function(){var view=this;var gradeView=this.baseView.gradeView;var baseView=view.baseView;view.peerAssessRequest(function(){view.loadContinuedAssessment();gradeView.load();baseView.scrollToTop()})},peerAssessRequest:function(successFunction){var view=this;view.baseView.toggleActionError("peer",null);view.peerSubmitEnabled(false);this.server.peerAssess(this.rubric.optionsSelected(),this.rubric.criterionFeedback(),this.overallFeedback()).done(successFunction).fail(function(errMsg){view.baseView.toggleActionError("peer",errMsg);view.peerSubmitEnabled(true)})},overallFeedback:function(overallFeedback){var selector="#assessment__rubric__question--feedback__value";if(typeof overallFeedback==="undefined"){return $(selector,this.element).val()}else{$(selector,this.element).val(overallFeedback)}}};OpenAssessment.ResponseView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.savedResponse="";this.lastChangeTime=Date.now();this.errorOnLastSave=false;this.autoSaveTimerId=null};OpenAssessment.ResponseView.prototype={AUTO_SAVE_POLL_INTERVAL:2e3,AUTO_SAVE_WAIT:3e4,load:function(){var view=this;this.server.render("submission").done(function(html){$("#openassessment__response",view.element).replaceWith(html);view.installHandlers();view.setAutoSaveEnabled(true)}).fail(function(errMsg){view.baseView.showLoadError("response")})},installHandlers:function(){var sel=$("#openassessment__response",this.element);var view=this;this.baseView.setUpCollapseExpand(sel);this.savedResponse=this.response();var handleChange=function(eventData){view.handleResponseChanged()};sel.find("#submission__answer__value").on("change keyup drop paste",handleChange);sel.find("#step--response__submit").click(function(eventObject){eventObject.preventDefault();view.submit()});sel.find("#submission__save").click(function(eventObject){eventObject.preventDefault();view.save()})},setAutoSaveEnabled:function(enabled){if(enabled){if(this.autoSaveTimerId===null){this.autoSaveTimerId=setInterval($.proxy(this.autoSave,this),this.AUTO_SAVE_POLL_INTERVAL)}}else{if(this.autoSaveTimerId!==null){clearInterval(this.autoSaveTimerId)}}},submitEnabled:function(enabled){var sel=$("#step--response__submit",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled)}},saveEnabled:function(enabled){var sel=$("#submission__save",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled)}},saveStatus:function(msg){var sel=$("#response__save_status h3",this.element);if(typeof msg==="undefined"){return sel.text()}else{var label=gettext("Status of Your Response");sel.html('<span class="sr">'+label+":"+"</span>\n"+msg)}},unsavedWarningEnabled:function(enabled){if(typeof enabled==="undefined"){return window.onbeforeunload!==null}else{if(enabled){window.onbeforeunload=function(){return gettext("If you leave this page without saving or submitting your response, you'll lose any work you've done on the response.")}}else{window.onbeforeunload=null}}},response:function(text){var sel=$("#submission__answer__value",this.element);if(typeof text==="undefined"){return sel.val()}else{sel.val(text)}},responseChanged:function(){var currentResponse=$.trim(this.response());var savedResponse=$.trim(this.savedResponse);return savedResponse!==currentResponse},autoSave:function(){var timeSinceLastChange=Date.now()-this.lastChangeTime;if(this.responseChanged()&&timeSinceLastChange>this.AUTO_SAVE_WAIT&&!this.errorOnLastSave){this.save()}},handleResponseChanged:function(){var isBlank=$.trim(this.response())!=="";this.submitEnabled(isBlank);if(this.responseChanged()){this.saveEnabled(isBlank);this.saveStatus(gettext("This response has not been saved."));this.unsavedWarningEnabled(true)}this.lastChangeTime=Date.now()},save:function(){this.errorOnLastSave=false;this.saveStatus(gettext("Saving..."));this.baseView.toggleActionError("save",null);this.unsavedWarningEnabled(false);var view=this;var savedResponse=this.response();this.server.save(savedResponse).done(function(){view.savedResponse=savedResponse;var currentResponse=view.response();view.submitEnabled(currentResponse!=="");if(currentResponse==savedResponse){view.saveEnabled(false);view.saveStatus(gettext("This response has been saved but not submitted."))}}).fail(function(errMsg){view.saveStatus(gettext("Error"));view.baseView.toggleActionError("save",errMsg);view.errorOnLastSave=true})},submit:function(){this.submitEnabled(false);var view=this;var baseView=this.baseView;this.confirmSubmission().pipe(function(){var submission=$("#submission__answer__value",view.element).val();baseView.toggleActionError("response",null);return view.server.submit(submission)}).done($.proxy(view.moveToNextStep,view)).fail(function(errCode,errMsg){if(errCode=="ENOMULTI"){view.moveToNextStep()}else{if(errMsg){baseView.toggleActionError("submit",errMsg)}view.submitEnabled(true)}})},moveToNextStep:function(){this.load();this.baseView.loadAssessmentModules();this.unsavedWarningEnabled(false)},confirmSubmission:function(){var msg="You're about to submit your response for this assignment. "+"After you submit this response, you can't change it or submit a new response.";return $.Deferred(function(defer){if(confirm(msg)){defer.resolve()}else{defer.reject()}})}};OpenAssessment.Rubric=function(element){this.element=element};OpenAssessment.Rubric.prototype={criterionFeedback:function(criterionFeedback){var selector="textarea.answer__value";var feedback={};$(selector,this.element).each(function(index,sel){if(typeof criterionFeedback!=="undefined"){$(sel).val(criterionFeedback[sel.name]);feedback[sel.name]=criterionFeedback[sel.name]}else{feedback[sel.name]=$(sel).val()}});return feedback},optionsSelected:function(optionsSelected){var selector="input[type=radio]";if(typeof optionsSelected==="undefined"){var options={};$(selector+":checked",this.element).each(function(index,sel){options[sel.name]=sel.value});return options}else{$(selector,this.element).prop("checked",false);$(selector,this.element).each(function(index,sel){if(optionsSelected.hasOwnProperty(sel.name)){if(sel.value==optionsSelected[sel.name]){$(sel).prop("checked",true)}}})}},canSubmitCallback:function(callback){$(this.element).change(function(){var numChecked=$("input[type=radio]:checked",this).length;var numAvailable=$(".field--radio.assessment__rubric__question",this).length;var canSubmit=numChecked==numAvailable;callback(canSubmit)})},showCorrections:function(corrections){var selector="input[type=radio]";var hasErrors=false;$(selector,this.element).each(function(index,sel){var listItem=$(sel).parents(".assessment__rubric__question");if(corrections.hasOwnProperty(sel.name)){hasErrors=true;listItem.find(".message--incorrect").removeClass("is--hidden");listItem.find(".message--correct").addClass("is--hidden")}else{listItem.find(".message--correct").removeClass("is--hidden");listItem.find(".message--incorrect").addClass("is--hidden")}});return hasErrors}};OpenAssessment.SelfView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.SelfView.prototype={load:function(){var view=this;this.server.render("self_assessment").done(function(html){$("#openassessment__self-assessment",view.element).replaceWith(html); if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}OpenAssessment.BaseView=function(runtime,element,server){this.runtime=runtime;this.element=element;this.server=server;this.responseView=new OpenAssessment.ResponseView(this.element,this.server,this);this.trainingView=new OpenAssessment.StudentTrainingView(this.element,this.server,this);this.selfView=new OpenAssessment.SelfView(this.element,this.server,this);this.peerView=new OpenAssessment.PeerView(this.element,this.server,this);this.gradeView=new OpenAssessment.GradeView(this.element,this.server,this);this.messageView=new OpenAssessment.MessageView(this.element,this.server,this);this.staffInfoView=new OpenAssessment.StaffInfoView(this.element,this.server,this)};OpenAssessment.BaseView.prototype={scrollToTop:function(){if($.scrollTo instanceof Function){$(window).scrollTo($("#openassessment__steps"),800,{offset:-50})}},setUpCollapseExpand:function(parentSel,onExpand){parentSel.find(".ui-toggle-visibility__control").click(function(eventData){var sel=$(eventData.target).closest(".ui-toggle-visibility");if(sel.hasClass("is--collapsed")&&onExpand!==undefined){onExpand()}sel.toggleClass("is--collapsed")})},load:function(){this.responseView.load();this.loadAssessmentModules();this.staffInfoView.load()},loadAssessmentModules:function(){this.trainingView.load();this.peerView.load();this.selfView.load();this.gradeView.load()},loadMessageView:function(){this.messageView.load()},toggleActionError:function(type,msg){var element=this.element;var container=null;if(type=="save"){container=".response__submission__actions"}else if(type=="submit"||type=="peer"||type=="self"||type=="student-training"){container=".step__actions"}else if(type=="feedback_assess"){container=".submission__feedback__actions"}if(container===null){if(msg!==null){console.log(msg)}}else{var msgHtml=msg===null?"":msg;$(container+" .message__content",element).html("<p>"+msgHtml+"</p>");$(container,element).toggleClass("has--error",msg!==null)}},showLoadError:function(step){var container="#openassessment__"+step;$(container).toggleClass("has--error",true);$(container+" .step__status__value i").removeClass().addClass("ico icon-warning-sign");$(container+" .step__status__value .copy").html(gettext("Unable to Load"))}};function OpenAssessmentBlock(runtime,element){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.BaseView(runtime,element,server);view.load()}OpenAssessment.StudioView=function(runtime,element,server){this.runtime=runtime;this.server=server;this.liveElement=$(element);var liveElement=this.liveElement;this.settingsFieldSelectors={promptBox:$("#openassessment_prompt_editor",liveElement),titleField:$("#openassessment_title_editor",liveElement),submissionStartField:$("#openassessment_submission_start_editor",liveElement),submissionDueField:$("#openassessment_submission_due_editor",liveElement),hasPeer:$("#include_peer_assessment",liveElement),hasSelf:$("#include_self_assessment",liveElement),hasAI:$("#include_ai_assessment",liveElement),hasTraining:$("#include_student_training",liveElement),peerMustGrade:$("#peer_assessment_must_grade",liveElement),peerGradedBy:$("#peer_assessment_graded_by",liveElement),peerStart:$("#peer_assessment_start_date",liveElement),peerDue:$("#peer_assessment_due_date",liveElement),selfStart:$("#self_assessment_start_date",liveElement),selfDue:$("#self_assessment_due_date",liveElement)};this.aiTrainingExamplesCodeBox=CodeMirror.fromTextArea($("#ai_training_examples",liveElement).first().get(0),{mode:"xml",lineNumbers:true,lineWrapping:true});this.studentTrainingExamplesCodeBox=CodeMirror.fromTextArea($("#student_training_examples",liveElement).first().get(0),{mode:"xml",lineNumbers:true,lineWrapping:true});var criterionHtml=$("#openassessment_criterion_1",liveElement).parent().html();this.criterionHtmlTemplate=criterionHtml.replace(new RegExp("1","g"),"C-C-C");var optionHtml=$("#openassessment_criterion_1_option_1",liveElement).parent().html();var criteriaReplaced=optionHtml.replace(new RegExp("criterion_1","g"),"criterion_C-C-C");this.optionHtmlTemplate=criteriaReplaced.replace(new RegExp("option_1","g"),"option_O-O-O");this.numberOfCriteria=0;this.numberOfOptions=[];this.rubricCriteriaSelectors=[];this.rubricFeedbackPrompt=$("#openassessment_rubric_feedback",liveElement);$("#openassessment_criterion_list",liveElement).empty();this.addNewCriterionToRubric();var view=this;$(".openassessment_save_button",liveElement).click(function(eventData){view.save()});$(".openassessment_cancel_button",liveElement).click(function(eventData){view.cancel()});$(".openassessment_editor_content_and_tabs",liveElement).tabs();view.addSettingsAssessmentCheckboxListener("ai_assessment",liveElement);view.addSettingsAssessmentCheckboxListener("self_assessment",liveElement);view.addSettingsAssessmentCheckboxListener("peer_assessment",liveElement);view.addSettingsAssessmentCheckboxListener("student_training",liveElement);$("#openassessment_rubric_add_criterion",liveElement).click(function(eventData){view.addNewCriterionToRubric(liveElement)})};OpenAssessment.StudioView.prototype={load:function(){var view=this;this.server.loadEditorContext().done(function(prompt,rubric,title,subStart,subDue,assessments){view.settingsFieldSelectors.submissionStartField.prop("value",subStart);view.settingsFieldSelectors.submissionDueField.prop("value",subDue);view.settingsFieldSelectors.promptBox.prop("value",prompt);view.settingsFieldSelectors.titleField.prop("value",title);view.settingsFieldSelectors.hasTraining.prop("checked",false).change();view.settingsFieldSelectors.hasPeer.prop("checked",false).change();view.settingsFieldSelectors.hasSelf.prop("checked",false).change();view.settingsFieldSelectors.hasAI.prop("checked",false).change();for(var i=0;i<assessments.length;i++){var assessment=assessments[i];if(assessment.name=="peer-assessment"){view.settingsFieldSelectors.peerMustGrade.prop("value",assessment.must_grade);view.settingsFieldSelectors.peerGradedBy.prop("value",assessment.must_be_graded_by);view.settingsFieldSelectors.peerStart.prop("value",assessment.start);view.settingsFieldSelectors.peerDue.prop("value",assessment.due);view.settingsFieldSelectors.hasPeer.prop("checked",true).change()}else if(assessment.name=="self-assessment"){view.settingsFieldSelectors.selfStart.prop("value",assessment.start);view.settingsFieldSelectors.selfDue.prop("value",assessment.due);view.settingsFieldSelectors.hasSelf.prop("checked",true).change()}else if(assessment.name=="example-based-assessment"){view.settingsFieldSelectors.aiTrainingExamplesCodeBox.setValue(assessment.examples);view.settingsFieldSelectors.hasAI.prop("checked",true).change()}else if(assessment.name=="student-training"){view.studentTrainingExamplesCodeBox.setValue(assessment.examples);view.settingsFieldSelectors.hasTraining.prop("checked",true).change()}}while(view.numberOfCriteria<rubric.criteria.length){view.addNewCriterionToRubric()}while(view.numberOfCriteria>rubric.criteria.length){view.removeCriterionFromRubric(1)}for(i=0;i<rubric.criteria.length;i++){while(view.numberOfOptions[i+1]<rubric.criteria[i].options.length){view.addNewOptionToCriterion(view.liveElement,i+1)}while(view.numberOfOptions[i+1]>rubric.criteria[i].options.length){view.removeOptionFromCriterion(view.liveElement,i+1,1)}}for(i=0;i<rubric.criteria.length;i++){var criterion=rubric.criteria[i];var selectors=view.rubricCriteriaSelectors[i+1];selectors.name.prop("value",criterion.name);selectors.prompt.prop("value",criterion.prompt);selectors.feedback=criterion.feedback;for(var j=0;j<criterion.options.length;j++){var option=criterion.options[j];var optionSelectors=selectors.options[j+1];optionSelectors.name.prop("value",option.name);optionSelectors.points.prop("value",option.points);optionSelectors.explanation.prop("value",option.explanation)}}if(rubric.feedbackprompt){view.rubricFeedbackPrompt.prop("value",rubric.feedbackprompt);view.hasRubricFeedbackPrompt=true}else{view.rubricFeedbackPrompt.prop("value","");view.hasRubricFeedbackPrompt=false}}).fail(function(msg){view.showError(msg)})},save:function(){var view=this;this.server.checkReleased().done(function(isReleased){if(isReleased){view.confirmPostReleaseUpdate($.proxy(view.updateEditorContext,view))}else{view.updateEditorContext()}}).fail(function(errMsg){view.showError(errMsg)})},addSettingsAssessmentCheckboxListener:function(name,liveElement){$("#include_"+name,liveElement).change(function(){if(this.checked){$("#"+name+"_description_closed",liveElement).fadeOut("fast");$("#"+name+"_settings_editor",liveElement).fadeIn()}else{$("#"+name+"_settings_editor",liveElement).fadeOut("fast");$("#"+name+"_description_closed",liveElement).fadeIn()}})},confirmPostReleaseUpdate:function(onConfirm){var msg=gettext("This problem has already been released. Any changes will apply only to future assessments.");if(confirm(msg)){onConfirm()}},addNewCriterionToRubric:function(){var view=this;var liveElement=this.liveElement;var newCriterionID=this.numberOfCriteria+1;this.numberOfCriteria+=1;this.numberOfOptions[newCriterionID]=0;var criterionHtml=this.criterionHtmlTemplate.replace(new RegExp("C-C-C","g"),""+newCriterionID);$("#openassessment_criterion_list",liveElement).append(criterionHtml);liveElement=$("#openassessment_criterion_"+newCriterionID);$(".openassessment_criterion_option_list",liveElement).empty();view.rubricCriteriaSelectors[newCriterionID]={criterion:liveElement,name:$(".openassessment_criterion_name",liveElement).first(),prompt:$(".openassessment_criterion_prompt",liveElement).first(),options:[],feedback:"disabled"};$("input:radio[value=disabled]",liveElement).prop("checked",true);view.addNewOptionToCriterion(liveElement,newCriterionID);$("#openassessment_display_criterion_"+newCriterionID,liveElement).change(function(){if(this.checked){$("#openassessment_criterion_body_"+newCriterionID,liveElement).fadeIn()}else{$("#openassessment_criterion_body_"+newCriterionID,liveElement).fadeOut()}});$("#openassessment_criterion_"+newCriterionID+"_remove",liveElement).click(function(eventData){view.removeCriterionFromRubric(newCriterionID)});$("#openassessment_criterion_"+newCriterionID+"_add_option",liveElement).click(function(eventData){view.addNewOptionToCriterion(liveElement,newCriterionID)});$(".openassessment_feedback_remove_button",liveElement).click(function(eventData){$(".openassessment_criterion_feedback_direction",liveElement).fadeOut();$(".openassessment_criterion_feedback_header_open",liveElement).fadeOut();$(".openassessment_criterion_feedback_header_closed",liveElement).fadeIn();$(".openassessment_feedback_remove_button",liveElement).fadeOut();view.rubricCriteriaSelectors[newCriterionID].hasFeedback=false});$(".openassessment_criterion_feedback_header_closed",liveElement).click(function(eventData){$(".openassessment_criterion_feedback_direction",liveElement).fadeIn();$(".openassessment_criterion_feedback_header_open",liveElement).fadeIn();$(".openassessment_criterion_feedback_header_closed",liveElement).fadeOut();$(".openassessment_feedback_remove_button",liveElement).fadeIn();view.rubricCriteriaSelectors[newCriterionID].hasFeedback=true});$(".openassessment_criterion_feedback_header_closed",liveElement).hide()},removeCriterionFromRubric:function(criterionToRemove){var view=this;var numCriteria=view.numberOfCriteria;var selectors=view.rubricCriteriaSelectors;for(var i=criterionToRemove;i<numCriteria;i++){selectors[i].name.prop("value",selectors[i+1].name.prop("value"));selectors[i].prompt.prop("value",selectors[i+1].prompt.prop("value"));selectors[i].feedback=selectors[i+1].feedback;$('input:radio[value="disabled"]',selectors[i].criterion).prop("checked",true);while(view.numberOfOptions[i]<view.numberOfOptions[i+1]){view.addNewOptionToCriterion(selectors[i].criteria,i)}while(view.numberOfOptions[i]>view.numberOfOptions[i+1]){view.removeOptionFromCriterion(selectors[i].criteria,i,1)}var options1=selectors[i].options;var options2=selectors[i+1].options;var numOptions2=view.numberOfOptions[i+1];for(var j=1;j<numOptions2;j++){options1[j].points.prop("value",options2[j].points.prop("value"));options1[j].name.prop("value",options2[j].name.prop("value"));options1[j].explanation.prop("value",options2[j].explanation.prop("value"))}}view.rubricCriteriaSelectors[view.rubricCriteriaSelectors.length-1].criterion.remove();view.rubricCriteriaSelectors=view.rubricCriteriaSelectors.slice(0,numCriteria);view.numberOfOptions=view.numberOfOptions.slice(0,numCriteria);view.numberOfCriteria-=1},addNewOptionToCriterion:function(liveElement,criterionID){var view=this;var newOptionID=this.numberOfOptions[criterionID]+1;this.numberOfOptions[criterionID]+=1;var optionHtml=this.optionHtmlTemplate;optionHtml=optionHtml.replace(new RegExp("C-C-C","g"),""+criterionID);optionHtml=optionHtml.replace(new RegExp("O-O-O","g"),""+newOptionID);$("#openassessment_criterion_"+criterionID+"_options",liveElement).append(optionHtml);liveElement=$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID);view.rubricCriteriaSelectors[criterionID].options[newOptionID]={option:liveElement,points:$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_points",liveElement),name:$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_name",liveElement),explanation:$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_explanation",liveElement)};$("#openassessment_criterion_"+criterionID+"_option_"+newOptionID+"_remove",liveElement).click(function(eventData){view.removeOptionFromCriterion(liveElement,criterionID,newOptionID)})},removeOptionFromCriterion:function(liveElement,criterionID,optionToRemove){var view=this;var numberOfOptions=view.numberOfOptions[criterionID];var optionSelectors=view.rubricCriteriaSelectors[criterionID].options;for(var i=optionToRemove;i<numberOfOptions;i++){optionSelectors[i].points.prop("value",optionSelectors[i+1].points.prop("value"));optionSelectors[i].name.prop("value",optionSelectors[i+1].name.prop("value"));optionSelectors[i].explanation.prop("value",optionSelectors[i+1].explanation.prop("value"))}optionSelectors[optionSelectors.length-1].option.remove();view.rubricCriteriaSelectors[criterionID].options=view.rubricCriteriaSelectors[criterionID].options.slice(0,optionSelectors.length-1);view.numberOfOptions[criterionID]-=1},updateEditorContext:function(){this.runtime.notify("save",{state:"start"});var rubricCriteria=[];for(var i=1;i<=this.numberOfCriteria;i++){var selectorDict=this.rubricCriteriaSelectors[i];var criterionValueDict={order_num:i-1,name:selectorDict.name.prop("value"),prompt:selectorDict.prompt.prop("value"),feedback:$("#openassessment_criterion_"+i+"_feedback").val()};var optionSelectorList=selectorDict.options;var optionValueList=[];for(var j=1;j<=this.numberOfOptions[i];j++){var optionSelectors=optionSelectorList[j];optionValueList=optionValueList.concat([{order_num:j-1,points:this._getInt(optionSelectors.points),name:optionSelectors.name.val(),explanation:optionSelectors.explanation.val()}])}criterionValueDict.options=optionValueList;rubricCriteria=rubricCriteria.concat([criterionValueDict])}var assessments=[];if(this.settingsFieldSelectors.hasTraining.prop("checked")){assessments.push({name:"student-training",examples:this.studentTrainingExamplesCodeBox.getValue()})}if(this.settingsFieldSelectors.hasPeer.prop("checked")){assessments.push({name:"peer-assessment",must_grade:this._getInt(this.settingsFieldSelectors.peerMustGrade),must_be_graded_by:this._getInt(this.settingsFieldSelectors.peerGradedBy),start:this._getDateTime(this.settingsFieldSelectors.peerStart),due:this._getDateTime(this.settingsFieldSelectors.peerDue)})}if(this.settingsFieldSelectors.hasSelf.prop("checked")){assessments.push({name:"self-assessment",start:this._getDateTime(this.settingsFieldSelectors.selfStart),due:this._getDateTime(this.settingsFieldSelectors.selfDue)})}if(this.settingsFieldSelectors.hasAI.prop("checked")){assessments.push({name:"example-based-assessment",examples:this.aiTrainingExamplesCodeBox.getValue()})}var view=this;this.server.updateEditorContext({title:this.settingsFieldSelectors.titleField.val(),prompt:this.settingsFieldSelectors.promptBox.val(),feedbackPrompt:this.rubricFeedbackPrompt.val(),submissionStart:this._getDateTime(this.settingsFieldSelectors.submissionStartField),submissionDue:this._getDateTime(this.settingsFieldSelectors.submissionDueField),criteria:rubricCriteria,assessments:assessments}).done(function(){view.runtime.notify("save",{state:"end"})}).fail(function(msg){view.showError(msg)})},cancel:function(){this.runtime.notify("cancel",{})},showError:function(errorMsg){this.runtime.notify("error",{msg:errorMsg})},_getDateTime:function(selector){var dateStr=selector.val();if(dateStr===""){return null}debugger;var timestamp=Date.parse(dateStr);if(isNaN(timestamp)){return null}return new Date(timestamp).toISOString()},_getInt:function(selector){return parseInt(selector.val(),10)}};function OpenAssessmentEditor(runtime,element){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.StudioView(runtime,element,server);view.load()}OpenAssessment.GradeView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.GradeView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("grade").done(function(html){$("#openassessment__grade",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){baseView.showLoadError("grade",errMsg)})},installHandlers:function(){var sel=$("#openassessment__grade",this.element);this.baseView.setUpCollapseExpand(sel);var view=this;sel.find("#feedback__submit").click(function(eventObject){eventObject.preventDefault();view.submitFeedbackOnAssessment()})},feedbackText:function(text){if(typeof text==="undefined"){return $("#feedback__remarks__value",this.element).val()}else{$("#feedback__remarks__value",this.element).val(text)}},feedbackOptions:function(options){var view=this;if(typeof options==="undefined"){return $.map($(".feedback__overall__value:checked",view.element),function(element,index){return $(element).val()})}else{$(".feedback__overall__value",this.element).prop("checked",false);$.each(options,function(index,opt){$("#feedback__overall__value--"+opt,view.element).prop("checked",true)})}},setHidden:function(sel,hidden){sel.toggleClass("is--hidden",hidden);sel.attr("aria-hidden",hidden?"true":"false")},isHidden:function(sel){return sel.hasClass("is--hidden")&&sel.attr("aria-hidden")=="true"},feedbackState:function(newState){var containerSel=$(".submission__feedback__content",this.element);var instructionsSel=containerSel.find(".submission__feedback__instructions");var fieldsSel=containerSel.find(".submission__feedback__fields");var actionsSel=containerSel.find(".submission__feedback__actions");var transitionSel=containerSel.find(".transition__status");var messageSel=containerSel.find(".message--complete");if(typeof newState==="undefined"){var isSubmitting=containerSel.hasClass("is--transitioning")&&containerSel.hasClass("is--submitting")&&!this.isHidden(transitionSel)&&this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var hasSubmitted=containerSel.hasClass("is--submitted")&&this.isHidden(transitionSel)&&!this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var isOpen=!containerSel.hasClass("is--submitted")&&!containerSel.hasClass("is--transitioning")&&!containerSel.hasClass("is--submitting")&&this.isHidden(transitionSel)&&this.isHidden(messageSel)&&!this.isHidden(instructionsSel)&&!this.isHidden(fieldsSel)&&!this.isHidden(actionsSel);if(isOpen){return"open"}else if(isSubmitting){return"submitting"}else if(hasSubmitted){return"submitted"}else{throw"Invalid feedback state"}}else{if(newState=="open"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,false);this.setHidden(fieldsSel,false);this.setHidden(actionsSel,false);this.setHidden(transitionSel,true);this.setHidden(messageSel,true)}else if(newState=="submitting"){containerSel.toggleClass("is--transitioning",true);containerSel.toggleClass("is--submitting",true);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,false);this.setHidden(messageSel,true)}else if(newState=="submitted"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",true);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,true);this.setHidden(messageSel,false)}}},submitFeedbackOnAssessment:function(){var view=this;var baseView=this.baseView;$("#feedback__submit",this.element).toggleClass("is--disabled",true);view.feedbackState("submitting");this.server.submitFeedbackOnAssessment(this.feedbackText(),this.feedbackOptions()).done(function(){view.feedbackState("submitted")}).fail(function(errMsg){baseView.toggleActionError("feedback_assess",errMsg)})}};OpenAssessment.MessageView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.MessageView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("message").done(function(html){$("#openassessment__message",view.element).replaceWith(html)}).fail(function(errMsg){baseView.showLoadError("message",errMsg)})}};OpenAssessment.PeerView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.PeerView.prototype={load:function(){var view=this;this.server.render("peer_assessment").done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.installHandlers(false)}).fail(function(errMsg){view.baseView.showLoadError("peer-assessment")});view.baseView.loadMessageView()},loadContinuedAssessment:function(){var view=this;this.server.renderContinuedPeer().done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.installHandlers(true)}).fail(function(errMsg){view.baseView.showLoadError("peer-assessment")})},installHandlers:function(isContinuedAssessment){var sel=$("#openassessment__peer-assessment",this.element);var view=this;this.baseView.setUpCollapseExpand(sel,$.proxy(view.loadContinuedAssessment,view));var rubricSelector=$("#peer-assessment--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(view.peerSubmitEnabled,view))}sel.find("#peer-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();if(!isContinuedAssessment){view.peerAssess()}else{view.continuedPeerAssess()}})},peerSubmitEnabled:function(enabled){var button=$("#peer-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled)}},peerAssess:function(){var view=this;var baseView=view.baseView;this.peerAssessRequest(function(){view.load();baseView.loadAssessmentModules();baseView.scrollToTop()})},continuedPeerAssess:function(){var view=this;var gradeView=this.baseView.gradeView;var baseView=view.baseView;view.peerAssessRequest(function(){view.loadContinuedAssessment();gradeView.load();baseView.scrollToTop()})},peerAssessRequest:function(successFunction){var view=this;view.baseView.toggleActionError("peer",null);view.peerSubmitEnabled(false);this.server.peerAssess(this.rubric.optionsSelected(),this.rubric.criterionFeedback(),this.overallFeedback()).done(successFunction).fail(function(errMsg){view.baseView.toggleActionError("peer",errMsg);view.peerSubmitEnabled(true)})},overallFeedback:function(overallFeedback){var selector="#assessment__rubric__question--feedback__value";if(typeof overallFeedback==="undefined"){return $(selector,this.element).val()}else{$(selector,this.element).val(overallFeedback)}}};OpenAssessment.ResponseView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.savedResponse="";this.lastChangeTime=Date.now();this.errorOnLastSave=false;this.autoSaveTimerId=null};OpenAssessment.ResponseView.prototype={AUTO_SAVE_POLL_INTERVAL:2e3,AUTO_SAVE_WAIT:3e4,load:function(){var view=this;this.server.render("submission").done(function(html){$("#openassessment__response",view.element).replaceWith(html);view.installHandlers();view.setAutoSaveEnabled(true)}).fail(function(errMsg){view.baseView.showLoadError("response")})},installHandlers:function(){var sel=$("#openassessment__response",this.element);var view=this;this.baseView.setUpCollapseExpand(sel);this.savedResponse=this.response();var handleChange=function(eventData){view.handleResponseChanged()};sel.find("#submission__answer__value").on("change keyup drop paste",handleChange);sel.find("#step--response__submit").click(function(eventObject){eventObject.preventDefault();view.submit()});sel.find("#submission__save").click(function(eventObject){eventObject.preventDefault();view.save()})},setAutoSaveEnabled:function(enabled){if(enabled){if(this.autoSaveTimerId===null){this.autoSaveTimerId=setInterval($.proxy(this.autoSave,this),this.AUTO_SAVE_POLL_INTERVAL)}}else{if(this.autoSaveTimerId!==null){clearInterval(this.autoSaveTimerId)}}},submitEnabled:function(enabled){var sel=$("#step--response__submit",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled)}},saveEnabled:function(enabled){var sel=$("#submission__save",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled)}},saveStatus:function(msg){var sel=$("#response__save_status h3",this.element);if(typeof msg==="undefined"){return sel.text()}else{var label=gettext("Status of Your Response");sel.html('<span class="sr">'+label+":"+"</span>\n"+msg)}},unsavedWarningEnabled:function(enabled){if(typeof enabled==="undefined"){return window.onbeforeunload!==null}else{if(enabled){window.onbeforeunload=function(){return gettext("If you leave this page without saving or submitting your response, you'll lose any work you've done on the response.")}}else{window.onbeforeunload=null}}},response:function(text){var sel=$("#submission__answer__value",this.element);if(typeof text==="undefined"){return sel.val()}else{sel.val(text)}},responseChanged:function(){var currentResponse=$.trim(this.response());var savedResponse=$.trim(this.savedResponse);return savedResponse!==currentResponse},autoSave:function(){var timeSinceLastChange=Date.now()-this.lastChangeTime;if(this.responseChanged()&&timeSinceLastChange>this.AUTO_SAVE_WAIT&&!this.errorOnLastSave){this.save()}},handleResponseChanged:function(){var isBlank=$.trim(this.response())!=="";this.submitEnabled(isBlank);if(this.responseChanged()){this.saveEnabled(isBlank);this.saveStatus(gettext("This response has not been saved."));this.unsavedWarningEnabled(true)}this.lastChangeTime=Date.now()},save:function(){this.errorOnLastSave=false;this.saveStatus(gettext("Saving..."));this.baseView.toggleActionError("save",null);this.unsavedWarningEnabled(false);var view=this;var savedResponse=this.response();this.server.save(savedResponse).done(function(){view.savedResponse=savedResponse;var currentResponse=view.response();view.submitEnabled(currentResponse!=="");if(currentResponse==savedResponse){view.saveEnabled(false);view.saveStatus(gettext("This response has been saved but not submitted."))}}).fail(function(errMsg){view.saveStatus(gettext("Error"));view.baseView.toggleActionError("save",errMsg);view.errorOnLastSave=true})},submit:function(){this.submitEnabled(false);var view=this;var baseView=this.baseView;this.confirmSubmission().pipe(function(){var submission=$("#submission__answer__value",view.element).val();baseView.toggleActionError("response",null);return view.server.submit(submission)}).done($.proxy(view.moveToNextStep,view)).fail(function(errCode,errMsg){if(errCode=="ENOMULTI"){view.moveToNextStep()}else{if(errMsg){baseView.toggleActionError("submit",errMsg)}view.submitEnabled(true)}})},moveToNextStep:function(){this.load();this.baseView.loadAssessmentModules();this.unsavedWarningEnabled(false)},confirmSubmission:function(){var msg="You're about to submit your response for this assignment. "+"After you submit this response, you can't change it or submit a new response.";return $.Deferred(function(defer){if(confirm(msg)){defer.resolve()}else{defer.reject()}})}};OpenAssessment.Rubric=function(element){this.element=element};OpenAssessment.Rubric.prototype={criterionFeedback:function(criterionFeedback){var selector="textarea.answer__value";var feedback={};$(selector,this.element).each(function(index,sel){if(typeof criterionFeedback!=="undefined"){$(sel).val(criterionFeedback[sel.name]);feedback[sel.name]=criterionFeedback[sel.name]}else{feedback[sel.name]=$(sel).val()}});return feedback},optionsSelected:function(optionsSelected){var selector="input[type=radio]";if(typeof optionsSelected==="undefined"){var options={};$(selector+":checked",this.element).each(function(index,sel){options[sel.name]=sel.value});return options}else{$(selector,this.element).prop("checked",false);$(selector,this.element).each(function(index,sel){if(optionsSelected.hasOwnProperty(sel.name)){if(sel.value==optionsSelected[sel.name]){$(sel).prop("checked",true)}}})}},canSubmitCallback:function(callback){$(this.element).change(function(){var numChecked=$("input[type=radio]:checked",this).length;var numAvailable=$(".field--radio.assessment__rubric__question",this).length;var canSubmit=numChecked==numAvailable;callback(canSubmit)})},showCorrections:function(corrections){var selector="input[type=radio]";var hasErrors=false;$(selector,this.element).each(function(index,sel){var listItem=$(sel).parents(".assessment__rubric__question");if(corrections.hasOwnProperty(sel.name)){hasErrors=true;listItem.find(".message--incorrect").removeClass("is--hidden");listItem.find(".message--correct").addClass("is--hidden")}else{listItem.find(".message--correct").removeClass("is--hidden");listItem.find(".message--incorrect").addClass("is--hidden")}});return hasErrors}};OpenAssessment.SelfView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.SelfView.prototype={load:function(){var view=this;this.server.render("self_assessment").done(function(html){$("#openassessment__self-assessment",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.showLoadError("self-assessment")})},installHandlers:function(){var view=this;var sel=$("#openassessment__self-assessment",view.element);this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#self-assessment--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.selfSubmitEnabled,this))}sel.find("#self-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.selfAssess()})},selfSubmitEnabled:function(enabled){var button=$("#self-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled)}},selfAssess:function(){var view=this;var baseView=this.baseView;baseView.toggleActionError("self",null);
view.installHandlers()}).fail(function(errMsg){view.showLoadError("self-assessment")})},installHandlers:function(){var view=this;var sel=$("#openassessment__self-assessment",view.element);this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#self-assessment--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.selfSubmitEnabled,this))}sel.find("#self-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.selfAssess()})},selfSubmitEnabled:function(enabled){var button=$("#self-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled)}},selfAssess:function(){var view=this;var baseView=this.baseView;baseView.toggleActionError("self",null);view.selfSubmitEnabled(false);var options=this.rubric.optionsSelected();this.server.selfAssess(options).done(function(){baseView.loadAssessmentModules();baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("self",errMsg);view.selfSubmitEnabled(true)})}};OpenAssessment.Server=function(runtime,element){this.runtime=runtime;this.element=element};OpenAssessment.Server.prototype={url:function(handler){return this.runtime.handlerUrl(this.element,handler)},render:function(component){var url=this.url("render_"+component);return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html"}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,[gettext("This section could not be loaded.")])})}).promise()},renderContinuedPeer:function(){var url=this.url("render_peer_assessment");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:{continue_grading:true}}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,[gettext("This section could not be loaded.")])})}).promise()},studentInfo:function(student_id){var url=this.url("render_student_info");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:{student_id:student_id}}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,[gettext("This section could not be loaded.")])})}).promise()},submit:function(submission){var url=this.url("submit");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission})}).done(function(data){var success=data[0];if(success){var studentId=data[1];var attemptNum=data[2];defer.resolveWith(this,[studentId,attemptNum])}else{var errorNum=data[1];var errorMsg=data[2];defer.rejectWith(this,[errorNum,errorMsg])}}).fail(function(data){defer.rejectWith(this,["AJAX",gettext("This response could not be submitted.")])})}).promise()},save:function(submission){var url=this.url("save_submission");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission})}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This response could not be saved.")])})}).promise()},submitFeedbackOnAssessment:function(text,options){var url=this.url("submit_feedback");var payload=JSON.stringify({feedback_text:text,feedback_options:options});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This feedback could not be submitted.")])})}).promise()},peerAssess:function(optionsSelected,criterionFeedback,overallFeedback){var url=this.url("peer_assess");var payload=JSON.stringify({options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})}).promise()},selfAssess:function(optionsSelected){var url=this.url("self_assess");var payload=JSON.stringify({options_selected:optionsSelected});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},trainingAssess:function(optionsSelected){var url=this.url("training_assess");var payload=JSON.stringify({options_selected:optionsSelected});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolveWith(this,[data.corrections])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},scheduleTraining:function(){var url=this.url("schedule_training");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},rescheduleUnfinishedTasks:function(){var url=this.url("reschedule_unfinished_tasks");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("One or more rescheduling tasks failed.")])})})},loadEditorContext:function(){var url=this.url("editor_context");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.prompt,data.rubric,data.title,data.submission_start,data.submission_due,data.assessments])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This problem could not be loaded.")])})}).promise()},updateEditorContext:function(prompt,rubric,title,sub_start,sub_due,assessments){var url=this.url("update_editor_context");var payload=JSON.stringify({prompt:prompt,rubric:rubric,title:title,submission_start:sub_start,submission_due:sub_due,assessments:assessments});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This problem could not be saved.")])})}).promise()},checkReleased:function(){var url=this.url("check_released");var payload='""';return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolveWith(this,[data.is_released])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("The server could not be contacted.")])})}).promise()}};if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}OpenAssessment.StaffInfoView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.StaffInfoView.prototype={load:function(){var view=this;if($("#openassessment__staff-info",view.element).length>0){this.server.render("staff_info").done(function(html){$("#openassessment__staff-info",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.baseView.showLoadError("staff_info")})}},loadStudentInfo:function(){var view=this;var sel=$("#openassessment__staff-info",this.element);var student_id=sel.find("#openassessment__student_id").val();this.server.studentInfo(student_id).done(function(html){$("#openassessment__student-info",view.element).replaceWith(html)}).fail(function(errMsg){view.showLoadError("student_info")})},installHandlers:function(){var sel=$("#openassessment__staff-info",this.element);var view=this;if(sel.length<=0){return}this.baseView.setUpCollapseExpand(sel,function(){});sel.find("#openassessment_student_info_form").submit(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});sel.find("#submit_student_id").click(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});sel.find("#schedule_training").click(function(eventObject){eventObject.preventDefault();view.scheduleTraining()});sel.find("#reschedule_unfinished_tasks").click(function(eventObject){eventObject.preventDefault();view.rescheduleUnfinishedTasks()})},scheduleTraining:function(){var view=this;this.server.scheduleTraining().done(function(msg){$("#schedule_training_message",this.element).text(msg)}).fail(function(errMsg){$("#schedule_training_message",this.element).text(errMsg)})},rescheduleUnfinishedTasks:function(){var view=this;this.server.rescheduleUnfinishedTasks().done(function(msg){$("#reschedule_unfinished_tasks_message",this.element).text(msg)}).fail(function(errMsg){$("#reschedule_unfinished_tasks_message",this.element).text(errMsg)})}};OpenAssessment.StudentTrainingView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.StudentTrainingView.prototype={load:function(){var view=this;this.server.render("student_training").done(function(html){$("#openassessment__student-training",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.baseView.showLoadError("student-training")})},installHandlers:function(){var sel=$("#openassessment__student-training",this.element);var view=this;this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#student-training--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.assessButtonEnabled,this))}sel.find("#student-training--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.assess()})},assess:function(){this.assessButtonEnabled(false);var options={};if(this.rubric!==null){options=this.rubric.optionsSelected()}var view=this;var baseView=this.baseView;this.server.trainingAssess(options).done(function(corrections){var incorrect=$("#openassessment__student-training--incorrect",this.element);var instructions=$("#openassessment__student-training--instructions",this.element);if(!view.rubric.showCorrections(corrections)){view.load();baseView.loadAssessmentModules();incorrect.addClass("is--hidden");instructions.removeClass("is--hidden")}else{instructions.addClass("is--hidden");incorrect.removeClass("is--hidden")}baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("student-training",errMsg);view.assessButtonEnabled(true)})},assessButtonEnabled:function(isEnabled){var button=$("#student-training--001__assessment__submit",this.element);if(typeof isEnabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!isEnabled)}}}; view.selfSubmitEnabled(false);var options=this.rubric.optionsSelected();this.server.selfAssess(options).done(function(){baseView.loadAssessmentModules();baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("self",errMsg);view.selfSubmitEnabled(true)})}};OpenAssessment.Server=function(runtime,element){this.runtime=runtime;this.element=element};OpenAssessment.Server.prototype={url:function(handler){return this.runtime.handlerUrl(this.element,handler)},render:function(component){var url=this.url("render_"+component);return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html"}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,[gettext("This section could not be loaded.")])})}).promise()},renderContinuedPeer:function(){var url=this.url("render_peer_assessment");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:{continue_grading:true}}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,[gettext("This section could not be loaded.")])})}).promise()},studentInfo:function(student_id){var url=this.url("render_student_info");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:{student_id:student_id}}).done(function(data){defer.resolveWith(this,[data])}).fail(function(data){defer.rejectWith(this,[gettext("This section could not be loaded.")])})}).promise()},submit:function(submission){var url=this.url("submit");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission})}).done(function(data){var success=data[0];if(success){var studentId=data[1];var attemptNum=data[2];defer.resolveWith(this,[studentId,attemptNum])}else{var errorNum=data[1];var errorMsg=data[2];defer.rejectWith(this,[errorNum,errorMsg])}}).fail(function(data){defer.rejectWith(this,["AJAX",gettext("This response could not be submitted.")])})}).promise()},save:function(submission){var url=this.url("save_submission");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission})}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This response could not be saved.")])})}).promise()},submitFeedbackOnAssessment:function(text,options){var url=this.url("submit_feedback");var payload=JSON.stringify({feedback_text:text,feedback_options:options});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This feedback could not be submitted.")])})}).promise()},peerAssess:function(optionsSelected,criterionFeedback,overallFeedback){var url=this.url("peer_assess");var payload=JSON.stringify({options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})}).promise()},selfAssess:function(optionsSelected){var url=this.url("self_assess");var payload=JSON.stringify({options_selected:optionsSelected});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},trainingAssess:function(optionsSelected){var url=this.url("training_assess");var payload=JSON.stringify({options_selected:optionsSelected});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolveWith(this,[data.corrections])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},scheduleTraining:function(){var url=this.url("schedule_training");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},rescheduleUnfinishedTasks:function(){var url=this.url("reschedule_unfinished_tasks");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("One or more rescheduling tasks failed.")])})})},loadEditorContext:function(){var url=this.url("editor_context");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""'}).done(function(data){if(data.success){defer.resolveWith(this,[data.prompt,data.rubric,data.title,data.submission_start,data.submission_due,data.assessments])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This problem could not be loaded.")])})}).promise()},updateEditorContext:function(kwargs){var url=this.url("update_editor_context");var payload=JSON.stringify({prompt:kwargs.prompt,feedback_prompt:kwargs.feedbackPrompt,title:kwargs.title,submission_start:kwargs.submissionStart,submission_due:kwargs.submissionDue,criteria:kwargs.criteria,assessments:kwargs.assessments});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("This problem could not be saved.")])})}).promise()},checkReleased:function(){var url=this.url("check_released");var payload='""';return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload}).done(function(data){if(data.success){defer.resolveWith(this,[data.is_released])}else{defer.rejectWith(this,[data.msg])}}).fail(function(data){defer.rejectWith(this,[gettext("The server could not be contacted.")])})}).promise()}};if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}OpenAssessment.StaffInfoView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.StaffInfoView.prototype={load:function(){var view=this;if($("#openassessment__staff-info",view.element).length>0){this.server.render("staff_info").done(function(html){$("#openassessment__staff-info",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.baseView.showLoadError("staff_info")})}},loadStudentInfo:function(){var view=this;var sel=$("#openassessment__staff-info",this.element);var student_id=sel.find("#openassessment__student_id").val();this.server.studentInfo(student_id).done(function(html){$("#openassessment__student-info",view.element).replaceWith(html)}).fail(function(errMsg){view.showLoadError("student_info")})},installHandlers:function(){var sel=$("#openassessment__staff-info",this.element);var view=this;if(sel.length<=0){return}this.baseView.setUpCollapseExpand(sel,function(){});sel.find("#openassessment_student_info_form").submit(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});sel.find("#submit_student_id").click(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});sel.find("#schedule_training").click(function(eventObject){eventObject.preventDefault();view.scheduleTraining()});sel.find("#reschedule_unfinished_tasks").click(function(eventObject){eventObject.preventDefault();view.rescheduleUnfinishedTasks()})},scheduleTraining:function(){var view=this;this.server.scheduleTraining().done(function(msg){$("#schedule_training_message",this.element).text(msg)}).fail(function(errMsg){$("#schedule_training_message",this.element).text(errMsg)})},rescheduleUnfinishedTasks:function(){var view=this;this.server.rescheduleUnfinishedTasks().done(function(msg){$("#reschedule_unfinished_tasks_message",this.element).text(msg)}).fail(function(errMsg){$("#reschedule_unfinished_tasks_message",this.element).text(errMsg)})}};OpenAssessment.StudentTrainingView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.StudentTrainingView.prototype={load:function(){var view=this;this.server.render("student_training").done(function(html){$("#openassessment__student-training",view.element).replaceWith(html);view.installHandlers()}).fail(function(errMsg){view.baseView.showLoadError("student-training")})},installHandlers:function(){var sel=$("#openassessment__student-training",this.element);var view=this;this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#student-training--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.assessButtonEnabled,this))}sel.find("#student-training--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.assess()})},assess:function(){this.assessButtonEnabled(false);var options={};if(this.rubric!==null){options=this.rubric.optionsSelected()}var view=this;var baseView=this.baseView;this.server.trainingAssess(options).done(function(corrections){var incorrect=$("#openassessment__student-training--incorrect",this.element);var instructions=$("#openassessment__student-training--instructions",this.element);if(!view.rubric.showCorrections(corrections)){view.load();baseView.loadAssessmentModules();incorrect.addClass("is--hidden");instructions.removeClass("is--hidden")}else{instructions.addClass("is--hidden");incorrect.removeClass("is--hidden")}baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("student-training",errMsg);view.assessButtonEnabled(true)})},assessButtonEnabled:function(isEnabled){var button=$("#student-training--001__assessment__submit",this.element);if(typeof isEnabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!isEnabled)}}};
\ No newline at end of file \ No newline at end of file
...@@ -10,140 +10,23 @@ describe("OpenAssessment.StudioView", function() { ...@@ -10,140 +10,23 @@ describe("OpenAssessment.StudioView", function() {
// Stub server that returns dummy data or reports errors. // Stub server that returns dummy data or reports errors.
var StubServer = function() { var StubServer = function() {
this.loadError = false;
this.updateError = false; this.updateError = false;
this.promptBox = "";
this.titleField = "";
this.submissionStartField = "";
this.submissionDueField = "";
this.hasPeer = true;
this.hasSelf = true;
this.hasTraining = false;
this.hasAI = false;
this.peerMustGrade = 2;
this.peerGradedBy = 3;
this.peerStart = '';
this.peerDue = '';
this.selfStart = '';
this.selfDue = '';
this.aiTrainingExamplesCodeBox = "";
this.studentTrainingExamplesCodeBox = "";
this.isReleased = false; this.isReleased = false;
this.receivedData = null;
this.rubric = { this.successPromise = $.Deferred(function(defer) {
prompt: 'This is the feedback prompt', defer.resolve();
criteria: [ });
{
order_num: 0,
name: 'This is the criterion name',
prompt: 'this is the criterion prompt',
feedback: 'disabled',
options: [
{
order_num: 0,
name: 'Did real bad',
points: 0,
explanation: 'Showed as little effort as I did making this test case interesting.'
}
]
}
]
};
this.errorPromise = $.Deferred(function(defer) { this.errorPromise = $.Deferred(function(defer) {
defer.rejectWith(this, ['Test error']); defer.rejectWith(this, ['Test error']);
}).promise(); }).promise();
this.loadEditorContext = function() { this.updateEditorContext = function(kwargs) {
var prompt = this.promptBox; if (this.updateError) {
var rubric = this.rubric;
var title = this.titleField;
var submission_start = this.submissionStartField;
var submission_due = this.submissionDueField;
var assessments = [];
if (this.hasTraining){
assessments = assessments.concat({
"name": "student-training",
"examples": this.studentTrainingExamplesCodeBox
});
}
if (this.hasPeer){
assessments = assessments.concat({
"name": "peer-assessment",
"start": this.peerStart,
"due": this.peerDue,
"must_grade": this.peerMustGrade,
"must_be_graded_by": this.peerGradedBy
});
}
if (this.hasSelf){
assessments = assessments.concat({
"name": "self-assessment",
"start": this.selfStart,
"due": this.selfDue
});
}
if (this.hasAI){
assessments = assessments.concat({
"name": "example-based-assessment",
"examples": this.aiTrainingExamplesCodeBox
});
}
if (!this.loadError) {
return $.Deferred(function(defer) {
defer.resolveWith(this, [prompt, rubric, title, submission_start, submission_due, assessments]);
}).promise();
}
else {
return this.errorPromise; return this.errorPromise;
} }
};
this.updateEditorContext = function(prompt, rubric, title, sub_start, sub_due, assessments) {
if (!this.updateError) {
this.promptBox = prompt;
this.rubric = rubric;
this.titleField = title;
this.submissionStartField = sub_start;
this.submissionDueField = sub_due;
this.hasPeer = false;
this.hasSelf = false;
this.hasAI = false;
this.hasTraining = false;
for (var i = 0; i < assessments.length; i++) {
var assessment = assessments[i];
if (assessment.name == 'peer-assessment') {
this.hasPeer = true;
this.peerMustGrade = assessment.must_grade;
this.peerGradedBy = assessment.must_be_graded_by;
this.peerStart = assessment.start;
this.peerDue = assessment.due;
} else if (assessment.name == 'self-assessment') {
this.hasSelf = true;
this.selfStart = assessment.start;
this.selfDue = assessment.due;
} else if (assessment.name == 'example-based-assessment') {
this.hasAI = true;
this.aiTrainingExamplesCodeBox = assessment.examples;
} else if (assessment.name == 'student-training') {
this.hasTraining = true;
this.studentTrainingExamplesCodeBox = assessment.examples;
}
}
return $.Deferred(function(defer) {
defer.resolve();
}).promise();
}
else { else {
return this.errorPromise; this.receivedData = kwargs;
return this.successPromise;
} }
}; };
...@@ -158,71 +41,7 @@ describe("OpenAssessment.StudioView", function() { ...@@ -158,71 +41,7 @@ describe("OpenAssessment.StudioView", function() {
var server = null; var server = null;
var view = null; var view = null;
var prompt = "How much do you like waffles?";
var rubric = {
criteria: [
{
order_num: 0,
name: "Proper appreciation of Gravity",
prompt: "How much respect did the person give waffles?",
feedback: "disabled",
options: [
{
order_num: 0,
points: 0,
name: "No",
explanation: "Not enough"
},
{
order_num: 1,
points: 2,
name: "Yes",
explanation: "An appropriate Amount"
}
]
}
]
};
var title = "The most important of all questions.";
var subStart = "";
var subDue = "2014-10-1T10:00:00";
var assessments = [
{
"name": "student-training",
"examples":
"<examples>"+
"<example>" +
"<answer>ẗëṡẗ äṅṡẅëṛ</answer>" +
"<select criterion=\"Test criterion\" option=\"Yes\" />" +
"<select criterion=\"Another test criterion\" option=\"No\" />" +
"</example>" +
"<example>" +
"<answer>äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ</answer>" +
"<select criterion=\"Another test criterion\" option=\"Yes\" />" +
"<select criterion=\"Test criterion\" option=\"No\" />" +
"</example>"+
"</examples>",
"start": "",
"due": ""
},
{
"name": "peer-assessment",
"must_grade": 5,
"must_be_graded_by": 3,
"start": "2014-10-04T00:00:00",
"due": ""
},
{
"name": "self-assessment",
"start": "",
"due": ""
}
];
beforeEach(function() { beforeEach(function() {
// Load the DOM fixture // Load the DOM fixture
jasmine.getFixtures().fixturesPath = 'base/fixtures'; jasmine.getFixtures().fixturesPath = 'base/fixtures';
loadFixtures('oa_edit.html'); loadFixtures('oa_edit.html');
...@@ -238,19 +57,10 @@ describe("OpenAssessment.StudioView", function() { ...@@ -238,19 +57,10 @@ describe("OpenAssessment.StudioView", function() {
view = new OpenAssessment.StudioView(runtime, el, server); view = new OpenAssessment.StudioView(runtime, el, server);
}); });
it("loads the editor context definition", function() { it("saves the editor context definition", function() {
// Initialize the view // Update the context
view.load(); view.settingsFieldSelectors.titleField.val('THIS IS THE NEW TITLE');
view.settingsFieldSelectors.submissionStartField.val('2014-01-01');
// Expect that the XML definition(s) were loaded
var prompt = view.settingsFieldSelectors.promptBox.prop('value');
expect(prompt).toEqual('');
});
it("saves the Editor Context definition", function() {
// Update the Context
view.settingsFieldSelectors.titleField.prop('value', 'THIS IS THE NEW TITLE');
// Save the updated editor definition // Save the updated editor definition
view.save(); view.save();
...@@ -260,7 +70,8 @@ describe("OpenAssessment.StudioView", function() { ...@@ -260,7 +70,8 @@ describe("OpenAssessment.StudioView", function() {
expect(runtime.notify).toHaveBeenCalledWith('save', {state: 'end'}); expect(runtime.notify).toHaveBeenCalledWith('save', {state: 'end'});
// Expect the server's context to have been updated // Expect the server's context to have been updated
expect(server.titleField).toEqual('THIS IS THE NEW TITLE'); expect(server.receivedData.title).toEqual('THIS IS THE NEW TITLE');
expect(server.receivedData.submissionStart).toEqual('2014-01-01T00:00:00.000Z');
}); });
it("confirms changes for a released problem", function() { it("confirms changes for a released problem", function() {
...@@ -279,44 +90,11 @@ describe("OpenAssessment.StudioView", function() { ...@@ -279,44 +90,11 @@ describe("OpenAssessment.StudioView", function() {
expect(view.confirmPostReleaseUpdate).toHaveBeenCalled(); expect(view.confirmPostReleaseUpdate).toHaveBeenCalled();
}); });
it("full integration test for load and update_editor_context", function() {
server.updateEditorContext(prompt, rubric, title, subStart, subDue, assessments);
view.load();
expect(view.settingsFieldSelectors.promptBox.prop('value')).toEqual(prompt);
expect(view.settingsFieldSelectors.titleField.prop('value')).toEqual(title);
expect(view.settingsFieldSelectors.submissionStartField.prop('value')).toEqual(subStart);
expect(view.settingsFieldSelectors.submissionDueField.prop('value')).toEqual(subDue);
expect(view.settingsFieldSelectors.hasPeer.prop('checked')).toEqual(true);
expect(view.settingsFieldSelectors.hasSelf.prop('checked')).toEqual(true);
expect(view.settingsFieldSelectors.hasAI.prop('checked')).toEqual(false);
expect(view.settingsFieldSelectors.hasTraining.prop('checked')).toEqual(true);
expect(view.settingsFieldSelectors.peerMustGrade.prop('value')).toEqual('5');
expect(view.settingsFieldSelectors.peerGradedBy.prop('value')).toEqual('3');
expect(view.settingsFieldSelectors.peerDue.prop('value')).toEqual("");
expect(view.settingsFieldSelectors.selfStart.prop('value')).toEqual("");
expect(view.settingsFieldSelectors.selfDue.prop('value')).toEqual("");
expect(view.aiTrainingExamplesCodeBox.getValue()).toEqual("");
expect(view.studentTrainingExamplesCodeBox.getValue()).toEqual(assessments[0].examples);
expect(view.settingsFieldSelectors.peerStart.prop('value')).toEqual("2014-10-04T00:00:00");
view.settingsFieldSelectors.titleField.prop('value', "This is the new title.");
view.updateEditorContext();
expect(server.titleField).toEqual("This is the new title.");
});
it("cancels editing", function() { it("cancels editing", function() {
view.cancel(); view.cancel();
expect(runtime.notify).toHaveBeenCalledWith('cancel', {}); expect(runtime.notify).toHaveBeenCalledWith('cancel', {});
}); });
it("displays an error when server reports a load XML error", function() {
server.loadError = true;
view.load();
expect(runtime.notify).toHaveBeenCalledWith('error', {msg: 'Test error'});
});
it("displays an error when server reports an update XML error", function() { it("displays an error when server reports an update XML error", function() {
server.updateError = true; server.updateError = true;
view.save(); view.save();
......
...@@ -31,6 +31,7 @@ describe("OpenAssessment.Server", function() { ...@@ -31,6 +31,7 @@ describe("OpenAssessment.Server", function() {
}; };
var PROMPT = "Hello this is the prompt yes."; var PROMPT = "Hello this is the prompt yes.";
var FEEDBACK_PROMPT = "Prompt for feedback";
var RUBRIC = '<rubric>'+ var RUBRIC = '<rubric>'+
'<criterion>'+ '<criterion>'+
...@@ -51,6 +52,14 @@ describe("OpenAssessment.Server", function() { ...@@ -51,6 +52,14 @@ describe("OpenAssessment.Server", function() {
'</criterion>'+ '</criterion>'+
'</rubric>'; '</rubric>';
var CRITERIA = [
'criteria',
'objects',
'would',
'be',
'here'
];
var ASSESSMENTS = [ var ASSESSMENTS = [
{ {
"name": "peer-assessment", "name": "peer-assessment",
...@@ -226,14 +235,25 @@ describe("OpenAssessment.Server", function() { ...@@ -226,14 +235,25 @@ describe("OpenAssessment.Server", function() {
it("updates the XBlock's Context definition", function() { it("updates the XBlock's Context definition", function() {
stubAjax(true, { success: true }); stubAjax(true, { success: true });
server.updateEditorContext( server.updateEditorContext({
PROMPT, RUBRIC, TITLE, SUBMISSION_START, SUBMISSION_DUE, ASSESSMENTS prompt: PROMPT,
); feedbackPrompt: FEEDBACK_PROMPT,
title: TITLE,
submissionStart: SUBMISSION_START,
submissionDue: SUBMISSION_DUE,
criteria: CRITERIA,
assessments: ASSESSMENTS
});
expect($.ajax).toHaveBeenCalledWith({ expect($.ajax).toHaveBeenCalledWith({
type: "POST", url: '/update_editor_context', type: "POST", url: '/update_editor_context',
data: JSON.stringify({ data: JSON.stringify({
prompt: PROMPT, rubric: RUBRIC, title: TITLE, submission_start: SUBMISSION_START, prompt: PROMPT,
submission_due: SUBMISSION_DUE, assessments: ASSESSMENTS feedback_prompt: FEEDBACK_PROMPT,
title: TITLE,
submission_start: SUBMISSION_START,
submission_due: SUBMISSION_DUE,
criteria: CRITERIA,
assessments: ASSESSMENTS
}) })
}); });
}); });
......
...@@ -68,7 +68,6 @@ OpenAssessment.StudioView = function(runtime, element, server) { ...@@ -68,7 +68,6 @@ OpenAssessment.StudioView = function(runtime, element, server) {
this.numberOfOptions = []; this.numberOfOptions = [];
this.rubricCriteriaSelectors = []; this.rubricCriteriaSelectors = [];
this.rubricFeedbackPrompt = $('#openassessment_rubric_feedback', liveElement); this.rubricFeedbackPrompt = $('#openassessment_rubric_feedback', liveElement);
this.hasRubricFeedbackPrompt = true;
$('#openassessment_criterion_list', liveElement).empty(); $('#openassessment_criterion_list', liveElement).empty();
this.addNewCriterionToRubric(); this.addNewCriterionToRubric();
...@@ -96,25 +95,6 @@ OpenAssessment.StudioView = function(runtime, element, server) { ...@@ -96,25 +95,6 @@ OpenAssessment.StudioView = function(runtime, element, server) {
view.addNewCriterionToRubric(liveElement); view.addNewCriterionToRubric(liveElement);
}); });
// Adds a listener which removes rubric feedback
$("#openassessment_rubric_feedback_remove", liveElement). click( function(eventData){
$("#openassessment_rubric_feedback_header_open", liveElement).fadeOut();
$("#openassessment_rubric_feedback_input_wrapper", liveElement).fadeOut();
$("#openassessment_rubric_feedback_header_closed", liveElement).fadeIn();
view.hasRubricFeedbackPrompt = false;
});
// Adds a listener which adds rubric feedback if not already displayed.
$("#openassessment_rubric_feedback_header_closed", liveElement). click( function(eventData){
$("#openassessment_rubric_feedback_header_closed", liveElement).fadeOut();
$("#openassessment_rubric_feedback_header_open", liveElement).fadeIn();
$("#openassessment_rubric_feedback_input_wrapper", liveElement).fadeIn();
view.hasRubricFeedbackPrompt = true;
});
// Initially Hides the rubric "add rubric feedback" div
$("#openassessment_rubric_feedback_header_closed", liveElement).hide();
}; };
OpenAssessment.StudioView.prototype = { OpenAssessment.StudioView.prototype = {
...@@ -339,7 +319,6 @@ OpenAssessment.StudioView.prototype = { ...@@ -339,7 +319,6 @@ OpenAssessment.StudioView.prototype = {
// Hides the criterion header used for adding // Hides the criterion header used for adding
$(".openassessment_criterion_feedback_header_closed", liveElement).hide(); $(".openassessment_criterion_feedback_header_closed", liveElement).hide();
}, },
/** /**
...@@ -444,7 +423,7 @@ OpenAssessment.StudioView.prototype = { ...@@ -444,7 +423,7 @@ OpenAssessment.StudioView.prototype = {
criterionID (string): The criterion ID that we are deleting from criterionID (string): The criterion ID that we are deleting from
optionToRemove (string): The option ID that we are "deleting" optionToRemove (string): The option ID that we are "deleting"
*/ */
removeOptionFromCriterion: function(liveElement, criterionID, optionToRemove){ removeOptionFromCriterion: function(liveElement, criterionID, optionToRemove) {
var view = this; var view = this;
var numberOfOptions = view.numberOfOptions[criterionID]; var numberOfOptions = view.numberOfOptions[criterionID];
var optionSelectors = view.rubricCriteriaSelectors[criterionID].options; var optionSelectors = view.rubricCriteriaSelectors[criterionID].options;
...@@ -473,12 +452,6 @@ OpenAssessment.StudioView.prototype = { ...@@ -473,12 +452,6 @@ OpenAssessment.StudioView.prototype = {
// to save so it can show the "Saving..." notification // to save so it can show the "Saving..." notification
this.runtime.notify('save', {state: 'start'}); this.runtime.notify('save', {state: 'start'});
// Send the updated XML to the server
var prompt = this.settingsFieldSelectors.promptBox.prop('value');
var title = this.settingsFieldSelectors.titleField.prop('value');
var subStart = this.settingsFieldSelectors.submissionStartField.prop('value');
var subDue = this.settingsFieldSelectors.submissionDueField.prop('value');
// Grabs values from all of our fields, and stores them in a format which can be easily validated. // Grabs values from all of our fields, and stores them in a format which can be easily validated.
var rubricCriteria = []; var rubricCriteria = [];
...@@ -497,77 +470,64 @@ OpenAssessment.StudioView.prototype = { ...@@ -497,77 +470,64 @@ OpenAssessment.StudioView.prototype = {
var optionSelectors = optionSelectorList[j]; var optionSelectors = optionSelectorList[j];
optionValueList = optionValueList.concat([{ optionValueList = optionValueList.concat([{
order_num: j-1, order_num: j-1,
points: optionSelectors.points.prop('value'), points: this._getInt(optionSelectors.points),
name: optionSelectors.name.prop('value'), name: optionSelectors.name.val(),
explanation: optionSelectors.explanation.prop('value') explanation: optionSelectors.explanation.val()
}]); }]);
} }
criterionValueDict.options = optionValueList; criterionValueDict.options = optionValueList;
rubricCriteria = rubricCriteria.concat([criterionValueDict]); rubricCriteria = rubricCriteria.concat([criterionValueDict]);
} }
var rubric = { 'criteria': rubricCriteria };
if (this.hasRubricFeedbackPrompt){
rubric.feedbackprompt = this.rubricFeedbackPrompt.prop('value');
}
var assessments = []; var assessments = [];
if (this.settingsFieldSelectors.hasTraining.prop('checked')){ if (this.settingsFieldSelectors.hasTraining.prop('checked')){
assessments[assessments.length] = { assessments.push({
"name": "student-training", name: "student-training",
"examples": this.studentTrainingExamplesCodeBox.getValue() examples: this.studentTrainingExamplesCodeBox.getValue()
}; });
} }
if (this.settingsFieldSelectors.hasPeer.prop('checked')) { if (this.settingsFieldSelectors.hasPeer.prop('checked')) {
var assessment = { assessments.push({
"name": "peer-assessment", name: "peer-assessment",
"must_grade": parseInt(this.settingsFieldSelectors.peerMustGrade.prop('value')), must_grade: this._getInt(this.settingsFieldSelectors.peerMustGrade),
"must_be_graded_by": parseInt(this.settingsFieldSelectors.peerGradedBy.prop('value')) must_be_graded_by: this._getInt(this.settingsFieldSelectors.peerGradedBy),
}; start: this._getDateTime(this.settingsFieldSelectors.peerStart),
var startStr = this.settingsFieldSelectors.peerStart.prop('value'); due: this._getDateTime(this.settingsFieldSelectors.peerDue)
var dueStr = this.settingsFieldSelectors.peerDue.prop('value'); });
if (startStr){
assessment = $.extend(assessment, {"start": startStr});
}
if (dueStr){
assessment = $.extend(assessment, {"due": dueStr});
}
assessments[assessments.length] = assessment;
} }
if (this.settingsFieldSelectors.hasSelf.prop('checked')) { if (this.settingsFieldSelectors.hasSelf.prop('checked')) {
var assessment = { assessments.push({
"name": "self-assessment" name: "self-assessment",
}; start: this._getDateTime(this.settingsFieldSelectors.selfStart),
var startStr = this.settingsFieldSelectors.selfStart.prop('value'); due: this._getDateTime(this.settingsFieldSelectors.selfDue)
var dueStr = this.settingsFieldSelectors.selfDue.prop('value'); });
if (startStr){
assessment = $.extend(assessment, {"start": startStr});
}
if (dueStr){
assessment = $.extend(assessment, {"due": dueStr});
}
assessments[assessments.length] = assessment;
} }
if (this.settingsFieldSelectors.hasAI.prop('checked')) { if (this.settingsFieldSelectors.hasAI.prop('checked')) {
assessments[assessments.length] = { assessments.push({
"name": "example-based-assessment", name: "example-based-assessment",
"examples": this.aiTrainingExamplesCodeBox.getValue() examples: this.aiTrainingExamplesCodeBox.getValue()
}; });
} }
var view = this; var view = this;
this.server.updateEditorContext(prompt, rubric, title, subStart, subDue, assessments).done(function () { this.server.updateEditorContext({
// Notify the client-side runtime that we finished saving title: this.settingsFieldSelectors.titleField.val(),
// so it can hide the "Saving..." notification. prompt: this.settingsFieldSelectors.promptBox.val(),
view.runtime.notify('save', {state: 'end'}); feedbackPrompt: this.rubricFeedbackPrompt.val(),
submissionStart: this._getDateTime(this.settingsFieldSelectors.submissionStartField),
// Reload the XML definition in the editor submissionDue: this._getDateTime(this.settingsFieldSelectors.submissionDueField),
view.load(); criteria: rubricCriteria,
assessments: assessments
}).done(
function () {
// Notify the client-side runtime that we finished saving
// so it can hide the "Saving..." notification.
// Then reload the view.
view.runtime.notify('save', {state: 'end'});
}).fail(function (msg) { }).fail(function (msg) {
view.showError(msg); view.showError(msg);
}); });
...@@ -589,7 +549,55 @@ OpenAssessment.StudioView.prototype = { ...@@ -589,7 +549,55 @@ OpenAssessment.StudioView.prototype = {
**/ **/
showError: function (errorMsg) { showError: function (errorMsg) {
this.runtime.notify('error', {msg: errorMsg}); this.runtime.notify('error', {msg: errorMsg});
},
/**
Retrieve a value from a datetime input.
Args:
selector: The JQuery selector for the datetime input.
Returns:
ISO-formatted datetime string or null
**/
_getDateTime: function(selector) {
var dateStr = selector.val();
// By convention, empty date strings are null,
// meaning choose the default date based on
// other dates set in the problem configuration.
if (dateStr === "") {
return null;
}
// Attempt to parse the date string
// TO DO: currently invalid dates also are set as null,
// which is probably NOT what the user wants!
// We should add proper validation here.
var timestamp = Date.parse(dateStr);
if (isNaN(timestamp)) {
return null;
}
// Send the datetime in ISO format
// This will also convert the timezone to UTC
return new Date(timestamp).toISOString();
},
/**
Retrieve an integer value from an input.
Args:
selector: The JQuery selector for the input.
Returns:
int
**/
_getInt: function(selector) {
return parseInt(selector.val(), 10);
} }
}; };
......
...@@ -427,26 +427,30 @@ OpenAssessment.Server.prototype = { ...@@ -427,26 +427,30 @@ OpenAssessment.Server.prototype = {
/** /**
Update the XBlock's XML definition on the server. Update the XBlock's XML definition on the server.
Return Kwargs:
title (string): The title of the problem.
prompt (string): The question prompt.
feedbackPrompt (string): The directions to the student for giving overall feedback on a submission.
submissionStart (ISO-formatted datetime string or null): The start date of the submission.
submissionDue (ISO-formatted datetime string or null): The date the submission is due.
criteria (list of object literals): The rubric criteria.
assessments (list of object literals): The assessments the student will be evaluated on.
Returns:
A JQuery promise, which resolves with no arguments A JQuery promise, which resolves with no arguments
and fails with an error message. and fails with an error message.
Example usage:
server.updateXml(xml).done(
function() {}
).fail(
function(err) { console.log(err); }
);
**/ **/
updateEditorContext: function(prompt, rubric, title, sub_start, sub_due, assessments) { updateEditorContext: function(kwargs) {
var url = this.url('update_editor_context'); var url = this.url('update_editor_context');
var payload = JSON.stringify({ var payload = JSON.stringify({
'prompt': prompt, prompt: kwargs.prompt,
'rubric': rubric, feedback_prompt: kwargs.feedbackPrompt,
'title': title, title: kwargs.title,
'submission_start': sub_start, submission_start: kwargs.submissionStart,
'submission_due': sub_due, submission_due: kwargs.submissionDue,
'assessments': assessments criteria: kwargs.criteria,
assessments: kwargs.assessments
}); });
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({ $.ajax({
......
...@@ -120,7 +120,10 @@ class StudentTrainingMixin(object): ...@@ -120,7 +120,10 @@ class StudentTrainingMixin(object):
examples examples
) )
context['training_essay'] = example['answer'] context['training_essay'] = example['answer']
context['training_rubric'] = example['rubric'] context['training_rubric'] = {
'criteria': example['rubric']['criteria'],
'points_possible': example['rubric']['points_possible']
}
template = 'openassessmentblock/student_training/student_training.html' template = 'openassessmentblock/student_training/student_training.html'
return template, context return template, context
......
...@@ -6,12 +6,14 @@ import copy ...@@ -6,12 +6,14 @@ import copy
import logging import logging
from django.template.context import Context from django.template.context import Context
from django.template.loader import get_template from django.template.loader import get_template
from django.utils.translation import ugettext as _, ugettext from django.utils.translation import ugettext as _
from voluptuous import MultipleInvalid
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fragment import Fragment from xblock.fragment import Fragment
from openassessment.xblock import xml from openassessment.xblock import xml
from openassessment.xblock.validation import validator from openassessment.xblock.validation import validator
from openassessment.xblock.xml import UpdateFromXmlError, parse_date, parse_examples_xml_str from openassessment.xblock.data_conversion import create_rubric_dict
from openassessment.xblock.schema import EDITOR_UPDATE_SCHEMA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -59,38 +61,37 @@ class StudioMixin(object): ...@@ -59,38 +61,37 @@ class StudioMixin(object):
Returns: Returns:
dict with keys 'success' (bool) and 'msg' (str) dict with keys 'success' (bool) and 'msg' (str)
""" """
missing_keys = list(
{'rubric', 'prompt', 'title', 'assessments', 'submission_start', 'submission_due'} - set(data.keys())
)
if missing_keys:
logger.warn(
'Must specify the following missing keys in request JSON dict: {}'.format(missing_keys)
)
return {'success': False, 'msg': _('Error updating XBlock configuration')}
# Validate and sanitize the data using a schema
# If the data is invalid, this means something is wrong with
# our JavaScript, so we log an exception.
try: try:
rubric = verify_rubric_format(data['rubric']) data = EDITOR_UPDATE_SCHEMA(data)
submission_due = xml.parse_date(data["submission_due"], name="submission due date") except MultipleInvalid:
submission_start = xml.parse_date(data["submission_start"], name="submission start date") logger.exception('Editor context is invalid')
assessments = parse_assessment_dictionaries(data["assessments"]) return {'success': False, 'msg': _('Error updating XBlock configuration')}
except xml.UpdateFromXmlError as ex:
return {'success': False, 'msg': _('An error occurred while saving: {error}').format(error=ex)}
xblock_validator = validator(self) xblock_validator = validator(self)
success, msg = xblock_validator(rubric, {'due': submission_due, 'start': submission_start}, assessments) success, msg = xblock_validator(
create_rubric_dict(data['prompt'], data['criteria']),
data['assessments'],
submission_start=data['submission_start'],
submission_due=data['submission_due'],
)
if not success: if not success:
return {'success': False, 'msg': _('Validation error: {error}').format(error=msg)} return {'success': False, 'msg': _('Validation error: {error}').format(error=msg)}
self.update( # At this point, all the input data has been validated,
rubric.get('criteria', []), # so we can safely modify the XBlock fields.
rubric.get('feedbackprompt', None), self.title = data['title']
assessments, self.prompt = data['prompt']
submission_due, self.rubric_criteria = data['criteria']
submission_start, self.rubric_assessments = data['assessments']
data["title"], self.rubric_feedback_prompt = data['feedback_prompt']
data["prompt"] self.submission_start = data['submission_start']
) self.submission_due = data['submission_due']
return {'success': True, 'msg': 'Successfully updated OpenAssessment XBlock'}
return {'success': True, 'msg': _(u'Successfully updated OpenAssessment XBlock')}
@XBlock.json_handler @XBlock.json_handler
def editor_context(self, data, suffix=''): def editor_context(self, data, suffix=''):
...@@ -172,203 +173,3 @@ class StudioMixin(object): ...@@ -172,203 +173,3 @@ class StudioMixin(object):
'success': True, 'msg': u'', 'success': True, 'msg': u'',
'is_released': self.is_released() 'is_released': self.is_released()
} }
def parse_assessment_dictionaries(input_assessments):
"""
Parses the elements of assessment dictionaries returned by the Studio UI into storable rubric_assessments
Args:
input_assessments (list of dict): A list of the dictionaries that are assembled in Javascript to
represent their modules. Some changes need to be made between this and the result:
-- Parse the XML examples from the Student Training and or AI
-- Parse all dates (including the assessment dates) correctly
Returns:
(list of dict): Can be directly assigned/stored in an openassessmentblock.rubric_assessments
"""
assessments_list = []
for assessment in input_assessments:
assessment_dict = dict()
# Assessment name
if 'name' in assessment:
assessment_dict['name'] = assessment.get('name')
else:
raise UpdateFromXmlError(_('All "assessment" elements must contain a "name" element.'))
# Assessment start
if 'start' in assessment:
parsed_start = parse_date(assessment.get('start'), name="{} start date".format(assessment.get('name')))
assessment_dict['start'] = parsed_start
else:
assessment_dict['start'] = None
# Assessment due
if 'due' in assessment:
parsed_due = parse_date(assessment.get('due'), name="{} due date".format(assessment.get('name')))
assessment_dict['due'] = parsed_due
else:
assessment_dict['due'] = None
# Assessment must_grade
if 'must_grade' in assessment:
try:
assessment_dict['must_grade'] = int(assessment.get('must_grade'))
except (ValueError, TypeError):
raise UpdateFromXmlError(_('The "must_grade" value must be a positive integer.'))
# Assessment must_be_graded_by
if 'must_be_graded_by' in assessment:
try:
assessment_dict['must_be_graded_by'] = int(assessment.get('must_be_graded_by'))
except (ValueError, TypeError):
raise UpdateFromXmlError(_('The "must_be_graded_by" value must be a positive integer.'))
# Training examples (can be for AI OR for Student Training)
if 'examples' in assessment:
try:
assessment_dict['examples'] = parse_examples_xml_str(assessment.get('examples'))
except UpdateFromXmlError as ex:
raise UpdateFromXmlError(_("There was an error in parsing the {name} examples: {ex}").format(
name=assessment_dict['name'], ex=ex
))
# Update the list of assessments
assessments_list.append(assessment_dict)
return assessments_list
def verify_rubric_format(rubric):
"""
Verifies that the rubric that was passed in follows the conventions that we expect, including
types and structure.
Args:
rubric (dict): Unsanitized version of our rubric. Usually taken from the GUI.
Returns:
rubric (dict): Sanitized version of the same form.
Raises:
UpdateFromXMLError
"""
if not isinstance(rubric, dict):
raise UpdateFromXmlError(_("The given rubric was not a dictionary of the form {criteria: [criteria1, criteria2...]}"))
if "criteria" not in rubric.keys():
raise UpdateFromXmlError(_("The given rubric did not contain a key for a list of criteria, and is invalid"))
if rubric.get('prompt', False):
if not isinstance(rubric['prompt'], basestring):
raise UpdateFromXmlError(_("The given rubric's feedback prompt was invalid, it must be a string."))
criteria = rubric["criteria"]
if not isinstance(criteria, list):
raise UpdateFromXmlError(_("The criteria term in the rubric dictionary corresponds to a non-list object."))
sanitized_criteria = []
for criterion in criteria:
if not isinstance(criterion, dict):
raise UpdateFromXmlError(_("A criterion given was not a dictionary."))
criterion = dict(criterion)
expected_keys = {'order_num', 'name', 'prompt', 'options', 'feedback'}
missing_keys = expected_keys - set(criterion.keys())
if missing_keys:
raise UpdateFromXmlError(_("The following keys were missing from the definition of one or more criteria: {}".format(", ".join(missing_keys))))
try:
name = unicode(criterion['name'])
except (TypeError, ValueError):
raise UpdateFromXmlError(_("The name value must be a string."))
try:
prompt = unicode(criterion['prompt'])
except (TypeError, ValueError):
raise UpdateFromXmlError(_("The prompt value must be a string."))
try:
feedback = unicode(criterion['feedback'])
except (TypeError, ValueError):
raise UpdateFromXmlError(_("The prompt value must be a string."))
try:
order_num = int(criterion['order_num'])
except (TypeError, ValueError):
raise UpdateFromXmlError(_("The order_num value must be an integer."))
if not isinstance(criterion['options'], list):
raise UpdateFromXmlError(_("The dictionary entry for 'options' in a criteria's dictionary definition must be a list."))
options = criterion['options']
sanitized_options = []
for option in options:
if not isinstance(option, dict):
raise UpdateFromXmlError(_("An option given was not a dictionary."))
expected_keys = {'order_num', 'name', 'points', 'explanation'}
unexpected_keys = list(set(option.keys()) - expected_keys)
missing_keys = list(expected_keys - set(option.keys()))
if missing_keys:
raise UpdateFromXmlError(_("The following keys were missing from the definition of one or more options: {}".format(", ".join(missing_keys))))
try:
option_name = unicode(option['name'])
except (TypeError, ValueError):
raise UpdateFromXmlError(_("All option names values must be strings."))
try:
option_explanation = unicode(option['explanation'])
except (TypeError, ValueError):
raise UpdateFromXmlError(_("All option explanation values must be strings."))
try:
option_points = int(option['points'])
except (TypeError, ValueError):
raise UpdateFromXmlError(_("All option point values must be integers."))
option_dict = {
"order_num": option['order_num'],
"name": option_name,
"explanation": option_explanation,
"points": option_points
}
sanitized_options.append(option_dict)
criterion_dict = {
"order_num": order_num,
"name": name,
"prompt": prompt,
"options": sanitized_options,
"feedback": feedback
}
sanitized_criteria.append(criterion_dict)
sanitized_rubric = {
'criteria': sanitized_criteria
}
if rubric.get('prompt'):
try:
sanitized_rubric['prompt'] = unicode(rubric.get('prompt'))
except (TypeError, ValueError):
raise UpdateFromXmlError(_("All prompt values must be strings."))
return sanitized_rubric
{ {
"no_rubric": { "no_criteria": {
"prompt": "My new prompt.", "prompt": "My new prompt.",
"title": "My new title.", "title": "My new title.",
"feedback_prompt": "Feedback prompt",
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
"must_grade": 5, "must_grade": 5,
"must_be_graded_by": 3, "must_be_graded_by": 3,
"start": "", "start": null,
"due": "" "due": null
}, },
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_due": "2014-02-27T09:46:28", "submission_due": "2014-02-27T09:46",
"submission_start": "2014-02-10T09:46:28", "submission_start": "2014-02-10T09:46",
"expected_error": "error" "expected_error": "error updating xblock configuration"
}, },
"no_prompt": { "no_prompt": {
"rubric": { "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "criteria": [
"criteria": [ {
{ "order_num": 0,
"order_num": 0, "name": "Test criterion",
"name": "Test criterion", "prompt": "Test criterion prompt",
"prompt": "Test criterion prompt", "options": [
"options": [ {
{ "order_num": 0,
"order_num": 0, "points": 0,
"points": 0, "name": "No",
"name": "No", "explanation": "No explanation"
"explanation": "No explanation" },
}, {
{ "order_num": 1,
"order_num": 1, "points": 2,
"points": 2, "name": "Yes",
"name": "Yes", "explanation": "Yes explanation"
"explanation": "Yes explanation" }
} ],
], "feedback": "optional"
"feedback": "optional" }
} ],
]
},
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
"must_grade": 5, "must_grade": 5,
"must_be_graded_by": 3, "must_be_graded_by": 3,
"start": "", "start": null,
"due": "" "due": null
}, },
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_due": "2014-02-27T09:46:28", "submission_due": "2014-02-27T09:46",
"submission_start": "2014-02-10T09:46:28", "submission_start": "2014-02-10T09:46",
"expected_error": "error" "expected_error": "error"
}, },
"no_submission_due": {
"rubric": { "no_feedback_prompt": {
"prompt": "Test Prompt", "prompt": "Test prompt",
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
"name": "Test criterion", "name": "Test criterion",
"prompt": "Test criterion prompt", "prompt": "Test criterion prompt",
"options": [ "options": [
{ {
"order_num": 0, "order_num": 0,
"points": 0, "points": 0,
"name": "No", "name": "No",
"explanation": "No explanation" "explanation": "No explanation"
}, },
{ {
"order_num": 1, "order_num": 1,
"points": 2, "points": 2,
"name": "Yes", "name": "Yes",
"explanation": "Yes explanation" "explanation": "Yes explanation"
} }
], ],
"feedback": "optional" "feedback": "optional"
} }
] ],
},
"prompt": "My new prompt.",
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
"must_grade": 5, "must_grade": 5,
"must_be_graded_by": 3, "must_be_graded_by": 3,
"start": "", "start": null,
"due": "" "due": null
}, },
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_start": "2014-02-10T09:46:28", "submission_due": "2014-02-27T09:46",
"submission_start": "2014-02-10T09:46",
"expected_error": "error" "expected_error": "error"
}, },
"invalid_dates_one": {
"rubric": {
"prompt": "Test Prompt",
"criteria": [
{
"order_num": 0,
"name": "Test criterion",
"prompt": "Test criterion prompt",
"options": [
{
"order_num": 0,
"points": 0,
"name": "No",
"explanation": "No explanation"
},
{
"order_num": 1,
"points": 2,
"name": "Yes",
"explanation": "Yes explanation"
}
],
"feedback": "optional"
}
]
},
"prompt": "My new prompt.",
"no_submission_due": {
"feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt",
"criteria": [
{
"order_num": 0,
"name": "Test criterion",
"prompt": "Test criterion prompt",
"options": [
{
"order_num": 0,
"points": 0,
"name": "No",
"explanation": "No explanation"
},
{
"order_num": 1,
"points": 2,
"name": "Yes",
"explanation": "Yes explanation"
}
],
"feedback": "optional"
}
],
"title": "My new title.",
"assessments": [
{
"name": "peer-assessment",
"must_grade": 5,
"must_be_graded_by": 3,
"start": null,
"due": null
},
{
"name": "self-assessment",
"start": null,
"due": null
}
],
"submission_start": "2014-02-10T09:46",
"expected_error": "error updating xblock configuration"
},
"invalid_dates_one": {
"feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt",
"criteria": [
{
"order_num": 0,
"name": "Test criterion",
"prompt": "Test criterion prompt",
"options": [
{
"order_num": 0,
"points": 0,
"name": "No",
"explanation": "No explanation"
},
{
"order_num": 1,
"points": 2,
"name": "Yes",
"explanation": "Yes explanation"
}
],
"feedback": "optional"
}
],
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
"must_grade": 5, "must_grade": 5,
"must_be_graded_by": 3, "must_be_graded_by": 3,
"start": "", "start": null,
"due": "" "due": null
}, },
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_due": "2012-02-27T09:46:28", "submission_due": "2012-02-27T09:46",
"submission_start": "2015-02-10T09:46:28", "submission_start": "2015-02-10T09:46",
"expected_error": "cannot be later" "expected_error": "cannot be later"
}, },
"invalid_dates_two": {
"rubric": {
"prompt": "Test Prompt",
"criteria": [
{
"order_num": 0,
"name": "Test criterion",
"prompt": "Test criterion prompt",
"options": [
{
"order_num": 0,
"points": 0,
"name": "No",
"explanation": "No explanation"
},
{
"order_num": 1,
"points": 2,
"name": "Yes",
"explanation": "Yes explanation"
}
],
"feedback": "optional"
}
]
},
"prompt": "My new prompt.",
"invalid_dates_two": {
"feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt",
"criteria": [
{
"order_num": 0,
"name": "Test criterion",
"prompt": "Test criterion prompt",
"options": [
{
"order_num": 0,
"points": 0,
"name": "No",
"explanation": "No explanation"
},
{
"order_num": 1,
"points": 2,
"name": "Yes",
"explanation": "Yes explanation"
}
],
"feedback": "optional"
}
],
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
"must_grade": 5, "must_grade": 5,
"must_be_graded_by": 3, "must_be_graded_by": 3,
"start": "", "start": null,
"due": "" "due": null
}, },
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "2003-01-02T00:00:00" "due": "2003-01-02T00:00"
} }
], ],
"submission_due": "2012-02-27T09:46:28", "submission_due": "2012-02-27T09:46",
"submission_start": "", "submission_start": null,
"expected_error": "cannot be later" "expected_error": "cannot be later"
}, },
"order num is string": { "order_num_is_string": {
"rubric": { "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"criteria": [ "criteria": [
{ {
"order_num": "Hello", "order_num": "Hello",
"name": "Test criterion", "name": "Test criterion",
"prompt": "Test criterion prompt", "prompt": "Test criterion prompt",
"options": [ "options": [
{ {
"order_num": 0, "order_num": 0,
"points": 0, "points": 0,
"name": "No", "name": "No",
"explanation": "No explanation" "explanation": "No explanation"
}, },
{ {
"order_num": 1, "order_num": 1,
"points": 2, "points": 2,
"name": "Yes", "name": "Yes",
"explanation": "Yes explanation" "explanation": "Yes explanation"
} }
], ],
"feedback": "optional" "feedback": "optional"
} }
] ],
},
"prompt": "My new prompt.",
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_due": "", "submission_due": null,
"submission_start": "", "submission_start": null,
"expected_error": "must be an integer" "expected_error": "error updating xblock configuration"
}, },
"rubric not a dictionary": { "feedback_missing": {
"rubric": [], "feedback_prompt": "Feedback prompt",
"prompt": "My new prompt.", "prompt": "Test Prompt",
"title": "My new title.", "criteria": [
"assessments": [
{ {
"name": "self-assessment", "order_num": 0,
"start": "", "name": "Test criterion",
"due": "" "prompt": "Test criterion prompt",
"options": [
{
"order_num": 0,
"points": 0,
"name": "No",
"explanation": "No explanation"
},
{
"order_num": 1,
"points": 2,
"name": "Yes",
"explanation": "Yes explanation"
}
]
} }
], ],
"submission_due": "",
"submission_start": "",
"expected_error": "dictionary of the form"
},
"feedback missing": {
"rubric": {
"prompt": "Test Prompt",
"criteria": [
{
"order_num": 0,
"name": "Test criterion",
"prompt": "Test criterion prompt",
"options": [
{
"order_num": 0,
"points": 0,
"name": "No",
"explanation": "No explanation"
},
{
"order_num": 1,
"points": 2,
"name": "Yes",
"explanation": "Yes explanation"
}
]
}
]
},
"prompt": "My new prompt.",
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_due": "", "submission_due": null,
"submission_start": "", "submission_start": null,
"expected_error": "feedback" "expected_error": "error updating xblock configuration"
}, },
"expected rubric keys missing": { "criterion_not_a_dict": {
"rubric": { "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt" "prompt": "prompty",
}, "criteria": [
"prompt": "My new prompt.", []
],
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_due": "", "submission_due": null,
"submission_start": "", "submission_start": null,
"expected_error": "did not contain a key" "expected_error": "error updating xblock configuration"
}, },
"prompt not a string": { "criteria_missing_keys": {
"rubric": { "feedback_prompt": "Feedback prompt",
"prompt": 5, "prompt": "Test Prompt",
"criteria": [ "criteria": [
{
"order_num": 0,
"name": "Test criterion",
"prompt": "Test criterion prompt",
"options": [
{
"order_num": 0,
"points": 0,
"name": "No",
"explanation": "No explanation"
},
{
"order_num": 1,
"points": 2,
"name": "Yes",
"explanation": "Yes explanation"
}
],
"feedback": "optional"
}
]
},
"prompt": "My new prompt.",
"title": "My new title.",
"assessments": [
{ {
"name": "self-assessment", "order_num": 0,
"start": "", "name": "Test criterion",
"due": "" "options": [
{
"order_num": 0,
"points": 0,
"name": "No",
"explanation": "No explanation"
},
{
"order_num": 1,
"points": 2,
"name": "Yes",
"explanation": "Yes explanation"
}
],
"feedback": "optional"
} }
], ],
"submission_due": "",
"submission_start": "",
"expected_error": "string"
},
"criterion not a dict": {
"rubric": {
"prompt": "prompty",
"criteria": [
[]
]
},
"prompt": "My new prompt.",
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_due": "", "submission_due": null,
"submission_start": "", "submission_start": null,
"expected_error": "not a dictionary" "expected_error": "error updating xblock configuration"
}, },
"criteria missing keys": { "options_not_a_list": {
"rubric": { "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"criteria": [ "criteria": [
{
"order_num": 0,
"name": "Test criterion",
"options": [
{
"order_num": 0,
"points": 0,
"name": "No",
"explanation": "No explanation"
},
{
"order_num": 1,
"points": 2,
"name": "Yes",
"explanation": "Yes explanation"
}
],
"feedback": "optional"
}
]
},
"prompt": "My new prompt.",
"title": "My new title.",
"assessments": [
{ {
"name": "self-assessment", "order_num": 0,
"start": "", "name": "Test criterion",
"due": "" "prompt": "Test criterion prompt",
"options": "not a list",
"feedback": "optional"
} }
], ],
"submission_due": "",
"submission_start": "",
"expected_error": "keys were missing"
},
"options not a list": {
"rubric": {
"prompt": "Test Prompt",
"criteria": [
{
"order_num": 0,
"name": "Test criterion",
"prompt": "Test criterion prompt",
"options": "not a list",
"feedback": "optional"
}
]
},
"prompt": "My new prompt.",
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_due": "", "submission_due": null,
"submission_start": "", "submission_start": null,
"expected_error": "must be a list" "expected_error": "error updating xblock configuration"
}, },
"option not a dictionary": { "option_not_a_dictionary": {
"rubric": { "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
"name": "Test criterion", "name": "Test criterion",
"prompt": "Test criterion prompt", "prompt": "Test criterion prompt",
"options": [ "options": [
{ {
"order_num": 0, "order_num": 0,
"points": 0, "points": 0,
"name": "No", "name": "No",
"explanation": "No explanation" "explanation": "No explanation"
}, },
[] []
], ],
"feedback": "optional" "feedback": "optional"
} }
] ],
},
"prompt": "My new prompt.",
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_due": "", "submission_due": null,
"submission_start": "", "submission_start": null,
"expected_error": "not a dictionary" "expected_error": "error updating xblock configuration"
}, },
"option missing keys": { "option_missing_keys": {
"rubric": { "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
"name": "Test criterion", "name": "Test criterion",
"prompt": "Test criterion prompt", "prompt": "Test criterion prompt",
"options": [ "options": [
{ {
"points": 0, "points": 0,
"name": "No", "name": "No",
"explanation": "No explanation" "explanation": "No explanation"
}, },
{ {
"order_num": 1, "order_num": 1,
"points": 2, "points": 2,
"name": "Yes", "name": "Yes",
"explanation": "Yes explanation" "explanation": "Yes explanation"
} }
], ],
"feedback": "optional" "feedback": "optional"
} }
] ],
},
"prompt": "My new prompt.",
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_due": "", "submission_due": null,
"submission_start": "", "submission_start": null,
"expected_error": "keys were missing" "expected_error": "error updating xblock configuration"
}, },
"option points must be int": { "option_points_must_be_int": {
"rubric": { "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
"name": "Test criterion", "name": "Test criterion",
"prompt": "Test criterion prompt", "prompt": "Test criterion prompt",
"options": [ "options": [
{ {
"order_num": 0, "order_num": 0,
"points": "a million stanley nickels", "points": "a million stanley nickels",
"name": "No", "name": "No",
"explanation": "No explanation" "explanation": "No explanation"
}, },
{ {
"order_num": 1, "order_num": 1,
"points": "One Shrutebuck", "points": "One Shrutebuck",
"name": "Yes", "name": "Yes",
"explanation": "Yes explanation" "explanation": "Yes explanation"
} }
], ],
"feedback": "optional" "feedback": "optional"
} }
] ],
},
"prompt": "My new prompt.",
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ],
"submission_due": "", "submission_due": null,
"submission_start": "", "submission_start": null,
"expected_error": "must be integers" "expected_error": "error updating xblock configuration"
} }
} }
{
"no-dates": {
"assessments_list": [
{
"name": "peer-assessment",
"start": "",
"due": "",
"must_grade": 5,
"must_be_graded_by": 3
},
{
"name": "self-assessment",
"due": "",
"start": ""
}
],
"results": [
{
"name": "peer-assessment",
"start": null,
"due": null,
"must_grade": 5,
"must_be_graded_by": 3
},
{
"name": "self-assessment",
"due": null,
"start": null
}
]
},
"student-training": {
"assessments_list": [
{
"name": "student-training",
"start": "",
"due": "",
"examples": "<example><answer>ẗëṡẗ äṅṡẅëṛ</answer><select criterion=\"Test criterion\" option=\"Yes\" /><select criterion=\"Another test criterion\" option=\"No\" /></example><example><answer>äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ</answer><select criterion=\"Another test criterion\" option=\"Yes\" /><select criterion=\"Test criterion\" option=\"No\" /></example>"
},
{
"name": "peer-assessment",
"start": "",
"due": "",
"must_grade": 5,
"must_be_graded_by": 3
},
{
"name": "self-assessment",
"due": "",
"start": ""
}
],
"results": [
{
"name": "student-training",
"due": null,
"start": null,
"examples": [
{
"answer": "ẗëṡẗ äṅṡẅëṛ",
"options_selected": [
{
"criterion": "Test criterion",
"option": "Yes"
},
{
"criterion": "Another test criterion",
"option": "No"
}
]
},
{
"answer": "äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ",
"options_selected": [
{
"criterion": "Another test criterion",
"option": "Yes"
},
{
"criterion": "Test criterion",
"option": "No"
}
]
}
]
},
{
"name": "peer-assessment",
"start": null,
"due": null,
"must_grade": 5,
"must_be_graded_by": 3
},
{
"name": "self-assessment",
"due": null,
"start": null
}
]
},
"date-parsing": {
"assessments_list": [
{
"name": "student-training",
"start": "2014-10-10T01:00:01",
"due": "",
"examples": "<example><answer>ẗëṡẗ äṅṡẅëṛ</answer><select criterion=\"Test criterion\" option=\"Yes\" /><select criterion=\"Another test criterion\" option=\"No\" /></example><example><answer>äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ</answer><select criterion=\"Another test criterion\" option=\"Yes\" /><select criterion=\"Test criterion\" option=\"No\" /></example>"
},
{
"name": "peer-assessment",
"start": "",
"due": "2015-01-01T00:00:00",
"must_grade": 5,
"must_be_graded_by": 3
},
{
"name": "self-assessment",
"due": "2015-01-01T00:00:00",
"start": ""
}
],
"results": [
{
"name": "student-training",
"due": null,
"start": "2014-10-10T01:00:01",
"examples": [
{
"answer": "ẗëṡẗ äṅṡẅëṛ",
"options_selected": [
{
"criterion": "Test criterion",
"option": "Yes"
},
{
"criterion": "Another test criterion",
"option": "No"
}
]
},
{
"answer": "äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ",
"options_selected": [
{
"criterion": "Another test criterion",
"option": "Yes"
},
{
"criterion": "Test criterion",
"option": "No"
}
]
}
]
},
{
"name": "peer-assessment",
"start": null,
"due": "2015-01-01T00:00:00",
"must_grade": 5,
"must_be_graded_by": 3
},
{
"name": "self-assessment",
"due": "2015-01-01T00:00:00",
"start": null
}
]
}
}
\ No newline at end of file
{
"date-parsing-due": {
"assessments_list": [
{
"name": "student-training",
"start": "2014-10-10T01:00:01",
"due": "",
"examples": "<examples><example><answer>ẗëṡẗ äṅṡẅëṛ</answer><select criterion=\"Test criterion\" option=\"Yes\" /><select criterion=\"Another test criterion\" option=\"No\" /></example><example><answer>äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ</answer><select criterion=\"Another test criterion\" option=\"Yes\" /><select criterion=\"Test criterion\" option=\"No\" /></example></examples>"
},
{
"name": "peer-assessment",
"start": "",
"due": "2015-01-01T00:00:HI",
"must_grade": 5,
"must_be_graded_by": 3
},
{
"name": "self-assessment",
"due": "2015-014-01",
"start": ""
}
]
},
"date-parsing-start": {
"assessments_list": [
{
"name": "peer-assessment",
"start": "2014-13-13T00:00:00",
"due": "",
"must_grade": 5,
"must_be_graded_by": 3
},
{
"name": "self-assessment",
"due": "",
"start": ""
}
]
},
"no-answers-in-examples": {
"assessments_list": [
{
"name": "student-training",
"start": "",
"due": "",
"examples": "<example><select criterion=\"Test criterion\" option=\"Yes\" /><select criterion=\"Another test criterion\" option=\"No\" /></example><example><answer>äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ</answer><select criterion=\"Another test criterion\" option=\"Yes\" /><select criterion=\"Test criterion\" option=\"No\" /></example>"
},
{
"name": "peer-assessment",
"start": "",
"due": "",
"must_grade": 5,
"must_be_graded_by": 3
},
{
"name": "self-assessment",
"due": "",
"start": ""
}
]
},
"must_grade": {
"assessments_list": [
{
"name": "peer-assessment",
"start": "",
"due": "",
"must_grade": "Not a number fool!",
"must_be_graded_by": 3
},
{
"name": "self-assessment",
"due": "",
"start": ""
}
]
},
"must_be_graded_by": {
"assessments_list": [
{
"name": "peer-assessment",
"start": "",
"due": "",
"must_grade": 3,
"must_be_graded_by": "Not a number fool!"
},
{
"name": "self-assessment",
"due": "",
"start": ""
}
]
}
}
\ No newline at end of file
...@@ -7,9 +7,6 @@ ...@@ -7,9 +7,6 @@
"training_num_available": 2, "training_num_available": 2,
"training_essay": "This is my answer.", "training_essay": "This is my answer.",
"training_rubric": { "training_rubric": {
"id": 2,
"content_hash": "de2bb2b7e2c6e3df014e53b8c65f37d511cc4344",
"structure_hash": "a513b20d93487d6d80e31e1d974bf22519332567",
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
......
{ {
"simple": { "simple": {
"rubric": { "criteria": [
"prompt": "Test Prompt", {
"criteria": [ "order_num": 0,
{ "name": "Test criterion",
"order_num": 0, "prompt": "Test criterion prompt",
"name": "Test criterion", "options": [
"prompt": "Test criterion prompt", {
"options": [ "order_num": 0,
{ "points": 0,
"order_num": 0, "name": "No",
"points": 0, "explanation": "No explanation"
"name": "No", },
"explanation": "No explanation" {
}, "order_num": 1,
{ "points": 2,
"order_num": 1, "name": "Yes",
"points": 2, "explanation": "Yes explanation"
"name": "Yes", }
"explanation": "Yes explanation" ],
} "feedback": "required"
], }
"feedback": "required" ],
}
]
},
"prompt": "My new prompt.", "prompt": "My new prompt.",
"submission_due": "4014-02-27T09:46:28", "feedback_prompt": "Feedback prompt",
"submission_start": "4014-02-10T09:46:28", "submission_due": "4014-02-27T09:46",
"submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
"must_grade": 5, "must_grade": 5,
"must_be_graded_by": 3, "must_be_graded_by": 3,
"start": "", "start": null,
"due": "4014-03-10T00:00:00" "due": "4014-03-10T00:00"
}, },
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ]
"expected-assessment": "peer-assessment",
"expected-criterion-prompt": "Test criterion prompt"
}, },
"unicode": { "unicode": {
"rubric": { "criteria": [
"prompt": "Ṫëṡẗ ṗṛöṁṗẗ", {
"criteria": [ "order_num": 0,
{ "name": "Ṫëṡẗ ċṛïẗëïṛöṅ",
"order_num": 0, "prompt": "Téśt ćŕítéíŕőń ṕŕőḿṕt",
"name": "Ṫëṡẗ ċṛïẗëïṛöṅ", "options": [
"prompt": "Téśt ćŕítéíŕőń ṕŕőḿṕt", {
"options": [ "order_num": 0,
{ "points": 0,
"order_num": 0, "name": "Ṅö",
"points": 0, "explanation": "Ńő éxṕĺáńátíőń"
"name": "Ṅö", },
"explanation": "Ńő éxṕĺáńátíőń" {
}, "order_num": 1,
{ "points": 2,
"order_num": 1, "name": "sǝʎ",
"points": 2, "explanation": "Чэѕ эхрlаиатіои"
"name": "sǝʎ", }
"explanation": "Чэѕ эхрlаиатіои" ],
} "feedback": "required"
], }
"feedback": "required" ],
}
]
},
"prompt": "Ṁÿ ṅëẅ ṗṛöṁṗẗ.", "prompt": "Ṁÿ ṅëẅ ṗṛöṁṗẗ.",
"submission_due": "4014-02-27T09:46:28", "feedback_prompt": "ḟëëḋḅäċḳ ṗṛöṁṗẗ",
"submission_start": "4014-02-10T09:46:28", "submission_due": "4014-02-27T09:46",
"submission_start": "4014-02-10T09:46",
"title": "ɯʎ uǝʍ ʇıʇןǝ", "title": "ɯʎ uǝʍ ʇıʇןǝ",
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
"must_grade": 5, "must_grade": 5,
"must_be_graded_by": 3, "must_be_graded_by": 3,
"start": "", "start": null,
"due": "4014-03-10T00:00:00" "due": "4014-03-10T00:00"
}, },
{ {
"name": "self-assessment", "name": "self-assessment",
"start": "", "start": null,
"due": "" "due": null
} }
], ]
"expected-assessment": "peer-assessment",
"expected-criterion-prompt": "Ṫëṡẗ ċṛïẗëṛïöṅ ṗṛöṁṗẗ"
} }
} }
...@@ -5,6 +5,7 @@ Tests for the student training step in the Open Assessment XBlock. ...@@ -5,6 +5,7 @@ Tests for the student training step in the Open Assessment XBlock.
import datetime import datetime
import ddt import ddt
import json import json
import pprint
from mock import patch from mock import patch
import pytz import pytz
from django.db import DatabaseError from django.db import DatabaseError
...@@ -196,7 +197,11 @@ class StudentTrainingAssessTest(XBlockHandlerTestCase): ...@@ -196,7 +197,11 @@ class StudentTrainingAssessTest(XBlockHandlerTestCase):
iso_date = context['training_due'].isoformat() iso_date = context['training_due'].isoformat()
self.assertEqual(iso_date, expected_context[key]) self.assertEqual(iso_date, expected_context[key])
else: else:
self.assertEqual(context[key], expected_context[key]) msg = u"Expected \n {expected} \n but found \n {actual}".format(
actual=pprint.pformat(context[key]),
expected=pprint.pformat(expected_context[key])
)
self.assertEqual(context[key], expected_context[key], msg=msg)
# Verify that we render without error # Verify that we render without error
resp = self.request(xblock, 'render_student_training', json.dumps({})) resp = self.request(xblock, 'render_student_training', json.dumps({}))
......
...@@ -48,10 +48,11 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -48,10 +48,11 @@ class StudioViewTest(XBlockHandlerTestCase):
@file_data('data/invalid_update_xblock.json') @file_data('data/invalid_update_xblock.json')
@scenario('data/basic_scenario.xml') @scenario('data/basic_scenario.xml')
def test_update_context_invalid_request_data(self, xblock, data): def test_update_context_invalid_request_data(self, xblock, data):
expected_error = data.pop('expected_error')
xblock.published_date = None xblock.published_date = None
resp = self.request(xblock, 'update_editor_context', json.dumps(data), response_format='json') resp = self.request(xblock, 'update_editor_context', json.dumps(data), response_format='json')
self.assertFalse(resp['success']) self.assertFalse(resp['success'])
self.assertIn(data['expected_error'], resp['msg'].lower()) self.assertIn(expected_error, resp['msg'].lower())
@file_data('data/invalid_rubric.json') @file_data('data/invalid_rubric.json')
@scenario('data/basic_scenario.xml') @scenario('data/basic_scenario.xml')
...@@ -67,7 +68,7 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -67,7 +68,7 @@ class StudioViewTest(XBlockHandlerTestCase):
# Verify the response fails # Verify the response fails
resp = self.request(xblock, 'update_editor_context', request, response_format='json') resp = self.request(xblock, 'update_editor_context', request, response_format='json')
self.assertFalse(resp['success']) self.assertFalse(resp['success'])
self.assertIn("the following keys were missing", resp['msg'].lower()) self.assertIn("error updating xblock configuration", resp['msg'].lower())
# Check that the XBlock fields were NOT updated # Check that the XBlock fields were NOT updated
# We don't need to be exhaustive here, because we have other unit tests # We don't need to be exhaustive here, because we have other unit tests
......
...@@ -235,11 +235,6 @@ class ValidationIntegrationTest(TestCase): ...@@ -235,11 +235,6 @@ class ValidationIntegrationTest(TestCase):
] ]
} }
SUBMISSION = {
"start": None,
"due": None
}
EXAMPLES = [ EXAMPLES = [
{ {
"answer": "ẗëṡẗ äṅṡẅëṛ", "answer": "ẗëṡẗ äṅṡẅëṛ",
...@@ -293,7 +288,7 @@ class ValidationIntegrationTest(TestCase): ...@@ -293,7 +288,7 @@ class ValidationIntegrationTest(TestCase):
self.validator = validator(self.oa_block) self.validator = validator(self.oa_block)
def test_validates_successfully(self): def test_validates_successfully(self):
is_valid, msg = self.validator(self.RUBRIC, self.SUBMISSION, self.ASSESSMENTS) is_valid, msg = self.validator(self.RUBRIC, self.ASSESSMENTS)
self.assertTrue(is_valid, msg=msg) self.assertTrue(is_valid, msg=msg)
self.assertEqual(msg, "") self.assertEqual(msg, "")
...@@ -303,7 +298,7 @@ class ValidationIntegrationTest(TestCase): ...@@ -303,7 +298,7 @@ class ValidationIntegrationTest(TestCase):
mutated_assessments[0]['examples'][0]['options_selected'][0]['criterion'] = 'Invalid criterion!' mutated_assessments[0]['examples'][0]['options_selected'][0]['criterion'] = 'Invalid criterion!'
# Expect a validation error # Expect a validation error
is_valid, msg = self.validator(self.RUBRIC, self.SUBMISSION, mutated_assessments) is_valid, msg = self.validator(self.RUBRIC, mutated_assessments)
self.assertFalse(is_valid) self.assertFalse(is_valid)
self.assertEqual(msg, u'Example 1 has an extra option for "Invalid criterion!"; Example 1 is missing an option for "vocabulary"') self.assertEqual(msg, u'Example 1 has an extra option for "Invalid criterion!"; Example 1 is missing an option for "vocabulary"')
...@@ -313,7 +308,7 @@ class ValidationIntegrationTest(TestCase): ...@@ -313,7 +308,7 @@ class ValidationIntegrationTest(TestCase):
mutated_assessments[0]['examples'][0]['options_selected'][0]['option'] = 'Invalid option!' mutated_assessments[0]['examples'][0]['options_selected'][0]['option'] = 'Invalid option!'
# Expect a validation error # Expect a validation error
is_valid, msg = self.validator(self.RUBRIC, self.SUBMISSION, mutated_assessments) is_valid, msg = self.validator(self.RUBRIC, mutated_assessments)
self.assertFalse(is_valid) self.assertFalse(is_valid)
self.assertEqual(msg, u'Example 1 has an invalid option for "vocabulary": "Invalid option!"') self.assertEqual(msg, u'Example 1 has an invalid option for "vocabulary": "Invalid option!"')
...@@ -327,12 +322,12 @@ class ValidationIntegrationTest(TestCase): ...@@ -327,12 +322,12 @@ class ValidationIntegrationTest(TestCase):
option['points'] = 1 option['points'] = 1
# Expect a validation error # Expect a validation error
is_valid, msg = self.validator(mutated_rubric, self.SUBMISSION, self.ASSESSMENTS) is_valid, msg = self.validator(mutated_rubric, self.ASSESSMENTS)
self.assertFalse(is_valid) self.assertFalse(is_valid)
self.assertEqual(msg, u'Example-based assessments cannot have duplicate point values.') self.assertEqual(msg, u'Example-based assessments cannot have duplicate point values.')
# But it should be okay if we don't have example-based assessment # But it should be okay if we don't have example-based assessment
no_example_based = copy.deepcopy(self.ASSESSMENTS)[1:] no_example_based = copy.deepcopy(self.ASSESSMENTS)[1:]
is_valid, msg = self.validator(mutated_rubric, self.SUBMISSION, no_example_based) is_valid, msg = self.validator(mutated_rubric, no_example_based)
self.assertTrue(is_valid) self.assertTrue(is_valid)
self.assertEqual(msg, u'') self.assertEqual(msg, u'')
...@@ -11,7 +11,6 @@ import dateutil.parser ...@@ -11,7 +11,6 @@ import dateutil.parser
from django.test import TestCase from django.test import TestCase
import ddt import ddt
from openassessment.xblock.openassessmentblock import OpenAssessmentBlock from openassessment.xblock.openassessmentblock import OpenAssessmentBlock
from openassessment.xblock.studio_mixin import parse_assessment_dictionaries
from openassessment.xblock.xml import ( from openassessment.xblock.xml import (
serialize_content, parse_from_xml_str, parse_rubric_xml_str, serialize_content, parse_from_xml_str, parse_rubric_xml_str,
parse_examples_xml_str, parse_assessments_xml_str, parse_examples_xml_str, parse_assessments_xml_str,
...@@ -359,25 +358,6 @@ class TestParseAssessmentsFromXml(TestCase): ...@@ -359,25 +358,6 @@ class TestParseAssessmentsFromXml(TestCase):
self.assertEqual(assessments, data['assessments']) self.assertEqual(assessments, data['assessments'])
@ddt.ddt
class TestParseAssessmentsFromDictionaries(TestCase):
@ddt.file_data('data/parse_assessment_dicts.json')
def test_parse_assessments_dictionary(self, data):
config = parse_assessment_dictionaries(data['assessments_list'])
if len(config) == 0:
# Prevents this test from passing benignly if parse_assessment_dictionaries returns []
self.assertTrue(False)
for config_assessment, correct_assessment in zip(config, data['results']):
self.assertEqual(config_assessment, correct_assessment)
@ddt.file_data('data/parse_assessment_dicts_error.json')
def test_parse_assessments_dictionary_error(self, data):
with self.assertRaises(UpdateFromXmlError):
parse_assessment_dictionaries(data['assessments_list'])
@ddt.ddt @ddt.ddt
class TestUpdateFromXml(TestCase): class TestUpdateFromXml(TestCase):
......
...@@ -299,7 +299,7 @@ def validator(oa_block, strict_post_release=True): ...@@ -299,7 +299,7 @@ def validator(oa_block, strict_post_release=True):
callable, of a form that can be passed to `update_from_xml`. callable, of a form that can be passed to `update_from_xml`.
""" """
def _inner(rubric_dict, submission_dict, assessments): def _inner(rubric_dict, assessments, submission_start=None, submission_due=None):
is_released = strict_post_release and oa_block.is_released() is_released = strict_post_release and oa_block.is_released()
...@@ -325,7 +325,7 @@ def validator(oa_block, strict_post_release=True): ...@@ -325,7 +325,7 @@ def validator(oa_block, strict_post_release=True):
return (False, msg) return (False, msg)
# Dates # Dates
submission_dates = [(submission_dict['start'], submission_dict['due'])] submission_dates = [(submission_start, submission_due)]
assessment_dates = [(asmnt.get('start'), asmnt.get('due')) for asmnt in assessments] assessment_dates = [(asmnt.get('start'), asmnt.get('due')) for asmnt in assessments]
success, msg = validate_dates(oa_block.start, oa_block.due, submission_dates + assessment_dates) success, msg = validate_dates(oa_block.start, oa_block.due, submission_dates + assessment_dates)
if not success: if not success:
......
...@@ -304,9 +304,6 @@ def parse_rubric_xml(rubric_root): ...@@ -304,9 +304,6 @@ def parse_rubric_xml(rubric_root):
Args: Args:
rubric_root (lxml.etree.Element): The root of the <rubric> node in the tree. rubric_root (lxml.etree.Element): The root of the <rubric> node in the tree.
validator (callable): Function that accepts a rubric dict and returns
a boolean indicating whether the rubric is semantically valid
and an error message string.
Returns: Returns:
dict, a serialized representation of a rubric, as defined by the peer grading serializers. dict, a serialized representation of a rubric, as defined by the peer grading serializers.
......
...@@ -22,6 +22,7 @@ loremipsum==1.0.2 ...@@ -22,6 +22,7 @@ loremipsum==1.0.2
python-dateutil==2.1 python-dateutil==2.1
pytz==2012h pytz==2012h
South==0.7.6 South==0.7.6
voluptuous==0.8.5
# AI grading # AI grading
git+https://github.com/edx/ease.git@f9f47fb6b5c7c8b6c3360efa72eb56561e1a03b0#egg=ease git+https://github.com/edx/ease.git@f9f47fb6b5c7c8b6c3360efa72eb56561e1a03b0#egg=ease
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