Skip to content
Snippets Groups Projects
course_grading_policy.js 6.00 KiB
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();

CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
    defaults : {
        course_location : null,
        graders : null,  // CourseGraderCollection
        grade_cutoffs : null,  // CourseGradeCutoff model
        grace_period : null // either null or { hours: n, minutes: m, ...}
    },
    parse: function(attributes) {
        if (attributes['course_location']) {
            attributes.course_location = new CMS.Models.Location(attributes.course_location, {parse:true});
        }
        if (attributes['graders']) {
            var graderCollection;
            // interesting race condition: if {parse:true} when newing, then parse called before .attributes created
            if (this.attributes && this.has('graders')) {
                graderCollection = this.get('graders');
                graderCollection.reset(attributes.graders);
            }
            else {
                graderCollection = new CMS.Models.Settings.CourseGraderCollection(attributes.graders);
                graderCollection.course_location = attributes['course_location'] || this.get('course_location');
            }
            attributes.graders = graderCollection;
        }
        return attributes;
    },
    url : function() {
        var location = this.get('course_location');
        return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/grading';
    },
    gracePeriodToDate : function() {
        var newDate = new Date();
        if (this.has('grace_period') && this.get('grace_period')['hours'])
            newDate.setHours(this.get('grace_period')['hours']);
        else newDate.setHours(0);
        if (this.has('grace_period') && this.get('grace_period')['minutes'])
            newDate.setMinutes(this.get('grace_period')['minutes']);
        else newDate.setMinutes(0);
        if (this.has('grace_period') && this.get('grace_period')['seconds'])
            newDate.setSeconds(this.get('grace_period')['seconds']);
        else newDate.setSeconds(0);

        return newDate;
    },
    dateToGracePeriod : function(date) {
        return {hours : date.getHours(), minutes : date.getMinutes(), seconds : date.getSeconds() };
    }
});

CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
    defaults: {
        "type" : "",	// must be unique w/in collection (ie. w/in course)
        "min_count" : 1,
        "drop_count" : 0,
        "short_label" : "",	// what to use in place of type if space is an issue
        "weight" : 0 // int 0..100
    },
    parse : function(attrs) {
        if (attrs['weight']) {
            if (!_.isNumber(attrs.weight)) attrs.weight = parseInt(attrs.weight);
        }
        if (attrs['min_count']) {
            if (!_.isNumber(attrs.min_count)) attrs.min_count = parseInt(attrs.min_count);
        }
        if (attrs['drop_count']) {
            if (!_.isNumber(attrs.drop_count)) attrs.drop_count = parseInt(attrs.drop_count);
        }
        return attrs;
    },
    validate : function(attrs) {
        var errors = {};
        if (_.has(attrs, 'type')) {
            if (_.isEmpty(attrs['type'])) {
                errors.type = "The assignment type must have a name.";
            }
            else {
                // FIXME somehow this.collection is unbound sometimes. I can't track down when
                var existing = this.collection && this.collection.some(function(other) { return (other.cid != this.cid) && (other.get('type') == attrs['type']);}, this);
                if (existing) {
                    errors.type = gettext("There's already another assignment type with this name.");
                }
            }
        }
        if (_.has(attrs, 'weight')) {
            var intWeight = parseInt(attrs.weight); // see if this ensures value saved is int
            if (!isFinite(intWeight) || /\D+/.test(attrs.weight) || intWeight < 0 || intWeight > 100) {
                errors.weight = gettext("Please enter an integer between 0 and 100.");
            }
            else {
                attrs.weight = intWeight;
                if (this.collection && attrs.weight > 0) {
                    // FIXME b/c saves don't update the models if validation fails, we should
                    // either revert the field value to the one in the model and make them make room
                    // or figure out a wholistic way to balance the vals across the whole
//                  if ((this.collection.sumWeights() + attrs.weight - this.get('weight')) > 100)
//                  errors.weight = "The weights cannot add to more than 100.";
                }
            }}
        if (_.has(attrs, 'min_count')) {
            if (!isFinite(attrs.min_count) || /\D+/.test(attrs.min_count)) {
                errors.min_count = gettext("Please enter an integer.");
            }
            else attrs.min_count = parseInt(attrs.min_count);
        }
        if (_.has(attrs, 'drop_count')) {
            if (!isFinite(attrs.drop_count) || /\D+/.test(attrs.drop_count)) {
                errors.drop_count = gettext("Please enter an integer.");
            }
            else attrs.drop_count = parseInt(attrs.drop_count);
        }
        if (_.has(attrs, 'min_count') && _.has(attrs, 'drop_count') && attrs.drop_count > attrs.min_count) {
            errors.drop_count = _.template(
                gettext("Cannot drop more <% attrs.types %> than will assigned."),
                attrs, {variable: 'attrs'});
        }
        if (!_.isEmpty(errors)) return errors;
    }
});

CMS.Models.Settings.CourseGraderCollection = Backbone.Collection.extend({
    model : CMS.Models.Settings.CourseGrader,
    course_location : null, // must be set to a Location object
    url : function() {
        return '/' + this.course_location.get('org') + "/" + this.course_location.get('course') + '/settings-grading/' + this.course_location.get('name') + '/';
    },
    sumWeights : function() {
        return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0);
    }
});