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([ define([
'backbone', 'backbone',
'utils/utils' 'underscore',
'utils/utils',
'backbone.super'
], ],
function (Backbone, function (Backbone,
_,
Utils) { Utils) {
'use strict'; 'use strict';
return Backbone.Collection.extend({ 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. * Validates the collection by iterating over the nested models.
* *
...@@ -14,6 +25,16 @@ define([ ...@@ -14,6 +25,16 @@ define([
*/ */
isValid: function () { isValid: function () {
return Utils.areModelsValid(this.models); 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([ ...@@ -11,7 +11,6 @@ define([
'underscore', 'underscore',
'collections/product_collection', 'collections/product_collection',
'models/course_seats/course_seat', 'models/course_seats/course_seat',
'models/course_seats/honor_seat',
'utils/course_utils', 'utils/course_utils',
'utils/utils' 'utils/utils'
], ],
...@@ -25,7 +24,6 @@ define([ ...@@ -25,7 +24,6 @@ define([
_, _,
ProductCollection, ProductCollection,
CourseSeat, CourseSeat,
HonorSeat,
CourseUtils, CourseUtils,
Utils) { Utils) {
'use strict'; 'use strict';
...@@ -102,7 +100,10 @@ define([ ...@@ -102,7 +100,10 @@ define([
key: 'products', key: 'products',
relatedModel: CourseSeat, relatedModel: CourseSeat,
includeInJSON: false, includeInJSON: false,
parse: true parse: true,
collectionOptions: function (model) {
return {course: model};
}
}], }],
/** /**
...@@ -196,7 +197,7 @@ define([ ...@@ -196,7 +197,7 @@ define([
if (_.isEmpty(seats) && _.contains(this.creatableSeatTypes, seatType)) { if (_.isEmpty(seats) && _.contains(this.creatableSeatTypes, seatType)) {
seatClass = CourseUtils.getCourseSeatModel(seatType); seatClass = CourseUtils.getCourseSeatModel(seatType);
/*jshint newcap: false */ /*jshint newcap: false */
seat = new seatClass(); seat = new seatClass({course: this});
/*jshint newcap: true */ /*jshint newcap: true */
this.get('products').add(seat); this.get('products').add(seat);
seats.push(seat); seats.push(seat);
......
define([ define([
'moment',
'models/product_model' 'models/product_model'
], ],
function (ProductModel) { function (moment,
Product) {
'use strict'; 'use strict';
return ProductModel.extend({ return Product.extend({
defaults: { defaults: {
certificate_type: null, certificate_type: null,
expires: null, expires: null,
...@@ -15,6 +17,8 @@ define([ ...@@ -15,6 +17,8 @@ define([
product_class: 'Seat' product_class: 'Seat'
}, },
course: null,
validation: { validation: {
price: { price: {
required: true, required: true,
...@@ -23,6 +27,21 @@ define([ ...@@ -23,6 +27,21 @@ define([
}, },
product_class: { product_class: {
oneOf: ['Seat'] 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([ define([
'underscore', 'underscore',
'models/course_model',
'models/course_seats/course_seat' 'models/course_seats/course_seat'
], ],
function (_, function (_,
Course,
CourseSeat) { CourseSeat) {
'use strict'; 'use strict';
...@@ -114,6 +116,38 @@ define([ ...@@ -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([ ...@@ -29,7 +29,12 @@ define([
validate: true validate: true
} }
}, },
'input[name=expires]': 'expires', 'input[name=expires]': {
observe: 'expires',
setOptions: {
validate: true
}
},
'input[name=id_verification_required]': { 'input[name=id_verification_required]': {
observe: 'id_verification_required', observe: 'id_verification_required',
onSet: 'cleanIdVerificationRequired' onSet: 'cleanIdVerificationRequired'
......
...@@ -36,7 +36,12 @@ define([ ...@@ -36,7 +36,12 @@ define([
validate: true validate: true
} }
}, },
'input[name=expires]': 'expires' 'input[name=expires]': {
observe: 'expires',
setOptions: {
validate: true
}
}
}, },
initialize: function (options) { initialize: function (options) {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<div class="col-sm-4"> <div class="col-sm-4">
<span class="price-label"><%= gettext('Price (in USD)') %>:</span> <span class="seat-price">$0.00</span> <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="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"> <input type="hidden" name="id_verification_required" value="false">
</div> </div>
<div class="col-sm-4 seat-certificate-type"> <div class="col-sm-4 seat-certificate-type">
......
...@@ -19,16 +19,21 @@ ...@@ -19,16 +19,21 @@
</div> </div>
<div class="expires"> <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" data-toggle="tooltip"
<div class="input-group-addon"><i class="fa fa-calendar" aria-hidden="true"></i></div> title="<%= gettext('Professional education courses have no upgrade deadline.') %>">
<input type="datetime-local" id="expires" name="expires" class="form-control" value="" <div class="input-group-addon"><i class="fa fa-calendar" aria-hidden="true"></i></div>
aria-describedby="expiresHelpBlock" disabled="disabled"> <input type="datetime-local" id="expires" name="expires" class="form-control"
</div> 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"> <span id="expiresHelpBlock" class="help-block">
<%= gettext('After this date/time, students can no longer enroll in this track.') %> <%= gettext('After this date/time, students can no longer enroll in this track.') %>
</span> </span>
</div>
</div> </div>
</div> </div>
<div class="col-sm-4 seat-certificate-type"> <div class="col-sm-4 seat-certificate-type">
......
...@@ -20,16 +20,20 @@ ...@@ -20,16 +20,20 @@
</div> </div>
<div class="expires"> <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">
<div class="input-group-addon"><i class="fa fa-calendar" aria-hidden="true"></i></div> <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="" <input type="datetime-local" id="expires" name="expires" class="form-control"
aria-describedby="expiresHelpBlock"> aria-describedby="expiresHelpBlock">
</div> </div>
<!-- NOTE: This help-block is here for validation messages. -->
<span class="help-block"></span>
<span id="expiresHelpBlock" class="help-block"> <span id="expiresHelpBlock" class="help-block">
<%= gettext('After this date/time, students can no longer enroll in this track.') %> <%= gettext('After this date/time, students can no longer enroll in this track.') %>
</span> </span>
</div>
</div> </div>
</div> </div>
<div class="col-sm-4 seat-certificate-type"> <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