Commit 7ab6d43a by Clinton Blackburn Committed by Clinton Blackburn

Validating course seat upgrade deadline

This deadline should always occur before the course verification deadline.

XCOM-581
parent 4fd2ac05
define([
'backbone',
'utils/utils'
'underscore',
'utils/utils',
'backbone.super'
],
function (Backbone,
_,
Utils) {
'use strict';
return Backbone.Collection.extend({
initialize: function (models, options) {
// NOTE (CCB): This is a hack to workaround an issue with Backbone.relational's reverseRelation
// not working properly.
if (options) {
this.course = options.course;
}
},
/**
* Validates the collection by iterating over the nested models.
*
......@@ -14,6 +25,16 @@ define([
*/
isValid: function () {
return Utils.areModelsValid(this.models);
},
set: function (models, options) {
_.each(models, function (model) {
if (_.isObject(model)) {
model.course = this.course;
}
}, this);
this._super(models, options);
}
});
}
......
......@@ -11,7 +11,6 @@ define([
'underscore',
'collections/product_collection',
'models/course_seats/course_seat',
'models/course_seats/honor_seat',
'utils/course_utils',
'utils/utils'
],
......@@ -25,7 +24,6 @@ define([
_,
ProductCollection,
CourseSeat,
HonorSeat,
CourseUtils,
Utils) {
'use strict';
......@@ -102,7 +100,10 @@ define([
key: 'products',
relatedModel: CourseSeat,
includeInJSON: false,
parse: true
parse: true,
collectionOptions: function (model) {
return {course: model};
}
}],
/**
......@@ -196,7 +197,7 @@ define([
if (_.isEmpty(seats) && _.contains(this.creatableSeatTypes, seatType)) {
seatClass = CourseUtils.getCourseSeatModel(seatType);
/*jshint newcap: false */
seat = new seatClass();
seat = new seatClass({course: this});
/*jshint newcap: true */
this.get('products').add(seat);
seats.push(seat);
......
define([
'moment',
'models/product_model'
],
function (ProductModel) {
function (moment,
Product) {
'use strict';
return ProductModel.extend({
return Product.extend({
defaults: {
certificate_type: null,
expires: null,
......@@ -15,6 +17,8 @@ define([
product_class: 'Seat'
},
course: null,
validation: {
price: {
required: true,
......@@ -23,6 +27,21 @@ define([
},
product_class: {
oneOf: ['Seat']
},
expires: function (value) {
var verificationDeadline,
course = this.course;
// No validation is needed for empty values or seats not linked to courses.
if (_.isEmpty(value) || !course) {
return;
}
// Determine if the supplied expiration date occurs after the course verification deadline.
verificationDeadline = course.get('verification_deadline');
if (verificationDeadline && !moment(value).isBefore(verificationDeadline)) {
return gettext('The upgrade deadline must occur BEFORE the verification deadline.');
}
}
},
......
define([
'underscore',
'models/course_model',
'models/course_seats/course_seat'
],
function (_,
Course,
CourseSeat) {
'use strict';
......@@ -114,6 +116,38 @@ define([
});
});
});
describe('expires validation', function () {
function assertExpiresInvalid(expires, verification_deadline) {
var msg = 'The upgrade deadline must occur BEFORE the verification deadline.';
model.set('expires', expires);
model.course = Course.findOrCreate({id: 'a/b/c', verification_deadline: verification_deadline});
expect(model.validate().expires).toEqual(msg);
expect(model.isValid(true)).toBeFalsy();
}
it('should do nothing if the CourseSeat has no associated Course', function () {
model.course = null;
expect(model.validation.expires('2015-01-01')).toBeUndefined();
});
it('should do nothing if the CourseSeat has no expiration value set', function () {
expect(model.validation.expires(null)).toBeUndefined();
expect(model.validation.expires(undefined)).toBeUndefined();
});
it('should return a message if the CourseSeat expires after the Course verification deadline',
function () {
assertExpiresInvalid('2016-01-01', '2014-01-01');
}
);
it('should return a message if the CourseSeat expires at the same time verification closes',
function () {
assertExpiresInvalid('2016-01-01', '2016-01-01');
}
);
});
});
}
);
......@@ -29,7 +29,12 @@ define([
validate: true
}
},
'input[name=expires]': 'expires',
'input[name=expires]': {
observe: 'expires',
setOptions: {
validate: true
}
},
'input[name=id_verification_required]': {
observe: 'id_verification_required',
onSet: 'cleanIdVerificationRequired'
......
......@@ -36,7 +36,12 @@ define([
validate: true
}
},
'input[name=expires]': 'expires'
'input[name=expires]': {
observe: 'expires',
setOptions: {
validate: true
}
}
},
initialize: function (options) {
......
......@@ -4,7 +4,7 @@
<div class="col-sm-4">
<span class="price-label"><%= gettext('Price (in USD)') %>:</span> <span class="seat-price">$0.00</span>
<input type="hidden" name="price" value="0">
<input type="hidden" name="certificate_type" value="">
<input type="hidden" name="certificate_type">
<input type="hidden" name="id_verification_required" value="false">
</div>
<div class="col-sm-4 seat-certificate-type">
......
......@@ -19,16 +19,21 @@
</div>
<div class="expires">
<label for="expires"><%= gettext('Upgrade Deadline') %></label>
<div class="form-group">
<label for="expires"><%= gettext('Upgrade Deadline') %></label>
<div class="input-group" data-toggle="tooltip" title="<%= gettext('Professional education courses have no upgrade deadline.') %>">
<div class="input-group-addon"><i class="fa fa-calendar" aria-hidden="true"></i></div>
<input type="datetime-local" id="expires" name="expires" class="form-control" value=""
aria-describedby="expiresHelpBlock" disabled="disabled">
</div>
<div class="input-group" data-toggle="tooltip"
title="<%= gettext('Professional education courses have no upgrade deadline.') %>">
<div class="input-group-addon"><i class="fa fa-calendar" aria-hidden="true"></i></div>
<input type="datetime-local" id="expires" name="expires" class="form-control"
aria-describedby="expiresHelpBlock" disabled="disabled">
</div>
<!-- NOTE: This help-block is here for validation messages. -->
<span class="help-block"></span>
<span id="expiresHelpBlock" class="help-block">
<%= gettext('After this date/time, students can no longer enroll in this track.') %>
</span>
</div>
</div>
</div>
<div class="col-sm-4 seat-certificate-type">
......
......@@ -20,16 +20,20 @@
</div>
<div class="expires">
<label for="expires"><%= gettext('Upgrade Deadline') %></label>
<div class="form-group">
<label for="expires"><%= gettext('Upgrade Deadline') %></label>
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-calendar" aria-hidden="true"></i></div>
<input type="datetime-local" id="expires" name="expires" class="form-control" value=""
aria-describedby="expiresHelpBlock">
</div>
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-calendar" aria-hidden="true"></i></div>
<input type="datetime-local" id="expires" name="expires" class="form-control"
aria-describedby="expiresHelpBlock">
</div>
<!-- NOTE: This help-block is here for validation messages. -->
<span class="help-block"></span>
<span id="expiresHelpBlock" class="help-block">
<%= gettext('After this date/time, students can no longer enroll in this track.') %>
</span>
</div>
</div>
</div>
<div class="col-sm-4 seat-certificate-type">
......
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