Commit 700a69ce by gradyward

Finished OA editing in three tabs

parents 721d1748 d92cce76
......@@ -4,7 +4,7 @@
<div class="openassessment-editor-content-and-tabs">
<div class="openassessment-editor-header">
<h6 id="oa-editor-window-title" class="title modal-window-title" >Editing: Open Assessment</h6>
<h6 id="oa-editor-window-title" class="title modal-window-title" >{% trans "Editing: Open Assessment" %}</h6>
<ul class="editor-modes action-list action-modes editor-tabs">
<li class="view-button oa-editor-tab"><a href="#oa-settings-editor-wrapper">{% trans "Settings" %}</a></li>
<li class="view-button oa-editor-tab"><a href="#oa-rubric-editor-wrapper">{% trans "Rubric" %}</a></li>
......@@ -13,27 +13,29 @@
</div>
<div id = "oa-prompt-editor-wrapper" class="oa-editor-content-wrapper">
<h2>Prompt Editor</h2>
<h2>{% trans "Prompt Editor" %}</h2>
<textarea class="openassessment-prompt-editor"></textarea>
</div>
<div id="oa-rubric-editor-wrapper" class="oa-editor-content-wrapper">
<h2>Rubric Editor</h2>
<h2>{% trans "Rubric Editor" %}</h2>
<div id="rubric-editor-wrapper">
<textarea class="openassessment-rubric-editor"></textarea>
</div>
</div>
<div id="oa-settings-editor-wrapper" class="oa-editor-content-wrapper">
<div id="oa-settings-editor-text-fields">
<h2>Settings Editor</h2>
<h2>Title:</h2>
<h2>{% trans "Settings Editor" %}</h2>
<h2>{% trans "Title:" %}</h2>
<input type="text" name="title" class="openassessment-title-editor">
<h2>Submission Start Date: </h2>
<h2>{% trans "Submission Start Date:" %}</h2>
<input type="text" name="start_date" class="openassessment-submission-start-editor">
<h2>Submission Due Date:</h2>
<h2>{% trans "Submission Due Date:" %}</h2>
<input type="text" name="due_date" class="openassessment-submission-due-editor">
</div>
<div id="oa-settings-assessments">
XML For Assessments:
<h2> {% trans "XML for Assessments" %} </h2>
<textarea class="openassessment-assessments-editor"></textarea>
</div>
</div>
......
......@@ -334,7 +334,7 @@ class OpenAssessmentBlock(
xblock_validator = validator(block, strict_post_release=False)
xblock_validator(
rubric,
{'due': config['submission_due']},
{ 'due': config['submission_due'], 'start': config['submission_start']},
config['rubric_assessments']
)
......
......@@ -12,18 +12,32 @@ describe("OpenAssessment.StudioView", function() {
var StubServer = function() {
this.loadError = false;
this.updateError = false;
this.xml = '<openassessment></openassessment>';
this.promptBox = "";
this.rubricXmlBox = "";
this.titleField = "";
this.submissionStartField = "";
this.submissionDueField = "";
this.assessmentsXmlBox = "";
this.isReleased = false;
this.errorPromise = $.Deferred(function(defer) {
defer.rejectWith(this, ['Test error']);
}).promise();
this.loadXml = function() {
var xml = this.xml;
this.loadEditorContext = function() {
var prompt = this.promptBox;
var rubric = this.rubricXmlBox;
var settings = {
title: this.titleField,
submission_start: this.submissionStartField,
submission_due: this.submissionDueField,
assessments: this.assessmentsXmlBox
};
if (!this.loadError) {
return $.Deferred(function(defer) {
defer.resolveWith(this, [xml]);
defer.resolveWith(this, [prompt, rubric, settings]);
}).promise();
}
else {
......@@ -31,9 +45,14 @@ describe("OpenAssessment.StudioView", function() {
}
};
this.updateXml = function(xml) {
this.updateEditorContext = function(prompt, rubricXml, title, sub_start, sub_due, assessmentsXml) {
if (!this.updateError) {
this.xml = xml;
this.promptBox = prompt;
this.rubricXmlBox = rubricXml;
this.titleField = title;
this.submissionStartField = sub_start;
this.submissionDueField = sub_due;
this.assessmentsXmlBox = assessmentsXml;
return $.Deferred(function(defer) {
defer.resolve();
}).promise();
......@@ -70,8 +89,8 @@ describe("OpenAssessment.StudioView", function() {
var el = $('#openassessment-editor').get(0);
view = new OpenAssessment.StudioView(runtime, el, server);
});
/*
it("loads the XML definition", function() {
it("loads the editor context definition", function() {
// Initialize the view
view.load();
......@@ -81,23 +100,23 @@ describe("OpenAssessment.StudioView", function() {
var assessments = view.assessmentsXmlBox.getValue()
expect(prompt).toEqual('');
expect(rubric).toEqual('<rubric></rubric>');
expect(assessments).toEqual('<assessments></assessments>');
expect(rubric).toEqual('');
expect(assessments).toEqual('');
});
it("saves the XML definition", function() {
// Update the XML
view.codeBox.setValue('<openassessment>test!</openassessment>');
it("saves the Editor Context definition", function() {
// Update the Context
view.titleField.value = 'THIS IS THE NEW TITLE';
// Save the updated XML
// Save the updated editor definition
view.save();
// Expect the saving notification to start/end
expect(runtime.notify).toHaveBeenCalledWith('save', {state: 'start'});
expect(runtime.notify).toHaveBeenCalledWith('save', {state: 'end'});
// Expect the server's XML to have been updated
expect(server.xml).toEqual('<openassessment>test!</openassessment>');
// Expect the server's context to have been updated
expect(server.titleField).toEqual('THIS IS THE NEW TITLE');
});
it("confirms changes for a released problem", function() {
......@@ -109,7 +128,7 @@ describe("OpenAssessment.StudioView", function() {
function(onConfirm) { onConfirm(); }
);
// Save the updated XML
// Save the updated context
view.save();
// Verify that the user was asked to confirm the changes
......@@ -129,8 +148,7 @@ describe("OpenAssessment.StudioView", function() {
it("displays an error when server reports an update XML error", function() {
server.updateError = true;
view.save('<openassessment>test!</openassessment>');
view.save();
expect(runtime.notify).toHaveBeenCalledWith('error', {msg: 'Test error'});
});
*/
});
......@@ -30,6 +30,39 @@ describe("OpenAssessment.Server", function() {
);
};
var PROMPT = "Hello this is the prompt yes.";
var RUBRIC = '<rubric>'+
'<criterion>'+
'<name>𝓒𝓸𝓷𝓬𝓲𝓼𝓮</name>'+
'<prompt>How concise is it?</prompt>'+
'<option points="3">'+
'<name>ﻉซƈﻉɭɭﻉกՇ</name>'+
'<explanation>Extremely concise</explanation>'+
'</option>'+
'<option points="2">'+
'<name>Ġööḋ</name>'+
'<explanation>Concise</explanation>'+
'</option>'+
'<option points="1">'+
'<name>ק๏๏г</name>'+
'<explanation>Wordy</explanation>'+
'</option>'+
'</criterion>'+
'</rubric>';
var assessments = '<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"/>' +
'</assessments>';
var SETTINGS = {
title: 'This is the title.',
submission_start: '2012-10-09T00:00:00',
submission_due: '2015-10-10T00:00:00',
assessments: assessments
};
beforeEach(function() {
// Create the server
// Since the runtime is a stub implementation that ignores the element passed to it,
......@@ -150,31 +183,38 @@ describe("OpenAssessment.Server", function() {
});
});
/*
it("loads the XBlock's XML definition", function() {
stubAjax(true, { success: true, xml: "<openassessment />" });
it("loads the XBlock's Context definition", function() {
stubAjax(true, { success: true, prompt: PROMPT, rubric: RUBRIC, settings: SETTINGS});
var loadedXml = "";
server.loadXml().done(function(xml) {
loadedXml = xml;
var loadedPrompt = "";
var loadedRubric = "";
var loadedSettings = "";
server.loadEditorContext().done(function(prompt, rubric, settings) {
loadedPrompt = prompt;
loadedRubric = rubric;
loadedSettings = settings;
});
expect(loadedXml).toEqual('<openassessment />');
expect(loadedPrompt).toEqual(PROMPT);
expect(loadedRubric).toEqual(RUBRIC);
expect(loadedSettings).toEqual(SETTINGS);
expect($.ajax).toHaveBeenCalledWith({
url: '/xml', type: "POST", data: '""'
url: '/editor_context', type: "POST", data: '""'
});
});
it("updates the XBlock's XML definition", function() {
it("updates the XBlock's Context definition", function() {
stubAjax(true, { success: true });
server.updateXml('<openassessment />');
server.updateEditorContext(
PROMPT, RUBRIC, SETTINGS.title, SETTINGS.submission_start, SETTINGS.submission_due, SETTINGS.assessments
);
expect($.ajax).toHaveBeenCalledWith({
url: '/update_xml', type: "POST",
data: JSON.stringify({xml: '<openassessment />'})
type: "POST", url: '/update_editor_context',
data: JSON.stringify({prompt: PROMPT, rubric: RUBRIC, settings: SETTINGS})
});
});
*/
it("Checks whether the XBlock has been released", function() {
stubAjax(true, { success: true, is_released: true });
......@@ -253,51 +293,51 @@ describe("OpenAssessment.Server", function() {
expect(receivedMsg).toEqual('test error');
});
/*
it("informs the caller of an Ajax error when loading XML", function() {
it("informs the caller of an Ajax error when loading the editor context", function() {
stubAjax(false, null);
var receivedMsg = null;
server.loadXml().fail(function(msg) {
server.loadEditorContext().fail(function(msg) {
receivedMsg = msg;
});
expect(receivedMsg).toContain("This problem could not be loaded");
});
it("informs the caller of an Ajax error when updating XML", function() {
it("informs the caller of an Ajax error when updating the editor context", function() {
stubAjax(false, null);
var receivedMsg = null;
server.updateXml('test').fail(function(msg) {
server.updateEditorContext('prompt', 'rubric', 'title', 'start', 'due', 'assessments').fail(function(msg) {
receivedMsg = msg;
});
expect(receivedMsg).toContain("This problem could not be saved");
});
it("informs the caller of a server error when loading XML", function() {
it("informs the caller of a server error when loading the editor context", function() {
stubAjax(true, { success: false, msg: "Test error" });
var receivedMsg = null;
server.updateXml('test').fail(function(msg) {
server.updateEditorContext('prompt', 'rubric', 'title', 'start', 'due', 'assessments').fail(function(msg) {
receivedMsg = msg;
});
expect(receivedMsg).toEqual("Test error");
});
it("informs the caller of a server error when updating XML", function() {
it("informs the caller of a server error when updating the editor context", function() {
stubAjax(true, { success: false, msg: "Test error" });
var receivedMsg = null;
server.loadXml().fail(function(msg) {
server.loadEditorContext().fail(function(msg) {
receivedMsg = msg;
});
expect(receivedMsg).toEqual("Test error");
});
*/
it("informs the caller of a server error when sending a peer assessment", function() {
stubAjax(true, {success:false, msg:'Test error!'});
......
......@@ -17,37 +17,44 @@ OpenAssessment.StudioView = function(runtime, element, server) {
// Initialize the code box
this.promptBox = $('.openassessment-prompt-editor').first().get(0);
live_element = $(element)
this.promptBox = live_element.find('.openassessment-prompt-editor').first().get(0);
this.rubricXmlBox = CodeMirror.fromTextArea(
$(element).find('.openassessment-rubric-editor').first().get(0),
live_element.find('.openassessment-rubric-editor').first().get(0),
{mode: "xml", lineNumbers: true, lineWrapping: true}
);
this.titleField = $(element).find('.openassessment-title-editor');
this.titleField = live_element.find('.openassessment-title-editor').first().get(0);
this.submissionStartField = $(element).find('.openassessment-submission-start-editor').first().get(0);
this.submissionStartField = live_element.find('.openassessment-submission-start-editor').first().get(0);
this.submissionDueField = $(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(
$(element).find('.openassessment-assessments-editor').first().get(0),
live_element.find('.openassessment-assessments-editor').first().get(0),
{mode: "xml", lineNumbers: true, lineWrapping: true}
);
// Install click handlers
var view = this;
$(element).find('.openassessment-save-button').click(
live_element.find('.openassessment-save-button').click(
function (eventData) {
view.save();
});
$(element).find('.openassessment-cancel-button').click(
live_element.find('.openassessment-cancel-button').click(
function (eventData) {
view.cancel();
});
$('.openassessment-editor-content-and-tabs').tabs();
live_element.find('.openassessment-editor-content-and-tabs').tabs({
activate: function (event, ui){
view.rubricXmlBox.refresh();
view.assessmentsXmlBox.refresh();
}
});
};
OpenAssessment.StudioView.prototype = {
......@@ -61,8 +68,6 @@ OpenAssessment.StudioView.prototype = {
function (prompt, rubricXml, settings) {
view.rubricXmlBox.setValue(rubricXml);
view.assessmentsXmlBox.setValue(settings.assessments);
view.rubricXmlBox.refresh();
view.assessmentsXmlBox.refresh();
view.submissionStartField.value = settings.submission_start;
view.submissionDueField.value = settings.submission_due;
view.promptBox.value = prompt;
......@@ -85,7 +90,7 @@ OpenAssessment.StudioView.prototype = {
this.server.checkReleased().done(
function (isReleased) {
if (isReleased) {
view.confirmPostReleaseUpdate($.proxy(view.updateEditorContext(), view));
view.confirmPostReleaseUpdate($.proxy(view.updateEditorContext, view));
}
else {
view.updateEditorContext();
......
......@@ -118,13 +118,9 @@ class StudioMixin(object):
# panel. This will adjust according to the fields laid out in this
# section.
submission_due = self.submission_due
if not self.submission_due:
submission_due = ''
submission_due = self.submission_due if self.submission_due else ''
submission_start = self.submission_start
if not submission_start:
submission_start = ''
submission_start = self.submission_start if self.submission_start else ''
settings = {
'submission_due': submission_due,
......
......@@ -78,6 +78,6 @@
"submission_due": "2012-02-27T09:46:28",
"submission_start": "2015-02-10T09:46:28"
},
"expected_error": "cannot be later"
"expected_error": "cannot be earlier"
}
}
\ No newline at end of file
......@@ -208,6 +208,7 @@ class ValidationIntegrationTest(TestCase):
}
SUBMISSION = {
"start": None,
"due": None
}
......
......@@ -124,16 +124,15 @@ class TestSerializeContent(TestCase):
parsed_expected = etree.fromstring("".join(data['expected_xml']))
# Pretty-print and reparse the expected XML
pretty_expected = etree.tostring(parsed_expected, pretty_print=True, encoding='utf-8')
pretty_expected = etree.tostring(parsed_expected, pretty_print=True, encoding='unicode')
parsed_expected = etree.fromstring(pretty_expected)
# Walk both trees, comparing elements and attributes
actual_elements = [el for el in parsed_actual.getiterator()]
expected_elements = [el for el in parsed_expected.getiterator()]
self.assertEqual(
len(actual_elements), len(expected_elements),
msg="Incorrect XML output:\nActual: {}\nExpected: {}".format(xml, pretty_expected)
msg=u"Incorrect XML output:\nActual: {}\nExpected: {}".format(xml, pretty_expected)
)
for actual, expected in zip(actual_elements, expected_elements):
......@@ -155,18 +154,23 @@ class TestSerializeContent(TestCase):
def test_serialize_rubric(self, data):
self._configure_xblock(data)
xml_str = serialize_rubric_to_xml_str(self.oa_block)
self.assertIn("<rubric>", xml_str)
if data['prompt']:
self.assertNotIn(data['prompt'], xml_str)
@ddt.file_data('data/serialize.json')
def test_serialize_examples(self, data):
self._configure_xblock(data)
for assessment in data['assessments']:
if 'student-training' == assessment['name']:
if 'student-training' == assessment['name'] and assessment['examples']:
xml_str = serialize_examples_to_xml_str(assessment)
self.assertIn(assessment['examples'][0]['answer'], xml_str)
@ddt.file_data('data/serialize.json')
def test_serialize_assessments(self, data):
self._configure_xblock(data)
xml_str = serialize_assessments_to_xml_str(self.oa_block)
self.assertIn(data['assessments'][0]['name'], xml_str)
def test_mutated_criteria_dict(self):
self.oa_block.title = "Test title"
......
......@@ -124,7 +124,7 @@ def _serialize_criteria(criteria_root, criteria_list):
_serialize_options(criterion_el, options_list)
def serialize_rubric(rubric_root, oa_block):
def serialize_rubric(rubric_root, oa_block, include_prompt=True):
"""
Serialize a rubric dictionary as XML, adding children to the XML
with root node `rubric_root`.
......@@ -138,11 +138,14 @@ def serialize_rubric(rubric_root, oa_block):
rubric_dict (dict): A dictionary representation of the rubric, of the form
described in the serialized Rubric model (peer grading serializers).
Kwargs:
include_prompt (bool): Whether or not to include the prompt in the
serialized format for a rubric. Defaults to True.
Returns:
None
"""
# Rubric prompt (default to empty text); None indicates no input element
if oa_block.prompt is not None:
if include_prompt and oa_block.prompt is not None:
prompt = etree.SubElement(rubric_root, 'prompt')
prompt.text = unicode(oa_block.prompt)
......@@ -172,7 +175,7 @@ def parse_date(date_str):
Raises:
UpdateFromXmlError
"""
if date_str == "" or date_str == u"":
if date_str == "":
return None
try:
# Get the date into ISO format
......@@ -180,7 +183,10 @@ def parse_date(date_str):
formatted_date = parsed_date.strftime("%Y-%m-%dT%H:%M:%S")
return unicode(formatted_date)
except (ValueError, TypeError):
raise UpdateFromXmlError(_('The format for the submission due date is invalid. Make sure the date is formatted as YYYY-MM-DDTHH:MM:SS.'))
msg = (
'The format for the given date ({}) is invalid. Make sure the date is formatted as YYYY-MM-DDTHH:MM:SS.'
).format(date_str)
raise UpdateFromXmlError(_(msg))
def _parse_options_xml(options_root):
......@@ -560,12 +566,15 @@ def serialize_content(oa_block):
serialize_content_to_xml(oa_block, root)
# Return a UTF-8 representation of the XML
return etree.tostring(root, pretty_print=True, encoding='utf-8')
return etree.tostring(root, pretty_print=True, encoding='unicode')
def serialize_rubric_to_xml_str(oa_block):
"""
Serialize the OpenAssessment XBlock's rubric into an XML string.
Serialize the OpenAssessment XBlock's rubric into an XML string. This is
designed to serialize the XBlock's rubric specifically for authoring. Since
the authoring view splits the prompt from the rubric, the serialized format
for the rubric does not contain the prompt.
Args:
oa_block (OpenAssessmentBlock): The open assessment block to serialize
......@@ -576,8 +585,8 @@ def serialize_rubric_to_xml_str(oa_block):
"""
rubric_root = etree.Element('rubric')
serialize_rubric(rubric_root, oa_block)
return etree.tostring(rubric_root, pretty_print=True, encoding='utf-8')
serialize_rubric(rubric_root, oa_block, include_prompt=False)
return etree.tostring(rubric_root, pretty_print=True, encoding='unicode')
def serialize_examples_to_xml_str(assessment):
......@@ -599,7 +608,7 @@ def serialize_examples_to_xml_str(assessment):
examples = []
examples_root = etree.Element('examples')
serialize_training_examples(examples, examples_root)
return etree.tostring(examples_root, pretty_print=True, encoding='utf-8')
return etree.tostring(examples_root, pretty_print=True, encoding='unicode')
def serialize_assessments_to_xml_str(oa_block):
......@@ -612,7 +621,7 @@ def serialize_assessments_to_xml_str(oa_block):
"""
assessments_root = etree.Element('assessments')
serialize_assessments(assessments_root, oa_block)
return etree.tostring(assessments_root, pretty_print=True, encoding='utf-8')
return etree.tostring(assessments_root, pretty_print=True, encoding='unicode')
def parse_from_xml(root):
......
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