Commit 1db66957 by Chris Dodge

Merge branch 'master' of github.com:edx/mitx into fix/cdodge/textbooks-not-exporting

parents 1b0eff52 ee527268
from xmodule.templates import update_templates from xmodule.templates import update_templates
from xmodule.modulestore.django import modulestore
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
...@@ -6,4 +7,4 @@ class Command(BaseCommand): ...@@ -6,4 +7,4 @@ class Command(BaseCommand):
help = 'Imports and updates the Studio component templates from the code pack and put in the DB' help = 'Imports and updates the Studio component templates from the code pack and put in the DB'
def handle(self, *args, **options): def handle(self, *args, **options):
update_templates() update_templates(modulestore('direct'))
...@@ -945,7 +945,7 @@ class TemplateTestCase(ModuleStoreTestCase): ...@@ -945,7 +945,7 @@ class TemplateTestCase(ModuleStoreTestCase):
self.assertIsNotNone(verify_create) self.assertIsNotNone(verify_create)
# now run cleanup # now run cleanup
update_templates() update_templates(modulestore('direct'))
# now try to find dangling template, it should not be in DB any longer # now try to find dangling template, it should not be in DB any longer
asserted = False asserted = False
......
...@@ -895,4 +895,4 @@ function saveSetSectionScheduleDate(e) { ...@@ -895,4 +895,4 @@ function saveSetSectionScheduleDate(e) {
hideModal(); hideModal();
}); });
} }
\ No newline at end of file
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
* Create a HesitateEvent and assign it as the event to execute: * Create a HesitateEvent and assign it as the event to execute:
* $(el).on('mouseEnter', CMS.HesitateEvent( expand, 'mouseLeave').trigger); * $(el).on('mouseEnter', CMS.HesitateEvent( expand, 'mouseLeave').trigger);
* It calls the executeOnTimeOut function with the event.currentTarget after the configurable timeout IFF the cancelSelector event * It calls the executeOnTimeOut function with the event.currentTarget after the configurable timeout IFF the cancelSelector event
* did not occur on the event.currentTarget. * did not occur on the event.currentTarget.
* *
* More specifically, when trigger is called (triggered by the event you bound it to), it starts a timer * More specifically, when trigger is called (triggered by the event you bound it to), it starts a timer
* which the cancelSelector event will cancel or if the timer finished, it executes the executeOnTimeOut function * which the cancelSelector event will cancel or if the timer finished, it executes the executeOnTimeOut function
* passing it the original event (whose currentTarget s/b the specific ele). It never accumulates events; however, it doesn't hurt for your * passing it the original event (whose currentTarget s/b the specific ele). It never accumulates events; however, it doesn't hurt for your
* code to minimize invocations of trigger by binding to mouseEnter v mouseOver and such. * code to minimize invocations of trigger by binding to mouseEnter v mouseOver and such.
* *
* NOTE: if something outside of this wants to cancel the event, invoke cachedhesitation.untrigger(null | anything); * NOTE: if something outside of this wants to cancel the event, invoke cachedhesitation.untrigger(null | anything);
*/ */
...@@ -25,7 +25,7 @@ CMS.HesitateEvent.DURATION = 800; ...@@ -25,7 +25,7 @@ CMS.HesitateEvent.DURATION = 800;
CMS.HesitateEvent.prototype.trigger = function(event) { CMS.HesitateEvent.prototype.trigger = function(event) {
if (event.data.timeoutEventId == null) { if (event.data.timeoutEventId == null) {
event.data.timeoutEventId = window.setTimeout( event.data.timeoutEventId = window.setTimeout(
function() { event.data.fireEvent(event); }, function() { event.data.fireEvent(event); },
CMS.HesitateEvent.DURATION); CMS.HesitateEvent.DURATION);
event.data.originalEvent = event; event.data.originalEvent = event;
$(event.data.originalEvent.delegateTarget).on(event.data.cancelSelector, event.data, event.data.untrigger); $(event.data.originalEvent.delegateTarget).on(event.data.cancelSelector, event.data, event.data.untrigger);
...@@ -45,4 +45,4 @@ CMS.HesitateEvent.prototype.untrigger = function(event) { ...@@ -45,4 +45,4 @@ CMS.HesitateEvent.prototype.untrigger = function(event) {
$(event.data.originalEvent.delegateTarget).off(event.data.cancelSelector, event.data.untrigger); $(event.data.originalEvent.delegateTarget).off(event.data.cancelSelector, event.data.untrigger);
} }
event.data.timeoutEventId = null; event.data.timeoutEventId = null;
}; };
\ No newline at end of file
...@@ -80,6 +80,6 @@ $(document).ready(function(){ ...@@ -80,6 +80,6 @@ $(document).ready(function(){
$('section.problem-edit').show(); $('section.problem-edit').show();
return false; return false;
}); });
}); });
// single per course holds the updates and handouts // single per course holds the updates and handouts
CMS.Models.CourseInfo = Backbone.Model.extend({ CMS.Models.CourseInfo = Backbone.Model.extend({
// This model class is not suited for restful operations and is considered just a server side initialized container // This model class is not suited for restful operations and is considered just a server side initialized container
url: '', url: '',
defaults: { defaults: {
"courseId": "", // the location url "courseId": "", // the location url
"updates" : null, // UpdateCollection "updates" : null, // UpdateCollection
"handouts": null // HandoutCollection "handouts": null // HandoutCollection
}, },
idAttribute : "courseId" idAttribute : "courseId"
}); });
// course update -- biggest kludge here is the lack of a real id to map updates to originals // course update -- biggest kludge here is the lack of a real id to map updates to originals
CMS.Models.CourseUpdate = Backbone.Model.extend({ CMS.Models.CourseUpdate = Backbone.Model.extend({
defaults: { defaults: {
...@@ -26,11 +26,11 @@ CMS.Models.CourseUpdate = Backbone.Model.extend({ ...@@ -26,11 +26,11 @@ CMS.Models.CourseUpdate = Backbone.Model.extend({
*/ */
CMS.Models.CourseUpdateCollection = Backbone.Collection.extend({ CMS.Models.CourseUpdateCollection = Backbone.Collection.extend({
url : function() {return this.urlbase + "course_info/updates/";}, url : function() {return this.urlbase + "course_info/updates/";},
model : CMS.Models.CourseUpdate model : CMS.Models.CourseUpdate
}); });
\ No newline at end of file
...@@ -16,7 +16,7 @@ CMS.Models.Location = Backbone.Model.extend({ ...@@ -16,7 +16,7 @@ CMS.Models.Location = Backbone.Model.extend({
}, },
_tagPattern : /[^:]+/g, _tagPattern : /[^:]+/g,
_fieldPattern : new RegExp('[^/]+','g'), _fieldPattern : new RegExp('[^/]+','g'),
parse: function(payload) { parse: function(payload) {
if (_.isArray(payload)) { if (_.isArray(payload)) {
return { return {
...@@ -25,7 +25,7 @@ CMS.Models.Location = Backbone.Model.extend({ ...@@ -25,7 +25,7 @@ CMS.Models.Location = Backbone.Model.extend({
course: payload[2], course: payload[2],
category: payload[3], category: payload[3],
name: payload[4] name: payload[4]
} };
} }
else if (_.isString(payload)) { else if (_.isString(payload)) {
this._tagPattern.lastIndex = 0; // odd regex behavior requires this to be reset sometimes this._tagPattern.lastIndex = 0; // odd regex behavior requires this to be reset sometimes
...@@ -65,4 +65,4 @@ CMS.Models.CourseRelative = Backbone.Model.extend({ ...@@ -65,4 +65,4 @@ CMS.Models.CourseRelative = Backbone.Model.extend({
CMS.Models.CourseRelativeCollection = Backbone.Collection.extend({ CMS.Models.CourseRelativeCollection = Backbone.Collection.extend({
model : CMS.Models.CourseRelative model : CMS.Models.CourseRelative
}); });
\ No newline at end of file
...@@ -6,5 +6,5 @@ CMS.Models.ModuleInfo = Backbone.Model.extend({ ...@@ -6,5 +6,5 @@ CMS.Models.ModuleInfo = Backbone.Model.extend({
"data": null, "data": null,
"metadata" : null, "metadata" : null,
"children" : null "children" : null
}, }
}); });
\ No newline at end of file
...@@ -11,7 +11,7 @@ CMS.Models.Settings.Advanced = Backbone.Model.extend({ ...@@ -11,7 +11,7 @@ CMS.Models.Settings.Advanced = Backbone.Model.extend({
validate: function (attrs) { validate: function (attrs) {
// Keys can no longer be edited. We are currently not validating values. // Keys can no longer be edited. We are currently not validating values.
}, },
save : function (attrs, options) { save : function (attrs, options) {
// wraps the save call w/ the deletion of the removed keys after we know the saved ones worked // wraps the save call w/ the deletion of the removed keys after we know the saved ones worked
options = options ? _.clone(options) : {}; options = options ? _.clone(options) : {};
...@@ -23,7 +23,7 @@ CMS.Models.Settings.Advanced = Backbone.Model.extend({ ...@@ -23,7 +23,7 @@ CMS.Models.Settings.Advanced = Backbone.Model.extend({
}; };
Backbone.Model.prototype.save.call(this, attrs, options); Backbone.Model.prototype.save.call(this, attrs, options);
}, },
afterSave : function(self) { afterSave : function(self) {
// remove deleted attrs // remove deleted attrs
if (!_.isEmpty(self.deleteKeys)) { if (!_.isEmpty(self.deleteKeys)) {
......
...@@ -66,7 +66,7 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ ...@@ -66,7 +66,7 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
save_videosource: function(newsource) { save_videosource: function(newsource) {
// newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string // 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 // 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}); if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.save({'intro_video': null});
// TODO remove all whitespace w/in string // TODO remove all whitespace w/in string
else { else {
if (this.get('intro_video') !== newsource) this.save('intro_video', newsource); if (this.get('intro_video') !== newsource) this.save('intro_video', newsource);
......
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object(); if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({ CMS.Models.Settings.CourseGradingPolicy = Backbone.Model.extend({
defaults : { defaults : {
course_location : null, course_location : null,
graders : null, // CourseGraderCollection graders : null, // CourseGraderCollection
grade_cutoffs : null, // CourseGradeCutoff model grade_cutoffs : null, // CourseGradeCutoff model
grace_period : null // either null or { hours: n, minutes: m, ...} grace_period : null // either null or { hours: n, minutes: m, ...}
}, },
...@@ -54,7 +54,7 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({ ...@@ -54,7 +54,7 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
"type" : "", // must be unique w/in collection (ie. w/in course) "type" : "", // must be unique w/in collection (ie. w/in course)
"min_count" : 1, "min_count" : 1,
"drop_count" : 0, "drop_count" : 0,
"short_label" : "", // what to use in place of type if space is an issue "short_label" : "", // what to use in place of type if space is an issue
"weight" : 0 // int 0..100 "weight" : 0 // int 0..100
}, },
parse : function(attrs) { parse : function(attrs) {
...@@ -125,4 +125,4 @@ CMS.Models.Settings.CourseGraderCollection = Backbone.Collection.extend({ ...@@ -125,4 +125,4 @@ CMS.Models.Settings.CourseGraderCollection = Backbone.Collection.extend({
sumWeights : function() { sumWeights : function() {
return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0); return this.reduce(function(subtotal, grader) { return subtotal + grader.get('weight'); }, 0);
} }
}); });
\ No newline at end of file
...@@ -93,4 +93,4 @@ CMS.Views.Checklists = Backbone.View.extend({ ...@@ -93,4 +93,4 @@ CMS.Views.Checklists = Backbone.View.extend({
error : CMS.ServerError error : CMS.ServerError
}); });
} }
}); });
\ No newline at end of file
...@@ -32,7 +32,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -32,7 +32,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
"click .post-actions > .edit-button" : "onEdit", "click .post-actions > .edit-button" : "onEdit",
"click .post-actions > .delete-button" : "onDelete" "click .post-actions > .delete-button" : "onDelete"
}, },
initialize: function() { initialize: function() {
var self = this; var self = this;
// instantiates an editor template for each update in the collection // instantiates an editor template for each update in the collection
...@@ -41,13 +41,13 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -41,13 +41,13 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
"/static/client_templates/course_info_update.html", "/static/client_templates/course_info_update.html",
function (raw_template) { function (raw_template) {
self.template = _.template(raw_template); self.template = _.template(raw_template);
self.render(); self.render();
} }
); );
// when the client refetches the updates as a whole, re-render them // when the client refetches the updates as a whole, re-render them
this.listenTo(this.collection, 'reset', this.render); this.listenTo(this.collection, 'reset', this.render);
}, },
render: function () { render: function () {
// iterate over updates and create views for each using the template // iterate over updates and create views for each using the template
var updateEle = this.$el.find("#course-update-list"); var updateEle = this.$el.find("#course-update-list");
...@@ -66,14 +66,14 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -66,14 +66,14 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
this.$el.find('.date').datepicker({ 'dateFormat': 'MM d, yy' }); this.$el.find('.date').datepicker({ 'dateFormat': 'MM d, yy' });
return this; return this;
}, },
onNew: function(event) { onNew: function(event) {
event.preventDefault(); event.preventDefault();
var self = this; var self = this;
// create new obj, insert into collection, and render this one ele overriding the hidden attr // create new obj, insert into collection, and render this one ele overriding the hidden attr
var newModel = new CMS.Models.CourseUpdate(); var newModel = new CMS.Models.CourseUpdate();
this.collection.add(newModel, {at : 0}); this.collection.add(newModel, {at : 0});
var $newForm = $(this.template({ updateModel : newModel })); var $newForm = $(this.template({ updateModel : newModel }));
var updateEle = this.$el.find("#course-update-list"); var updateEle = this.$el.find("#course-update-list");
...@@ -87,7 +87,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -87,7 +87,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
lineWrapping: true, lineWrapping: true,
}); });
} }
$newForm.addClass('editing'); $newForm.addClass('editing');
this.$currentPost = $newForm.closest('li'); this.$currentPost = $newForm.closest('li');
...@@ -99,21 +99,21 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -99,21 +99,21 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
$('.date').datepicker('destroy'); $('.date').datepicker('destroy');
$('.date').datepicker({ 'dateFormat': 'MM d, yy' }); $('.date').datepicker({ 'dateFormat': 'MM d, yy' });
}, },
onSave: function(event) { onSave: function(event) {
event.preventDefault(); event.preventDefault();
var targetModel = this.eventModel(event); var targetModel = this.eventModel(event);
targetModel.set({ date : this.dateEntry(event).val(), content : this.$codeMirror.getValue() }); targetModel.set({ date : this.dateEntry(event).val(), content : this.$codeMirror.getValue() });
// push change to display, hide the editor, submit the change // push change to display, hide the editor, submit the change
targetModel.save({}, {error : CMS.ServerError}); targetModel.save({}, {error : CMS.ServerError});
this.closeEditor(this); this.closeEditor(this);
analytics.track('Saved Course Update', { analytics.track('Saved Course Update', {
'course': course_location_analytics, 'course': course_location_analytics,
'date': this.dateEntry(event).val() 'date': this.dateEntry(event).val()
}); });
}, },
onCancel: function(event) { onCancel: function(event) {
event.preventDefault(); event.preventDefault();
// change editor contents back to model values and hide the editor // change editor contents back to model values and hide the editor
...@@ -121,13 +121,13 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -121,13 +121,13 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
var targetModel = this.eventModel(event); var targetModel = this.eventModel(event);
this.closeEditor(this, !targetModel.id); this.closeEditor(this, !targetModel.id);
}, },
onEdit: function(event) { onEdit: function(event) {
event.preventDefault(); event.preventDefault();
var self = this; var self = this;
this.$currentPost = $(event.target).closest('li'); this.$currentPost = $(event.target).closest('li');
this.$currentPost.addClass('editing'); this.$currentPost.addClass('editing');
$(this.editor(event)).show(); $(this.editor(event)).show();
var $textArea = this.$currentPost.find(".new-update-content").first(); var $textArea = this.$currentPost.find(".new-update-content").first();
if (this.$codeMirror == null ) { if (this.$codeMirror == null ) {
...@@ -154,13 +154,13 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -154,13 +154,13 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
analytics.track('Deleted Course Update', { analytics.track('Deleted Course Update', {
'course': course_location_analytics, 'course': course_location_analytics,
'date': this.dateEntry(event).val() 'date': this.dateEntry(event).val()
}); });
var targetModel = this.eventModel(event); var targetModel = this.eventModel(event);
this.modelDom(event).remove(); this.modelDom(event).remove();
var cacheThis = this; var cacheThis = this;
targetModel.destroy({success : function (model, response) { targetModel.destroy({success : function (model, response) {
cacheThis.collection.fetch({success : function() {cacheThis.render();}, cacheThis.collection.fetch({success : function() {cacheThis.render();},
error : CMS.ServerError}); error : CMS.ServerError});
}, },
...@@ -192,17 +192,17 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -192,17 +192,17 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
this.$codeMirror = null; this.$codeMirror = null;
self.$currentPost.find('.CodeMirror').remove(); self.$currentPost.find('.CodeMirror').remove();
}, },
// Dereferencing from events to screen elements // Dereferencing from events to screen elements
eventModel: function(event) { eventModel: function(event) {
// not sure if it should be currentTarget or delegateTarget // not sure if it should be currentTarget or delegateTarget
return this.collection.get($(event.currentTarget).attr("name")); return this.collection.get($(event.currentTarget).attr("name"));
}, },
modelDom: function(event) { modelDom: function(event) {
return $(event.currentTarget).closest("li"); return $(event.currentTarget).closest("li");
}, },
editor: function(event) { editor: function(event) {
var li = $(event.currentTarget).closest("li"); var li = $(event.currentTarget).closest("li");
if (li) return $(li).find("form").first(); if (li) return $(li).find("form").first();
...@@ -216,7 +216,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -216,7 +216,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
contentEntry: function(event) { contentEntry: function(event) {
return $(event.currentTarget).closest("li").find(".new-update-content").first(); return $(event.currentTarget).closest("li").find(".new-update-content").first();
}, },
dateDisplay: function(event) { dateDisplay: function(event) {
return $(event.currentTarget).closest("li").find("#date-display").first(); return $(event.currentTarget).closest("li").find("#date-display").first();
}, },
...@@ -224,7 +224,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -224,7 +224,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
contentDisplay: function(event) { contentDisplay: function(event) {
return $(event.currentTarget).closest("li").find(".update-contents").first(); return $(event.currentTarget).closest("li").find(".update-contents").first();
} }
}); });
// the handouts view is dumb right now; it needs tied to a model and all that jazz // the handouts view is dumb right now; it needs tied to a model and all that jazz
...@@ -245,7 +245,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({ ...@@ -245,7 +245,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
"/static/client_templates/course_info_handouts.html", "/static/client_templates/course_info_handouts.html",
function (raw_template) { function (raw_template) {
self.template = _.template(raw_template); self.template = _.template(raw_template);
self.render(); self.render();
} }
); );
}, },
...@@ -253,8 +253,8 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({ ...@@ -253,8 +253,8 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
} }
); );
}, },
render: function () { render: function () {
var updateEle = this.$el; var updateEle = this.$el;
var self = this; var self = this;
this.$el.html( this.$el.html(
...@@ -313,4 +313,4 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({ ...@@ -313,4 +313,4 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
self.$form.find('.CodeMirror').remove(); self.$form.find('.CodeMirror').remove();
this.$codeMirror = null; this.$codeMirror = null;
} }
}); });
\ No newline at end of file
...@@ -16,7 +16,7 @@ CMS.Models.AssignmentGrade = Backbone.Model.extend({ ...@@ -16,7 +16,7 @@ CMS.Models.AssignmentGrade = Backbone.Model.extend({
urlRoot : function() { urlRoot : function() {
if (this.has('location')) { if (this.has('location')) {
var location = this.get('location'); var location = this.get('location');
return '/' + location.get('org') + "/" + location.get('course') + '/' + location.get('category') + '/' return '/' + location.get('org') + "/" + location.get('course') + '/' + location.get('category') + '/'
+ location.get('name') + '/gradeas/'; + location.get('name') + '/gradeas/';
} }
else return ""; else return "";
...@@ -37,14 +37,14 @@ CMS.Views.OverviewAssignmentGrader = Backbone.View.extend({ ...@@ -37,14 +37,14 @@ CMS.Views.OverviewAssignmentGrader = Backbone.View.extend({
'<a data-tooltip="Mark/unmark this subsection as graded" class="menu-toggle" href="#">' + '<a data-tooltip="Mark/unmark this subsection as graded" class="menu-toggle" href="#">' +
'<% if (!hideSymbol) {%><span class="ss-icon ss-standard">&#x2713;</span><%};%>' + '<% if (!hideSymbol) {%><span class="ss-icon ss-standard">&#x2713;</span><%};%>' +
'</a>' + '</a>' +
'<ul class="menu">' + '<ul class="menu">' +
'<% graders.each(function(option) { %>' + '<% graders.each(function(option) { %>' +
'<li><a <% if (option.get("type") == assignmentType) {%>class="is-selected" <%}%> href="#"><%= option.get("type") %></a></li>' + '<li><a <% if (option.get("type") == assignmentType) {%>class="is-selected" <%}%> href="#"><%= option.get("type") %></a></li>' +
'<% }) %>' + '<% }) %>' +
'<li><a class="gradable-status-notgraded" href="#">Not Graded</a></li>' + '<li><a class="gradable-status-notgraded" href="#">Not Graded</a></li>' +
'</ul>'); '</ul>');
this.assignmentGrade = new CMS.Models.AssignmentGrade({ this.assignmentGrade = new CMS.Models.AssignmentGrade({
assignmentUrl : this.$el.closest('.id-holder').data('id'), assignmentUrl : this.$el.closest('.id-holder').data('id'),
graderType : this.$el.data('initial-status')}); graderType : this.$el.data('initial-status')});
// TODO throw exception if graders is null // TODO throw exception if graders is null
this.graders = this.options['graders']; this.graders = this.options['graders'];
...@@ -78,13 +78,13 @@ CMS.Views.OverviewAssignmentGrader = Backbone.View.extend({ ...@@ -78,13 +78,13 @@ CMS.Views.OverviewAssignmentGrader = Backbone.View.extend({
}, },
selectGradeType : function(e) { selectGradeType : function(e) {
e.preventDefault(); e.preventDefault();
this.removeMenu(e); this.removeMenu(e);
// TODO I'm not happy with this string fetch via the html for what should be an id. I'd rather use the id attr // TODO I'm not happy with this string fetch via the html for what should be an id. I'd rather use the id attr
// of the CourseGradingPolicy model or null for Not Graded (NOTE, change template's if check for is-selected accordingly) // of the CourseGradingPolicy model or null for Not Graded (NOTE, change template's if check for is-selected accordingly)
this.assignmentGrade.save('graderType', $(e.target).text()); this.assignmentGrade.save('graderType', $(e.target).text());
this.render(); this.render();
} }
}) })
\ No newline at end of file
...@@ -6,26 +6,26 @@ $(document).ready(function() { ...@@ -6,26 +6,26 @@ $(document).ready(function() {
$('.unit').draggable({ $('.unit').draggable({
axis: 'y', axis: 'y',
handle: '.drag-handle', handle: '.drag-handle',
zIndex: 999, zIndex: 999,
start: initiateHesitate, start: initiateHesitate,
// left 2nd arg in as inert selector b/c i was uncertain whether we'd try to get the shove up/down // left 2nd arg in as inert selector b/c i was uncertain whether we'd try to get the shove up/down
// to work in the future // to work in the future
drag: generateCheckHoverState('.collapsed', ''), drag: generateCheckHoverState('.collapsed', ''),
stop: removeHesitate, stop: removeHesitate,
revert: "invalid" revert: "invalid"
}); });
// Subsection reordering // Subsection reordering
$('.id-holder').draggable({ $('.id-holder').draggable({
axis: 'y', axis: 'y',
handle: '.section-item .drag-handle', handle: '.section-item .drag-handle',
zIndex: 999, zIndex: 999,
start: initiateHesitate, start: initiateHesitate,
drag: generateCheckHoverState('.courseware-section.collapsed', ''), drag: generateCheckHoverState('.courseware-section.collapsed', ''),
stop: removeHesitate, stop: removeHesitate,
revert: "invalid" revert: "invalid"
}); });
// Section reordering // Section reordering
$('.courseware-section').draggable({ $('.courseware-section').draggable({
axis: 'y', axis: 'y',
...@@ -33,8 +33,8 @@ $(document).ready(function() { ...@@ -33,8 +33,8 @@ $(document).ready(function() {
stack: '.courseware-section', stack: '.courseware-section',
revert: "invalid" revert: "invalid"
}); });
$('.sortable-unit-list').droppable({ $('.sortable-unit-list').droppable({
accept : '.unit', accept : '.unit',
greedy: true, greedy: true,
...@@ -50,7 +50,7 @@ $(document).ready(function() { ...@@ -50,7 +50,7 @@ $(document).ready(function() {
drop: onSubsectionReordered, drop: onSubsectionReordered,
greedy: true greedy: true
}); });
// Section reordering // Section reordering
$('.courseware-overview').droppable({ $('.courseware-overview').droppable({
accept : '.courseware-section', accept : '.courseware-section',
...@@ -58,7 +58,7 @@ $(document).ready(function() { ...@@ -58,7 +58,7 @@ $(document).ready(function() {
drop: onSectionReordered, drop: onSectionReordered,
greedy: true greedy: true
}); });
// stop clicks on drag bars from doing their thing w/o stopping drag // stop clicks on drag bars from doing their thing w/o stopping drag
$('.drag-handle').click(function(e) {e.preventDefault(); }); $('.drag-handle').click(function(e) {e.preventDefault(); });
...@@ -87,7 +87,7 @@ function computeIntersection(droppable, uiHelper, y) { ...@@ -87,7 +87,7 @@ function computeIntersection(droppable, uiHelper, y) {
$.extend(droppable, {offset : $(droppable).offset()}); $.extend(droppable, {offset : $(droppable).offset()});
var t = droppable.offset.top, var t = droppable.offset.top,
b = t + droppable.proportions.height; b = t + droppable.proportions.height;
if (t === b) { if (t === b) {
...@@ -118,10 +118,10 @@ function generateCheckHoverState(selectorsToOpen, selectorsToShove) { ...@@ -118,10 +118,10 @@ function generateCheckHoverState(selectorsToOpen, selectorsToShove) {
this[c === "isout" ? "isover" : "isout"] = false; this[c === "isout" ? "isover" : "isout"] = false;
$(this).trigger(c === "isover" ? "dragEnter" : "dragLeave"); $(this).trigger(c === "isover" ? "dragEnter" : "dragLeave");
}); });
$(selectorsToShove).each(function() { $(selectorsToShove).each(function() {
var intersectsBottom = computeIntersection(this, ui.helper, (draggable.positionAbs || draggable.position.absolute).top); var intersectsBottom = computeIntersection(this, ui.helper, (draggable.positionAbs || draggable.position.absolute).top);
if ($(this).hasClass('ui-dragging-pushup')) { if ($(this).hasClass('ui-dragging-pushup')) {
if (!intersectsBottom) { if (!intersectsBottom) {
console.log('not up', $(this).data('id')); console.log('not up', $(this).data('id'));
...@@ -132,10 +132,10 @@ function generateCheckHoverState(selectorsToOpen, selectorsToShove) { ...@@ -132,10 +132,10 @@ function generateCheckHoverState(selectorsToOpen, selectorsToShove) {
console.log('up', $(this).data('id')); console.log('up', $(this).data('id'));
$(this).addClass('ui-dragging-pushup'); $(this).addClass('ui-dragging-pushup');
} }
var intersectsTop = computeIntersection(this, ui.helper, var intersectsTop = computeIntersection(this, ui.helper,
(draggable.positionAbs || draggable.position.absolute).top + draggable.helperProportions.height); (draggable.positionAbs || draggable.position.absolute).top + draggable.helperProportions.height);
if ($(this).hasClass('ui-dragging-pushdown')) { if ($(this).hasClass('ui-dragging-pushdown')) {
if (!intersectsTop) { if (!intersectsTop) {
console.log('not down', $(this).data('id')); console.log('not down', $(this).data('id'));
...@@ -146,7 +146,7 @@ function generateCheckHoverState(selectorsToOpen, selectorsToShove) { ...@@ -146,7 +146,7 @@ function generateCheckHoverState(selectorsToOpen, selectorsToShove) {
console.log('down', $(this).data('id')); console.log('down', $(this).data('id'));
$(this).addClass('ui-dragging-pushdown'); $(this).addClass('ui-dragging-pushdown');
} }
}); });
} }
} }
...@@ -159,20 +159,20 @@ function removeHesitate(event, ui) { ...@@ -159,20 +159,20 @@ function removeHesitate(event, ui) {
} }
function expandSection(event) { function expandSection(event) {
$(event.delegateTarget).removeClass('collapsed', 400); $(event.delegateTarget).removeClass('collapsed', 400);
// don't descend to icon's on children (which aren't under first child) only to this element's icon // don't descend to icon's on children (which aren't under first child) only to this element's icon
$(event.delegateTarget).children().first().find('.expand-collapse-icon').removeClass('expand', 400).addClass('collapse'); $(event.delegateTarget).children().first().find('.expand-collapse-icon').removeClass('expand', 400).addClass('collapse');
} }
function onUnitReordered(event, ui) { function onUnitReordered(event, ui) {
// a unit's been dropped on this subsection, // a unit's been dropped on this subsection,
// figure out where it came from and where it slots in. // figure out where it came from and where it slots in.
_handleReorder(event, ui, 'subsection-id', 'li:.leaf'); _handleReorder(event, ui, 'subsection-id', 'li:.leaf');
} }
function onSubsectionReordered(event, ui) { function onSubsectionReordered(event, ui) {
// a subsection has been dropped on this section, // a subsection has been dropped on this section,
// figure out where it came from and where it slots in. // figure out where it came from and where it slots in.
_handleReorder(event, ui, 'section-id', 'li:.branch'); _handleReorder(event, ui, 'section-id', 'li:.branch');
} }
...@@ -182,7 +182,7 @@ function onSectionReordered(event, ui) { ...@@ -182,7 +182,7 @@ function onSectionReordered(event, ui) {
} }
function _handleReorder(event, ui, parentIdField, childrenSelector) { function _handleReorder(event, ui, parentIdField, childrenSelector) {
// figure out where it came from and where it slots in. // figure out where it came from and where it slots in.
var subsection_id = $(event.target).data(parentIdField); var subsection_id = $(event.target).data(parentIdField);
var _els = $(event.target).children(childrenSelector); var _els = $(event.target).children(childrenSelector);
var children = _els.map(function(idx, el) { return $(el).data('id'); }).get(); var children = _els.map(function(idx, el) { return $(el).data('id'); }).get();
......
CMS.ServerError = function(model, error) { CMS.ServerError = function(model, error) {
// this handler is for the client:server communication not the validation errors which handleValidationError catches // this handler is for the client:server communication not the validation errors which handleValidationError catches
window.alert("Server Error: " + error.responseText); window.alert("Server Error: " + error.responseText);
}; };
\ No newline at end of file
CMS.Views.ValidatingView = Backbone.View.extend({ CMS.Views.ValidatingView = Backbone.View.extend({
// Intended as an abstract class which catches validation errors on the model and // Intended as an abstract class which catches validation errors on the model and
// decorates the fields. Needs wiring per class, but this initialization shows how // decorates the fields. Needs wiring per class, but this initialization shows how
// either have your init call this one or copy the contents // either have your init call this one or copy the contents
initialize : function() { initialize : function() {
this.listenTo(this.model, 'error', CMS.ServerError); this.listenTo(this.model, 'error', CMS.ServerError);
...@@ -15,7 +15,7 @@ CMS.Views.ValidatingView = Backbone.View.extend({ ...@@ -15,7 +15,7 @@ CMS.Views.ValidatingView = Backbone.View.extend({
"change textarea" : "clearValidationErrors" "change textarea" : "clearValidationErrors"
}, },
fieldToSelectorMap : { fieldToSelectorMap : {
// Your subclass must populate this w/ all of the model keys and dom selectors // Your subclass must populate this w/ all of the model keys and dom selectors
// which may be the subjects of validation errors // which may be the subjects of validation errors
}, },
_cacheValidationErrors : [], _cacheValidationErrors : [],
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
@include box-sizing(border-box); @include box-sizing(border-box);
.copy { .copy {
@include font-size(13); @extend .t-copy-sub2;
} }
} }
...@@ -184,12 +184,12 @@ ...@@ -184,12 +184,12 @@
} }
.action-primary { .action-primary {
@include font-size(13); @extend .t-action3;
font-weight: 600; font-weight: 600;
} }
.action-secondary { .action-secondary {
@include font-size(13); @extend .t-action3;
} }
} }
} }
...@@ -367,12 +367,12 @@ ...@@ -367,12 +367,12 @@
} }
.copy { .copy {
@include font-size(13); @extend .t-copy-sub2;
width: flex-grid(10, 12); width: flex-grid(10, 12);
color: $gray-l2; color: $gray-l2;
.title { .title {
@include font-size(14); @extend .t-title-4;
margin-bottom: 0; margin-bottom: 0;
color: $white; color: $white;
} }
...@@ -409,13 +409,13 @@ ...@@ -409,13 +409,13 @@
.action-primary { .action-primary {
@include blue-button(); @include blue-button();
@include font-size(13); @extend .t-action3;
border-color: $blue-d2; border-color: $blue-d2;
font-weight: 600; font-weight: 600;
} }
.action-secondary { .action-secondary {
@include font-size(13); @extend .t-action3;
} }
} }
...@@ -504,7 +504,7 @@ ...@@ -504,7 +504,7 @@
// adopted alerts // adopted alerts
.alert { .alert {
@include font-size(14); @extend .t-copy-sub2;
@include box-sizing(border-box); @include box-sizing(border-box);
@include clearfix(); @include clearfix();
margin: 0 auto; margin: 0 auto;
...@@ -530,7 +530,7 @@ ...@@ -530,7 +530,7 @@
} }
.copy { .copy {
@include font-size(13); @extend .t-copy-sub2;
width: flex-grid(10, 12); width: flex-grid(10, 12);
color: $gray-l2; color: $gray-l2;
...@@ -568,12 +568,12 @@ ...@@ -568,12 +568,12 @@
} }
.action-primary { .action-primary {
@include font-size(13); @extend .t-action3;
font-weight: 600; font-weight: 600;
} }
.action-secondary { .action-secondary {
@include font-size(13); @extend .t-action3;
} }
} }
} }
...@@ -730,7 +730,7 @@ body.uxdesign.alerts { ...@@ -730,7 +730,7 @@ body.uxdesign.alerts {
border-radius: 3px; border-radius: 3px;
background: #fbf6e1; background: #fbf6e1;
// background: #edbd3c; // background: #edbd3c;
font-size: 14px; @extend .t-copy-sub1;
@include clearfix; @include clearfix;
.alert-message { .alert-message {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// ==================== // ====================
// headings/titles // headings/titles
.t-title-1, .t-title-2, .t-title-3, .t-title-4, .t-title-5, .t-title-5 { .t-title-1, .t-title-2, .t-title-3, .t-title-4, .t-title-5 {
color: $gray-d3; color: $gray-d3;
} }
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
} }
.t-title-4 { .t-title-4 {
@include font-size(14);
} }
.t-title-5 { .t-title-5 {
...@@ -82,4 +82,4 @@ ...@@ -82,4 +82,4 @@
// misc // misc
.t-icon { .t-icon {
line-height: 0; line-height: 0;
} }
\ No newline at end of file
...@@ -114,6 +114,7 @@ ...@@ -114,6 +114,7 @@
<li><a href="#alert-announcement2" class="show-alert">Show Announcement</a></li> <li><a href="#alert-announcement2" class="show-alert">Show Announcement</a></li>
<li><a href="#alert-announcement1" class="show-alert">Show Announcement with Actions</a></li> <li><a href="#alert-announcement1" class="show-alert">Show Announcement with Actions</a></li>
<li><a href="#alert-activation" class="show-alert">Show Activiation</a></li> <li><a href="#alert-activation" class="show-alert">Show Activiation</a></li>
<li><a href="#alert-threeActions" class="show-alert">Alert with three actions</a></li>
</ul> </ul>
</section> </section>
...@@ -129,6 +130,10 @@ ...@@ -129,6 +130,10 @@
<ul> <ul>
<li> <li>
<a href="#notification-changesMade" class="show-notification">Show Changes Made (used in Advanced Settings)</a>
<a href="#notification-changesMade" class="hide-notification">Hide Changes Made (used in Advanced Settings)</a>
</li>
<li>
<a href="#notification-change" class="show-notification">Show Change Warning</a> <a href="#notification-change" class="show-notification">Show Change Warning</a>
<a href="#notification-change" class="hide-notification">Hide Change Warning</a> <a href="#notification-change" class="hide-notification">Hide Change Warning</a>
</li> </li>
...@@ -151,6 +156,10 @@ ...@@ -151,6 +156,10 @@
<a href="#notification-help" class="show-notification">Show Help</a> <a href="#notification-help" class="show-notification">Show Help</a>
<a href="#notification-help" class="hide-notification">Hide Help</a> <a href="#notification-help" class="hide-notification">Hide Help</a>
</li> </li>
<li>
<a href="#notification-threeActions" class="show-notification">Show Notification with three actions</a>
<a href="#notification-threeActions" class="hide-notification">Hide Notification with three actions</a>
</li>
</ul> </ul>
</section> </section>
...@@ -182,6 +191,33 @@ ...@@ -182,6 +191,33 @@
</%block> </%block>
<%block name="view_alerts"> <%block name="view_alerts">
<!-- alert: 3 actions -->
<div class="wrapper wrapper-alert wrapper-alert-warning" id="alert-threeActions">
<div class="alert warning has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-warning">&#x26A0;</i>
<div class="copy">
<h2 class="title title-3">You are editing a draft</h2>
<p class="message">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Alert Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="action action-save action-primary">Save Draft</a>
</li>
<li class="nav-item">
<a href="#" class="action action-cancel action-secondary">Disgard Draft</a>
</li>
<li class="nav-item">
<a href="#" class="action action-secondary">Do Something Elsee</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- alert: you're editing a draft --> <!-- alert: you're editing a draft -->
<div class="wrapper wrapper-alert wrapper-alert-warning" id="alert-draft"> <div class="wrapper wrapper-alert wrapper-alert-warning" id="alert-draft">
<div class="alert warning has-actions"> <div class="alert warning has-actions">
...@@ -196,10 +232,10 @@ ...@@ -196,10 +232,10 @@
<h3 class="sr">Alert Actions</h3> <h3 class="sr">Alert Actions</h3>
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button save-button action-primary">Save Draft</a> <a href="#" class="action action-save action-primary">Save Draft</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button cancel-button action-secondary">Disgard Draft</a> <a href="#" class="action action-cancel action-secondary">Disgard Draft</a>
</li> </li>
</ul> </ul>
</nav> </nav>
...@@ -220,10 +256,10 @@ ...@@ -220,10 +256,10 @@
<h3 class="sr">Alert Actions</h3> <h3 class="sr">Alert Actions</h3>
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button save-button action-primary">Go to Newer Version</a> <a href="#" class="action action-save action-primary">Go to Newer Version</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button cancel-button action-secondary">Continue Editing</a> <a href="#" class="action action-cancel action-secondary">Continue Editing</a>
</li> </li>
</ul> </ul>
</nav> </nav>
...@@ -297,7 +333,7 @@ ...@@ -297,7 +333,7 @@
<h3 class="sr">Alert Actions</h3> <h3 class="sr">Alert Actions</h3>
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button cancel-button action-primary">Cancel Your Submission</a> <a href="#" class="action action-cancel action-primary">Cancel Your Submission</a>
</li> </li>
</ul> </ul>
</nav> </nav>
...@@ -367,13 +403,13 @@ ...@@ -367,13 +403,13 @@
<%block name="view_notifications"> <%block name="view_notifications">
<!-- notification: change has been made and a save is needed --> <!-- notification: change has been made and a save is needed -->
<div class="wrapper wrapper-notification wrapper-notification-change" id="notification-change" role="status"> <div class="wrapper wrapper-notification wrapper-notification-change" aria-hidden="true" role="dialog" aria-labelledby="notification-change-title" aria-describedby="notification-change-description" id="notification-change">
<div class="notification change has-actions"> <div class="notification change has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-change">&#x1F4DD;</i> <i class="ss-icon ss-symbolicons-block icon icon-change">&#x1F4DD;</i>
<div class="copy"> <div class="copy">
<h2 class="title title-3">You've Made Some Changes</h2> <h2 class="title title-3" id="notification-change-title">You've Made Some Changes</h2>
<p class="message">Your changes will not take effect until you <strong>save your progress</strong>.</p> <p class="message" id="notification-change-description">Your changes will not take effect until you <strong>save your progress</strong>.</p>
</div> </div>
<nav class="nav-actions"> <nav class="nav-actions">
...@@ -390,6 +426,57 @@ ...@@ -390,6 +426,57 @@
</div> </div>
</div> </div>
<!-- notification: three actions example -->
<div class="wrapper wrapper-notification wrapper-notification-change" aria-hidden="true" role="dialog" aria-labelledby="notification-threeActions-title" aria-describedby="notification-threeActions-description" id="notification-threeActions">
<div class="notification change has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-change">&#x1F4DD;</i>
<div class="copy">
<h2 class="title title-3" id="notification-threeActions-title">You've Made Some Changes</h2>
<p class="message" id="notification-threeActions-description">Your changes will not take effect until you <strong>save your progress</strong>.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Notification Actions</h3>
<ul>
<li class="nav-item">
<a href="#" class="action-primary">Save Changes</a>
</li>
<li class="nav-item">
<a href="#" class="action-secondary">Don't Save</a>
</li>
<li class="nav-item">
<a href="#" class="action-secondary">Do something else</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- notification: change has been made and a save is needed -->
<div class="wrapper wrapper-notification wrapper-notification-warning" aria-hidden="true" role="dialog" aria-labelledby="notification-changesMade-title" aria-describedby="notification-changesMade-description" id="notification-changesMade">
<div class="notification warning has-actions">
<i class="ss-icon ss-symbolicons-block icon icon-warning">&#x26A0;</i>
<div class="copy">
<h2 class="title title-3" id="notification-changesMade-title">You've Made Some Changes</h2>
<p id="notification-changesMade-description">Your changes will not take effect until you <strong>save your progress</strong>. Take care with key and value formatting, as validation is <strong>not implemented</strong>.</p>
</div>
<nav class="nav-actions">
<h3 class="sr">Notification Actions</h3>
<ul>
<li class="nav-item">
<a href="" class="action action-save action-primary">Save Changes</a>
</li>
<li class="nav-item">
<a href="" class="action action-cancel action-secondary">Cancel</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- notification: newer version exists --> <!-- notification: newer version exists -->
<div class="wrapper wrapper-notification wrapper-notification-warning" id="notification-version" aria-hidden="true" role="dialog" aria-labelledby="notification-warning-title" aria-describedby="notification-warning-description"> <div class="wrapper wrapper-notification wrapper-notification-warning" id="notification-version" aria-hidden="true" role="dialog" aria-labelledby="notification-warning-title" aria-describedby="notification-warning-description">
<div class="notification warning has-actions"> <div class="notification warning has-actions">
...@@ -404,10 +491,10 @@ ...@@ -404,10 +491,10 @@
<h3 class="sr">Notification Actions</h3> <h3 class="sr">Notification Actions</h3>
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button save-button action-primary">Go to Newer Version</a> <a href="#" class="action action-save action-primary">Go to Newer Version</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="button cancel-button action-secondary">Continue Editing</a> <a href="#" class="action action-cancel action-secondary">Continue Editing</a>
</li> </li>
</ul> </ul>
</nav> </nav>
...@@ -428,10 +515,10 @@ ...@@ -428,10 +515,10 @@
<h3 class="sr">Notification Actions</h3> <h3 class="sr">Notification Actions</h3>
<ul> <ul>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="action-primary">Yes, I want to Edit X</a> <a href="#" class="action action-proceed action-primary">Yes, I want to Edit X</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="action-secondary">No, I do not</a> <a href="#" class="action action-cancel action-secondary">No, I do not</a>
</li> </li>
</ul> </ul>
</nav> </nav>
......
...@@ -137,4 +137,4 @@ def clear_courses(): ...@@ -137,4 +137,4 @@ def clear_courses():
# $ mongo test_xmodule --eval "db.dropDatabase()" # $ mongo test_xmodule --eval "db.dropDatabase()"
_MODULESTORES = {} _MODULESTORES = {}
modulestore().collection.drop() modulestore().collection.drop()
update_templates() update_templates(modulestore('direct'))
...@@ -49,7 +49,10 @@ class _ZendeskApi(object): ...@@ -49,7 +49,10 @@ class _ZendeskApi(object):
settings.ZENDESK_USER, settings.ZENDESK_USER,
settings.ZENDESK_API_KEY, settings.ZENDESK_API_KEY,
use_api_token=True, use_api_token=True,
api_version=2 api_version=2,
# As of 2012-05-08, Zendesk is using a CA that is not
# installed on our servers
client_args={"disable_ssl_certificate_validation": True}
) )
def create_ticket(self, ticket): def create_ticket(self, ticket):
......
...@@ -1783,7 +1783,7 @@ class FormulaResponse(LoncapaResponse): ...@@ -1783,7 +1783,7 @@ class FormulaResponse(LoncapaResponse):
response_tag = 'formularesponse' response_tag = 'formularesponse'
hint_tag = 'formulahint' hint_tag = 'formulahint'
allowed_inputfields = ['textline'] allowed_inputfields = ['textline']
required_attributes = ['answer'] required_attributes = ['answer', 'samples']
max_inputfields = 1 max_inputfields = 1
def setup_response(self): def setup_response(self):
......
...@@ -476,7 +476,15 @@ class MongoModuleStore(ModuleStoreBase): ...@@ -476,7 +476,15 @@ class MongoModuleStore(ModuleStoreBase):
''' '''
# TODO (vshnayder): Why do I have to specify i4x here? # TODO (vshnayder): Why do I have to specify i4x here?
course_filter = Location("i4x", category="course") course_filter = Location("i4x", category="course")
return self.get_items(course_filter) return [
course
for course
in self.get_items(course_filter)
if not (
course.location.org == 'edx' and
course.location.course == 'templates'
)
]
def _find_one(self, location): def _find_one(self, location):
'''Look for a given location in the collection. If revision is not '''Look for a given location in the collection. If revision is not
......
...@@ -42,7 +42,7 @@ class ModuleStoreTestCase(TestCase): ...@@ -42,7 +42,7 @@ class ModuleStoreTestCase(TestCase):
num_templates = modulestore.collection.find(query).count() num_templates = modulestore.collection.find(query).count()
if num_templates < 1: if num_templates < 1:
update_templates() update_templates(modulestore)
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
......
...@@ -7,6 +7,7 @@ from pprint import pprint ...@@ -7,6 +7,7 @@ from pprint import pprint
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.mongo import MongoModuleStore from xmodule.modulestore.mongo import MongoModuleStore
from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.templates import update_templates
from .test_modulestore import check_path_to_location from .test_modulestore import check_path_to_location
from . import DATA_DIR from . import DATA_DIR
...@@ -45,6 +46,7 @@ class TestMongoModuleStore(object): ...@@ -45,6 +46,7 @@ class TestMongoModuleStore(object):
# Explicitly list the courses to load (don't want the big one) # Explicitly list the courses to load (don't want the big one)
courses = ['toy', 'simple'] courses = ['toy', 'simple']
import_from_xml(store, DATA_DIR, courses) import_from_xml(store, DATA_DIR, courses)
update_templates(store)
return store return store
@staticmethod @staticmethod
...@@ -103,3 +105,11 @@ class TestMongoModuleStore(object): ...@@ -103,3 +105,11 @@ class TestMongoModuleStore(object):
def test_path_to_location(self): def test_path_to_location(self):
'''Make sure that path_to_location works''' '''Make sure that path_to_location works'''
check_path_to_location(self.store) check_path_to_location(self.store)
def test_get_courses_has_no_templates(self):
courses = self.store.get_courses()
for course in courses:
assert_false(
course.location.org == 'edx' and course.location.course == 'templates',
'{0} is a template course'.format(course)
)
...@@ -19,7 +19,6 @@ from collections import defaultdict ...@@ -19,7 +19,6 @@ from collections import defaultdict
from .x_module import XModuleDescriptor from .x_module import XModuleDescriptor
from .mako_module import MakoDescriptorSystem from .mako_module import MakoDescriptorSystem
from .modulestore import Location from .modulestore import Location
from .modulestore.django import modulestore
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -50,7 +49,7 @@ class TemplateTestSystem(MakoDescriptorSystem): ...@@ -50,7 +49,7 @@ class TemplateTestSystem(MakoDescriptorSystem):
) )
def update_templates(): def update_templates(modulestore):
""" """
Updates the set of templates in the modulestore with all templates currently Updates the set of templates in the modulestore with all templates currently
available from the installed plugins available from the installed plugins
...@@ -58,7 +57,7 @@ def update_templates(): ...@@ -58,7 +57,7 @@ def update_templates():
# cdodge: build up a list of all existing templates. This will be used to determine which # cdodge: build up a list of all existing templates. This will be used to determine which
# templates have been removed from disk - and thus we need to remove from the DB # templates have been removed from disk - and thus we need to remove from the DB
templates_to_delete = modulestore('direct').get_items(['i4x', 'edx', 'templates', None, None, None]) templates_to_delete = modulestore.get_items(['i4x', 'edx', 'templates', None, None, None])
for category, templates in all_templates().items(): for category, templates in all_templates().items():
for template in templates: for template in templates:
...@@ -86,9 +85,9 @@ def update_templates(): ...@@ -86,9 +85,9 @@ def update_templates():
), exc_info=True) ), exc_info=True)
continue continue
modulestore('direct').update_item(template_location, template.data) modulestore.update_item(template_location, template.data)
modulestore('direct').update_children(template_location, template.children) modulestore.update_children(template_location, template.children)
modulestore('direct').update_metadata(template_location, template.metadata) modulestore.update_metadata(template_location, template.metadata)
# remove template from list of templates to delete # remove template from list of templates to delete
templates_to_delete = [t for t in templates_to_delete if t.location != template_location] templates_to_delete = [t for t in templates_to_delete if t.location != template_location]
...@@ -97,4 +96,4 @@ def update_templates(): ...@@ -97,4 +96,4 @@ def update_templates():
if len(templates_to_delete) > 0: if len(templates_to_delete) > 0:
logging.debug('deleting dangling templates = {0}'.format(templates_to_delete)) logging.debug('deleting dangling templates = {0}'.format(templates_to_delete))
for template in templates_to_delete: for template in templates_to_delete:
modulestore('direct').delete_item(template.location) modulestore.delete_item(template.location)
// Backbone.js 0.9.10 (function(){var t=this;var e=t.Backbone;var i=[];var r=i.push;var s=i.slice;var n=i.splice;var a;if(typeof exports!=="undefined"){a=exports}else{a=t.Backbone={}}a.VERSION="1.0.0";var h=t._;if(!h&&typeof require!=="undefined")h=require("underscore");a.$=t.jQuery||t.Zepto||t.ender||t.$;a.noConflict=function(){t.Backbone=e;return this};a.emulateHTTP=false;a.emulateJSON=false;var o=a.Events={on:function(t,e,i){if(!l(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,i){if(!l(this,"once",t,[e,i])||!e)return this;var r=this;var s=h.once(function(){r.off(t,s);e.apply(this,arguments)});s._callback=e;return this.on(t,s,i)},off:function(t,e,i){var r,s,n,a,o,u,c,f;if(!this._events||!l(this,"off",t,[e,i]))return this;if(!t&&!e&&!i){this._events={};return this}a=t?[t]:h.keys(this._events);for(o=0,u=a.length;o<u;o++){t=a[o];if(n=this._events[t]){this._events[t]=r=[];if(e||i){for(c=0,f=n.length;c<f;c++){s=n[c];if(e&&e!==s.callback&&e!==s.callback._callback||i&&i!==s.context){r.push(s)}}}if(!r.length)delete this._events[t]}}return this},trigger:function(t){if(!this._events)return this;var e=s.call(arguments,1);if(!l(this,"trigger",t,e))return this;var i=this._events[t];var r=this._events.all;if(i)c(i,e);if(r)c(r,arguments);return this},stopListening:function(t,e,i){var r=this._listeners;if(!r)return this;var s=!e&&!i;if(typeof e==="object")i=this;if(t)(r={})[t._listenerId]=t;for(var n in r){r[n].off(e,i,this);if(s)delete this._listeners[n]}return this}};var u=/\s+/;var l=function(t,e,i,r){if(!i)return true;if(typeof i==="object"){for(var s in i){t[e].apply(t,[s,i[s]].concat(r))}return false}if(u.test(i)){var n=i.split(u);for(var a=0,h=n.length;a<h;a++){t[e].apply(t,[n[a]].concat(r))}return false}return true};var c=function(t,e){var i,r=-1,s=t.length,n=e[0],a=e[1],h=e[2];switch(e.length){case 0:while(++r<s)(i=t[r]).callback.call(i.ctx);return;case 1:while(++r<s)(i=t[r]).callback.call(i.ctx,n);return;case 2:while(++r<s)(i=t[r]).callback.call(i.ctx,n,a);return;case 3:while(++r<s)(i=t[r]).callback.call(i.ctx,n,a,h);return;default:while(++r<s)(i=t[r]).callback.apply(i.ctx,e)}};var f={listenTo:"on",listenToOnce:"once"};h.each(f,function(t,e){o[e]=function(e,i,r){var s=this._listeners||(this._listeners={});var n=e._listenerId||(e._listenerId=h.uniqueId("l"));s[n]=e;if(typeof i==="object")r=this;e[t](i,r,this);return this}});o.bind=o.on;o.unbind=o.off;h.extend(a,o);var d=a.Model=function(t,e){var i;var r=t||{};e||(e={});this.cid=h.uniqueId("c");this.attributes={};h.extend(this,h.pick(e,p));if(e.parse)r=this.parse(r,e)||{};if(i=h.result(this,"defaults")){r=h.defaults({},r,i)}this.set(r,e);this.changed={};this.initialize.apply(this,arguments)};var p=["url","urlRoot","collection"];h.extend(d.prototype,o,{changed:null,validationError:null,idAttribute:"id",initialize:function(){},toJSON:function(t){return h.clone(this.attributes)},sync:function(){return a.sync.apply(this,arguments)},get:function(t){return this.attributes[t]},escape:function(t){return h.escape(this.get(t))},has:function(t){return this.get(t)!=null},set:function(t,e,i){var r,s,n,a,o,u,l,c;if(t==null)return this;if(typeof t==="object"){s=t;i=e}else{(s={})[t]=e}i||(i={});if(!this._validate(s,i))return false;n=i.unset;o=i.silent;a=[];u=this._changing;this._changing=true;if(!u){this._previousAttributes=h.clone(this.attributes);this.changed={}}c=this.attributes,l=this._previousAttributes;if(this.idAttribute in s)this.id=s[this.idAttribute];for(r in s){e=s[r];if(!h.isEqual(c[r],e))a.push(r);if(!h.isEqual(l[r],e)){this.changed[r]=e}else{delete this.changed[r]}n?delete c[r]:c[r]=e}if(!o){if(a.length)this._pending=true;for(var f=0,d=a.length;f<d;f++){this.trigger("change:"+a[f],this,c[a[f]],i)}}if(u)return this;if(!o){while(this._pending){this._pending=false;this.trigger("change",this,i)}}this._pending=false;this._changing=false;return this},unset:function(t,e){return this.set(t,void 0,h.extend({},e,{unset:true}))},clear:function(t){var e={};for(var i in this.attributes)e[i]=void 0;return this.set(e,h.extend({},t,{unset:true}))},hasChanged:function(t){if(t==null)return!h.isEmpty(this.changed);return h.has(this.changed,t)},changedAttributes:function(t){if(!t)return this.hasChanged()?h.clone(this.changed):false;var e,i=false;var r=this._changing?this._previousAttributes:this.attributes;for(var s in t){if(h.isEqual(r[s],e=t[s]))continue;(i||(i={}))[s]=e}return i},previous:function(t){if(t==null||!this._previousAttributes)return null;return this._previousAttributes[t]},previousAttributes:function(){return h.clone(this._previousAttributes)},fetch:function(t){t=t?h.clone(t):{};if(t.parse===void 0)t.parse=true;var e=this;var i=t.success;t.success=function(r){if(!e.set(e.parse(r,t),t))return false;if(i)i(e,r,t);e.trigger("sync",e,r,t)};R(this,t);return this.sync("read",this,t)},save:function(t,e,i){var r,s,n,a=this.attributes;if(t==null||typeof t==="object"){r=t;i=e}else{(r={})[t]=e}if(r&&(!i||!i.wait)&&!this.set(r,i))return false;i=h.extend({validate:true},i);if(!this._validate(r,i))return false;if(r&&i.wait){this.attributes=h.extend({},a,r)}if(i.parse===void 0)i.parse=true;var o=this;var u=i.success;i.success=function(t){o.attributes=a;var e=o.parse(t,i);if(i.wait)e=h.extend(r||{},e);if(h.isObject(e)&&!o.set(e,i)){return false}if(u)u(o,t,i);o.trigger("sync",o,t,i)};R(this,i);s=this.isNew()?"create":i.patch?"patch":"update";if(s==="patch")i.attrs=r;n=this.sync(s,this,i);if(r&&i.wait)this.attributes=a;return n},destroy:function(t){t=t?h.clone(t):{};var e=this;var i=t.success;var r=function(){e.trigger("destroy",e,e.collection,t)};t.success=function(s){if(t.wait||e.isNew())r();if(i)i(e,s,t);if(!e.isNew())e.trigger("sync",e,s,t)};if(this.isNew()){t.success();return false}R(this,t);var s=this.sync("delete",this,t);if(!t.wait)r();return s},url:function(){var t=h.result(this,"urlRoot")||h.result(this.collection,"url")||U();if(this.isNew())return t;return t+(t.charAt(t.length-1)==="/"?"":"/")+encodeURIComponent(this.id)},parse:function(t,e){return t},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return this.id==null},isValid:function(t){return this._validate({},h.extend(t||{},{validate:true}))},_validate:function(t,e){if(!e.validate||!this.validate)return true;t=h.extend({},this.attributes,t);var i=this.validationError=this.validate(t,e)||null;if(!i)return true;this.trigger("invalid",this,i,h.extend(e||{},{validationError:i}));return false}});var v=["keys","values","pairs","invert","pick","omit"];h.each(v,function(t){d.prototype[t]=function(){var e=s.call(arguments);e.unshift(this.attributes);return h[t].apply(h,e)}});var g=a.Collection=function(t,e){e||(e={});if(e.url)this.url=e.url;if(e.model)this.model=e.model;if(e.comparator!==void 0)this.comparator=e.comparator;this._reset();this.initialize.apply(this,arguments);if(t)this.reset(t,h.extend({silent:true},e))};var m={add:true,remove:true,merge:true};var y={add:true,merge:false,remove:false};h.extend(g.prototype,o,{model:d,initialize:function(){},toJSON:function(t){return this.map(function(e){return e.toJSON(t)})},sync:function(){return a.sync.apply(this,arguments)},add:function(t,e){return this.set(t,h.defaults(e||{},y))},remove:function(t,e){t=h.isArray(t)?t.slice():[t];e||(e={});var i,r,s,n;for(i=0,r=t.length;i<r;i++){n=this.get(t[i]);if(!n)continue;delete this._byId[n.id];delete this._byId[n.cid];s=this.indexOf(n);this.models.splice(s,1);this.length--;if(!e.silent){e.index=s;n.trigger("remove",n,this,e)}this._removeReference(n)}return this},set:function(t,e){e=h.defaults(e||{},m);if(e.parse)t=this.parse(t,e);if(!h.isArray(t))t=t?[t]:[];var i,s,a,o,u,l;var c=e.at;var f=this.comparator&&c==null&&e.sort!==false;var d=h.isString(this.comparator)?this.comparator:null;var p=[],v=[],g={};for(i=0,s=t.length;i<s;i++){if(!(a=this._prepareModel(t[i],e)))continue;if(u=this.get(a)){if(e.remove)g[u.cid]=true;if(e.merge){u.set(a.attributes,e);if(f&&!l&&u.hasChanged(d))l=true}}else if(e.add){p.push(a);a.on("all",this._onModelEvent,this);this._byId[a.cid]=a;if(a.id!=null)this._byId[a.id]=a}}if(e.remove){for(i=0,s=this.length;i<s;++i){if(!g[(a=this.models[i]).cid])v.push(a)}if(v.length)this.remove(v,e)}if(p.length){if(f)l=true;this.length+=p.length;if(c!=null){n.apply(this.models,[c,0].concat(p))}else{r.apply(this.models,p)}}if(l)this.sort({silent:true});if(e.silent)return this;for(i=0,s=p.length;i<s;i++){(a=p[i]).trigger("add",a,this,e)}if(l)this.trigger("sort",this,e);return this},reset:function(t,e){e||(e={});for(var i=0,r=this.models.length;i<r;i++){this._removeReference(this.models[i])}e.previousModels=this.models;this._reset();this.add(t,h.extend({silent:true},e));if(!e.silent)this.trigger("reset",this,e);return this},push:function(t,e){t=this._prepareModel(t,e);this.add(t,h.extend({at:this.length},e));return t},pop:function(t){var e=this.at(this.length-1);this.remove(e,t);return e},unshift:function(t,e){t=this._prepareModel(t,e);this.add(t,h.extend({at:0},e));return t},shift:function(t){var e=this.at(0);this.remove(e,t);return e},slice:function(t,e){return this.models.slice(t,e)},get:function(t){if(t==null)return void 0;return this._byId[t.id!=null?t.id:t.cid||t]},at:function(t){return this.models[t]},where:function(t,e){if(h.isEmpty(t))return e?void 0:[];return this[e?"find":"filter"](function(e){for(var i in t){if(t[i]!==e.get(i))return false}return true})},findWhere:function(t){return this.where(t,true)},sort:function(t){if(!this.comparator)throw new Error("Cannot sort a set without a comparator");t||(t={});if(h.isString(this.comparator)||this.comparator.length===1){this.models=this.sortBy(this.comparator,this)}else{this.models.sort(h.bind(this.comparator,this))}if(!t.silent)this.trigger("sort",this,t);return this},sortedIndex:function(t,e,i){e||(e=this.comparator);var r=h.isFunction(e)?e:function(t){return t.get(e)};return h.sortedIndex(this.models,t,r,i)},pluck:function(t){return h.invoke(this.models,"get",t)},fetch:function(t){t=t?h.clone(t):{};if(t.parse===void 0)t.parse=true;var e=t.success;var i=this;t.success=function(r){var s=t.reset?"reset":"set";i[s](r,t);if(e)e(i,r,t);i.trigger("sync",i,r,t)};R(this,t);return this.sync("read",this,t)},create:function(t,e){e=e?h.clone(e):{};if(!(t=this._prepareModel(t,e)))return false;if(!e.wait)this.add(t,e);var i=this;var r=e.success;e.success=function(s){if(e.wait)i.add(t,e);if(r)r(t,s,e)};t.save(null,e);return t},parse:function(t,e){return t},clone:function(){return new this.constructor(this.models)},_reset:function(){this.length=0;this.models=[];this._byId={}},_prepareModel:function(t,e){if(t instanceof d){if(!t.collection)t.collection=this;return t}e||(e={});e.collection=this;var i=new this.model(t,e);if(!i._validate(t,e)){this.trigger("invalid",this,t,e);return false}return i},_removeReference:function(t){if(this===t.collection)delete t.collection;t.off("all",this._onModelEvent,this)},_onModelEvent:function(t,e,i,r){if((t==="add"||t==="remove")&&i!==this)return;if(t==="destroy")this.remove(e,r);if(e&&t==="change:"+e.idAttribute){delete this._byId[e.previous(e.idAttribute)];if(e.id!=null)this._byId[e.id]=e}this.trigger.apply(this,arguments)}});var _=["forEach","each","map","collect","reduce","foldl","inject","reduceRight","foldr","find","detect","filter","select","reject","every","all","some","any","include","contains","invoke","max","min","toArray","size","first","head","take","initial","rest","tail","drop","last","without","indexOf","shuffle","lastIndexOf","isEmpty","chain"];h.each(_,function(t){g.prototype[t]=function(){var e=s.call(arguments);e.unshift(this.models);return h[t].apply(h,e)}});var w=["groupBy","countBy","sortBy"];h.each(w,function(t){g.prototype[t]=function(e,i){var r=h.isFunction(e)?e:function(t){return t.get(e)};return h[t](this.models,r,i)}});var b=a.View=function(t){this.cid=h.uniqueId("view");this._configure(t||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()};var x=/^(\S+)\s*(.*)$/;var E=["model","collection","el","id","attributes","className","tagName","events"];h.extend(b.prototype,o,{tagName:"div",$:function(t){return this.$el.find(t)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();this.stopListening();return this},setElement:function(t,e){if(this.$el)this.undelegateEvents();this.$el=t instanceof a.$?t:a.$(t);this.el=this.$el[0];if(e!==false)this.delegateEvents();return this},delegateEvents:function(t){if(!(t||(t=h.result(this,"events"))))return this;this.undelegateEvents();for(var e in t){var i=t[e];if(!h.isFunction(i))i=this[t[e]];if(!i)continue;var r=e.match(x);var s=r[1],n=r[2];i=h.bind(i,this);s+=".delegateEvents"+this.cid;if(n===""){this.$el.on(s,i)}else{this.$el.on(s,n,i)}}return this},undelegateEvents:function(){this.$el.off(".delegateEvents"+this.cid);return this},_configure:function(t){if(this.options)t=h.extend({},h.result(this,"options"),t);h.extend(this,h.pick(t,E));this.options=t},_ensureElement:function(){if(!this.el){var t=h.extend({},h.result(this,"attributes"));if(this.id)t.id=h.result(this,"id");if(this.className)t["class"]=h.result(this,"className");var e=a.$("<"+h.result(this,"tagName")+">").attr(t);this.setElement(e,false)}else{this.setElement(h.result(this,"el"),false)}}});a.sync=function(t,e,i){var r=k[t];h.defaults(i||(i={}),{emulateHTTP:a.emulateHTTP,emulateJSON:a.emulateJSON});var s={type:r,dataType:"json"};if(!i.url){s.url=h.result(e,"url")||U()}if(i.data==null&&e&&(t==="create"||t==="update"||t==="patch")){s.contentType="application/json";s.data=JSON.stringify(i.attrs||e.toJSON(i))}if(i.emulateJSON){s.contentType="application/x-www-form-urlencoded";s.data=s.data?{model:s.data}:{}}if(i.emulateHTTP&&(r==="PUT"||r==="DELETE"||r==="PATCH")){s.type="POST";if(i.emulateJSON)s.data._method=r;var n=i.beforeSend;i.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",r);if(n)return n.apply(this,arguments)}}if(s.type!=="GET"&&!i.emulateJSON){s.processData=false}if(s.type==="PATCH"&&window.ActiveXObject&&!(window.external&&window.external.msActiveXFilteringEnabled)){s.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var o=i.xhr=a.ajax(h.extend(s,i));e.trigger("request",e,o,i);return o};var k={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};a.ajax=function(){return a.$.ajax.apply(a.$,arguments)};var S=a.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var $=/\((.*?)\)/g;var T=/(\(\?)?:\w+/g;var H=/\*\w+/g;var A=/[\-{}\[\]+?.,\\\^$|#\s]/g;h.extend(S.prototype,o,{initialize:function(){},route:function(t,e,i){if(!h.isRegExp(t))t=this._routeToRegExp(t);if(h.isFunction(e)){i=e;e=""}if(!i)i=this[e];var r=this;a.history.route(t,function(s){var n=r._extractParameters(t,s);i&&i.apply(r,n);r.trigger.apply(r,["route:"+e].concat(n));r.trigger("route",e,n);a.history.trigger("route",r,e,n)});return this},navigate:function(t,e){a.history.navigate(t,e);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=h.result(this,"routes");var t,e=h.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(A,"\\$&").replace($,"(?:$1)?").replace(T,function(t,e){return e?t:"([^/]+)"}).replace(H,"(.*?)");return new RegExp("^"+t+"$")},_extractParameters:function(t,e){var i=t.exec(e).slice(1);return h.map(i,function(t){return t?decodeURIComponent(t):null})}});var I=a.History=function(){this.handlers=[];h.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var N=/^[#\/]|\s+$/g;var P=/^\/+|\/+$/g;var O=/msie [\w.]+/;var C=/\/$/;I.started=false;h.extend(I.prototype,o,{interval:50,getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=this.location.pathname;var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.substr(i.length)}else{t=this.getHash()}}return t.replace(N,"")},start:function(t){if(I.started)throw new Error("Backbone.history has already been started");I.started=true;this.options=h.extend({},{root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var e=this.getFragment();var i=document.documentMode;var r=O.exec(navigator.userAgent.toLowerCase())&&(!i||i<=7);this.root=("/"+this.root+"/").replace(P,"/");if(r&&this._wantsHashChange){this.iframe=a.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow;this.navigate(e)}if(this._hasPushState){a.$(window).on("popstate",this.checkUrl)}else if(this._wantsHashChange&&"onhashchange"in window&&!r){a.$(window).on("hashchange",this.checkUrl)}else if(this._wantsHashChange){this._checkUrlInterval=setInterval(this.checkUrl,this.interval)}this.fragment=e;var s=this.location;var n=s.pathname.replace(/[^\/]$/,"$&/")===this.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!n){this.fragment=this.getFragment(null,true);this.location.replace(this.root+this.location.search+"#"+this.fragment);return true}else if(this._wantsPushState&&this._hasPushState&&n&&s.hash){this.fragment=this.getHash().replace(N,"");this.history.replaceState({},document.title,this.root+this.fragment+s.search)}if(!this.options.silent)return this.loadUrl()},stop:function(){a.$(window).off("popstate",this.checkUrl).off("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);I.started=false},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();if(e===this.fragment&&this.iframe){e=this.getFragment(this.getHash(this.iframe))}if(e===this.fragment)return false;if(this.iframe)this.navigate(e);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(t){var e=this.fragment=this.getFragment(t);var i=h.any(this.handlers,function(t){if(t.route.test(e)){t.callback(e);return true}});return i},navigate:function(t,e){if(!I.started)return false;if(!e||e===true)e={trigger:e};t=this.getFragment(t||"");if(this.fragment===t)return;this.fragment=t;var i=this.root+t;if(this._hasPushState){this.history[e.replace?"replaceState":"pushState"]({},document.title,i)}else if(this._wantsHashChange){this._updateHash(this.location,t,e.replace);if(this.iframe&&t!==this.getFragment(this.getHash(this.iframe))){if(!e.replace)this.iframe.document.open().close();this._updateHash(this.iframe.location,t,e.replace)}}else{return this.location.assign(i)}if(e.trigger)this.loadUrl(t)},_updateHash:function(t,e,i){if(i){var r=t.href.replace(/(javascript:|#).*$/,"");t.replace(r+"#"+e)}else{t.hash="#"+e}}});a.history=new I;var j=function(t,e){var i=this;var r;if(t&&h.has(t,"constructor")){r=t.constructor}else{r=function(){return i.apply(this,arguments)}}h.extend(r,i,e);var s=function(){this.constructor=r};s.prototype=i.prototype;r.prototype=new s;if(t)h.extend(r.prototype,t);r.__super__=i.prototype;return r};d.extend=g.extend=S.extend=b.extend=I.extend=j;var U=function(){throw new Error('A "url" property or function must be specified')};var R=function(t,e){var i=e.error;e.error=function(r){if(i)i(t,r,e);t.trigger("error",t,r,e)}}}).call(this);
/*
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. //@ sourceMappingURL=backbone-min.map
// Backbone may be freely distributed under the MIT license. */
// For all details and documentation:
// http://backbonejs.org
(function(){var n=this,B=n.Backbone,h=[],C=h.push,u=h.slice,D=h.splice,g;g="undefined"!==typeof exports?exports:n.Backbone={};g.VERSION="0.9.10";var f=n._;!f&&"undefined"!==typeof require&&(f=require("underscore"));g.$=n.jQuery||n.Zepto||n.ender;g.noConflict=function(){n.Backbone=B;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var v=/\s+/,q=function(a,b,c,d){if(!c)return!0;if("object"===typeof c)for(var e in c)a[b].apply(a,[e,c[e]].concat(d));else if(v.test(c)){c=c.split(v);e=0;for(var f=c.length;e<
f;e++)a[b].apply(a,[c[e]].concat(d))}else return!0},w=function(a,b){var c,d=-1,e=a.length;switch(b.length){case 0:for(;++d<e;)(c=a[d]).callback.call(c.ctx);break;case 1:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0]);break;case 2:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0],b[1]);break;case 3:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0],b[1],b[2]);break;default:for(;++d<e;)(c=a[d]).callback.apply(c.ctx,b)}},h=g.Events={on:function(a,b,c){if(!q(this,"on",a,[b,c])||!b)return this;this._events||(this._events=
{});(this._events[a]||(this._events[a]=[])).push({callback:b,context:c,ctx:c||this});return this},once:function(a,b,c){if(!q(this,"once",a,[b,c])||!b)return this;var d=this,e=f.once(function(){d.off(a,e);b.apply(this,arguments)});e._callback=b;this.on(a,e,c);return this},off:function(a,b,c){var d,e,t,g,j,l,k,h;if(!this._events||!q(this,"off",a,[b,c]))return this;if(!a&&!b&&!c)return this._events={},this;g=a?[a]:f.keys(this._events);j=0;for(l=g.length;j<l;j++)if(a=g[j],d=this._events[a]){t=[];if(b||
c){k=0;for(h=d.length;k<h;k++)e=d[k],(b&&b!==e.callback&&b!==e.callback._callback||c&&c!==e.context)&&t.push(e)}this._events[a]=t}return this},trigger:function(a){if(!this._events)return this;var b=u.call(arguments,1);if(!q(this,"trigger",a,b))return this;var c=this._events[a],d=this._events.all;c&&w(c,b);d&&w(d,arguments);return this},listenTo:function(a,b,c){var d=this._listeners||(this._listeners={}),e=a._listenerId||(a._listenerId=f.uniqueId("l"));d[e]=a;a.on(b,"object"===typeof b?this:c,this);
return this},stopListening:function(a,b,c){var d=this._listeners;if(d){if(a)a.off(b,"object"===typeof b?this:c,this),!b&&!c&&delete d[a._listenerId];else{"object"===typeof b&&(c=this);for(var e in d)d[e].off(b,c,this);this._listeners={}}return this}}};h.bind=h.on;h.unbind=h.off;f.extend(g,h);var r=g.Model=function(a,b){var c,d=a||{};this.cid=f.uniqueId("c");this.attributes={};b&&b.collection&&(this.collection=b.collection);b&&b.parse&&(d=this.parse(d,b)||{});if(c=f.result(this,"defaults"))d=f.defaults({},
d,c);this.set(d,b);this.changed={};this.initialize.apply(this,arguments)};f.extend(r.prototype,h,{changed:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},sync:function(){return g.sync.apply(this,arguments)},get:function(a){return this.attributes[a]},escape:function(a){return f.escape(this.get(a))},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e,g,p,j,l,k;if(null==a)return this;"object"===typeof a?(e=a,c=b):(e={})[a]=b;c||(c={});
if(!this._validate(e,c))return!1;g=c.unset;p=c.silent;a=[];j=this._changing;this._changing=!0;j||(this._previousAttributes=f.clone(this.attributes),this.changed={});k=this.attributes;l=this._previousAttributes;this.idAttribute in e&&(this.id=e[this.idAttribute]);for(d in e)b=e[d],f.isEqual(k[d],b)||a.push(d),f.isEqual(l[d],b)?delete this.changed[d]:this.changed[d]=b,g?delete k[d]:k[d]=b;if(!p){a.length&&(this._pending=!0);b=0;for(d=a.length;b<d;b++)this.trigger("change:"+a[b],this,k[a[b]],c)}if(j)return this;
if(!p)for(;this._pending;)this._pending=!1,this.trigger("change",this,c);this._changing=this._pending=!1;return this},unset:function(a,b){return this.set(a,void 0,f.extend({},b,{unset:!0}))},clear:function(a){var b={},c;for(c in this.attributes)b[c]=void 0;return this.set(b,f.extend({},a,{unset:!0}))},hasChanged:function(a){return null==a?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._changing?
this._previousAttributes:this.attributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return null==a||!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=a.success;a.success=function(a,d,e){if(!a.set(a.parse(d,e),e))return!1;b&&b(a,d,e)};return this.sync("read",this,a)},save:function(a,b,c){var d,e,g=this.attributes;
null==a||"object"===typeof a?(d=a,c=b):(d={})[a]=b;if(d&&(!c||!c.wait)&&!this.set(d,c))return!1;c=f.extend({validate:!0},c);if(!this._validate(d,c))return!1;d&&c.wait&&(this.attributes=f.extend({},g,d));void 0===c.parse&&(c.parse=!0);e=c.success;c.success=function(a,b,c){a.attributes=g;var k=a.parse(b,c);c.wait&&(k=f.extend(d||{},k));if(f.isObject(k)&&!a.set(k,c))return!1;e&&e(a,b,c)};a=this.isNew()?"create":c.patch?"patch":"update";"patch"===a&&(c.attrs=d);a=this.sync(a,this,c);d&&c.wait&&(this.attributes=
g);return a},destroy:function(a){a=a?f.clone(a):{};var b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};a.success=function(a,b,e){(e.wait||a.isNew())&&d();c&&c(a,b,e)};if(this.isNew())return a.success(this,null,a),!1;var e=this.sync("delete",this,a);a.wait||d();return e},url:function(){var a=f.result(this,"urlRoot")||f.result(this.collection,"url")||x();return this.isNew()?a:a+("/"===a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},
isNew:function(){return null==this.id},isValid:function(a){return!this.validate||!this.validate(this.attributes,a)},_validate:function(a,b){if(!b.validate||!this.validate)return!0;a=f.extend({},this.attributes,a);var c=this.validationError=this.validate(a,b)||null;if(!c)return!0;this.trigger("invalid",this,c,b||{});return!1}});var s=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);void 0!==b.comparator&&(this.comparator=b.comparator);this.models=[];this._reset();this.initialize.apply(this,
arguments);a&&this.reset(a,f.extend({silent:!0},b))};f.extend(s.prototype,h,{model:r,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},sync:function(){return g.sync.apply(this,arguments)},add:function(a,b){a=f.isArray(a)?a.slice():[a];b||(b={});var c,d,e,g,p,j,l,k,h,m;l=[];k=b.at;h=this.comparator&&null==k&&!1!=b.sort;m=f.isString(this.comparator)?this.comparator:null;c=0;for(d=a.length;c<d;c++)(e=this._prepareModel(g=a[c],b))?(p=this.get(e))?b.merge&&(p.set(g===
e?e.attributes:g,b),h&&(!j&&p.hasChanged(m))&&(j=!0)):(l.push(e),e.on("all",this._onModelEvent,this),this._byId[e.cid]=e,null!=e.id&&(this._byId[e.id]=e)):this.trigger("invalid",this,g,b);l.length&&(h&&(j=!0),this.length+=l.length,null!=k?D.apply(this.models,[k,0].concat(l)):C.apply(this.models,l));j&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=l.length;c<d;c++)(e=l[c]).trigger("add",e,this,b);j&&this.trigger("sort",this,b);return this},remove:function(a,b){a=f.isArray(a)?a.slice():[a];
b||(b={});var c,d,e,g;c=0;for(d=a.length;c<d;c++)if(g=this.get(a[c]))delete this._byId[g.id],delete this._byId[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:this.length},b));return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},
b));return a},shift:function(a){var b=this.at(0);this.remove(b,a);return b},slice:function(a,b){return this.models.slice(a,b)},get:function(a){if(null!=a)return this._idAttr||(this._idAttr=this.model.prototype.idAttribute),this._byId[a.id||a.cid||a[this._idAttr]||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){if(!this.comparator)throw Error("Cannot sort a set without a comparator");
a||(a={});f.isString(this.comparator)||1===this.comparator.length?this.models=this.sortBy(this.comparator,this):this.models.sort(f.bind(this.comparator,this));a.silent||this.trigger("sort",this,a);return this},pluck:function(a){return f.invoke(this.models,"get",a)},update:function(a,b){b=f.extend({add:!0,merge:!0,remove:!0},b);b.parse&&(a=this.parse(a,b));var c,d,e,g,h=[],j=[],l={};f.isArray(a)||(a=a?[a]:[]);if(b.add&&!b.remove)return this.add(a,b);d=0;for(e=a.length;d<e;d++)c=a[d],g=this.get(c),
b.remove&&g&&(l[g.cid]=!0),(b.add&&!g||b.merge&&g)&&h.push(c);if(b.remove){d=0;for(e=this.models.length;d<e;d++)c=this.models[d],l[c.cid]||j.push(c)}j.length&&this.remove(j,b);h.length&&this.add(h,b);return this},reset:function(a,b){b||(b={});b.parse&&(a=this.parse(a,b));for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);b.previousModels=this.models.slice();this._reset();a&&this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=
a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=a.success;a.success=function(a,d,e){a[e.update?"update":"reset"](d,e);b&&b(a,d,e)};return this.sync("read",this,a)},create:function(a,b){b=b?f.clone(b):{};if(!(a=this._prepareModel(a,b)))return!1;b.wait||this.add(a,b);var c=this,d=b.success;b.success=function(a,b,f){f.wait&&c.add(a,f);d&&d(a,b,f)};a.save(null,b);return a},parse:function(a){return a},clone:function(){return new this.constructor(this.models)},_reset:function(){this.length=0;this.models.length=
0;this._byId={}},_prepareModel:function(a,b){if(a instanceof r)return a.collection||(a.collection=this),a;b||(b={});b.collection=this;var c=new this.model(a,b);return!c._validate(a,b)?!1:c},_removeReference:function(a){this===a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"===a||"remove"===a)&&c!==this||("destroy"===a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],null!=b.id&&(this._byId[b.id]=
b)),this.trigger.apply(this,arguments))},sortedIndex:function(a,b,c){b||(b=this.comparator);var d=f.isFunction(b)?b:function(a){return a.get(b)};return f.sortedIndex(this.models,a,d,c)}});f.each("forEach each map collect reduce foldl inject reduceRight foldr find detect filter select reject every all some any include contains invoke max min toArray size first head take initial rest tail drop last without indexOf shuffle lastIndexOf isEmpty chain".split(" "),function(a){s.prototype[a]=function(){var b=
u.call(arguments);b.unshift(this.models);return f[a].apply(f,b)}});f.each(["groupBy","countBy","sortBy"],function(a){s.prototype[a]=function(b,c){var d=f.isFunction(b)?b:function(a){return a.get(b)};return f[a](this.models,d,c)}});var y=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},E=/\((.*?)\)/g,F=/(\(\?)?:\w+/g,G=/\*\w+/g,H=/[\-{}\[\]+?.,\\\^$|#\s]/g;f.extend(y.prototype,h,{initialize:function(){},route:function(a,b,c){f.isRegExp(a)||
(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));this.trigger("route",b,d);g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b);return this},_bindRoutes:function(){if(this.routes)for(var a,b=f.keys(this.routes);null!=(a=b.pop());)this.route(a,this.routes[a])},_routeToRegExp:function(a){a=a.replace(H,"\\$&").replace(E,"(?:$1)?").replace(F,
function(a,c){return c?a:"([^/]+)"}).replace(G,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl");"undefined"!==typeof window&&(this.location=window.location,this.history=window.history)},z=/^[#\/]|\s+$/g,I=/^\/+|\/+$/g,J=/msie [\w.]+/,K=/\/$/;m.started=!1;f.extend(m.prototype,h,{interval:50,getHash:function(a){return(a=(a||this).location.href.match(/#(.*)$/))?a[1]:""},getFragment:function(a,
b){if(null==a)if(this._hasPushState||!this._wantsHashChange||b){a=this.location.pathname;var c=this.root.replace(K,"");a.indexOf(c)||(a=a.substr(c.length))}else a=this.getHash();return a.replace(z,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this.root=this.options.root;this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!(!this.options.pushState||
!this.history||!this.history.pushState);a=this.getFragment();var b=document.documentMode,b=J.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b);this.root=("/"+this.root+"/").replace(I,"/");b&&this._wantsHashChange&&(this.iframe=g.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a));if(this._hasPushState)g.$(window).on("popstate",this.checkUrl);else if(this._wantsHashChange&&"onhashchange"in window&&!b)g.$(window).on("hashchange",this.checkUrl);
else this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,this.interval));this.fragment=a;a=this.location;b=a.pathname.replace(/[^\/]$/,"$&/")===this.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),this.location.replace(this.root+this.location.search+"#"+this.fragment),!0;this._wantsPushState&&(this._hasPushState&&b&&a.hash)&&(this.fragment=this.getHash().replace(z,""),this.history.replaceState({},document.title,
this.root+this.fragment+a.search));if(!this.options.silent)return this.loadUrl()},stop:function(){g.$(window).off("popstate",this.checkUrl).off("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a===this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a===this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},
loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};a=this.getFragment(a||"");if(this.fragment!==a){this.fragment=a;var c=this.root+a;if(this._hasPushState)this.history[b.replace?"replaceState":"pushState"]({},document.title,c);else if(this._wantsHashChange)this._updateHash(this.location,a,b.replace),this.iframe&&a!==this.getFragment(this.getHash(this.iframe))&&
(b.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,a,b.replace));else return this.location.assign(c);b.trigger&&this.loadUrl(a)}},_updateHash:function(a,b,c){c?(c=a.href.replace(/(javascript:|#).*$/,""),a.replace(c+"#"+b)):a.hash="#"+b}});g.history=new m;var A=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},L=/^(\S+)\s*(.*)$/,M="model collection el id attributes className tagName events".split(" ");
f.extend(A.prototype,h,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();this.stopListening();return this},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof g.$?a:g.$(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=f.result(this,"events"))){this.undelegateEvents();for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);
if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(L),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);if(""===d)this.$el.on(e,c);else this.$el.on(e,d,c)}}},undelegateEvents:function(){this.$el.off(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},f.result(this,"options"),a));f.extend(this,f.pick(a,M));this.options=a},_ensureElement:function(){if(this.el)this.setElement(f.result(this,"el"),!1);else{var a=f.extend({},f.result(this,"attributes"));
this.id&&(a.id=f.result(this,"id"));this.className&&(a["class"]=f.result(this,"className"));a=g.$("<"+f.result(this,"tagName")+">").attr(a);this.setElement(a,!1)}}});var N={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=N[a];f.defaults(c||(c={}),{emulateHTTP:g.emulateHTTP,emulateJSON:g.emulateJSON});var e={type:d,dataType:"json"};c.url||(e.url=f.result(b,"url")||x());if(null==c.data&&b&&("create"===a||"update"===a||"patch"===a))e.contentType="application/json",
e.data=JSON.stringify(c.attrs||b.toJSON(c));c.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(c.emulateHTTP&&("PUT"===d||"DELETE"===d||"PATCH"===d)){e.type="POST";c.emulateJSON&&(e.data._method=d);var h=c.beforeSend;c.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d);if(h)return h.apply(this,arguments)}}"GET"!==e.type&&!c.emulateJSON&&(e.processData=!1);var m=c.success;c.success=function(a){m&&m(b,a,c);b.trigger("sync",b,a,c)};
var j=c.error;c.error=function(a){j&&j(b,a,c);b.trigger("error",b,a,c)};a=c.xhr=g.ajax(f.extend(e,c));b.trigger("request",b,a,c);return a};g.ajax=function(){return g.$.ajax.apply(g.$,arguments)};r.extend=s.extend=y.extend=A.extend=m.extend=function(a,b){var c=this,d;d=a&&f.has(a,"constructor")?a.constructor:function(){return c.apply(this,arguments)};f.extend(d,c,b);var e=function(){this.constructor=d};e.prototype=c.prototype;d.prototype=new e;a&&f.extend(d.prototype,a);d.__super__=c.prototype;return d};
var x=function(){throw Error('A "url" property or function must be specified');}}).call(this);
(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,v=e.reduce,h=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.3";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduce===v)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduceRight===h)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?-1!=n.indexOf(t):E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2);return w.map(n,function(n){return(w.isFunction(t)?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t){return w.isEmpty(t)?[]:w.filter(n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var F=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=F(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||void 0===r)return 1;if(e>r||void 0===e)return-1}return n.index<t.index?-1:1}),"value")};var k=function(n,t,r,e){var u={},i=F(t||w.identity);return A(n,function(t,a){var o=i.call(r,t,a,n);e(u,o,t)}),u};w.groupBy=function(n,t,r){return k(n,t,r,function(n,t,r){(w.has(n,t)?n[t]:n[t]=[]).push(r)})},w.countBy=function(n,t,r){return k(n,t,r,function(n,t){w.has(n,t)||(n[t]=0),n[t]++})},w.sortedIndex=function(n,t,r,e){r=null==r?w.identity:F(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i};var I=function(){};w.bind=function(n,t){var r,e;if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));if(!w.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));I.prototype=n.prototype;var u=new I;I.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},w.bindAll=function(n){var t=o.call(arguments,1);return 0==t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=S(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&S(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return S(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),w.isFunction=function(n){return"function"==typeof n},w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return void 0===n},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+(0|Math.random()*(t-n+1))};var T={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"}};T.unescape=w.invert(T.escape);var M={escape:RegExp("["+w.keys(T.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(T.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(M[n],function(t){return T[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=""+ ++N;return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){r=w.defaults({},r,w.templateSettings);var e=RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(D,function(n){return"\\"+B[n]}),r&&(i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(i+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),a&&(i+="';\n"+a+"\n__p+='"),u=o+t.length,t}),i+="';\n",r.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=Function(r.variable||"obj","_",i)}catch(o){throw o.source=i,o}if(t)return a(t,w);var c=function(n){return a.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+i+"}",c},w.chain=function(n){return w(n).chain()};var z=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.4";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2),e=w.isFunction(t);return w.map(n,function(n){return(e?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t,r){return w.isEmpty(t)?r?null:[]:w[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.findWhere=function(n,t){return w.where(n,t,!0)},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var k=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=k(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index<t.index?-1:1}),"value")};var F=function(n,t,r,e){var u={},i=k(t||w.identity);return A(n,function(t,a){var o=i.call(r,t,a,n);e(u,o,t)}),u};w.groupBy=function(n,t,r){return F(n,t,r,function(n,t,r){(w.has(n,t)?n[t]:n[t]=[]).push(r)})},w.countBy=function(n,t,r){return F(n,t,r,function(n,t){w.has(n,t)||(n[t]=0),n[t]++})},w.sortedIndex=function(n,t,r,e){r=null==r?w.identity:k(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i},w.bind=function(n,t){if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));var r=o.call(arguments,2);return function(){return n.apply(t,r.concat(o.call(arguments)))}},w.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},w.bindAll=function(n){var t=o.call(arguments,1);return 0===t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var I=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=I(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&I(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return I(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),"function"!=typeof/./&&(w.isFunction=function(n){return"function"==typeof n}),w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return n===void 0},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var M={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"}};M.unescape=w.invert(M.escape);var S={escape:RegExp("["+w.keys(M.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(M.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(S[n],function(t){return M[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=++N+"";return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,q={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},B=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){var e;r=w.defaults({},r,w.templateSettings);var u=RegExp([(r.escape||T).source,(r.interpolate||T).source,(r.evaluate||T).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(B,function(n){return"\\"+q[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,w);var c=function(n){return e.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},w.chain=function(n){return w(n).chain()};var D=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);
\ No newline at end of file \ No newline at end of file
...@@ -64,6 +64,7 @@ CACHES = ENV_TOKENS['CACHES'] ...@@ -64,6 +64,7 @@ CACHES = ENV_TOKENS['CACHES']
DEFAULT_FROM_EMAIL = ENV_TOKENS.get('DEFAULT_FROM_EMAIL', DEFAULT_FROM_EMAIL) DEFAULT_FROM_EMAIL = ENV_TOKENS.get('DEFAULT_FROM_EMAIL', DEFAULT_FROM_EMAIL)
DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS.get('DEFAULT_FEEDBACK_EMAIL', DEFAULT_FEEDBACK_EMAIL) DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS.get('DEFAULT_FEEDBACK_EMAIL', DEFAULT_FEEDBACK_EMAIL)
ADMINS = ENV_TOKENS.get('ADMINS', ADMINS) ADMINS = ENV_TOKENS.get('ADMINS', ADMINS)
SERVER_EMAIL = ENV_TOKENS.get('SERVER_EMAIL', SERVER_EMAIL)
#Timezone overrides #Timezone overrides
TIME_ZONE = ENV_TOKENS.get('TIME_ZONE', TIME_ZONE) TIME_ZONE = ENV_TOKENS.get('TIME_ZONE', TIME_ZONE)
......
...@@ -265,6 +265,7 @@ IGNORABLE_404_ENDS = ('favicon.ico') ...@@ -265,6 +265,7 @@ IGNORABLE_404_ENDS = ('favicon.ico')
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DEFAULT_FROM_EMAIL = 'registration@edx.org' DEFAULT_FROM_EMAIL = 'registration@edx.org'
DEFAULT_FEEDBACK_EMAIL = 'feedback@edx.org' DEFAULT_FEEDBACK_EMAIL = 'feedback@edx.org'
SERVER_EMAIL = 'devops@edx.org'
ADMINS = ( ADMINS = (
('edX Admins', 'admin@edx.org'), ('edX Admins', 'admin@edx.org'),
) )
......
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! from datetime import datetime %>
<%! import pytz %>
<%! from django.conf import settings %> <%! from django.conf import settings %>
<%! from courseware.tabs import get_discussion_link %> <%! from courseware.tabs import get_discussion_link %>
...@@ -79,9 +81,16 @@ discussion_link = get_discussion_link(course) if course else None ...@@ -79,9 +81,16 @@ discussion_link = get_discussion_link(course) if course else None
<hr> <hr>
</header> </header>
<%
dst = datetime.now(pytz.utc).astimezone(pytz.timezone("America/New_York")).dst()
business_hours = "13:00 UTC to 21:00 UTC" if dst else "14:00 UTC to 22:00 UTC"
%>
<p> <p>
Thanks for your feedback. We will read your message, and our Thank you for your inquiry or feedback. We typically respond to a
support team may contact you to respond or ask for further clarification. request within one business day (Monday to Friday,
${business_hours}.) In the meantime, please review our
<a href="/help" target="_blank">detailed FAQs</a>
where most questions have already been answered.
</p> </p>
<div class="close-modal"> <div class="close-modal">
......
...@@ -374,35 +374,6 @@ ...@@ -374,35 +374,6 @@
</div> </div>
</article> </article>
<article id="devops-engineer-systems-administrator" class="job">
<div class="inner-wrapper">
<h3><strong>DEVOPS ENGINEER – SYSTEMS ADMINISTRATOR</strong></h3>
<p>The Devop Engineers at edX help develop and maintain the infrastructure in AWS for all services and systems required to run edX. We're seeking a capable systems administrator who is unafraid of scripting languages and development to build out tools in order to improve the functionality of edX. The devops team primarily focuses on the provisioning, configuration, and deployment of services at edX. If you have a passion for automation and constant improvement then we want to hear from you. Our production environment is primarily built on Ubuntu (in AWS) and we use Puppet and Fabric to manage most of the environment.</p>
<p>In addition to the primary task of building infrastructure the Devops team supports the developers in a variety of other contexts, including helping with desktop development environments if required. We participate in on-call and emergency support and there will be occasional out of normal hours work required.</p>
<p><strong>Responsibilities:</strong></p>
<ul>
<li>Work with developers and staff to maintain and improve the infrastructure of edX.</li>
<li>Assist where needed with other technical support tasks to support the fast moving pace of edX.</li>
<li>Rapidly diagnose and resolve faults with organization-wide servers and services, and communicate to users as appropriate.</li>
</ul>
<p><strong>Requirements:</strong></p>
<ul>
<li>Bachelor's degree in engineering or computer science. But we're all about education, so let us know how you gained what you need to succeed in this role: projects after completing 6.00x or CS50x, Xbox cheevos, on-line guilds led, large scale innovations championed.</li>
<li>Three or more years of systems administration. </li>
<li>Must have an excellent working knowledge of Linux both as an end-user and as an administrator.</li>
<li>Must be adept in programming/scripting languages such as Python, Ruby, Bash.</li>
<li>Must be familiar with a configuration management system such as Puppet, Chef, Ansible.</li>
<li>Must have experience running web applications in a production environment.</li>
<li>Must have excellent personal interaction skills as the position requires interfacing with a wide range of people up to board level.</li>
<li>Ideally possesses experience with some of the following technologies: nginx, mysql, mongodb, django environments, splunk, git.</li>
</ul>
<p>If you are interested in this position, please send an email to <a href="mailto:jobs@edx.org">jobs@edx.org</a>.</p>
</div>
</article>
<article id="learning-sciences-engineer" class="job"> <article id="learning-sciences-engineer" class="job">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h3><strong>LEARNING SCIENCES ENGINEER</strong></h3> <h3><strong>LEARNING SCIENCES ENGINEER</strong></h3>
...@@ -483,39 +454,7 @@ development and program management teams.</p> ...@@ -483,39 +454,7 @@ development and program management teams.</p>
<p>If you are interested in this position, please send an email to <a href="mailto:jobs@edx.org">jobs@edx.org</a>.</p> <p>If you are interested in this position, please send an email to <a href="mailto:jobs@edx.org">jobs@edx.org</a>.</p>
</div> </div>
</article> </article>
<article id="front-end-developer" class="job">
<article id="web-designer" class="job">
<div class="inner-wrapper">
<h3><strong>WEB DESIGNER, PRODUCT TEAM</strong></h3>
<p>EdX is looking for a Web Designer to join our Product Team and shape the experience of edX's online learning tools. With thousands and thousands of students and hundreds of professors using our software every day, our online learning tools have to sing. Our ideal candidates are passionate and picky about what makes a good user experience; sweat the mechanical, visual, and transactional details when designing; know how to bring an idea or project from a sketch on paper to being alive in a browser; can instinctually bring organization to a design meeting, deliverable, or project; and thrive on collaboration with colleagues and constant iteration/refinement.</p>
<p>As an edX Designer, you:</p>
<ul>
<li>Have an innate sense of – and strong opinion about – good usability when it comes to web applications, and an ability to clearly articulate both.</li>
<li>Understand established interactive technologies and possess an undying thirst to learn about new ones.</li>
<li>Define and work within visual themes based on your excellent understanding of grids, typography, color, and design principles.</li>
<li>Marry design aesthetics to user experiences while keeping in mind accessibility, usability, and web standards.</li>
<li>Can use HTML5, CSS3, and DOM-manipulating JavaScript to represent your designs in the browser.</li>
<li>Conceptualize and articulate complex ideas to drive decisions, facilitate understanding, and reach consensus.</li>
<li>Document your thinking using appropriately chosen, informed deliverables such as sketches, wireframes, prototypes, site maps/flows, personas, style tiles, and design comps.</li>
<li>Have a perfectionist mindset, but won’t lose momentum in projects because of it.</li>
<li>Expertly present user experience and design recommendations to team members.</li>
</ul>
<p><strong>Requirements:</strong></p>
<ul>
<li>Have at least 2 years of professional, post-collegiate experience.</li>
<li>Have a BA, BS, BFA, or equivalent work experience in areas such as human-computer interaction, information science, graphic or industrial design, computer science, fine arts, social sciences such as psychology, or another related field. But we're all about education, so let us know how you gained what you need to succeed in this role: projects after completing 6.00x or CS50x, Xbox cheevos, on-line guilds led, large scale innovations championed.</li>
</ul>
<p><strong>About the Product Design Team:</strong></p>
<p>We are a small team with a startup, lean culture, committed to building tools that help our users learn and teach online. Working alongside developers, course staff, product owners, and project stakeholders, our Designers shepherd the experience of an idea or tool through research and strategy phases and lead the Information Architecture, Interaction Design, Visual Design, and Front End Development efforts in bringing that experience to life. We enjoy holding Design Studio exercises, finding the right design tool to do the job efficiently, and our CSS preprocessors.</p>
<p>If you wish to apply, please send your resume (PDF, text, or Word Doc), a thoughtful email that includes specifics about how your previous experience matches the Designer role at edX, and online samples of your work to <a href="mailto:jobs@edx.org">jobs@edx.org</a>. Candidates who do not provide these will not be considered. EdX is open to considering candidates outside of the Boston/Cambridge, MA area who are willing to relocate.</p>
</div>
</article>
<article id="front-end-developer" class="job">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h3><strong>FRONT END DEVELOPER</strong></h3> <h3><strong>FRONT END DEVELOPER</strong></h3>
<p>edX is looking for a Front End Developer to join our Product and Engineering Teams to shape the experience of all of edX's online learning tools. Thousands of students learn with us every day – the way they connect with their courses, their professors and edX is through our ever more powerful front end. Our ideal candidates not only know modern front end development best practices, but make organization standards and teach others with them; sweat the mechanical, visual, and transactional details when bring a design to life in the browser; can instinctually bring organization to their HTML/CSS/JavaScript, documentation, or project; and thrive on collaborating with both designers and developers throughout a project's lifecycle.</p> <p>edX is looking for a Front End Developer to join our Product and Engineering Teams to shape the experience of all of edX's online learning tools. Thousands of students learn with us every day – the way they connect with their courses, their professors and edX is through our ever more powerful front end. Our ideal candidates not only know modern front end development best practices, but make organization standards and teach others with them; sweat the mechanical, visual, and transactional details when bring a design to life in the browser; can instinctually bring organization to their HTML/CSS/JavaScript, documentation, or project; and thrive on collaborating with both designers and developers throughout a project's lifecycle.</p>
...@@ -545,6 +484,72 @@ development and program management teams.</p> ...@@ -545,6 +484,72 @@ development and program management teams.</p>
<p>If you wish to apply, please send your resume (PDF, text, or Word Doc), a thoughtful email that includes specifics about how your previous experience matches the Front End Developer role at edX, and online samples of your work to <a href="mailto:jobs@edx.org">jobs@edx.org</a>. Candidates who do not provide these will not be considered. EdX is open to considering candidates outside of the Boston/Cambridge, MA area who are willing to relocate.</p> <p>If you wish to apply, please send your resume (PDF, text, or Word Doc), a thoughtful email that includes specifics about how your previous experience matches the Front End Developer role at edX, and online samples of your work to <a href="mailto:jobs@edx.org">jobs@edx.org</a>. Candidates who do not provide these will not be considered. EdX is open to considering candidates outside of the Boston/Cambridge, MA area who are willing to relocate.</p>
</div> </div>
</article> </article>
<article id="test-engineer" class="job">
<div class="inner-wrapper">
<h3><strong>TEST ENGINEER</strong></h3>
<p>EdX is looking for a Software Engineer in Test to help architect and implement improvements to our testing infrastructure and write code to validate and verify development and deployment of our MOOC platform.</p>
<p>You are an experienced professional who is passionate about and current with cutting edge methodologies and practices for delivering high quality software. For example, you understand and can articulate the difference between BDD and TDD. You champion for developers to be confident in the quality of their code by giving them the tools they need to create and execute their own tests. You write unit tests that follow best practices for each layer of an MVC architecture. You work side by side with the DevOps team to define environments and automate their buildouts.</p>
<p><strong>Responsibilities:</strong></p>
<ul>
<li>Review software designs with a focus on code quality, risk, and testability</li>
<li>Build tools and frameworks that enable fellow engineers be more productive, write better code and test it themselves</li>
<li>Code test automation at all levels including class library, web application framework, javascript, and end-to-end</li>
<li>Enable metrics collection to measure adoption and expand the reach of the delivered tools</li>
<li>Fix framework bugs and improve test architecture, including adding required unit tests</li>
<li>Train and mentor other team members</li>
</ul>
<p><strong>Qualifications:</strong></p>
<ul>
<li>Excellent coding skills across a number of languages: Python or other high level programming languages, Javascript, bash, etc.</li>
<li>Experience in building test automation frameworks</li>
<li>Comfortable with source code in various languages (Python/Django, Ruby/Rails, Javascript/Backbone/JQuery, etc.)</li>
<li>Highly proficient in a Unix/Linux environment</li>
<li>Experience with database technologies from SQLite to MongoDB</li>
<li>Familiar with deployment automation (Puppet, Jenkins, AWS)</li>
<li>Open Source development experience preferred, extra points for sharing your GitHub / StackOverflow / etc. profile</li>
</ul>
<p>If you are interested in this position, please send an email to <a href="mailto:jobs@edx.org">jobs@edx.org</a>.</p>
</div>
</article>
<article id="coordinator-university-business-affairs" class="job">
<div class="inner-wrapper">
<h3><strong>COORDINATOR OF UNIVERSITY AND BUSINESS AFFAIRS</strong></h3>
<p>EdX is looking for a Coordinator of External Affairs, to streamline, organize and maintain our efforts in Business Development and University Relations.</p>
<p><strong>There are 4 primary areas of responsibility:</strong></p>
<ol>
<li>To ensure all visits to and from the edX offices by any partners and affiliates are managed, coordinated, and documented. This involves developing itineraries, booking flights and schedules, and managing meetings and events in concert with members of our executive team in University Relations and Business Development and our consortium of partners.</li>
<li>To maintain a database of partners and prospects and manage any data flows/reporting required.</li>
<li>To manage the information flow, recording activity on the edX Wiki page by synthesizing data and analysis from all visits and meetings and create updates on the edX Wiki page.</li>
<li>To act as a central point of contact for all relationship and event activity within this scope.</li>
</ol>
<p><strong>Detailed Responsibilities:</strong></p>
<ul>
<li>Provide support and coordinate activities for these 3 executives</li>
<li>Acquire strong user knowledge of related systems, processes and tools</li>
<li>Participate in the new partner on-boarding process</li>
<li>Provide an escalation point for Sales personnel for systems, procedures and policies</li>
<li>Maintain Salesforce database for client/partner set up and support information, generating reports as needed</li>
<li>Document proofreading, editing as directed for proposals, contracts, contact and call reports</li>
<li>Coordinate and manage travel, events and meetings, including invitations, RSVP’s, hotel/meeting space contracts, and providing event materials to attendees</li>
</ul>
<p><strong>Qualifications:</strong></p>
<ul>
<li>5-7 years of experience in a similar project/coordinator type position with progressively responsible administrative experience</li>
<li>Self-starter, possessing tenacity and a desire for challenges, not afraid to take risks, and the initiative to get things done with little direction </li>
<li>Superior interpersonal and communications skills, including concise writing and editing skills</li>
<li>Strong organizational skills to manage multiple competing priorities and projects with attention to detail </li>
<li>Exceptional ability to effectively interact with multiple external and internal stakeholders </li>
<li>Adept at analyzing complex issues with the ability to synthesize data and perform gap analyses</li>
<li>Performs well with a variety of disciplines while remaining effective in a high-volume, fast-pace start-up environment with high workload</li>
<li>Must be proficient in: MS PowerPoint, Word and Excel, Salesforce.com, and online tools such as Google docs and Wiki, and knowledge of Kanban is also helpful</li>
</ul>
<p>If you are interested in this position, please send an email to <a href="mailto:jobs@edx.org">jobs@edx.org</a>.</p>
</div>
</article>
</section> </section>
...@@ -559,11 +564,11 @@ development and program management teams.</p> ...@@ -559,11 +564,11 @@ development and program management teams.</p>
<a href="#director-of-product-management">Director, Product Management</a> <a href="#director-of-product-management">Director, Product Management</a>
<a href="#content-engineer">Content Engineer</a> <a href="#content-engineer">Content Engineer</a>
<a href="#software-engineer">Software Engineer</a> <a href="#software-engineer">Software Engineer</a>
<a href="#devops-engineer-systems-administrator">Devops Engineer - Systems Administrator</a>
<a href="#learning-sciences-engineer">Learning Sciences Engineer</a> <a href="#learning-sciences-engineer">Learning Sciences Engineer</a>
<a href="#sales-engineer">Sales Engineer, Business Development Team</a> <a href="#sales-engineer">Sales Engineer, Business Development Team</a>
<a href="#web-designer">Web Designer, Product Team</a>
<a href="#front-end-developer">Front End Developer</a> <a href="#front-end-developer">Front End Developer</a>
<a href="#test-engineer">Test Engineer</a>
<a href="#coordinator-university-business-affairs">Coordinator of University and Business Affairs</a>
</nav> </nav>
<h2>How to Apply</h2> <h2>How to Apply</h2>
<p>E-mail your resume, cover letter and any other materials to <a href="mailto:jobs@edx.org">jobs@edx.org</a></p> <p>E-mail your resume, cover letter and any other materials to <a href="mailto:jobs@edx.org">jobs@edx.org</a></p>
......
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