Merge branch 'master' into feature/cas/manual-policy

Conflicts:
	cms/djangoapps/contentstore/features/common.py
	cms/djangoapps/contentstore/features/section.py
	cms/djangoapps/contentstore/tests/test_course_settings.py
	cms/djangoapps/contentstore/views.py
	cms/static/js/models/settings/course_grading_policy.js
	cms/static/js/views/settings/main_settings_view.js
	cms/static/sass/_settings.scss
	cms/templates/settings.html
	cms/urls.py
This commit is contained in:
cahrens
2013-02-19 13:27:35 -05:00
502 changed files with 44000 additions and 7759 deletions

View File

@@ -1,85 +1,83 @@
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
defaults: {
location : null, // the course's Location model, required
start_date: null, // maps to 'start'
end_date: null, // maps to 'end'
enrollment_start: null,
enrollment_end: null,
syllabus: null,
overview: "",
intro_video: null,
effort: null // an int or null
},
// When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset)
parse: function(attributes) {
if (attributes['course_location']) {
attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true});
}
if (attributes['start_date']) {
attributes.start_date = new Date(attributes.start_date);
}
if (attributes['end_date']) {
attributes.end_date = new Date(attributes.end_date);
}
if (attributes['enrollment_start']) {
attributes.enrollment_start = new Date(attributes.enrollment_start);
}
if (attributes['enrollment_end']) {
attributes.enrollment_end = new Date(attributes.enrollment_end);
}
return attributes;
},
validate: function(newattrs) {
// Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
// A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
var errors = {};
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
errors.end_date = "The course end date cannot be before the course start date.";
}
if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) {
errors.enrollment_start = "The course start date cannot be before the enrollment start date.";
}
if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date.";
}
if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment end date cannot be after the course end date.";
}
if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) {
if (this._videokey_illegal_chars.exec(newattrs.intro_video)) {
errors.intro_video = "Key should only contain letters, numbers, _, or -";
}
// TODO check if key points to a real video using google's youtube api
}
if (!_.isEmpty(errors)) return errors;
// NOTE don't return empty errors as that will be interpreted as an error state
},
url: function() {
var location = this.get('location');
return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details';
},
_videokey_illegal_chars : /[^a-zA-Z0-9_-]/g,
save_videosource: function(newsource) {
// newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string
// returns the videosource for the preview which iss the key whose speed is closest to 1
if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.save({'intro_video': null},
{ error : CMS.ServerError});
// TODO remove all whitespace w/in string
else {
if (this.get('intro_video') !== newsource) this.save('intro_video', newsource,
{ error : CMS.ServerError});
}
return this.videosourceSample();
},
videosourceSample : function() {
if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video');
else return "";
}
defaults: {
location : null, // the course's Location model, required
start_date: null, // maps to 'start'
end_date: null, // maps to 'end'
enrollment_start: null,
enrollment_end: null,
syllabus: null,
overview: "",
intro_video: null,
effort: null // an int or null
},
// When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset)
parse: function(attributes) {
if (attributes['course_location']) {
attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true});
}
if (attributes['start_date']) {
attributes.start_date = new Date(attributes.start_date);
}
if (attributes['end_date']) {
attributes.end_date = new Date(attributes.end_date);
}
if (attributes['enrollment_start']) {
attributes.enrollment_start = new Date(attributes.enrollment_start);
}
if (attributes['enrollment_end']) {
attributes.enrollment_end = new Date(attributes.enrollment_end);
}
return attributes;
},
validate: function(newattrs) {
// Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
// A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
var errors = {};
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
errors.end_date = "The course end date cannot be before the course start date.";
}
if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) {
errors.enrollment_start = "The course start date cannot be before the enrollment start date.";
}
if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date.";
}
if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment end date cannot be after the course end date.";
}
if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) {
if (this._videokey_illegal_chars.exec(newattrs.intro_video)) {
errors.intro_video = "Key should only contain letters, numbers, _, or -";
}
// TODO check if key points to a real video using google's youtube api
}
if (!_.isEmpty(errors)) return errors;
// NOTE don't return empty errors as that will be interpreted as an error state
},
url: function() {
var location = this.get('location');
return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/details';
},
_videokey_illegal_chars : /[^a-zA-Z0-9_-]/g,
save_videosource: function(newsource) {
// newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string
// returns the videosource for the preview which iss the key whose speed is closest to 1
if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.set({'intro_video': null});
// TODO remove all whitespace w/in string
else {
if (this.get('intro_video') !== newsource) this.set('intro_video', newsource);
}
return this.videosourceSample();
},
videosourceSample : function() {
if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video');
else return "";
}
});

View File

@@ -13,7 +13,8 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
}
if (attributes['graders']) {
var graderCollection;
if (this.has('graders')) {
// 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);
}
@@ -27,7 +28,7 @@ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
},
url : function() {
var location = this.get('course_location');
return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/grading';
return '/' + location.get('org') + "/" + location.get('course') + '/settings-details/' + location.get('name') + '/section/grading';
},
gracePeriodToDate : function() {
var newDate = new Date();
@@ -119,7 +120,7 @@ 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') + '/grades/' + this.course_location.get('name') + '/';
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);

View File

@@ -1,43 +1,42 @@
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseSettings = Backbone.Model.extend({
// a container for the models representing the n possible tabbed states
defaults: {
courseLocation: null,
// NOTE: keep these sync'd w/ the data-section names in settings-page-menu
details: null,
faculty: null,
grading: null,
problems: null,
discussions: null
},
// a container for the models representing the n possible tabbed states
defaults: {
courseLocation: null,
details: null,
faculty: null,
grading: null,
problems: null,
discussions: null
},
retrieve: function(submodel, callback) {
if (this.get(submodel)) callback();
else {
var cachethis = this;
switch (submodel) {
case 'details':
var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')});
details.fetch( {
success : function(model) {
cachethis.set('details', model);
callback(model);
}
});
break;
case 'grading':
var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')});
grading.fetch( {
success : function(model) {
cachethis.set('grading', model);
callback(model);
}
});
break;
retrieve: function(submodel, callback) {
if (this.get(submodel)) callback();
else {
var cachethis = this;
switch (submodel) {
case 'details':
var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')});
details.fetch( {
success : function(model) {
cachethis.set('details', model);
callback(model);
}
});
break;
case 'grading':
var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')});
grading.fetch( {
success : function(model) {
cachethis.set('grading', model);
callback(model);
}
});
break;
default:
break;
}
}
}
default:
break;
}
}
}
})