Commit 7fc8fcde by Peter Fogg

Merge pull request #752 from edx/peter-fogg/unlimited-graceperiod

Allow grace periods of > 24 hours.
parents 0138c082 eb5b6cc6
......@@ -99,3 +99,21 @@ Feature: Course Grading
And I have populated the course
And I am viewing the grading settings
Then I cannot edit the "Fail" grade range
Scenario: User can set a grace period greater than one day
Given I have opened a new course in Studio
And I have populated the course
And I am viewing the grading settings
When I change the grace period to "48:00"
And I press the "Save" notification button
And I reload the page
Then I see the grace period is "48:00"
Scenario: Grace periods of more than 59 minutes are wrapped to the correct time
Given I have opened a new course in Studio
And I have populated the course
And I am viewing the grading settings
When I change the grace period to "01:99"
And I press the "Save" notification button
And I reload the page
Then I see the grace period is "02:39"
......@@ -143,6 +143,21 @@ def cannot_edit_fail(_step):
except InvalidElementStateException:
pass # We should get this exception on failing to edit the element
@step(u'I change the grace period to "(.*)"$')
def i_change_grace_period(_step, grace_period):
grace_period_css = '#course-grading-graceperiod'
ele = world.css_find(grace_period_css).first
ele.value = grace_period
@step(u'I see the grace period is "(.*)"$')
def the_grace_period_is(_step, grace_period):
grace_period_css = '#course-grading-graceperiod'
ele = world.css_find(grace_period_css).first
assert ele.value == grace_period
def get_type_index(name):
name_id = '#course-grading-assignment-name'
all_types = world.css_find(name_id)
......
......@@ -246,7 +246,8 @@ PIPELINE_JS = {
'js/models/metadata_model.js', 'js/views/metadata_editor_view.js',
'js/models/uploads.js', 'js/views/uploads.js',
'js/models/textbook.js', 'js/views/textbook.js',
'js/views/assets.js', 'js/utility.js'],
'js/views/assets.js', 'js/utility.js',
'js/models/settings/course_grading_policy.js'],
'output_filename': 'js/cms-application.js',
'test_order': 0
},
......
describe "CMS.Models.Settings.CourseGradingPolicy", ->
beforeEach ->
@model = new CMS.Models.Settings.CourseGradingPolicy()
describe "parse", ->
it "sets a null grace period to 00:00", ->
attrs = @model.parse(grace_period: null)
expect(attrs.grace_period).toEqual(
hours: 0,
minutes: 0
)
describe "parseGracePeriod", ->
it "parses a time in HH:MM format", ->
time = @model.parseGracePeriod("07:19")
expect(time).toEqual(
hours: 7,
minutes: 19
)
it "returns null on an incorrectly formatted string", ->
expect(@model.parseGracePeriod("asdf")).toBe(null)
expect(@model.parseGracePeriod("7:19")).toBe(null)
expect(@model.parseGracePeriod("1000:00")).toBe(null)
......@@ -24,6 +24,14 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
}
attributes.graders = graderCollection;
}
// If grace period is unset or equal to 00:00 on the server,
// it's received as null
if (attributes['grace_period'] === null) {
attributes.grace_period = {
hours: 0,
minutes: 0
}
}
return attributes;
},
url : function() {
......@@ -44,8 +52,25 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
return newDate;
},
dateToGracePeriod : function(date) {
return {hours : date.getHours(), minutes : date.getMinutes(), seconds : date.getSeconds() };
parseGracePeriod : function(grace_period) {
// Enforce hours:minutes format
if(!/^\d{2,3}:\d{2}$/.test(grace_period)) {
return null;
}
var pieces = grace_period.split(/:/);
return {
hours: parseInt(pieces[0], 10),
minutes: parseInt(pieces[1], 10)
}
},
validate : function(attrs) {
if(_.has(attrs, 'grace_period')) {
if(attrs['grace_period'] === null) {
return {
'grace_period': gettext('Grace period must be specified in HH:MM format.')
}
}
}
}
});
......
......@@ -28,9 +28,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
this.setupCutoffs();
// Instrument grace period
this.$el.find('#course-grading-graceperiod').timepicker();
// instantiates an editor template for each update in the collection
// Because this calls render, put it after everything which render may depend upon to prevent race condition.
window.templateLoader.loadRemoteTemplate("course_grade_policy",
......@@ -51,6 +48,10 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
// prevent bootstrap race condition by event dispatch
if (!this.template) return;
this.clearValidationErrors();
this.renderGracePeriod();
// Create and render the grading type subs
var self = this;
var gradelist = this.$el.find('.course-grading-assignment-list');
......@@ -87,13 +88,6 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
// render the grade cutoffs
this.renderCutoffBar();
var graceEle = this.$el.find('#course-grading-graceperiod');
graceEle.timepicker({'timeFormat' : 'H:i'}); // init doesn't take setTime
if (this.model.has('grace_period')) graceEle.timepicker('setTime', this.model.gracePeriodToDate());
// remove any existing listeners to keep them from piling on b/c render gets called frequently
graceEle.off('change', this.setGracePeriod);
graceEle.on('change', this, this.setGracePeriod);
return this;
},
addAssignmentType : function(e) {
......@@ -103,17 +97,26 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
fieldToSelectorMap : {
'grace_period' : 'course-grading-graceperiod'
},
renderGracePeriod: function() {
var format = function(time) {
return time >= 10 ? time.toString() : '0' + time;
};
var grace_period = this.model.get('grace_period');
this.$el.find('#course-grading-graceperiod').val(
format(grace_period.hours) + ':' + format(grace_period.minutes)
);
},
setGracePeriod : function(event) {
var self = event.data;
self.clearValidationErrors();
var newVal = self.model.dateToGracePeriod($(event.currentTarget).timepicker('getTime'));
self.model.set('grace_period', newVal, {validate: true});
this.clearValidationErrors();
var newVal = this.model.parseGracePeriod($(event.currentTarget).val());
this.model.set('grace_period', newVal, {validate: true});
},
updateModel : function(event) {
if (!this.selectorToField[event.currentTarget.id]) return;
switch (this.selectorToField[event.currentTarget.id]) {
case 'grace_period': // handled above
case 'grace_period':
this.setGracePeriod(event);
break;
default:
......
......@@ -97,7 +97,7 @@ from contentstore import utils
<ol class="list-input">
<li class="field text" id="field-course-grading-graceperiod">
<label for="course-grading-graceperiod">${_("Grace Period on Deadline:")}</label>
<input type="text" class="short time" id="course-grading-graceperiod" value="0:00" placeholder="HH:MM" autocomplete="off" />
<input type="text" class="short time" id="course-grading-graceperiod" value="00:00" placeholder="HH:MM" autocomplete="off" />
<span class="tip tip-inline">${_("Leeway on due dates")}</span>
</li>
</ol>
......
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