Commit a73a0638 by gradyward

A completed first draft of the authoring changes.

parent dfb745c4
...@@ -2133,47 +2133,104 @@ hr.divider, ...@@ -2133,47 +2133,104 @@ hr.divider,
.step--student-training .message--incorrect.is--hidden .step__header { .step--student-training .message--incorrect.is--hidden .step__header {
border-bottom: none; } border-bottom: none; }
#openassessment-editor .openassessment-editor-content-and-tabs { #openassessment-editor {
width: 100%; margin-bottom: 0; }
height: 370px; } #openassessment-editor .openassessment-editor-content-and-tabs {
#openassessment-editor .openassessment-editor-header { width: 100%;
background-color: #e5e5e5; height: 370px; }
width: 100%; #openassessment-editor .openassessment-editor-header {
top: 0; } background-color: #e5e5e5;
#openassessment-editor #oa-editor-window-title { width: 100%;
float: left; } top: 0; }
#openassessment-editor .oa-editor-tab { #openassessment-editor #oa-editor-window-title {
float: right; float: left; }
padding: 2.5px 5px; #openassessment-editor .oa-editor-tab {
margin: 2.5px 5px; } float: right;
#openassessment-editor .oa-editor-content-wrapper { padding: 2.5px 5px;
height: 100%; margin: 2.5px 5px;
width: 100%; border-radius: 2.5px;
padding: 5px 10px; } box-shadow: none;
#openassessment-editor .openassessment-prompt-editor { border: 0; }
width: 100%; #openassessment-editor .oa-editor-content-wrapper {
height: 100%; height: 100%;
resize: none; } width: 100%;
#openassessment-editor .openassessment-rubric-editor { padding: 5px 10px; }
width: 100%; #openassessment-editor .openassessment-prompt-editor {
height: 100%; } width: 100%;
#openassessment-editor .openassessment-assessments-editor { height: 100%;
width: 100%; } resize: none; }
#openassessment-editor #oa-settings-editor-text-fields { #openassessment-editor .openassessment-rubric-editor {
float: left; width: 100%;
width: 30%; } height: 100%; }
#openassessment-editor #oa-settings-assessments { #openassessment-editor .openassessment-assessments-editor {
float: right; width: 100%; }
width: 70%; #openassessment-editor #oa-settings-editor-wrapper {
height: 100%; } overflow-y: scroll; }
#openassessment-editor .xblock-actions { #openassessment-editor #openassessment-title-editor {
background-color: #e5e5e5; width: 300px;
position: absolute; margin-left: 50px; }
width: 100%; #openassessment-editor .openassessment-number-field {
bottom: 0; } width: 25px; }
#openassessment-editor .openassessment-date-field {
width: 130px; }
#openassessment-editor .openassessment-description {
font-size: 75%; }
#openassessment-editor .openassessment-text-field-wrapper {
width: 50%;
text-align: center; }
#openassessment-editor .right-text-field-wrapper {
float: right; }
#openassessment-editor .left-text-field-wrapper {
float: left; }
#openassessment-editor .openassessment-due-date-editor {
height: 30px; }
#openassessment-editor .openassessment-inclusion-wrapper {
background-color: #dadbdc;
padding: 2.5px 5px;
margin: 2.5px 5px;
border-radius: 2.5px; }
#openassessment-editor label {
padding-right: 10px; }
#openassessment-editor .xblock-actions {
background-color: #e5e5e5;
position: absolute;
width: 100%;
bottom: 0; }
#openassessment-editor .peer-number-constraints {
margin-bottom: 10px; }
#openassessment-editor .ui-widget-header .ui-state-default {
background: #e5e5e5; }
#openassessment-editor .ui-widget-header .ui-state-default a {
color: #202021;
text-transform: uppercase;
outline-color: transparent; }
#openassessment-editor .ui-widget-header .ui-state-active {
background: #414243;
color: whitesmoke; }
#openassessment-editor .ui-widget-header .ui-state-active a {
color: whitesmoke;
text-transform: uppercase;
outline-color: transparent; }
#openassessment-editor input[type="checkbox"] {
display: none; }
#openassessment-editor input[type="checkbox"] + label:before {
font-family: "FontAwesome";
display: inline-block;
margin-right: 10px;
width: auto;
height: auto;
content: "\f096"; }
#openassessment-editor input[type="checkbox"]:checked + label:before {
content: "\f046"; }
#openassessment-editor hr {
background-color: #d4d4d4;
color: #414243;
height: 1px;
border: 0px;
clear: both; }
.modal-content { .modal-content {
height: 500px !important; } height: 470px !important; }
.openassessment .self-assessment__display__header, .openassessment .peer-assessment__display__header, .openassessment .step__header { .openassessment .self-assessment__display__header, .openassessment .peer-assessment__display__header, .openassessment .step__header {
margin-bottom: 0 !important; margin-bottom: 0 !important;
......
...@@ -17,7 +17,21 @@ describe("OpenAssessment.StudioView", function() { ...@@ -17,7 +17,21 @@ describe("OpenAssessment.StudioView", function() {
this.titleField = ""; this.titleField = "";
this.submissionStartField = ""; this.submissionStartField = "";
this.submissionDueField = ""; this.submissionDueField = "";
this.assessmentsXmlBox = ""; this.hasPeer = true;
this.hasSelf = true;
this.hasTraining = true;
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;
...@@ -28,16 +42,27 @@ describe("OpenAssessment.StudioView", function() { ...@@ -28,16 +42,27 @@ describe("OpenAssessment.StudioView", function() {
this.loadEditorContext = function() { this.loadEditorContext = function() {
var prompt = this.promptBox; var prompt = this.promptBox;
var rubric = this.rubricXmlBox; var rubric = this.rubricXmlBox;
var settings = { var title = this.titleField;
title: this.titleField, var submission_start = this.submissionStartField;
submission_start: this.submissionStartField, var submission_due = this.submissionDueField;
submission_due: this.submissionDueField, var assessments = [
assessments: this.assessmentsXmlBox {
}; name: "peer",
must_grade: this.peerMustGrade,
must_be_graded_by: this.peerGradedBy,
start: this.peerStart,
due: this.peerDue
},
{
name: "self",
start: this.selfStart,
due: this.selfDue
}
];
if (!this.loadError) { if (!this.loadError) {
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
defer.resolveWith(this, [prompt, rubric, settings]); defer.resolveWith(this, [prompt, rubric, title, submission_start, submission_due, assessments]);
}).promise(); }).promise();
} }
else { else {
...@@ -45,14 +70,39 @@ describe("OpenAssessment.StudioView", function() { ...@@ -45,14 +70,39 @@ describe("OpenAssessment.StudioView", function() {
} }
}; };
this.updateEditorContext = function(prompt, rubricXml, title, sub_start, sub_due, assessmentsXml) { this.updateEditorContext = function(prompt, rubricXml, title, sub_start, sub_due, assessments) {
if (!this.updateError) { if (!this.updateError) {
this.promptBox = prompt; this.promptBox = prompt;
this.rubricXmlBox = rubricXml; this.rubricXmlBox = rubricXml;
this.titleField = title; this.titleField = title;
this.submissionStartField = sub_start; this.submissionStartField = sub_start;
this.submissionDueField = sub_due; this.submissionDueField = sub_due;
this.assessmentsXmlBox = assessmentsXml;
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) { return $.Deferred(function(defer) {
defer.resolve(); defer.resolve();
}).promise(); }).promise();
...@@ -97,11 +147,9 @@ describe("OpenAssessment.StudioView", function() { ...@@ -97,11 +147,9 @@ describe("OpenAssessment.StudioView", function() {
// Expect that the XML definition(s) were loaded // Expect that the XML definition(s) were loaded
var rubric = view.rubricXmlBox.getValue(); var rubric = view.rubricXmlBox.getValue();
var prompt = view.promptBox.value; var prompt = view.promptBox.value;
var assessments = view.assessmentsXmlBox.getValue()
expect(prompt).toEqual(''); expect(prompt).toEqual('');
expect(rubric).toEqual(''); expect(rubric).toEqual('');
expect(assessments).toEqual('');
}); });
it("saves the Editor Context definition", function() { it("saves the Editor Context definition", function() {
......
...@@ -51,17 +51,24 @@ describe("OpenAssessment.Server", function() { ...@@ -51,17 +51,24 @@ describe("OpenAssessment.Server", function() {
'</criterion>'+ '</criterion>'+
'</rubric>'; '</rubric>';
var assessments = '<assessments>' + var ASSESSMENTS = [
'<assessment name="peer-assessment" must_grade="1" must_be_graded_by="1" due="2000-01-02"/>' + {
'<assessment name="self-assessment" due="2000-01-8"/>' + "name": "peer-assessment",
'</assessments>'; "must_grade": 5,
"must_be_graded_by": 3,
var SETTINGS = { "start": "",
title: 'This is the title.', "due": "4014-03-10T00:00:00"
submission_start: '2012-10-09T00:00:00', },
submission_due: '2015-10-10T00:00:00', {
assessments: assessments "name": "self-assessment",
}; "start": "",
"due": ""
}
];
var TITLE = 'This is the title.';
var SUBMISSION_START = '2012-10-09T00:00:00';
var SUBMISSION_DUE = '2015-10-10T00:00:00';
beforeEach(function() { beforeEach(function() {
// Create the server // Create the server
...@@ -184,20 +191,33 @@ describe("OpenAssessment.Server", function() { ...@@ -184,20 +191,33 @@ describe("OpenAssessment.Server", function() {
}); });
it("loads the XBlock's Context definition", function() { it("loads the XBlock's Context definition", function() {
stubAjax(true, { success: true, prompt: PROMPT, rubric: RUBRIC, settings: SETTINGS}); stubAjax(true, {
success: true, prompt: PROMPT, rubric: RUBRIC, title: TITLE,
submission_start: SUBMISSION_START, submission_due: SUBMISSION_DUE, assessments: ASSESSMENTS
});
var loadedPrompt = ""; var loadedPrompt = "";
var loadedRubric = ""; var loadedRubric = "";
var loadedSettings = ""; var loadedAssessments = [];
server.loadEditorContext().done(function(prompt, rubric, settings) { var loadedTitle = "";
var loadedStart = "";
var loadedDue = "";
server.loadEditorContext().done(function(prompt, rubric, title, sub_start, sub_due, assessments) {
loadedPrompt = prompt; loadedPrompt = prompt;
loadedRubric = rubric; loadedRubric = rubric;
loadedSettings = settings; loadedTitle = title;
loadedStart = sub_start;
loadedDue = sub_due;
loadedAssessments = assessments;
}); });
expect(loadedPrompt).toEqual(PROMPT); expect(loadedPrompt).toEqual(PROMPT);
expect(loadedRubric).toEqual(RUBRIC); expect(loadedRubric).toEqual(RUBRIC);
expect(loadedSettings).toEqual(SETTINGS); expect(loadedTitle).toEqual(TITLE);
expect(loadedStart).toEqual(SUBMISSION_START);
expect(loadedDue).toEqual(SUBMISSION_DUE);
expect(loadedAssessments).toEqual(ASSESSMENTS);
expect($.ajax).toHaveBeenCalledWith({ expect($.ajax).toHaveBeenCalledWith({
url: '/editor_context', type: "POST", data: '""' url: '/editor_context', type: "POST", data: '""'
}); });
...@@ -207,11 +227,14 @@ describe("OpenAssessment.Server", function() { ...@@ -207,11 +227,14 @@ describe("OpenAssessment.Server", function() {
stubAjax(true, { success: true }); stubAjax(true, { success: true });
server.updateEditorContext( server.updateEditorContext(
PROMPT, RUBRIC, SETTINGS.title, SETTINGS.submission_start, SETTINGS.submission_due, SETTINGS.assessments PROMPT, RUBRIC, TITLE, SUBMISSION_START, SUBMISSION_DUE, ASSESSMENTS
); );
expect($.ajax).toHaveBeenCalledWith({ expect($.ajax).toHaveBeenCalledWith({
type: "POST", url: '/update_editor_context', type: "POST", url: '/update_editor_context',
data: JSON.stringify({prompt: PROMPT, rubric: RUBRIC, settings: SETTINGS}) data: JSON.stringify({
prompt: PROMPT, rubric: RUBRIC, title: TITLE, submission_start: SUBMISSION_START,
submission_due: SUBMISSION_DUE, assessments: ASSESSMENTS
})
}); });
}); });
......
...@@ -157,9 +157,7 @@ function OpenAssessmentBlock(runtime, element) { ...@@ -157,9 +157,7 @@ function OpenAssessmentBlock(runtime, element) {
/** /**
Render views within the base view on page load. Render views within the base view on page load.
**/ **/
$(function($) { var server = new OpenAssessment.Server(runtime, element);
var server = new OpenAssessment.Server(runtime, element); var view = new OpenAssessment.BaseView(runtime, element, server);
var view = new OpenAssessment.BaseView(runtime, element, server); view.load();
view.load();
});
} }
...@@ -32,8 +32,27 @@ OpenAssessment.StudioView = function(runtime, element, server) { ...@@ -32,8 +32,27 @@ OpenAssessment.StudioView = function(runtime, element, server) {
this.submissionDueField = live_element.find('.openassessment-submission-due-editor').first().get(0); this.submissionDueField = live_element.find('.openassessment-submission-due-editor').first().get(0);
this.assessmentsXmlBox = CodeMirror.fromTextArea( // Finds our boolean checkboxes that indicate the assessment definition
live_element.find('.openassessment-assessments-editor').first().get(0), this.hasPeer = live_element.find('#include-peer-assessment');
this.hasSelf = live_element.find('#include-self-assessment');
this.hasAI = live_element.find('#include-ai-assessment');
this.hasTraining = live_element.find('#include-student-training');
this.peerMustGrade = live_element.find('#peer-assessment-must-grade');
this.peerGradedBy = live_element.find('#peer-assessment-graded-by');
this.peerStart = live_element.find('#peer-assessment-start-date');
this.peerDue = live_element.find('#peer-assessment-due-date');
this.selfStart = live_element.find('#self-assessment-start-date');
this.selfDue = live_element.find('#self-assessment-due-date');
this.aiTrainingExamplesCodeBox = CodeMirror.fromTextArea(
live_element.find('#ai-training-examples').get(0),
{mode: "xml", lineNumbers: true, lineWrapping: true}
);
this.studentTrainingExamplesCodeBox = CodeMirror.fromTextArea(
live_element.find('#student-training-examples').get(0),
{mode: "xml", lineNumbers: true, lineWrapping: true} {mode: "xml", lineNumbers: true, lineWrapping: true}
); );
...@@ -52,9 +71,49 @@ OpenAssessment.StudioView = function(runtime, element, server) { ...@@ -52,9 +71,49 @@ OpenAssessment.StudioView = function(runtime, element, server) {
live_element.find('.openassessment-editor-content-and-tabs').tabs({ live_element.find('.openassessment-editor-content-and-tabs').tabs({
activate: function (event, ui){ activate: function (event, ui){
view.rubricXmlBox.refresh(); view.rubricXmlBox.refresh();
view.assessmentsXmlBox.refresh();
} }
}); });
live_element.find('#include-peer-assessment').change(function () {
if (this.checked){
$("#peer-assessment-description-closed", live_element).fadeOut('fast');
$("#peer-assessment-settings-editor", live_element).fadeIn();
} else {
$("#peer-assessment-settings-editor", live_element).fadeOut('fast');
$("#peer-assessment-description-closed", live_element).fadeIn();
}
});
live_element.find('#include-self-assessment').change(function () {
if (this.checked){
$("#self-assessment-description-closed", live_element).fadeOut('fast');
$("#self-assessment-settings-editor", live_element).fadeIn();
} else {
$("#self-assessment-settings-editor", live_element).fadeOut('fast');
$("#self-assessment-description-closed", live_element).fadeIn();
}
});
live_element.find('#include-ai-assessment').change(function () {
if (this.checked){
$("#ai-assessment-description-closed", live_element).fadeOut('fast');
$("#ai-assessment-settings-editor", live_element).fadeIn();
} else {
$("#ai-assessment-settings-editor", live_element).fadeOut('fast');
$("#ai-assessment-description-closed", live_element).fadeIn();
}
});
live_element.find('#include-student-training').change(function () {
if (this.checked){
$("#student-training-description-closed", live_element).fadeOut('fast');
$("#student-training-settings-editor", live_element).fadeIn();
} else {
$("#student-training-settings-editor", live_element).fadeOut('fast');
$("#student-training-description-closed", live_element).fadeIn();
}
});
}; };
OpenAssessment.StudioView.prototype = { OpenAssessment.StudioView.prototype = {
...@@ -65,13 +124,38 @@ OpenAssessment.StudioView.prototype = { ...@@ -65,13 +124,38 @@ OpenAssessment.StudioView.prototype = {
load: function () { load: function () {
var view = this; var view = this;
this.server.loadEditorContext().done( this.server.loadEditorContext().done(
function (prompt, rubricXml, settings) { function (prompt, rubricXml, title, sub_start, sub_due, assessments) {
view.rubricXmlBox.setValue(rubricXml); view.rubricXmlBox.setValue(rubricXml);
view.assessmentsXmlBox.setValue(settings.assessments); view.submissionStartField.value = sub_start;
view.submissionStartField.value = settings.submission_start; view.submissionDueField.value = sub_due;
view.submissionDueField.value = settings.submission_due;
view.promptBox.value = prompt; view.promptBox.value = prompt;
view.titleField.value = settings.title; view.titleField.value = title;
view.hasPeer.prop('checked',false).change();
view.hasSelf.prop('checked',false).change();
view.hasTraining.prop('checked',false).change();
view.hasAI.prop('checked',false).change();
for (var i = 0; i < assessments.length; i++) {
var assessment = assessments[i];
if (assessment.name == 'peer-assessment') {
view.hasPeer.prop('checked', true).change();
view.peerMustGrade.prop('value', assessment.must_grade);
view.peerGradedBy.prop('value', assessment.must_be_graded_by);
view.peerStart.prop('value', assessment.start);
view.peerDue.prop('value', assessment.due);
} else if (assessment.name == 'self-assessment') {
view.hasSelf.prop('checked', true).change();
view.selfStart.prop('value', assessment.start);
view.selfDue.prop('value', assessment.due);
} else if (assessment.name == 'example-based-assessment') {
view.hasAI.prop('checked', true).change();
view.aiTrainingExamplesCodeBox.setValue(assessment.examples);
} else if (assessment.name == 'student-training') {
view.hasTraining.prop('checked', true).change();
view.studentTrainingExamplesCodeBox.setValue(assessment.examples);
} else {
}
}
}).fail(function (msg) { }).fail(function (msg) {
view.showError(msg); view.showError(msg);
} }
...@@ -98,7 +182,7 @@ OpenAssessment.StudioView.prototype = { ...@@ -98,7 +182,7 @@ OpenAssessment.StudioView.prototype = {
} }
).fail(function (errMsg) { ).fail(function (errMsg) {
view.showError(msg); view.showError(msg);
}); });
}, },
/** /**
...@@ -131,10 +215,58 @@ OpenAssessment.StudioView.prototype = { ...@@ -131,10 +215,58 @@ OpenAssessment.StudioView.prototype = {
var title = this.titleField.value; var title = this.titleField.value;
var sub_start = this.submissionStartField.value; var sub_start = this.submissionStartField.value;
var sub_due = this.submissionDueField.value; var sub_due = this.submissionDueField.value;
var assessmentsXml = this.assessmentsXmlBox.getValue();
var assessments = [];
if (this.hasTraining.prop('checked')){
assessments[assessments.length] = {
"name": "student-training",
"examples": this.studentTrainingExamplesCodeBox.getValue()
};
}
if (this.hasPeer.prop('checked')) {
var assessment = {
"name": "peer-assessment",
"must_grade": parseInt(this.peerMustGrade.prop('value')),
"must_be_graded_by": parseInt(this.peerGradedBy.prop('value'))
};
var start_str = this.peerStart.prop('value');
var due_str = this.peerDue.prop('value');
if (start_str){
assessment = $.extend(assessment, {"start": start_str})
}
if (due_str){
assessment = $.extend(assessment, {"due": due_str})
}
assessments[assessments.length] = assessment;
}
if (this.hasSelf.prop('checked')) {
assessment = {
"name": "self-assessment"
};
start_str = this.selfStart.prop('value');
due_str = this.selfDue.prop('value');
if (start_str){
assessment = $.extend(assessment, {"start": start_str})
}
if (due_str){
assessment = $.extend(assessment, {"due": due_str})
}
assessments[assessments.length] = assessment;
}
if (this.hasAI.prop('checked')) {
assessments[assessments.length] = {
"name": "example-based-assessment",
"algorithm_id": "ease",
"examples": this.aiTrainingExamplesCodeBox.getValue()
};
}
var view = this; var view = this;
this.server.updateEditorContext(prompt, rubricXml, title, sub_start, sub_due, assessmentsXml).done(function () { this.server.updateEditorContext(prompt, rubricXml, title, sub_start, sub_due, assessments).done(function () {
// Notify the client-side runtime that we finished saving // Notify the client-side runtime that we finished saving
// so it can hide the "Saving..." notification. // so it can hide the "Saving..." notification.
view.runtime.notify('save', {state: 'end'}); view.runtime.notify('save', {state: 'end'});
...@@ -172,9 +304,9 @@ function OpenAssessmentEditor(runtime, element) { ...@@ -172,9 +304,9 @@ function OpenAssessmentEditor(runtime, element) {
/** /**
Initialize the editing interface on page load. Initialize the editing interface on page load.
**/ **/
$(function ($) {
var server = new OpenAssessment.Server(runtime, element); var server = new OpenAssessment.Server(runtime, element);
var view = new OpenAssessment.StudioView(runtime, element, server); var view = new OpenAssessment.StudioView(runtime, element, server);
view.load(); view.load();
});
}; };
\ No newline at end of file
...@@ -356,7 +356,9 @@ OpenAssessment.Server.prototype = { ...@@ -356,7 +356,9 @@ OpenAssessment.Server.prototype = {
$.ajax({ $.ajax({
type: "POST", url: url, data: "\"\"" type: "POST", url: url, data: "\"\""
}).done(function(data) { }).done(function(data) {
if (data.success) { defer.resolveWith(this, [data.prompt, data.rubric, data.settings]); } 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]); } else { defer.rejectWith(this, [data.msg]); }
}).fail(function(data) { }).fail(function(data) {
defer.rejectWith(this, [gettext('This problem could not be loaded.')]); defer.rejectWith(this, [gettext('This problem could not be loaded.')]);
...@@ -367,7 +369,7 @@ OpenAssessment.Server.prototype = { ...@@ -367,7 +369,7 @@ OpenAssessment.Server.prototype = {
/** /**
Update the XBlock's XML definition on the server. Update the XBlock's XML definition on the server.
Returns: Return
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.
...@@ -378,15 +380,16 @@ OpenAssessment.Server.prototype = { ...@@ -378,15 +380,16 @@ OpenAssessment.Server.prototype = {
function(err) { console.log(err); } function(err) { console.log(err); }
); );
**/ **/
updateEditorContext: function(prompt, rubricXml, title, sub_start, sub_due, assessmentsXml) { updateEditorContext: function(prompt, rubricXml, title, sub_start, sub_due, assessments) {
var url = this.url('update_editor_context'); var url = this.url('update_editor_context');
var settings = { var payload = JSON.stringify({
'prompt': prompt,
'rubric': rubricXml,
'title': title, 'title': title,
'submission_start': sub_start, 'submission_start': sub_start,
'submission_due': sub_due, 'submission_due': sub_due,
'assessments': assessmentsXml 'assessments': assessments
}; });
var payload = JSON.stringify({'prompt': prompt, 'rubric': rubricXml, 'settings': settings});
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({ $.ajax({
type: "POST", url: url, data: payload type: "POST", url: url, data: payload
......
...@@ -171,6 +171,7 @@ ...@@ -171,6 +171,7 @@
// -------------------- // --------------------
#openassessment-editor { #openassessment-editor {
margin-bottom: 0;
.openassessment-editor-content-and-tabs { .openassessment-editor-content-and-tabs {
width: 100%; width: 100%;
...@@ -191,6 +192,9 @@ ...@@ -191,6 +192,9 @@
float: right; float: right;
padding: ($baseline-v/8) ($baseline-h/8); padding: ($baseline-v/8) ($baseline-h/8);
margin: ($baseline-v/8) ($baseline-h/8); margin: ($baseline-v/8) ($baseline-h/8);
border-radius: ($baseline-v/8);
box-shadow: none;
border: 0;
} }
.oa-editor-content-wrapper { .oa-editor-content-wrapper {
...@@ -215,14 +219,57 @@ ...@@ -215,14 +219,57 @@
} }
#oa-settings-editor-text-fields { #oa-settings-editor-text-fields {
float: left;
width: 30%;
} }
#oa-settings-assessments{ #oa-settings-editor-wrapper {
overflow-y: scroll;
}
#openassessment-title-editor {
width: 300px;
margin-left: 50px;
}
.openassessment-number-field{
width: 25px;
}
.openassessment-date-field{
width: 130px;
}
.openassessment-description{
font-size: 75%;
}
.openassessment-text-field-wrapper{
width: 50%;
text-align: center;
}
.right-text-field-wrapper {
float: right; float: right;
width: 70%; }
height: 100%;
.left-text-field-wrapper {
float: left;
}
.openassessment-due-date-editor{
height: 30px;
}
.openassessment-inclusion-wrapper{
background-color: $edx-gray-l3;
input[type="checkbox"] {
}
padding: ($baseline-v/8) ($baseline-h/8);
margin: ($baseline-v/8) ($baseline-h/8);
border-radius: ($baseline-v)/8;
}
label{
padding-right: 10px;
} }
.xblock-actions { .xblock-actions {
...@@ -231,8 +278,58 @@ ...@@ -231,8 +278,58 @@
width: 100%; width: 100%;
bottom: 0; bottom: 0;
} }
.peer-number-constraints{
margin-bottom: 10px;
}
.ui-widget-header .ui-state-default{
background: #e5e5e5;
a{
color: $edx-gray-d4;
text-transform: uppercase;
outline-color: transparent;
}
}
.ui-widget-header .ui-state-active{
background: $edx-gray-d3;
color: $white;
a{
color: $white;
text-transform: uppercase;
outline-color: transparent;
}
}
input[type="checkbox"]{
display: none;
}
input[type="checkbox"] + label:before {
font-family: "FontAwesome";
display: inline-block;
margin-right: ($baseline-h/4);
width: auto;
height: auto;
content: "\f096";
}
input[type="checkbox"]:checked + label:before{
content: "\f046";
}
hr {
background-color: #d4d4d4;
color: $edx-gray-d3;
height: 1px;
border: 0px;
clear: both;
}
} }
.modal-content { .modal-content {
height: 500px !important; height: 470px !important;
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
Studio editing view for OpenAssessment XBlock. Studio editing view for OpenAssessment XBlock.
""" """
import pkg_resources import pkg_resources
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
...@@ -42,12 +43,14 @@ class StudioMixin(object): ...@@ -42,12 +43,14 @@ class StudioMixin(object):
Update the XBlock's configuration. Update the XBlock's configuration.
Args: Args:
data (dict): Data from the request; should have a value for the keys data (dict): Data from the request; should have a value for the keys: 'rubric', 'prompt',
'rubric', 'settings' and 'prompt'. The 'rubric' should be an XML 'title', 'submission_start', 'submission_due', and 'assessments'.
representation of the new rubric. The 'prompt' should be a plain -- The 'rubric' should be an XML representation of the new rubric.
text prompt. The 'settings' should be a dict of 'title', -- The 'prompt' and 'title' should be plain text.
'submission_due', 'submission_start' and the XML configuration for -- The dates 'submission_start' and 'submission_due' are both ISO strings
all 'assessments'. -- The 'assessments' is a list of asessment dictionaries (much like self.rubric_assessments)
with the notable exception that all examples (for Student Training and eventually AI)
are in XML string format and need to be parsed into dictionaries.
Kwargs: Kwargs:
suffix (str): Not used suffix (str): Not used
...@@ -55,18 +58,20 @@ class StudioMixin(object): ...@@ -55,18 +58,20 @@ 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', 'settings', 'prompt'} - set(data.keys())) missing_keys = list(
{'rubric', 'prompt', 'title', 'assessments', 'submission_start', 'submission_due'} - set(data.keys())
)
if missing_keys: if missing_keys:
logger.warn( logger.warn(
'Must specify the following keys in request JSON dict: {}'.format(missing_keys) 'Must specify the following missing keys in request JSON dict: {}'.format(missing_keys)
) )
return {'success': False, 'msg': _('Error updating XBlock configuration')} return {'success': False, 'msg': _('Error updating XBlock configuration')}
settings = data['settings']
try: try:
rubric = xml.parse_rubric_xml_str(data['rubric']) rubric = xml.parse_rubric_xml_str(data["rubric"])
assessments = xml.parse_assessments_xml_str(settings['assessments']) submission_due = xml.parse_date(data["submission_due"])
submission_due = xml.parse_date(settings["submission_due"]) submission_start = xml.parse_date(data["submission_start"])
submission_start = xml.parse_date(settings["submission_start"]) assessments = xml.parse_assessment_dictionaries(data["assessments"])
except xml.UpdateFromXmlError as ex: except xml.UpdateFromXmlError as ex:
return {'success': False, 'msg': _('An error occurred while saving: {error}').format(error=ex)} return {'success': False, 'msg': _('An error occurred while saving: {error}').format(error=ex)}
...@@ -81,7 +86,7 @@ class StudioMixin(object): ...@@ -81,7 +86,7 @@ class StudioMixin(object):
assessments, assessments,
submission_due, submission_due,
submission_start, submission_start,
settings["title"], data["title"],
data["prompt"] data["prompt"]
) )
return {'success': True, 'msg': 'Successfully updated OpenAssessment XBlock'} return {'success': True, 'msg': 'Successfully updated OpenAssessment XBlock'}
...@@ -100,15 +105,27 @@ class StudioMixin(object): ...@@ -100,15 +105,27 @@ class StudioMixin(object):
suffix (str): Not used suffix (str): Not used
Returns: Returns:
dict with keys 'success' (bool), 'message' (unicode), dict with keys
'rubric' (unicode), 'prompt' (unicode), and 'settings' (dict) 'success' (bool), 'message' (unicode), 'rubric' (unicode), 'prompt' (unicode),
'title' (unicode), 'submission_start' (unicode), 'submission_due' (unicode), 'assessments (dict)
""" """
try: try:
assessments = xml.serialize_assessments_to_xml_str(self)
rubric = xml.serialize_rubric_to_xml_str(self) rubric = xml.serialize_rubric_to_xml_str(self)
# We do not expect serialization to raise an exception,
# but if it does, handle it gracefully. # Copies the rubric assessments so that we can change student training examples from dict -> str without
# negatively modifying the openassessmentblock definition.
assessment_list = copy.deepcopy(self.rubric_assessments)
# Finds the student training dictionary, if it exists, and replaces the examples with their XML definition
student_training_dictionary = [d for d in assessment_list if d["name"] == "student-training"]
if student_training_dictionary:
# Our for loop will return a list. Select the first element of that list if it exists.
student_training_dictionary = student_training_dictionary[0]
examples = xml.serialize_examples_to_xml_str(student_training_dictionary)
student_training_dictionary["examples"] = examples
# We do not expect serialization to raise an exception, but if it does, handle it gracefully.
except Exception as ex: except Exception as ex:
msg = _('An unexpected error occurred while loading the problem: {error}').format(error=ex) msg = _('An unexpected error occurred while loading the problem: {error}').format(error=ex)
logger.error(msg) logger.error(msg)
...@@ -122,19 +139,15 @@ class StudioMixin(object): ...@@ -122,19 +139,15 @@ class StudioMixin(object):
submission_start = self.submission_start if self.submission_start else '' submission_start = self.submission_start if self.submission_start else ''
settings = {
'submission_due': submission_due,
'submission_start': submission_start,
'title': self.title,
'assessments': assessments
}
return { return {
'success': True, 'success': True,
'msg': '', 'msg': '',
'rubric': rubric, 'rubric': rubric,
'prompt': self.prompt, 'prompt': self.prompt,
'settings': settings 'submission_due': submission_due,
'submission_start': submission_start,
'title': self.title,
'assessments': assessment_list
} }
@XBlock.json_handler @XBlock.json_handler
...@@ -157,3 +170,4 @@ class StudioMixin(object): ...@@ -157,3 +170,4 @@ class StudioMixin(object):
'success': True, 'msg': u'', 'success': True, 'msg': u'',
'is_released': self.is_released() 'is_released': self.is_released()
} }
...@@ -11,17 +11,24 @@ ...@@ -11,17 +11,24 @@
"</rubric>" "</rubric>"
], ],
"prompt": "My new prompt.", "prompt": "My new prompt.",
"settings": { "submission_due": "4014-02-27T09:46:28",
"title": "My new title.", "submission_start": "4014-02-10T09:46:28",
"assessments": [ "title": "My new title.",
"<assessments>", "assessments": [
"<assessment name=\"peer-assessment\" must_grade=\"5\" must_be_graded_by=\"3\" />", {
"<assessment name=\"self-assessment\" />", "name": "peer-assessment",
"</assessments>" "must_grade": 5,
], "must_be_graded_by": 3,
"submission_due": "2014-02-27T09:46:28", "start": "",
"submission_start": "2014-02-10T09:46:28" "due": "4014-03-10T00:00:00"
}, },
{
"name": "self-assessment",
"start": "",
"due": ""
}
],
"expected-assessment": "peer-assessment", "expected-assessment": "peer-assessment",
"expected-criterion-prompt": "Test criterion prompt" "expected-criterion-prompt": "Test criterion prompt"
} }
......
{ {
"no_rubric": { "no_rubric": {
"prompt": "My new prompt.", "prompt": "My new prompt.",
"settings": { "title": "My new title.",
"title": "My new title.", "assessments": [
"assessments": [ {
"<assessments>", "name": "peer-assessment",
"<assessment name=\"peer-assessment\" must_grade=\"5\" must_be_graded_by=\"3\" />", "must_grade": 5,
"<assessment name=\"self-assessment\" />", "must_be_graded_by": 3,
"</assessments>" "start": "",
], "due": ""
"submission_due": "2014-02-27T09:46:28", },
"submission_start": "2014-02-10T09:46:28" {
}, "name": "self-assessment",
"start": "",
"due": ""
}
],
"submission_due": "2014-02-27T09:46:28",
"submission_start": "2014-02-10T09:46:28",
"expected_error": "error" "expected_error": "error"
}, },
"no_prompt": { "no_prompt": {
...@@ -26,20 +32,26 @@ ...@@ -26,20 +32,26 @@
"</criterion>", "</criterion>",
"</rubric>" "</rubric>"
], ],
"settings": { "title": "My new title.",
"title": "My new title.", "assessments": [
"assessments": [ {
"<assessments>", "name": "peer-assessment",
"<assessment name=\"peer-assessment\" must_grade=\"5\" must_be_graded_by=\"3\" />", "must_grade": 5,
"<assessment name=\"self-assessment\" />", "must_be_graded_by": 3,
"</assessments>" "start": "",
], "due": ""
"submission_due": "2014-02-27T09:46:28", },
"submission_start": "2014-02-10T09:46:28" {
}, "name": "self-assessment",
"start": "",
"due": ""
}
],
"submission_due": "2014-02-27T09:46:28",
"submission_start": "2014-02-10T09:46:28",
"expected_error": "error" "expected_error": "error"
}, },
"no_settings": { "no_submission_due": {
"rubric": [ "rubric": [
"<rubric>", "<rubric>",
"<prompt>Test prompt</prompt>", "<prompt>Test prompt</prompt>",
...@@ -52,9 +64,59 @@ ...@@ -52,9 +64,59 @@
"</rubric>" "</rubric>"
], ],
"prompt": "My new prompt.", "prompt": "My new prompt.",
"title": "My new title.",
"assessments": [
{
"name": "peer-assessment",
"must_grade": 5,
"must_be_graded_by": 3,
"start": "",
"due": ""
},
{
"name": "self-assessment",
"start": "",
"due": ""
}
],
"submission_start": "2014-02-10T09:46:28",
"expected_error": "error" "expected_error": "error"
}, },
"invalid_dates": { "invalid_dates_one": {
"rubric": [
"<rubric>",
"<prompt>Test prompt</prompt>",
"<criterion>",
"<name>Test criterion</name>",
"<prompt>Test criterion prompt</prompt>",
"<option points=\"0\"><name>No</name><explanation>No explanation</explanation></option>",
"<option points=\"2\"><name>Yes</name><explanation>Yes explanation</explanation></option>",
"</criterion>",
"</rubric>"
],
"prompt": "My new prompt.",
"title": "My new title.",
"assessments": [
{
"name": "peer-assessment",
"must_grade": 5,
"must_be_graded_by": 3,
"start": "",
"due": ""
},
{
"name": "self-assessment",
"start": "",
"due": ""
}
],
"submission_due": "2012-02-27T09:46:28",
"submission_start": "2015-02-10T09:46:28",
"expected_error": "cannot be later"
},
"invalid_dates_two": {
"rubric": [ "rubric": [
"<rubric>", "<rubric>",
"<prompt>Test prompt</prompt>", "<prompt>Test prompt</prompt>",
...@@ -67,17 +129,25 @@ ...@@ -67,17 +129,25 @@
"</rubric>" "</rubric>"
], ],
"prompt": "My new prompt.", "prompt": "My new prompt.",
"settings": {
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
"<assessments>", {
"<assessment name=\"peer-assessment\" must_grade=\"5\" must_be_graded_by=\"3\" />", "name": "peer-assessment",
"<assessment name=\"self-assessment\" start=\"2010-01-01\" due=\"2003-01-01\"/>", "must_grade": 5,
"</assessments>" "must_be_graded_by": 3,
"start": "",
"due": ""
},
{
"name": "self-assessment",
"start": "",
"due": "2003-01-02T00:00:00"
}
], ],
"submission_due": "2012-02-27T09:46:28", "submission_due": "2012-02-27T09:46:28",
"submission_start": "2015-02-10T09:46:28" "submission_start": "",
}, "expected_error": "cannot be later"
"expected_error": "cannot be earlier"
} }
} }
\ No newline at end of file
{
"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
...@@ -12,17 +12,24 @@ ...@@ -12,17 +12,24 @@
"</rubric>" "</rubric>"
], ],
"prompt": "My new prompt.", "prompt": "My new prompt.",
"settings": { "submission_due": "4014-02-27T09:46:28",
"title": "My new title.", "submission_start": "4014-02-10T09:46:28",
"assessments": [ "title": "My new title.",
"<assessments>", "assessments": [
"<assessment name=\"peer-assessment\" must_grade=\"5\" must_be_graded_by=\"3\" />", {
"<assessment name=\"self-assessment\" />", "name": "peer-assessment",
"</assessments>" "must_grade": 5,
], "must_be_graded_by": 3,
"submission_due": "4014-02-27T09:46:28", "start": "",
"submission_start": "4014-02-10T09:46:28" "due": "4014-03-10T00:00:00"
}, },
{
"name": "self-assessment",
"start": "",
"due": ""
}
],
"expected-assessment": "peer-assessment", "expected-assessment": "peer-assessment",
"expected-criterion-prompt": "Test criterion prompt" "expected-criterion-prompt": "Test criterion prompt"
} }
......
...@@ -33,8 +33,12 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -33,8 +33,12 @@ class StudioViewTest(XBlockHandlerTestCase):
rubric = etree.fromstring(resp['rubric']) rubric = etree.fromstring(resp['rubric'])
self.assertEqual(rubric.tag, 'rubric') self.assertEqual(rubric.tag, 'rubric')
assessments = etree.fromstring(resp['settings']['assessments']) # Verify that every assessment in the list of assessments has a name.
self.assertEqual(assessments.tag, 'assessments') for assessment_dict in resp['assessments']:
self.assertTrue(assessment_dict.get('name', False))
if assessment_dict.get('name') == 'studnet-training':
examples = etree.fromstring(assessment_dict['examples'])
self.assertEqual(examples.tag, 'examples')
@mock.patch('openassessment.xblock.xml.serialize_rubric_to_xml_str') @mock.patch('openassessment.xblock.xml.serialize_rubric_to_xml_str')
@scenario('data/basic_scenario.xml') @scenario('data/basic_scenario.xml')
...@@ -52,7 +56,6 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -52,7 +56,6 @@ class StudioViewTest(XBlockHandlerTestCase):
def test_update_xblock(self, xblock, data): def test_update_xblock(self, xblock, data):
# First, parse XML data into a single string. # First, parse XML data into a single string.
data['rubric'] = "".join(data['rubric']) data['rubric'] = "".join(data['rubric'])
data['settings']['assessments'] = "".join(data['settings']['assessments'])
xblock.published_date = None xblock.published_date = None
# Test that we can update the xblock with the expected configuration. # Test that we can update the xblock with the expected configuration.
request = json.dumps(data) request = json.dumps(data)
...@@ -66,7 +69,7 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -66,7 +69,7 @@ class StudioViewTest(XBlockHandlerTestCase):
# Check that the XBlock fields were updated # Check that the XBlock fields were 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
# that verify this extensively. # that verify this extensively.
self.assertEqual(xblock.title, data['settings']['title']) self.assertEqual(xblock.title, data['title'])
self.assertEqual(xblock.prompt, data['prompt']) self.assertEqual(xblock.prompt, data['prompt'])
self.assertEqual(xblock.rubric_assessments[0]['name'], data['expected-assessment']) self.assertEqual(xblock.rubric_assessments[0]['name'], data['expected-assessment'])
self.assertEqual(xblock.rubric_criteria[0]['prompt'], data['expected-criterion-prompt']) self.assertEqual(xblock.rubric_criteria[0]['prompt'], data['expected-criterion-prompt'])
...@@ -76,7 +79,6 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -76,7 +79,6 @@ class StudioViewTest(XBlockHandlerTestCase):
def test_update_context_post_release(self, xblock, data): def test_update_context_post_release(self, xblock, data):
# First, parse XML data into a single string. # First, parse XML data into a single string.
data['rubric'] = "".join(data['rubric']) data['rubric'] = "".join(data['rubric'])
data['settings']['assessments'] = "".join(data['settings']['assessments'])
# XBlock start date defaults to already open, # XBlock start date defaults to already open,
# so we should get an error when trying to update anything that change the number of points # so we should get an error when trying to update anything that change the number of points
...@@ -93,9 +95,6 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -93,9 +95,6 @@ class StudioViewTest(XBlockHandlerTestCase):
if 'rubric' in data: if 'rubric' in data:
data['rubric'] = "".join(data['rubric']) data['rubric'] = "".join(data['rubric'])
if 'settings' in data and 'assessments' in data['settings']:
data['settings']['assessments'] = "".join(data['settings']['assessments'])
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')
...@@ -107,7 +106,6 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -107,7 +106,6 @@ class StudioViewTest(XBlockHandlerTestCase):
def test_update_rubric_invalid(self, xblock, data): def test_update_rubric_invalid(self, xblock, data):
# First, parse XML data into a single string. # First, parse XML data into a single string.
data['rubric'] = "".join(data['rubric']) data['rubric'] = "".join(data['rubric'])
data['settings']['assessments'] = "".join(data['settings']['assessments'])
request = json.dumps(data) request = json.dumps(data)
......
...@@ -15,7 +15,7 @@ from openassessment.xblock.xml import ( ...@@ -15,7 +15,7 @@ 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,
serialize_rubric_to_xml_str, serialize_examples_to_xml_str, serialize_rubric_to_xml_str, serialize_examples_to_xml_str,
serialize_assessments_to_xml_str, UpdateFromXmlError serialize_assessments_to_xml_str, UpdateFromXmlError, parse_assessment_dictionaries
) )
...@@ -358,6 +358,21 @@ class TestParseAssessmentsFromXml(TestCase): ...@@ -358,6 +358,21 @@ 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'])
for i in range(0, len(config)):
self.assertEqual(config[i], data['results'][i])
@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):
...@@ -399,3 +414,4 @@ class TestUpdateFromXml(TestCase): ...@@ -399,3 +414,4 @@ class TestUpdateFromXml(TestCase):
def test_parse_from_xml_error(self, data): def test_parse_from_xml_error(self, data):
with self.assertRaises(UpdateFromXmlError): with self.assertRaises(UpdateFromXmlError):
parse_from_xml_str("".join(data['xml'])) parse_from_xml_str("".join(data['xml']))
...@@ -295,7 +295,7 @@ def validator(oa_block, strict_post_release=True): ...@@ -295,7 +295,7 @@ def validator(oa_block, strict_post_release=True):
# Dates # Dates
submission_dates = [(submission_dict['start'], submission_dict['due'])] submission_dates = [(submission_dict['start'], submission_dict['due'])]
assessment_dates = [(asmnt['start'], asmnt['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:
return (False, msg) return (False, msg)
......
...@@ -374,6 +374,68 @@ def parse_examples_xml(examples): ...@@ -374,6 +374,68 @@ def parse_examples_xml(examples):
return examples_list return examples_list
def parse_assessment_dictionaries(input_assessments):
assessments_list = []
for assessment in input_assessments:
assessment_dict = dict()
# Assessment name
if assessment.get('name'):
assessment_dict['name'] = unicode(assessment.get('name'))
else:
raise UpdateFromXmlError(_('All "assessment" elements must contain a "name" element.'))
# Assessment start
if assessment.get('start'):
try:
parsed_start = parse_date(assessment.get('start'))
assessment_dict['start'] = parsed_start
except UpdateFromXmlError:
raise UpdateFromXmlError(_('The date format in the "start" attribute is invalid. Make sure the date is formatted as YYYY-MM-DDTHH:MM:SS.'))
else:
assessment_dict['start'] = None
# Assessment due
if assessment.get('due'):
try:
parsed_due = parse_date(assessment.get('due'))
assessment_dict['due'] = parsed_due
except UpdateFromXmlError:
raise UpdateFromXmlError(_('The date format in the "due" attribute is invalid. Make sure the date is formatted as YYYY-MM-DDTHH:MM:SS.'))
else:
assessment_dict['due'] = None
# Assessment must_grade
if assessment.get('must_grade'):
try:
assessment_dict['must_grade'] = int(assessment.get('must_grade'))
except ValueError:
raise UpdateFromXmlError(_('The "must_grade" value must be a positive integer.'))
# Assessment must_be_graded_by
if assessment.get('must_be_graded_by'):
try:
assessment_dict['must_be_graded_by'] = int(assessment.get('must_be_graded_by'))
except ValueError:
raise UpdateFromXmlError(_('The "must_be_graded_by" value must be a positive integer.'))
# Training examples (can be for AI or for Student Training)
if assessment.get('examples'):
try:
assessment_dict['examples'] = parse_examples_xml_str(assessment.get('examples'))
except (UpdateFromXmlError, UnicodeError) as ex:
raise UpdateFromXmlError(_(
"There was an error in parsing the {0} examples: {1}".format(assessment.get('name'), ex)
))
# Update the list of assessments
assessments_list.append(assessment_dict)
return assessments_list
def parse_assessments_xml(assessments_root): def parse_assessments_xml(assessments_root):
""" """
Parse the <assessments> element in the OpenAssessment XBlock's content XML. Parse the <assessments> element in the OpenAssessment XBlock's content XML.
...@@ -765,8 +827,12 @@ def parse_examples_xml_str(xml): ...@@ -765,8 +827,12 @@ def parse_examples_xml_str(xml):
""" """
xml = u"<data>" + xml + u"</data>" if "<examples>" not in xml:
return parse_examples_xml(list(_unicode_to_xml(xml))) xml = u"<data>" + xml + u"</data>"
else:
xml = unicode(xml)
return parse_examples_xml(list(_unicode_to_xml(xml).findall('example')))
def _unicode_to_xml(xml): def _unicode_to_xml(xml):
......
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