Commit 043e493b by Will Daly

Implement leaderboard authoring

parent 25412f9e
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
</div> </div>
<p class="setting-help">{% trans "The date and time when students can no longer submit responses." %}</p> <p class="setting-help">{% trans "The date and time when students can no longer submit responses." %}</p>
</li> </li>
<li id="openassessment_sumbission_image_wrapper" class="field comp-setting-entry"> <li id="openassessment_submission_image_wrapper" class="field comp-setting-entry">
<div class="wrapper-comp-setting"> <div class="wrapper-comp-setting">
<label for="openassessment_submission_image_editor" class="setting-label">{% trans "Allow Image Responses"%}</label> <label for="openassessment_submission_image_editor" class="setting-label">{% trans "Allow Image Responses"%}</label>
<select id="openassessment_submission_image_editor" class="input setting-input" name="image submission"> <select id="openassessment_submission_image_editor" class="input setting-input" name="image submission">
...@@ -109,6 +109,20 @@ ...@@ -109,6 +109,20 @@
</div> </div>
<p class="setting-help">{% trans "Specify whether students can submit an image file along with their text response." %}</p> <p class="setting-help">{% trans "Specify whether students can submit an image file along with their text response." %}</p>
</li> </li>
<li id="openassessment_leaderboard_wrapper" class="field comp-setting-entry">
<div class="wrapper-comp-setting">
<label for="openassessment_leaderboard_editor" class="setting-label">{% trans "Number of Leaderboard Scores" %}</label>
<input
id="openassessment_leaderboard_editor"
class="input setting-input"
type="number"
value="{{ leaderboard_show }}"
min="0" max="99"
/>
</div>
<p class="setting-help">{% trans "Set the number of scores to display on the leaderboard. If set to 0, the leaderboard will not be shown." %}</p>
</li>
<li>
</ul> </ul>
<p class="openassessment_description" id="openassessment_step_select_description"> <p class="openassessment_description" id="openassessment_step_select_description">
......
...@@ -380,7 +380,8 @@ class OpenAssessmentBlock( ...@@ -380,7 +380,8 @@ class OpenAssessmentBlock(
create_rubric_dict(config['prompt'], config['rubric_criteria']), create_rubric_dict(config['prompt'], config['rubric_criteria']),
config['rubric_assessments'], config['rubric_assessments'],
submission_start=config['submission_start'], submission_start=config['submission_start'],
submission_due=config['submission_due'] submission_due=config['submission_due'],
leaderboard_show=config['leaderboard_show']
) )
block.rubric_criteria = config['rubric_criteria'] block.rubric_criteria = config['rubric_criteria']
......
...@@ -72,6 +72,7 @@ EDITOR_UPDATE_SCHEMA = Schema({ ...@@ -72,6 +72,7 @@ EDITOR_UPDATE_SCHEMA = Schema({
Required('submission_start'): Any(datetime_validator, None), Required('submission_start'): Any(datetime_validator, None),
Required('submission_due'): Any(datetime_validator, None), Required('submission_due'): Any(datetime_validator, None),
Required('allow_file_upload'): bool, Required('allow_file_upload'): bool,
Required('leaderboard_show'): int,
Required('assessments'): [ Required('assessments'): [
Schema({ Schema({
Required('name'): All(utf8_validator, In(VALID_ASSESSMENT_TYPES)), Required('name'): All(utf8_validator, In(VALID_ASSESSMENT_TYPES)),
......
...@@ -402,6 +402,7 @@ ...@@ -402,6 +402,7 @@
"title": "The most important of all questions.", "title": "The most important of all questions.",
"submission_start": "2014-01-02T12:15", "submission_start": "2014-01-02T12:15",
"submission_due": "2014-10-01T04:53", "submission_due": "2014-10-01T04:53",
"leaderboard_show": 12,
"criteria": [ "criteria": [
{ {
"name": "criterion_1", "name": "criterion_1",
...@@ -482,6 +483,7 @@ ...@@ -482,6 +483,7 @@
"title": "Test title", "title": "Test title",
"submission_start": "2014-01-1T10:00:00", "submission_start": "2014-01-1T10:00:00",
"submission_due": "2014-10-1T10:00:00", "submission_due": "2014-10-1T10:00:00",
"leaderboard_show": 12,
"criteria": [ "criteria": [
{ {
"name": "criterion_with_two_options", "name": "criterion_with_two_options",
......
...@@ -239,7 +239,9 @@ describe("OpenAssessment.Server", function() { ...@@ -239,7 +239,9 @@ describe("OpenAssessment.Server", function() {
submissionDue: SUBMISSION_DUE, submissionDue: SUBMISSION_DUE,
criteria: CRITERIA, criteria: CRITERIA,
assessments: ASSESSMENTS, assessments: ASSESSMENTS,
editorAssessmentsOrder: EDITOR_ASSESSMENTS_ORDER editorAssessmentsOrder: EDITOR_ASSESSMENTS_ORDER,
imageSubmissionEnabled: true,
leaderboardNum: 15
}); });
expect($.ajax).toHaveBeenCalledWith({ expect($.ajax).toHaveBeenCalledWith({
type: "POST", url: '/update_editor_context', type: "POST", url: '/update_editor_context',
...@@ -251,7 +253,9 @@ describe("OpenAssessment.Server", function() { ...@@ -251,7 +253,9 @@ describe("OpenAssessment.Server", function() {
submission_due: SUBMISSION_DUE, submission_due: SUBMISSION_DUE,
criteria: CRITERIA, criteria: CRITERIA,
assessments: ASSESSMENTS, assessments: ASSESSMENTS,
editor_assessments_order: EDITOR_ASSESSMENTS_ORDER editor_assessments_order: EDITOR_ASSESSMENTS_ORDER,
allow_file_upload: true,
leaderboard_show: 15
}) })
}); });
}); });
......
...@@ -48,6 +48,7 @@ describe("OpenAssessment.StudioView", function() { ...@@ -48,6 +48,7 @@ describe("OpenAssessment.StudioView", function() {
submissionStart: "2014-01-02T12:15", submissionStart: "2014-01-02T12:15",
submissionDue: "2014-10-01T04:53", submissionDue: "2014-10-01T04:53",
imageSubmissionEnabled: false, imageSubmissionEnabled: false,
leaderboardNum: 12,
criteria: [ criteria: [
{ {
order_num: 0, order_num: 0,
...@@ -149,6 +150,7 @@ describe("OpenAssessment.StudioView", function() { ...@@ -149,6 +150,7 @@ describe("OpenAssessment.StudioView", function() {
expect(server.receivedData.submissionStart).toEqual(EXPECTED_SERVER_DATA.submissionStart); expect(server.receivedData.submissionStart).toEqual(EXPECTED_SERVER_DATA.submissionStart);
expect(server.receivedData.submissionDue).toEqual(EXPECTED_SERVER_DATA.submissionDue); expect(server.receivedData.submissionDue).toEqual(EXPECTED_SERVER_DATA.submissionDue);
expect(server.receivedData.imageSubmissionEnabled).toEqual(EXPECTED_SERVER_DATA.imageSubmissionEnabled); expect(server.receivedData.imageSubmissionEnabled).toEqual(EXPECTED_SERVER_DATA.imageSubmissionEnabled);
expect(server.receivedData.leaderboardNum).toEqual(EXPECTED_SERVER_DATA.leaderboardNum);
// Criteria // Criteria
for (var criterion_idx = 0; criterion_idx < EXPECTED_SERVER_DATA.criteria.length; criterion_idx++) { for (var criterion_idx = 0; criterion_idx < EXPECTED_SERVER_DATA.criteria.length; criterion_idx++) {
......
...@@ -91,6 +91,14 @@ describe("OpenAssessment.EditSettingsView", function() { ...@@ -91,6 +91,14 @@ describe("OpenAssessment.EditSettingsView", function() {
expect(view.imageSubmissionEnabled()).toBe(false); expect(view.imageSubmissionEnabled()).toBe(false);
}); });
it("sets and loads the leaderboard number", function() {
view.leaderboardNum(18);
expect(view.leaderboardNum()).toEqual(18);
view.leaderboardNum(0);
expect(view.leaderboardNum()).toEqual(0);
});
it("builds a description of enabled assessments", function() { it("builds a description of enabled assessments", function() {
// Depends on the template having an original order // Depends on the template having an original order
// of training --> peer --> self --> ai // of training --> peer --> self --> ai
...@@ -157,6 +165,32 @@ describe("OpenAssessment.EditSettingsView", function() { ...@@ -157,6 +165,32 @@ describe("OpenAssessment.EditSettingsView", function() {
); );
}); });
it("validates the leaderboard number field", function() {
// Valid value for the leaderboard number
view.leaderboardNum(0);
expect(view.validate()).toBe(true);
expect(view.validationErrors()).toEqual([]);
// Below the minimum
view.leaderboardNum(-1);
expect(view.validate()).toBe(false);
expect(view.validationErrors()).toContain(
"Leaderboard number is invalid"
);
// Clear validation errors
view.clearValidationErrors();
expect(view.validationErrors()).toEqual([]);
// Valid, near the maximum
view.leaderboardNum(100);
expect(view.validate()).toBe(true);
// Above the maximum
view.leaderboardNum(101);
expect(view.validate()).toBe(false);
});
it("validates assessment views", function() { it("validates assessment views", function() {
// Simulate one of the assessment views being invalid // Simulate one of the assessment views being invalid
assessmentViews[PEER].isValid = false; assessmentViews[PEER].isValid = false;
......
...@@ -418,6 +418,7 @@ if (typeof OpenAssessment.Server == "undefined" || !OpenAssessment.Server) { ...@@ -418,6 +418,7 @@ if (typeof OpenAssessment.Server == "undefined" || !OpenAssessment.Server) {
criteria (list of object literals): The rubric criteria. criteria (list of object literals): The rubric criteria.
assessments (list of object literals): The assessments the student will be evaluated on. assessments (list of object literals): The assessments the student will be evaluated on.
imageSubmissionEnabled (boolean): TRUE if image attachments are allowed. imageSubmissionEnabled (boolean): TRUE if image attachments are allowed.
leaderboardNum (int): The number of scores to show in the leaderboard.
Returns: Returns:
A JQuery promise, which resolves with no arguments A JQuery promise, which resolves with no arguments
...@@ -435,7 +436,8 @@ if (typeof OpenAssessment.Server == "undefined" || !OpenAssessment.Server) { ...@@ -435,7 +436,8 @@ if (typeof OpenAssessment.Server == "undefined" || !OpenAssessment.Server) {
criteria: kwargs.criteria, criteria: kwargs.criteria,
assessments: kwargs.assessments, assessments: kwargs.assessments,
editor_assessments_order: kwargs.editorAssessmentsOrder, editor_assessments_order: kwargs.editorAssessmentsOrder,
allow_file_upload: kwargs.imageSubmissionEnabled allow_file_upload: kwargs.imageSubmissionEnabled,
leaderboard_show: kwargs.leaderboardNum
}); });
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({ $.ajax({
......
...@@ -193,6 +193,7 @@ OpenAssessment.StudioView.prototype = { ...@@ -193,6 +193,7 @@ OpenAssessment.StudioView.prototype = {
submissionDue: view.settingsView.submissionDue(), submissionDue: view.settingsView.submissionDue(),
assessments: view.settingsView.assessmentsDescription(), assessments: view.settingsView.assessmentsDescription(),
imageSubmissionEnabled: view.settingsView.imageSubmissionEnabled(), imageSubmissionEnabled: view.settingsView.imageSubmissionEnabled(),
leaderboardNum: view.settingsView.leaderboardNum(),
editorAssessmentsOrder: view.settingsView.editorAssessmentsOrder() editorAssessmentsOrder: view.settingsView.editorAssessmentsOrder()
}).done( }).done(
// Notify the client-side runtime that we finished saving // Notify the client-side runtime that we finished saving
......
...@@ -27,6 +27,11 @@ OpenAssessment.EditSettingsView = function(element, assessmentViews) { ...@@ -27,6 +27,11 @@ OpenAssessment.EditSettingsView = function(element, assessmentViews) {
"#openassessment_submission_due_time" "#openassessment_submission_due_time"
).install(); ).install();
this.leaderboardIntField = new OpenAssessment.IntField(
$("#openassessment_leaderboard_editor", this.element),
{ min: 0, max: 100 }
);
this.initializeSortableAssessments(); this.initializeSortableAssessments();
}; };
...@@ -128,7 +133,7 @@ OpenAssessment.EditSettingsView.prototype = { ...@@ -128,7 +133,7 @@ OpenAssessment.EditSettingsView.prototype = {
**/ **/
imageSubmissionEnabled: function(isEnabled) { imageSubmissionEnabled: function(isEnabled) {
var sel = $("#openassessment_submission_image_editor", this.settingsElement); var sel = $("#openassessment_submission_image_editor", this.settingsElement);
if (typeof(isEnabled) !== "undefined") { if (isEnabled !== undefined) {
if (isEnabled) { sel.val(1); } if (isEnabled) { sel.val(1); }
else { sel.val(0); } else { sel.val(0); }
} }
...@@ -136,6 +141,24 @@ OpenAssessment.EditSettingsView.prototype = { ...@@ -136,6 +141,24 @@ OpenAssessment.EditSettingsView.prototype = {
}, },
/** /**
Get or set the number of scores to show in the leaderboard.
If set to 0, the leaderboard will not be shown.
Args:
num (int, optional)
Returns:
int
**/
leaderboardNum: function(num) {
if (num !== undefined) {
this.leaderboardIntField.set(num);
}
return this.leaderboardIntField.get(num);
},
/**
Construct a list of enabled assessments and their properties. Construct a list of enabled assessments and their properties.
...@@ -212,6 +235,7 @@ OpenAssessment.EditSettingsView.prototype = { ...@@ -212,6 +235,7 @@ OpenAssessment.EditSettingsView.prototype = {
isValid = (this.startDatetimeControl.validate() && isValid); isValid = (this.startDatetimeControl.validate() && isValid);
isValid = (this.dueDatetimeControl.validate() && isValid); isValid = (this.dueDatetimeControl.validate() && isValid);
isValid = (this.leaderboardIntField.validate() && isValid);
// Validate each of the *enabled* assessment views // Validate each of the *enabled* assessment views
$.each(this.assessmentViews, function() { $.each(this.assessmentViews, function() {
...@@ -240,10 +264,14 @@ OpenAssessment.EditSettingsView.prototype = { ...@@ -240,10 +264,14 @@ OpenAssessment.EditSettingsView.prototype = {
if (this.dueDatetimeControl.validationErrors().length > 0) { if (this.dueDatetimeControl.validationErrors().length > 0) {
errors.push("Submission due is invalid"); errors.push("Submission due is invalid");
} }
if (this.leaderboardIntField.validationErrors().length > 0) {
errors.push("Leaderboard number is invalid");
}
$.each(this.assessmentViews, function() { $.each(this.assessmentViews, function() {
errors = errors.concat(this.validationErrors()); errors = errors.concat(this.validationErrors());
}); });
return errors; return errors;
}, },
...@@ -253,6 +281,7 @@ OpenAssessment.EditSettingsView.prototype = { ...@@ -253,6 +281,7 @@ OpenAssessment.EditSettingsView.prototype = {
clearValidationErrors: function() { clearValidationErrors: function() {
this.startDatetimeControl.clearValidationErrors(); this.startDatetimeControl.clearValidationErrors();
this.dueDatetimeControl.clearValidationErrors(); this.dueDatetimeControl.clearValidationErrors();
this.leaderboardIntField.clearValidationErrors();
$.each(this.assessmentViews, function() { $.each(this.assessmentViews, function() {
this.clearValidationErrors(); this.clearValidationErrors();
}); });
......
...@@ -114,6 +114,7 @@ class StudioMixin(object): ...@@ -114,6 +114,7 @@ class StudioMixin(object):
'criteria': criteria, 'criteria': criteria,
'feedbackprompt': self.rubric_feedback_prompt, 'feedbackprompt': self.rubric_feedback_prompt,
'allow_file_upload': self.allow_file_upload, 'allow_file_upload': self.allow_file_upload,
'leaderboard_show': self.leaderboard_show,
'editor_assessments_order': [ 'editor_assessments_order': [
make_django_template_key(asmnt) make_django_template_key(asmnt)
for asmnt in editor_assessments_order for asmnt in editor_assessments_order
...@@ -187,6 +188,7 @@ class StudioMixin(object): ...@@ -187,6 +188,7 @@ class StudioMixin(object):
data['assessments'], data['assessments'],
submission_start=data['submission_start'], submission_start=data['submission_start'],
submission_due=data['submission_due'], submission_due=data['submission_due'],
leaderboard_show=data['leaderboard_show']
) )
if not success: if not success:
return {'success': False, 'msg': self._('Validation error: {error}').format(error=msg)} return {'success': False, 'msg': self._('Validation error: {error}').format(error=msg)}
...@@ -203,6 +205,7 @@ class StudioMixin(object): ...@@ -203,6 +205,7 @@ class StudioMixin(object):
self.submission_start = data['submission_start'] self.submission_start = data['submission_start']
self.submission_due = data['submission_due'] self.submission_due = data['submission_due']
self.allow_file_upload = bool(data['allow_file_upload']) self.allow_file_upload = bool(data['allow_file_upload'])
self.leaderboard_show = data['leaderboard_show']
return {'success': True, 'msg': self._(u'Successfully updated OpenAssessment XBlock')} return {'success': True, 'msg': self._(u'Successfully updated OpenAssessment XBlock')}
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
"title": "My new title.", "title": "My new title.",
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
...@@ -26,6 +27,7 @@ ...@@ -26,6 +27,7 @@
"no_prompt": { "no_prompt": {
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -74,6 +76,7 @@ ...@@ -74,6 +76,7 @@
"no_feedback_prompt": { "no_feedback_prompt": {
"prompt": "Test prompt", "prompt": "Test prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -123,6 +126,7 @@ ...@@ -123,6 +126,7 @@
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -171,6 +175,7 @@ ...@@ -171,6 +175,7 @@
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -221,6 +226,7 @@ ...@@ -221,6 +226,7 @@
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -271,6 +277,7 @@ ...@@ -271,6 +277,7 @@
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": "Hello", "order_num": "Hello",
...@@ -313,6 +320,7 @@ ...@@ -313,6 +320,7 @@
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -354,6 +362,7 @@ ...@@ -354,6 +362,7 @@
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"prompt": "prompty", "prompt": "prompty",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
[] []
], ],
...@@ -374,6 +383,7 @@ ...@@ -374,6 +383,7 @@
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -415,6 +425,7 @@ ...@@ -415,6 +425,7 @@
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -442,6 +453,7 @@ ...@@ -442,6 +453,7 @@
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -478,6 +490,7 @@ ...@@ -478,6 +490,7 @@
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -519,6 +532,7 @@ ...@@ -519,6 +532,7 @@
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"prompt": "Test Prompt", "prompt": "Test Prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -669,6 +683,7 @@ ...@@ -669,6 +683,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "student-training", "name": "student-training",
...@@ -723,6 +738,7 @@ ...@@ -723,6 +738,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "student-training", "name": "student-training",
...@@ -775,6 +791,7 @@ ...@@ -775,6 +791,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "student-training", "name": "student-training",
...@@ -830,6 +847,7 @@ ...@@ -830,6 +847,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "student-training", "name": "student-training",
...@@ -885,6 +903,7 @@ ...@@ -885,6 +903,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "student-training", "name": "student-training",
...@@ -934,6 +953,7 @@ ...@@ -934,6 +953,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "student-training", "name": "student-training",
...@@ -990,6 +1010,7 @@ ...@@ -990,6 +1010,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
...@@ -1038,6 +1059,7 @@ ...@@ -1038,6 +1059,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
...@@ -1087,6 +1109,7 @@ ...@@ -1087,6 +1109,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
...@@ -1112,6 +1135,7 @@ ...@@ -1112,6 +1135,7 @@
"prompt": "Test Prompt", "prompt": "Test Prompt",
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -1160,6 +1184,7 @@ ...@@ -1160,6 +1184,7 @@
"prompt": "Test Prompt", "prompt": "Test Prompt",
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -1208,6 +1233,7 @@ ...@@ -1208,6 +1233,7 @@
"prompt": "Test Prompt", "prompt": "Test Prompt",
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
...@@ -1279,6 +1305,7 @@ ...@@ -1279,6 +1305,7 @@
"prompt": "Test Prompt", "prompt": "Test Prompt",
"feedback_prompt": "Feedback prompt", "feedback_prompt": "Feedback prompt",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"criteria": [ "criteria": [
{ {
"order_num": 0, "order_num": 0,
......
...@@ -452,69 +452,6 @@ ...@@ -452,69 +452,6 @@
] ]
}, },
"leaderboard_num_zero": {
"xml": [
"<openassessment leaderboard_show=\"0\">",
"<title>Foo</title>",
"<assessments>",
"<assessment name=\"peer-assessment\" start=\"2014-02-27T09:46:28\" due=\"2014-03-01T00:00:00\" must_grade=\"5\" must_be_graded_by=\"3\" />",
"<assessment name=\"self-assessment\" start=\"2014-04-01T00:00:00\" due=\"2014-06-01T00:00:00\" />",
"</assessments>",
"<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>",
"</openassessment>"
]
},
"leaderboard_num_negative": {
"xml": [
"<openassessment leaderboard_show=\"-1\">",
"<title>Foo</title>",
"<assessments>",
"<assessment name=\"peer-assessment\" start=\"2014-02-27T09:46:28\" due=\"2014-03-01T00:00:00\" must_grade=\"5\" must_be_graded_by=\"3\" />",
"<assessment name=\"self-assessment\" start=\"2014-04-01T00:00:00\" due=\"2014-06-01T00:00:00\" />",
"</assessments>",
"<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>",
"</openassessment>"
]
},
"leaderboard_num_too_high": {
"xml": [
"<openassessment leaderboard_show=\"101\">",
"<title>Foo</title>",
"<assessments>",
"<assessment name=\"peer-assessment\" start=\"2014-02-27T09:46:28\" due=\"2014-03-01T00:00:00\" must_grade=\"5\" must_be_graded_by=\"3\" />",
"<assessment name=\"self-assessment\" start=\"2014-04-01T00:00:00\" due=\"2014-06-01T00:00:00\" />",
"</assessments>",
"<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>",
"</openassessment>"
]
},
"leaderboard_num_not_integer": { "leaderboard_num_not_integer": {
"xml": [ "xml": [
"<openassessment leaderboard_show=\"not_an_int\">", "<openassessment leaderboard_show=\"not_an_int\">",
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
...@@ -80,6 +81,7 @@ ...@@ -80,6 +81,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "ɯʎ uǝʍ ʇıʇןǝ", "title": "ɯʎ uǝʍ ʇıʇןǝ",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "peer-assessment", "name": "peer-assessment",
...@@ -129,6 +131,7 @@ ...@@ -129,6 +131,7 @@
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"title": "My new title.", "title": "My new title.",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"assessments": [ "assessments": [
{ {
"name": "student-training", "name": "student-training",
...@@ -189,6 +192,7 @@ ...@@ -189,6 +192,7 @@
"submission_due": "4014-02-27T09:46", "submission_due": "4014-02-27T09:46",
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"allow_file_upload": false, "allow_file_upload": false,
"leaderboard_show": 0,
"title": "My new title.", "title": "My new title.",
"assessments": [ "assessments": [
{ {
......
...@@ -22,6 +22,7 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -22,6 +22,7 @@ class StudioViewTest(XBlockHandlerTestCase):
"submission_start": "4014-02-10T09:46", "submission_start": "4014-02-10T09:46",
"submission_due": "4014-02-27T09:46", "submission_due": "4014-02-27T09:46",
"allow_file_upload": False, "allow_file_upload": False,
"leaderboard_show": 4,
"assessments": [{"name": "self-assessment"}], "assessments": [{"name": "self-assessment"}],
"editor_assessments_order": [ "editor_assessments_order": [
"student-training", "student-training",
...@@ -127,6 +128,11 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -127,6 +128,11 @@ class StudioViewTest(XBlockHandlerTestCase):
self.assertTrue(resp['success'], msg=resp.get('msg')) self.assertTrue(resp['success'], msg=resp.get('msg'))
@scenario('data/basic_scenario.xml') @scenario('data/basic_scenario.xml')
def test_include_leaderboard_in_editor(self, xblock):
xblock.leaderboard_show = 15
self.assertEqual(xblock.editor_context()['leaderboard_show'], 15)
@scenario('data/basic_scenario.xml')
def test_update_editor_context_saves_assessment_order(self, xblock): def test_update_editor_context_saves_assessment_order(self, xblock):
# Update the XBlock with a different editor assessment order # Update the XBlock with a different editor assessment order
data = copy.deepcopy(self.UPDATE_EDITOR_DATA) data = copy.deepcopy(self.UPDATE_EDITOR_DATA)
...@@ -140,6 +146,7 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -140,6 +146,7 @@ class StudioViewTest(XBlockHandlerTestCase):
self.assertTrue(resp['success'], msg=resp.get('msg')) self.assertTrue(resp['success'], msg=resp.get('msg'))
self.assertEqual(xblock.editor_assessments_order, data['editor_assessments_order']) self.assertEqual(xblock.editor_assessments_order, data['editor_assessments_order'])
@scenario('data/basic_scenario.xml') @scenario('data/basic_scenario.xml')
def test_update_editor_context_saves_assessment_order_with_ai(self, xblock): def test_update_editor_context_saves_assessment_order_with_ai(self, xblock):
# Update the XBlock with a different editor assessment order # Update the XBlock with a different editor assessment order
...@@ -159,6 +166,15 @@ class StudioViewTest(XBlockHandlerTestCase): ...@@ -159,6 +166,15 @@ class StudioViewTest(XBlockHandlerTestCase):
self.assertTrue(resp['success'], msg=resp.get('msg')) self.assertTrue(resp['success'], msg=resp.get('msg'))
self.assertEqual(xblock.editor_assessments_order, data['editor_assessments_order']) self.assertEqual(xblock.editor_assessments_order, data['editor_assessments_order'])
@scenario('data/basic_scenario.xml')
def test_update_editor_context_saves_leaderboard(self, xblock):
data = copy.deepcopy(self.UPDATE_EDITOR_DATA)
data['leaderboard_show'] = 42
xblock.published_date = None
resp = self.request(xblock, 'update_editor_context', json.dumps(data), response_format='json')
self.assertTrue(resp['success'], msg=resp.get('msg'))
self.assertEqual(xblock.leaderboard_show, 42)
@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):
......
...@@ -334,3 +334,31 @@ class ValidationIntegrationTest(TestCase): ...@@ -334,3 +334,31 @@ class ValidationIntegrationTest(TestCase):
is_valid, msg = self.validator(mutated_rubric, 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'')
def test_leaderboard_num_validation(self):
self._assert_leaderboard_num_valid(-1, False)
self._assert_leaderboard_num_valid(0, True)
self._assert_leaderboard_num_valid(1, True)
self._assert_leaderboard_num_valid(100, True)
self._assert_leaderboard_num_valid(101, False)
self._assert_leaderboard_num_valid(102, False)
def _assert_leaderboard_num_valid(self, num, expected_is_valid):
"""
Check that the leaderboard number is either valid or invalid.
Args:
num (int): The leaderboard number to check
expected_is_valid (bool): Whether the number is valid or invalid.
Raises:
AssertionError
"""
is_valid, msg = self.validator(self.RUBRIC, self.ASSESSMENTS, num)
if expected_is_valid:
self.assertTrue(is_valid, msg="Leaderboard num {num} should be valid".format(num=num))
self.assertEqual(msg, '')
else:
self.assertFalse(is_valid, msg="Leaderboard num {num} should be invalid".format(num=num))
self.assertEqual(msg, 'Leaderboard number is invalid.')
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
Validate changes to an XBlock before it is updated. Validate changes to an XBlock before it is updated.
""" """
from collections import Counter from collections import Counter
from submissions.api import MAX_TOP_SUBMISSIONS
from openassessment.assessment.serializers import rubric_from_dict, InvalidRubric from openassessment.assessment.serializers import rubric_from_dict, InvalidRubric
from openassessment.assessment.api.student_training import validate_training_examples from openassessment.assessment.api.student_training import validate_training_examples
from openassessment.xblock.resolve_dates import resolve_dates, DateValidationError, InvalidDateFormat from openassessment.xblock.resolve_dates import resolve_dates, DateValidationError, InvalidDateFormat
...@@ -316,7 +317,7 @@ def validator(oa_block, _, strict_post_release=True): ...@@ -316,7 +317,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, assessments, submission_start=None, submission_due=None): def _inner(rubric_dict, assessments, leaderboard_show=0, 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()
...@@ -348,6 +349,10 @@ def validator(oa_block, _, strict_post_release=True): ...@@ -348,6 +349,10 @@ def validator(oa_block, _, strict_post_release=True):
if not success: if not success:
return (False, msg) return (False, msg)
# Leaderboard
if leaderboard_show < 0 or leaderboard_show > MAX_TOP_SUBMISSIONS:
return (False, _("Leaderboard number is invalid."))
# Success! # Success!
return (True, u'') return (True, u'')
......
...@@ -6,7 +6,6 @@ import lxml.etree as etree ...@@ -6,7 +6,6 @@ import lxml.etree as etree
import pytz import pytz
import dateutil.parser import dateutil.parser
import defusedxml.ElementTree as safe_etree import defusedxml.ElementTree as safe_etree
from submissions.api import MAX_TOP_SUBMISSIONS
class UpdateFromXmlError(Exception): class UpdateFromXmlError(Exception):
...@@ -755,13 +754,6 @@ def parse_from_xml(root): ...@@ -755,13 +754,6 @@ def parse_from_xml(root):
if 'leaderboard_show' in root.attrib: if 'leaderboard_show' in root.attrib:
try: try:
leaderboard_show = int(root.attrib['leaderboard_show']) leaderboard_show = int(root.attrib['leaderboard_show'])
if leaderboard_show < 1:
raise UpdateFromXmlError('The leaderboard must have a positive integer value.')
if leaderboard_show > MAX_TOP_SUBMISSIONS:
msg = 'The number of leaderboard scores must be less than {max_num}'.format(
max_num=MAX_TOP_SUBMISSIONS
)
raise UpdateFromXmlError(msg)
except (TypeError, ValueError): except (TypeError, ValueError):
raise UpdateFromXmlError('The leaderboard must have an integer value.') raise UpdateFromXmlError('The leaderboard must have an integer value.')
......
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