Commit 0014b289 by Victor Shnayder

Merge remote-tracking branch 'origin/master' into 700x-sandbox

parents c81abe1e 75449359
import logging
from static_replace import replace_static_urls from static_replace import replace_static_urls
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from django.http import Http404
from lxml import etree
import re
from django.http import HttpResponseBadRequest, Http404
def get_module_info(store, location, parent_location=None, rewrite_static_links=False): def get_module_info(store, location, parent_location=None, rewrite_static_links=False):
try: try:
if location.revision is None: if location.revision is None:
module = store.get_item(location) module = store.get_item(location)
else: else:
module = store.get_item(location) module = store.get_item(location)
except ItemNotFoundError: except ItemNotFoundError:
raise Http404 # create a new one
template_location = Location(['i4x', 'edx', 'templates', location.category, 'Empty'])
module = store.clone_item(template_location, location)
data = module.definition['data'] data = module.definition['data']
if rewrite_static_links: if rewrite_static_links:
data = replace_static_urls( data = replace_static_urls(
module.definition['data'], module.definition['data'],
None,
course_namespace=Location([
module.location.tag,
module.location.org,
module.location.course,
None, None,
None course_namespace=Location([
]) module.location.tag,
) module.location.org,
module.location.course,
None,
None
])
)
return { return {
'id': module.location.url(), 'id': module.location.url(),
'data': data, 'data': data,
'metadata': module.metadata 'metadata': module.metadata
...@@ -39,58 +37,56 @@ def get_module_info(store, location, parent_location=None, rewrite_static_links= ...@@ -39,58 +37,56 @@ def get_module_info(store, location, parent_location=None, rewrite_static_links=
def set_module_info(store, location, post_data): def set_module_info(store, location, post_data):
module = None module = None
isNew = False try:
try: if location.revision is None:
if location.revision is None: module = store.get_item(location)
module = store.get_item(location) else:
else: module = store.get_item(location)
module = store.get_item(location) except:
except: pass
pass
if module is None: if module is None:
# new module at this location # new module at this location
# presume that we have an 'Empty' template # presume that we have an 'Empty' template
template_location = Location(['i4x', 'edx', 'templates', location.category, 'Empty']) template_location = Location(['i4x', 'edx', 'templates', location.category, 'Empty'])
module = store.clone_item(template_location, location) module = store.clone_item(template_location, location)
isNew = True
if post_data.get('data') is not None: if post_data.get('data') is not None:
data = post_data['data'] data = post_data['data']
store.update_item(location, data) store.update_item(location, data)
# cdodge: note calling request.POST.get('children') will return None if children is an empty array # cdodge: note calling request.POST.get('children') will return None if children is an empty array
# so it lead to a bug whereby the last component to be deleted in the UI was not actually # so it lead to a bug whereby the last component to be deleted in the UI was not actually
# deleting the children object from the children collection # deleting the children object from the children collection
if 'children' in post_data and post_data['children'] is not None: if 'children' in post_data and post_data['children'] is not None:
children = post_data['children'] children = post_data['children']
store.update_children(location, children) store.update_children(location, children)
# cdodge: also commit any metadata which might have been passed along in the # cdodge: also commit any metadata which might have been passed along in the
# POST from the client, if it is there # POST from the client, if it is there
# NOTE, that the postback is not the complete metadata, as there's system metadata which is # NOTE, that the postback is not the complete metadata, as there's system metadata which is
# not presented to the end-user for editing. So let's fetch the original and # not presented to the end-user for editing. So let's fetch the original and
# 'apply' the submitted metadata, so we don't end up deleting system metadata # 'apply' the submitted metadata, so we don't end up deleting system metadata
if post_data.get('metadata') is not None: if post_data.get('metadata') is not None:
posted_metadata = post_data['metadata'] posted_metadata = post_data['metadata']
# update existing metadata with submitted metadata (which can be partial) # update existing metadata with submitted metadata (which can be partial)
# IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it' # IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it'
for metadata_key in posted_metadata.keys(): for metadata_key in posted_metadata.keys():
# let's strip out any metadata fields from the postback which have been identified as system metadata # let's strip out any metadata fields from the postback which have been identified as system metadata
# and therefore should not be user-editable, so we should accept them back from the client # and therefore should not be user-editable, so we should accept them back from the client
if metadata_key in module.system_metadata_fields: if metadata_key in module.system_metadata_fields:
del posted_metadata[metadata_key] del posted_metadata[metadata_key]
elif posted_metadata[metadata_key] is None: elif posted_metadata[metadata_key] is None:
# remove both from passed in collection as well as the collection read in from the modulestore # remove both from passed in collection as well as the collection read in from the modulestore
if metadata_key in module.metadata: if metadata_key in module.metadata:
del module.metadata[metadata_key] del module.metadata[metadata_key]
del posted_metadata[metadata_key] del posted_metadata[metadata_key]
# overlay the new metadata over the modulestore sourced collection to support partial updates # overlay the new metadata over the modulestore sourced collection to support partial updates
module.metadata.update(posted_metadata) module.metadata.update(posted_metadata)
# commit to datastore # commit to datastore
store.update_metadata(location, module.metadata) store.update_metadata(location, module.metadata)
...@@ -122,7 +122,8 @@ def index(request): ...@@ -122,7 +122,8 @@ def index(request):
course.location.course, course.location.course,
course.location.name])) course.location.name]))
for course in courses], for course in courses],
'user': request.user 'user': request.user,
'disable_course_creation': settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False) and not request.user.is_staff
}) })
...@@ -1259,6 +1260,10 @@ def edge(request): ...@@ -1259,6 +1260,10 @@ def edge(request):
@login_required @login_required
@expect_json @expect_json
def create_new_course(request): def create_new_course(request):
if settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False) and not request.user.is_staff:
raise PermissionDenied()
# This logic is repeated in xmodule/modulestore/tests/factories.py # This logic is repeated in xmodule/modulestore/tests/factories.py
# so if you change anything here, you need to also change it there. # so if you change anything here, you need to also change it there.
# TODO: write a test that creates two courses, one with the factory and # TODO: write a test that creates two courses, one with the factory and
......
...@@ -165,13 +165,6 @@ STATICFILES_DIRS = [ ...@@ -165,13 +165,6 @@ STATICFILES_DIRS = [
# This is how you would use the textbook images locally # This is how you would use the textbook images locally
# ("book", ENV_ROOT / "book_images") # ("book", ENV_ROOT / "book_images")
] ]
if os.path.isdir(GITHUB_REPO_ROOT):
STATICFILES_DIRS += [
# TODO (cpennington): When courses aren't loaded from github, remove this
(course_dir, GITHUB_REPO_ROOT / course_dir)
for course_dir in os.listdir(GITHUB_REPO_ROOT)
if os.path.isdir(GITHUB_REPO_ROOT / course_dir)
]
# Locale/Internationalization # Locale/Internationalization
TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
......
...@@ -37,7 +37,9 @@ ...@@ -37,7 +37,9 @@
<h1>My Courses</h1> <h1>My Courses</h1>
<article class="my-classes"> <article class="my-classes">
% if user.is_active: % if user.is_active:
<a href="#" class="new-button new-course-button"><span class="plus-icon white"></span> New Course</a> % if not disable_course_creation:
<a href="#" class="new-button new-course-button"><span class="plus-icon white"></span> New Course</a>
%endif
<ul class="class-list"> <ul class="class-list">
%for course, url in courses: %for course, url in courses:
<li> <li>
......
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
$cancelButton.bind('click', hideNewUserForm); $cancelButton.bind('click', hideNewUserForm);
$('.new-user-button').bind('click', showNewUserForm); $('.new-user-button').bind('click', showNewUserForm);
$body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel); $('body').bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
$('.remove-user').click(function() { $('.remove-user').click(function() {
$.ajax({ $.ajax({
......
...@@ -206,7 +206,7 @@ from contentstore import utils ...@@ -206,7 +206,7 @@ from contentstore import utils
<section class="setting-details-marketing"> <section class="setting-details-marketing">
<header> <header>
<h3>Introducing Your Course</h3> <h3>Introducing Your Course</h3>
<span class="detail">Information for perspective students</span> <span class="detail">Information for prospective students</span>
</header> </header>
<div class="row row-col2"> <div class="row row-col2">
......
import datetime import datetime
import feedparser import feedparser
#import itertools
import json import json
import logging import logging
import random import random
import string import string
import sys import sys
#import time
import urllib import urllib
import uuid import uuid
...@@ -16,17 +14,19 @@ from django.contrib.auth import logout, authenticate, login ...@@ -16,17 +14,19 @@ from django.contrib.auth import logout, authenticate, login
from django.contrib.auth.forms import PasswordResetForm from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.cache import cache
from django.core.context_processors import csrf from django.core.context_processors import csrf
from django.core.mail import send_mail from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.core.validators import validate_email, validate_slug, ValidationError from django.core.validators import validate_email, validate_slug, ValidationError
from django.db import IntegrityError from django.db import IntegrityError
from django.http import HttpResponse, HttpResponseForbidden, Http404 from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django_future.csrf import ensure_csrf_cookie, csrf_exempt
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from django.core.cache import cache
from django_future.csrf import ensure_csrf_cookie, csrf_exempt
from student.models import (Registration, UserProfile, TestCenterUser, TestCenterUserForm, from student.models import (Registration, UserProfile, TestCenterUser, TestCenterUserForm,
TestCenterRegistration, TestCenterRegistrationForm, TestCenterRegistration, TestCenterRegistrationForm,
PendingNameChange, PendingEmailChange, PendingNameChange, PendingEmailChange,
...@@ -38,12 +38,15 @@ from certificates.models import CertificateStatuses, certificate_status_for_stud ...@@ -38,12 +38,15 @@ from certificates.models import CertificateStatuses, certificate_status_for_stud
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location
#from datetime import date
from collections import namedtuple from collections import namedtuple
from courseware.courses import get_courses, sort_by_announcement from courseware.courses import get_courses, sort_by_announcement
from courseware.access import has_access from courseware.access import has_access
from courseware.models import StudentModuleCache
from courseware.views import get_module_for_descriptor, jump_to
from courseware.module_render import get_instance_module
from statsd import statsd from statsd import statsd
...@@ -1066,27 +1069,134 @@ def accept_name_change(request): ...@@ -1066,27 +1069,134 @@ def accept_name_change(request):
return accept_name_change_by_id(int(request.POST['id'])) return accept_name_change_by_id(int(request.POST['id']))
# TODO: This is a giant kludge to give Pearson something to test against ASAP.
# Will need to get replaced by something that actually ties into TestCenterUser
@csrf_exempt @csrf_exempt
def test_center_login(request): def test_center_login(request):
if not settings.MITX_FEATURES.get('ENABLE_PEARSON_HACK_TEST'): # errors are returned by navigating to the error_url, adding a query parameter named "code"
raise Http404 # which contains the error code describing the exceptional condition.
def makeErrorURL(error_url, error_code):
log.error("generating error URL with error code {}".format(error_code))
return "{}?code={}".format(error_url, error_code);
# get provided error URL, which will be used as a known prefix for returning error messages to the
# Pearson shell.
error_url = request.POST.get("errorURL")
# TODO: check that the parameters have not been tampered with, by comparing the code provided by Pearson
# with the code we calculate for the same parameters.
if 'code' not in request.POST:
return HttpResponseRedirect(makeErrorURL(error_url, "missingSecurityCode"));
code = request.POST.get("code")
# calculate SHA for query string
# TODO: figure out how to get the original query string, so we can hash it and compare.
if 'clientCandidateID' not in request.POST:
return HttpResponseRedirect(makeErrorURL(error_url, "missingClientCandidateID"));
client_candidate_id = request.POST.get("clientCandidateID") client_candidate_id = request.POST.get("clientCandidateID")
# TODO: check remaining parameters, and maybe at least log if they're not matching
# expected values....
# registration_id = request.POST.get("registrationID") # registration_id = request.POST.get("registrationID")
exit_url = request.POST.get("exitURL") # exit_url = request.POST.get("exitURL")
error_url = request.POST.get("errorURL")
# find testcenter_user that matches the provided ID:
try:
testcenteruser = TestCenterUser.objects.get(client_candidate_id=client_candidate_id)
except TestCenterUser.DoesNotExist:
log.error("not able to find demographics for cand ID {}".format(client_candidate_id))
return HttpResponseRedirect(makeErrorURL(error_url, "invalidClientCandidateID"));
# find testcenter_registration that matches the provided exam code:
# Note that we could rely in future on either the registrationId or the exam code,
# or possibly both. But for now we know what to do with an ExamSeriesCode,
# while we currently have no record of RegistrationID values at all.
if 'vueExamSeriesCode' not in request.POST:
# we are not allowed to make up a new error code, according to Pearson,
# so instead of "missingExamSeriesCode", we use a valid one that is
# inaccurate but at least distinct. (Sigh.)
log.error("missing exam series code for cand ID {}".format(client_candidate_id))
return HttpResponseRedirect(makeErrorURL(error_url, "missingPartnerID"));
exam_series_code = request.POST.get('vueExamSeriesCode')
# special case for supporting test user:
if client_candidate_id == "edX003671291147" and exam_series_code != '6002x001':
log.warning("test user {} using unexpected exam code {}, coercing to 6002x001".format(client_candidate_id, exam_series_code))
exam_series_code = '6002x001'
registrations = TestCenterRegistration.objects.filter(testcenter_user=testcenteruser, exam_series_code=exam_series_code)
if not registrations:
log.error("not able to find exam registration for exam {} and cand ID {}".format(exam_series_code, client_candidate_id))
return HttpResponseRedirect(makeErrorURL(error_url, "noTestsAssigned"));
# TODO: figure out what to do if there are more than one registrations....
# for now, just take the first...
registration = registrations[0]
course_id = registration.course_id
course = course_from_id(course_id) # assume it will be found....
if not course:
log.error("not able to find course from ID {} for cand ID {}".format(course_id, client_candidate_id))
return HttpResponseRedirect(makeErrorURL(error_url, "incorrectCandidateTests"));
exam = course.get_test_center_exam(exam_series_code)
if not exam:
log.error("not able to find exam {} for course ID {} and cand ID {}".format(exam_series_code, course_id, client_candidate_id))
return HttpResponseRedirect(makeErrorURL(error_url, "incorrectCandidateTests"));
location = exam.exam_url
log.info("proceeding with test of cand {} on exam {} for course {}: URL = {}".format(client_candidate_id, exam_series_code, course_id, location))
# check if the test has already been taken
timelimit_descriptor = modulestore().get_instance(course_id, Location(location))
if not timelimit_descriptor:
log.error("cand {} on exam {} for course {}: descriptor not found for location {}".format(client_candidate_id, exam_series_code, course_id, location))
return HttpResponseRedirect(makeErrorURL(error_url, "missingClientProgram"));
timelimit_module_cache = StudentModuleCache.cache_for_descriptor_descendents(course_id, testcenteruser.user,
timelimit_descriptor, depth=None)
timelimit_module = get_module_for_descriptor(request.user, request, timelimit_descriptor,
timelimit_module_cache, course_id, position=None)
if not timelimit_module.category == 'timelimit':
log.error("cand {} on exam {} for course {}: non-timelimit module at location {}".format(client_candidate_id, exam_series_code, course_id, location))
return HttpResponseRedirect(makeErrorURL(error_url, "missingClientProgram"));
if timelimit_module and timelimit_module.has_ended:
log.warning("cand {} on exam {} for course {}: test already over at {}".format(client_candidate_id, exam_series_code, course_id, timelimit_module.ending_at))
return HttpResponseRedirect(makeErrorURL(error_url, "allTestsTaken"));
# check if we need to provide an accommodation:
time_accommodation_mapping = {'ET12ET' : 'ADDHALFTIME',
'ET30MN' : 'ADD30MIN',
'ETDBTM' : 'ADDDOUBLE', }
time_accommodation_code = None
for code in registration.get_accommodation_codes():
if code in time_accommodation_mapping:
time_accommodation_code = time_accommodation_mapping[code]
# special, hard-coded client ID used by Pearson shell for testing:
if client_candidate_id == "edX003671291147": if client_candidate_id == "edX003671291147":
user = authenticate(username=settings.PEARSON_TEST_USER, time_accommodation_code = 'TESTING'
password=settings.PEARSON_TEST_PASSWORD)
login(request, user) if time_accommodation_code:
return redirect('/courses/MITx/6.002x/2012_Fall/courseware/Final_Exam/Final_Exam_Fall_2012/') timelimit_module.accommodation_code = time_accommodation_code
else: instance_module = get_instance_module(course_id, testcenteruser.user, timelimit_module, timelimit_module_cache)
return HttpResponseForbidden() instance_module.state = timelimit_module.get_instance_state()
instance_module.save()
log.info("cand {} on exam {} for course {}: receiving accommodation {}".format(client_candidate_id, exam_series_code, course_id, time_accommodation_code))
# UGLY HACK!!!
# Login assumes that authentication has occurred, and that there is a
# backend annotation on the user object, indicating which backend
# against which the user was authenticated. We're authenticating here
# against the registration entry, and assuming that the request given
# this information is correct, we allow the user to be logged in
# without a password. This could all be formalized in a backend object
# that does the above checking.
# TODO: (brian) create a backend class to do this.
# testcenteruser.user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
testcenteruser.user.backend = "%s.%s" % ("TestcenterAuthenticationModule", "TestcenterAuthenticationClass")
login(request, testcenteruser.user)
# And start the test:
return jump_to(request, course_id, location)
def _get_news(top=None): def _get_news(top=None):
......
...@@ -999,7 +999,7 @@ def sympy_check2(): ...@@ -999,7 +999,7 @@ def sympy_check2():
self.context['debug'] = self.system.DEBUG self.context['debug'] = self.system.DEBUG
# exec the check function # exec the check function
if type(self.code) == str: if isinstance(self.code, basestring):
try: try:
exec self.code in self.context['global_context'], self.context exec self.code in self.context['global_context'], self.context
correct = self.context['correct'] correct = self.context['correct']
......
test_problem_display.js
test_problem_generator.js
test_problem_grader.js
xproblem.js
\ No newline at end of file
// Generated by CoffeeScript 1.4.0
(function() {
var MinimaxProblemDisplay, root,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
MinimaxProblemDisplay = (function(_super) {
__extends(MinimaxProblemDisplay, _super);
function MinimaxProblemDisplay(state, submission, evaluation, container, submissionField, parameters) {
this.state = state;
this.submission = submission;
this.evaluation = evaluation;
this.container = container;
this.submissionField = submissionField;
this.parameters = parameters != null ? parameters : {};
MinimaxProblemDisplay.__super__.constructor.call(this, this.state, this.submission, this.evaluation, this.container, this.submissionField, this.parameters);
}
MinimaxProblemDisplay.prototype.render = function() {};
MinimaxProblemDisplay.prototype.createSubmission = function() {
var id, value, _ref, _results;
this.newSubmission = {};
if (this.submission != null) {
_ref = this.submission;
_results = [];
for (id in _ref) {
value = _ref[id];
_results.push(this.newSubmission[id] = value);
}
return _results;
}
};
MinimaxProblemDisplay.prototype.getCurrentSubmission = function() {
return this.newSubmission;
};
return MinimaxProblemDisplay;
})(XProblemDisplay);
root = typeof exports !== "undefined" && exports !== null ? exports : this;
root.TestProblemDisplay = TestProblemDisplay;
}).call(this);
// Generated by CoffeeScript 1.4.0
(function() {
var TestProblemGenerator, root,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
TestProblemGenerator = (function(_super) {
__extends(TestProblemGenerator, _super);
function TestProblemGenerator(seed, parameters) {
this.parameters = parameters != null ? parameters : {};
TestProblemGenerator.__super__.constructor.call(this, seed, this.parameters);
}
TestProblemGenerator.prototype.generate = function() {
this.problemState.value = this.parameters.value;
return this.problemState;
};
return TestProblemGenerator;
})(XProblemGenerator);
root = typeof exports !== "undefined" && exports !== null ? exports : this;
root.generatorClass = TestProblemGenerator;
}).call(this);
// Generated by CoffeeScript 1.4.0
(function() {
var TestProblemGrader, root,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
TestProblemGrader = (function(_super) {
__extends(TestProblemGrader, _super);
function TestProblemGrader(submission, problemState, parameters) {
this.submission = submission;
this.problemState = problemState;
this.parameters = parameters != null ? parameters : {};
TestProblemGrader.__super__.constructor.call(this, this.submission, this.problemState, this.parameters);
}
TestProblemGrader.prototype.solve = function() {
return this.solution = {
0: this.problemState.value
};
};
TestProblemGrader.prototype.grade = function() {
var allCorrect, id, value, valueCorrect, _ref;
if (!(this.solution != null)) {
this.solve();
}
allCorrect = true;
_ref = this.solution;
for (id in _ref) {
value = _ref[id];
valueCorrect = this.submission != null ? value === this.submission[id] : false;
this.evaluation[id] = valueCorrect;
if (!valueCorrect) {
allCorrect = false;
}
}
return allCorrect;
};
return TestProblemGrader;
})(XProblemGrader);
root = typeof exports !== "undefined" && exports !== null ? exports : this;
root.graderClass = TestProblemGrader;
}).call(this);
// Generated by CoffeeScript 1.4.0
(function() {
var XProblemDisplay, XProblemGenerator, XProblemGrader, root;
XProblemGenerator = (function() {
function XProblemGenerator(seed, parameters) {
this.parameters = parameters != null ? parameters : {};
this.random = new MersenneTwister(seed);
this.problemState = {};
}
XProblemGenerator.prototype.generate = function() {
return console.error("Abstract method called: XProblemGenerator.generate");
};
return XProblemGenerator;
})();
XProblemDisplay = (function() {
function XProblemDisplay(state, submission, evaluation, container, submissionField, parameters) {
this.state = state;
this.submission = submission;
this.evaluation = evaluation;
this.container = container;
this.submissionField = submissionField;
this.parameters = parameters != null ? parameters : {};
}
XProblemDisplay.prototype.render = function() {
return console.error("Abstract method called: XProblemDisplay.render");
};
XProblemDisplay.prototype.updateSubmission = function() {
return this.submissionField.val(JSON.stringify(this.getCurrentSubmission()));
};
XProblemDisplay.prototype.getCurrentSubmission = function() {
return console.error("Abstract method called: XProblemDisplay.getCurrentSubmission");
};
return XProblemDisplay;
})();
XProblemGrader = (function() {
function XProblemGrader(submission, problemState, parameters) {
this.submission = submission;
this.problemState = problemState;
this.parameters = parameters != null ? parameters : {};
this.solution = null;
this.evaluation = {};
}
XProblemGrader.prototype.solve = function() {
return console.error("Abstract method called: XProblemGrader.solve");
};
XProblemGrader.prototype.grade = function() {
return console.error("Abstract method called: XProblemGrader.grade");
};
return XProblemGrader;
})();
root = typeof exports !== "undefined" && exports !== null ? exports : this;
root.XProblemGenerator = XProblemGenerator;
root.XProblemDisplay = XProblemDisplay;
root.XProblemGrader = XProblemGrader;
}).call(this);
...@@ -34,6 +34,7 @@ setup( ...@@ -34,6 +34,7 @@ setup(
"section = xmodule.backcompat_module:SemanticSectionDescriptor", "section = xmodule.backcompat_module:SemanticSectionDescriptor",
"sequential = xmodule.seq_module:SequenceDescriptor", "sequential = xmodule.seq_module:SequenceDescriptor",
"slides = xmodule.backcompat_module:TranslateCustomTagDescriptor", "slides = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"timelimit = xmodule.timelimit_module:TimeLimitDescriptor",
"vertical = xmodule.vertical_module:VerticalDescriptor", "vertical = xmodule.vertical_module:VerticalDescriptor",
"video = xmodule.video_module:VideoDescriptor", "video = xmodule.video_module:VideoDescriptor",
"videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor", "videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor",
......
...@@ -648,7 +648,7 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -648,7 +648,7 @@ class CourseDescriptor(SequenceDescriptor):
raise ValueError("First appointment date must be before last appointment date") raise ValueError("First appointment date must be before last appointment date")
if self.registration_end_date > self.last_eligible_appointment_date: if self.registration_end_date > self.last_eligible_appointment_date:
raise ValueError("Registration end date must be before last appointment date") raise ValueError("Registration end date must be before last appointment date")
self.exam_url = exam_info.get('Exam_URL')
def _try_parse_time(self, key): def _try_parse_time(self, key):
""" """
...@@ -704,6 +704,10 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -704,6 +704,10 @@ class CourseDescriptor(SequenceDescriptor):
else: else:
return None return None
def get_test_center_exam(self, exam_series_code):
exams = [exam for exam in self.test_center_exams if exam.exam_series_code == exam_series_code]
return exams[0] if len(exams) == 1 else None
@property @property
def title(self): def title(self):
return self.display_name return self.display_name
......
...@@ -107,12 +107,13 @@ class @HTMLEditingDescriptor ...@@ -107,12 +107,13 @@ class @HTMLEditingDescriptor
# In order for isDirty() to return true ONLY if edits have been made after setting the text, # In order for isDirty() to return true ONLY if edits have been made after setting the text,
# both the startContent must be sync'ed up and the dirty flag set to false. # both the startContent must be sync'ed up and the dirty flag set to false.
visualEditor.startContent = visualEditor.getContent({format: "raw", no_events: 1}); visualEditor.startContent = visualEditor.getContent({format: "raw", no_events: 1});
visualEditor.isNotDirty = true
@focusVisualEditor(visualEditor) @focusVisualEditor(visualEditor)
@showingVisualEditor = true @showingVisualEditor = true
focusVisualEditor: (visualEditor) => focusVisualEditor: (visualEditor) =>
visualEditor.focus() visualEditor.focus()
# Need to mark editor as not dirty both when it is initially created and when we switch back to it.
visualEditor.isNotDirty = true
if not @$mceToolbar? if not @$mceToolbar?
@$mceToolbar = $(@element).find('table.mceToolbar') @$mceToolbar = $(@element).find('table.mceToolbar')
......
...@@ -73,7 +73,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): ...@@ -73,7 +73,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
# VS[compat]. Take this out once course conversion is done (perhaps leave the uniqueness check) # VS[compat]. Take this out once course conversion is done (perhaps leave the uniqueness check)
# tags that really need unique names--they store (or should store) state. # tags that really need unique names--they store (or should store) state.
need_uniq_names = ('problem', 'sequential', 'video', 'course', 'chapter', 'videosequence') need_uniq_names = ('problem', 'sequential', 'video', 'course', 'chapter', 'videosequence', 'timelimit')
attr = xml_data.attrib attr = xml_data.attrib
tag = xml_data.tag tag = xml_data.tag
......
...@@ -84,18 +84,21 @@ class ConditionalModuleTest(unittest.TestCase): ...@@ -84,18 +84,21 @@ class ConditionalModuleTest(unittest.TestCase):
descriptor = self.modulestore.get_instance(course.id, location, depth=None) descriptor = self.modulestore.get_instance(course.id, location, depth=None)
location = descriptor.location location = descriptor.location
instance_state = instance_states.get(location.category, None) instance_state = instance_states.get(location.category, None)
print "inner_get_module, location.category=%s, inst_state=%s" % (location.category, instance_state) print "inner_get_module, location=%s, inst_state=%s" % (location, instance_state)
return descriptor.xmodule_constructor(test_system)(instance_state, shared_state) return descriptor.xmodule_constructor(test_system)(instance_state, shared_state)
location = Location(["i4x", "edX", "cond_test", "conditional", "condone"]) location = Location(["i4x", "edX", "cond_test", "conditional", "condone"])
module = inner_get_module(location)
def replace_urls(text, staticfiles_prefix=None, replace_prefix='/static/', course_namespace=None): def replace_urls(text, staticfiles_prefix=None, replace_prefix='/static/', course_namespace=None):
return text return text
test_system.replace_urls = replace_urls test_system.replace_urls = replace_urls
test_system.get_module = inner_get_module test_system.get_module = inner_get_module
module = inner_get_module(location)
print "module: ", module print "module: ", module
print "module definition: ", module.definition
print "module children: ", module.get_children()
print "module display items (children): ", module.get_display_items()
html = module.get_html() html = module.get_html()
print "html type: ", type(html) print "html type: ", type(html)
......
import json
import logging
from lxml import etree
from time import time
from xmodule.editing_module import XMLEditingDescriptor
from xmodule.xml_module import XmlDescriptor
from xmodule.x_module import XModule
from xmodule.progress import Progress
from xmodule.exceptions import NotFoundError
log = logging.getLogger(__name__)
class TimeLimitModule(XModule):
'''
Wrapper module which imposes a time constraint for the completion of its child.
'''
def __init__(self, system, location, definition, descriptor, instance_state=None,
shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs)
self.rendered = False
self.beginning_at = None
self.ending_at = None
self.accommodation_code = None
if instance_state is not None:
state = json.loads(instance_state)
if 'beginning_at' in state:
self.beginning_at = state['beginning_at']
if 'ending_at' in state:
self.ending_at = state['ending_at']
if 'accommodation_code' in state:
self.accommodation_code = state['accommodation_code']
# For a timed activity, we are only interested here
# in time-related accommodations, and these should be disjoint.
# (For proctored exams, it is possible to have multiple accommodations
# apply to an exam, so they require accommodating a multi-choice.)
TIME_ACCOMMODATION_CODES = (('NONE', 'No Time Accommodation'),
('ADDHALFTIME', 'Extra Time - 1 1/2 Time'),
('ADD30MIN', 'Extra Time - 30 Minutes'),
('DOUBLE', 'Extra Time - Double Time'),
('TESTING', 'Extra Time -- Large amount for testing purposes')
)
def _get_accommodated_duration(self, duration):
'''
Get duration for activity, as adjusted for accommodations.
Input and output are expressed in seconds.
'''
if self.accommodation_code is None or self.accommodation_code == 'NONE':
return duration
elif self.accommodation_code == 'ADDHALFTIME':
# TODO: determine what type to return
return int(duration * 1.5)
elif self.accommodation_code == 'ADD30MIN':
return (duration + (30 * 60))
elif self.accommodation_code == 'DOUBLE':
return (duration * 2)
elif self.accommodation_code == 'TESTING':
# when testing, set timer to run for a week at a time.
return 3600 * 24 * 7
@property
def has_begun(self):
return self.beginning_at is not None
@property
def has_ended(self):
if not self.ending_at:
return False
return self.ending_at < time()
def begin(self, duration):
'''
Sets the starting time and ending time for the activity,
based on the duration provided (in seconds).
'''
self.beginning_at = time()
modified_duration = self._get_accommodated_duration(duration)
self.ending_at = self.beginning_at + modified_duration
def get_end_time_in_ms(self):
return int(self.ending_at * 1000)
def get_instance_state(self):
state = {}
if self.beginning_at:
state['beginning_at'] = self.beginning_at
if self.ending_at:
state['ending_at'] = self.ending_at
if self.accommodation_code:
state['accommodation_code'] = self.accommodation_code
return json.dumps(state)
def get_html(self):
self.render()
return self.content
def get_progress(self):
''' Return the total progress, adding total done and total available.
(assumes that each submodule uses the same "units" for progress.)
'''
# TODO: Cache progress or children array?
children = self.get_children()
progresses = [child.get_progress() for child in children]
progress = reduce(Progress.add_counts, progresses)
return progress
def handle_ajax(self, dispatch, get):
raise NotFoundError('Unexpected dispatch type')
def render(self):
if self.rendered:
return
# assumes there is one and only one child, so it only renders the first child
children = self.get_display_items()
if children:
child = children[0]
self.content = child.get_html()
self.rendered = True
def get_icon_class(self):
children = self.get_children()
if children:
return children[0].get_icon_class()
else:
return "other"
class TimeLimitDescriptor(XMLEditingDescriptor, XmlDescriptor):
module_class = TimeLimitModule
# For remembering when a student started, and when they should end
stores_state = True
@classmethod
def definition_from_xml(cls, xml_object, system):
children = []
for child in xml_object:
try:
children.append(system.process_xml(etree.tostring(child, encoding='unicode')).location.url())
except Exception as e:
log.exception("Unable to load child when parsing TimeLimit wrapper. Continuing...")
if system.error_tracker is not None:
system.error_tracker("ERROR: " + str(e))
continue
return {'children': children}
def definition_to_xml(self, resource_fs):
xml_object = etree.Element('timelimit')
for child in self.get_children():
xml_object.append(
etree.fromstring(child.export_to_xml(resource_fs)))
return xml_object
...@@ -13,14 +13,8 @@ ASSUMPTIONS: modules have unique IDs, even across different module_types ...@@ -13,14 +13,8 @@ ASSUMPTIONS: modules have unique IDs, even across different module_types
""" """
from django.db import models from django.db import models
#from django.core.cache import cache
from django.contrib.auth.models import User from django.contrib.auth.models import User
#from cache_toolbox import cache_model, cache_relation
#CACHE_TIMEOUT = 60 * 60 * 4 # Set the cache timeout to be four hours
class StudentModule(models.Model): class StudentModule(models.Model):
""" """
Keeps student state for a particular module in a particular course. Keeps student state for a particular module in a particular course.
...@@ -30,6 +24,7 @@ class StudentModule(models.Model): ...@@ -30,6 +24,7 @@ class StudentModule(models.Model):
MODULE_TYPES = (('problem', 'problem'), MODULE_TYPES = (('problem', 'problem'),
('video', 'video'), ('video', 'video'),
('html', 'html'), ('html', 'html'),
('timelimit', 'timelimit'),
) )
## These three are the key for the object ## These three are the key for the object
module_type = models.CharField(max_length=32, choices=MODULE_TYPES, default='problem', db_index=True) module_type = models.CharField(max_length=32, choices=MODULE_TYPES, default='problem', db_index=True)
......
...@@ -8,7 +8,7 @@ from django.core.context_processors import csrf ...@@ -8,7 +8,7 @@ from django.core.context_processors import csrf
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import Http404 from django.http import Http404, HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
#from django.views.decorators.csrf import ensure_csrf_cookie #from django.views.decorators.csrf import ensure_csrf_cookie
...@@ -20,7 +20,7 @@ from courseware.access import has_access ...@@ -20,7 +20,7 @@ from courseware.access import has_access
from courseware.courses import (get_courses, get_course_with_access, from courseware.courses import (get_courses, get_course_with_access,
get_courses_by_university, sort_by_announcement) get_courses_by_university, sort_by_announcement)
import courseware.tabs as tabs import courseware.tabs as tabs
from courseware.models import StudentModuleCache from courseware.models import StudentModule, StudentModuleCache
from module_render import toc_for_course, get_module, get_instance_module, get_module_for_descriptor from module_render import toc_for_course, get_module, get_instance_module, get_module_for_descriptor
from django_comment_client.utils import get_discussion_title from django_comment_client.utils import get_discussion_title
...@@ -153,6 +153,76 @@ def save_child_position(seq_module, child_name, instance_module): ...@@ -153,6 +153,76 @@ def save_child_position(seq_module, child_name, instance_module):
instance_module.state = seq_module.get_instance_state() instance_module.state = seq_module.get_instance_state()
instance_module.save() instance_module.save()
def check_for_active_timelimit_module(request, course_id, course):
'''
Looks for a timing module for the given user and course that is currently active.
If found, returns a context dict with timer-related values to enable display of time remaining.
'''
context = {}
timelimit_student_modules = StudentModule.objects.filter(student=request.user, course_id=course_id, module_type='timelimit')
if timelimit_student_modules:
for timelimit_student_module in timelimit_student_modules:
# get the corresponding section_descriptor for the given StudentModel entry:
module_state_key = timelimit_student_module.module_state_key
timelimit_descriptor = modulestore().get_instance(course_id, Location(module_state_key))
timelimit_module_cache = StudentModuleCache.cache_for_descriptor_descendents(course.id, request.user,
timelimit_descriptor, depth=None)
timelimit_module = get_module_for_descriptor(request.user, request, timelimit_descriptor,
timelimit_module_cache, course.id, position=None)
if timelimit_module is not None and timelimit_module.category == 'timelimit' and \
timelimit_module.has_begun and not timelimit_module.has_ended:
location = timelimit_module.location
# determine where to go when the timer expires:
if 'time_expired_redirect_url' not in timelimit_descriptor.metadata:
raise Http404("No {0} metadata at this location: {1} ".format('time_expired_redirect_url', location))
time_expired_redirect_url = timelimit_descriptor.metadata.get('time_expired_redirect_url')
context['time_expired_redirect_url'] = time_expired_redirect_url
# Fetch the end time (in GMT) as stored in the module when it was started.
# This value should be UTC time as number of milliseconds since epoch.
end_date = timelimit_module.get_end_time_in_ms()
context['timer_expiration_datetime'] = end_date
if 'suppress_toplevel_navigation' in timelimit_descriptor.metadata:
context['suppress_toplevel_navigation'] = timelimit_descriptor.metadata['suppress_toplevel_navigation']
return_url = reverse('jump_to', kwargs={'course_id':course_id, 'location':location})
context['timer_navigation_return_url'] = return_url
return context
def update_timelimit_module(user, course_id, student_module_cache, timelimit_descriptor, timelimit_module):
'''
Updates the state of the provided timing module, starting it if it hasn't begun.
Returns dict with timer-related values to enable display of time remaining.
Returns 'timer_expiration_datetime' in dict if timer is still active, and not if timer has expired.
'''
context = {}
# determine where to go when the exam ends:
if 'time_expired_redirect_url' not in timelimit_descriptor.metadata:
raise Http404("No {0} metadata at this location: {1} ".format('time_expired_redirect_url', timelimit_module.location))
time_expired_redirect_url = timelimit_descriptor.metadata.get('time_expired_redirect_url')
context['time_expired_redirect_url'] = time_expired_redirect_url
if not timelimit_module.has_ended:
if not timelimit_module.has_begun:
# user has not started the exam, so start it now.
if 'duration' not in timelimit_descriptor.metadata:
raise Http404("No {0} metadata at this location: {1} ".format('duration', timelimit_module.location))
# The user may have an accommodation that has been granted to them.
# This accommodation information should already be stored in the module's state.
duration = int(timelimit_descriptor.metadata.get('duration'))
timelimit_module.begin(duration)
# we have changed state, so we need to persist the change:
instance_module = get_instance_module(course_id, user, timelimit_module, student_module_cache)
instance_module.state = timelimit_module.get_instance_state()
instance_module.save()
# the exam has been started, either because the student is returning to the
# exam page, or because they have just visited it. Fetch the end time (in GMT) as stored
# in the module when it was started.
# This value should be UTC time as number of milliseconds since epoch.
context['timer_expiration_datetime'] = timelimit_module.get_end_time_in_ms()
# also use the timed module to determine whether top-level navigation is visible:
if 'suppress_toplevel_navigation' in timelimit_descriptor.metadata:
context['suppress_toplevel_navigation'] = timelimit_descriptor.metadata['suppress_toplevel_navigation']
return context
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -222,7 +292,7 @@ def index(request, course_id, chapter=None, section=None, ...@@ -222,7 +292,7 @@ def index(request, course_id, chapter=None, section=None,
instance_module = get_instance_module(course_id, request.user, course_module, student_module_cache) instance_module = get_instance_module(course_id, request.user, course_module, student_module_cache)
save_child_position(course_module, chapter, instance_module) save_child_position(course_module, chapter, instance_module)
else: else:
raise Http404 raise Http404('No chapter descriptor found with name {}'.format(chapter))
chapter_module = course_module.get_child_by(lambda m: m.url_name == chapter) chapter_module = course_module.get_child_by(lambda m: m.url_name == chapter)
if chapter_module is None: if chapter_module is None:
...@@ -251,7 +321,20 @@ def index(request, course_id, chapter=None, section=None, ...@@ -251,7 +321,20 @@ def index(request, course_id, chapter=None, section=None,
instance_module = get_instance_module(course_id, request.user, chapter_module, student_module_cache) instance_module = get_instance_module(course_id, request.user, chapter_module, student_module_cache)
save_child_position(chapter_module, section, instance_module) save_child_position(chapter_module, section, instance_module)
# check here if this section *is* a timed module.
if section_module.category == 'timelimit':
timer_context = update_timelimit_module(request.user, course_id, student_module_cache,
section_descriptor, section_module)
if 'timer_expiration_datetime' in timer_context:
context.update(timer_context)
else:
# if there is no expiration defined, then we know the timer has expired:
return HttpResponseRedirect(timer_context['time_expired_redirect_url'])
else:
# check here if this page is within a course that has an active timed module running. If so, then
# add in the appropriate timer information to the rendering context:
context.update(check_for_active_timelimit_module(request, course_id, course))
context['content'] = section_module.get_html() context['content'] = section_module.get_html()
else: else:
# section is none, so display a message # section is none, so display a message
......
...@@ -238,8 +238,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None ...@@ -238,8 +238,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
###### PMathML input ###### ###### PMathML input ######
# convert mathml answer to formula # convert mathml answer to formula
try: try:
if dynamath: mmlans = dynamath[0] if dynamath else None
mmlans = dynamath[0]
except Exception, err: except Exception, err:
mmlans = None mmlans = None
if not mmlans: if not mmlans:
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
@import 'course/courseware/sidebar'; @import 'course/courseware/sidebar';
@import 'course/courseware/amplifier'; @import 'course/courseware/amplifier';
@import 'course/layout/calculator'; @import 'course/layout/calculator';
@import 'course/layout/timer';
// course-specific courseware (all styles in these files should be gated by a // course-specific courseware (all styles in these files should be gated by a
// course-specific class). This should be replaced with a better way of // course-specific class). This should be replaced with a better way of
......
div.timer-main {
position: fixed;
z-index: 99;
top: 0;
right: 0;
width: 100%;
border-top: 2px solid #000;
div#timer_wrapper {
position: absolute;
top: -3px;
right: 10px;
background: #000;
color: #fff;
padding: 10px 20px;
border-radius: 3px;
}
.timer_return_url {
display: block;
margin-bottom: 5px;
border-bottom: 1px solid tint(#000, 20%);
padding-bottom: 5px;
font-size: 13px;
}
.timer_label {
color: #b0b0b0;
font-size: 13px;
margin-bottom: 3px;
}
#exam_timer {
font-weight: bold;
font-size: 15px;
letter-spacing: 1px;
}
}
...@@ -59,12 +59,75 @@ ...@@ -59,12 +59,75 @@
}); });
}); });
</script> </script>
% if timer_expiration_datetime:
<script type="text/javascript">
var timer = {
timer_inst : null,
get_remaining_secs : function() {
// set the end time when the template is rendered.
// This value should be UTC time as number of milliseconds since epoch.
var endTime = new Date(${timer_expiration_datetime});
var currentTime = new Date();
var remaining_secs = Math.floor((endTime - currentTime)/1000);
return remaining_secs;
},
get_time_string : function() {
function pretty_time_string(num) {
return ( num < 10 ? "0" : "" ) + num;
}
// count down in terms of hours, minutes, and seconds:
var hours = pretty_time_string(Math.floor(remaining_secs / 3600));
remaining_secs = remaining_secs % 3600;
var minutes = pretty_time_string(Math.floor(remaining_secs / 60));
remaining_secs = remaining_secs % 60;
var seconds = pretty_time_string(Math.floor(remaining_secs));
var remainingTimeString = hours + ":" + minutes + ":" + seconds;
return remainingTimeString;
},
update_time : function(self) {
remaining_secs = self.get_remaining_secs();
if (remaining_secs <= 0) {
self.end(self);
}
$('#exam_timer').text(self.get_time_string(remaining_secs));
},
start : function() { var that = this;
this.timer_inst = setInterval(function(){ that.update_time(that); }, 1000);
},
end : function(self) {
clearInterval(self.timer_inst);
// redirect to specified URL:
window.location = "${time_expired_redirect_url}";
}
}
// start timer right away:
timer.start();
</script>
% endif
</%block> </%block>
<%include file="/courseware/course_navigation.html" args="active_page='courseware'" /> % if timer_expiration_datetime:
<div class="timer-main">
<div id="timer_wrapper">
% if timer_navigation_return_url:
<a href="${timer_navigation_return_url}" class="timer_return_url">Return to Exam</a>
% endif
<div class="timer_label">Time Remaining:</div> <div id="exam_timer" class="timer_value">&nbsp;</div>
</div>
</div>
% endif
% if accordion:
<%include file="/courseware/course_navigation.html" args="active_page='courseware'" />
% endif
<section class="container"> <section class="container">
<div class="course-wrapper"> <div class="course-wrapper">
% if accordion:
<section aria-label="Course Navigation" class="course-index"> <section aria-label="Course Navigation" class="course-index">
<header id="open_close_accordion"> <header id="open_close_accordion">
<a href="#">close</a> <a href="#">close</a>
...@@ -76,6 +139,7 @@ ...@@ -76,6 +139,7 @@
</nav> </nav>
</div> </div>
</section> </section>
% endif
<section class="course-content"> <section class="course-content">
${content} ${content}
......
...@@ -29,13 +29,18 @@ ...@@ -29,13 +29,18 @@
<body class="<%block name='bodyclass'/>"> <body class="<%block name='bodyclass'/>">
% if not suppress_toplevel_navigation:
<%include file="navigation.html" /> <%include file="navigation.html" />
% endif
<section class="content-wrapper"> <section class="content-wrapper">
${self.body()} ${self.body()}
<%block name="bodyextra"/> <%block name="bodyextra"/>
</section> </section>
% if not suppress_toplevel_navigation:
<%include file="footer.html" /> <%include file="footer.html" />
% endif
<%static:js group='application'/> <%static:js group='application'/>
<%static:js group='module-js'/> <%static:js group='module-js'/>
......
${module_content} ${module_content}
%if edit_link: %if location.category in ['problem','video','html']:
% if edit_link:
<div> <div>
<a href="${edit_link}">Edit</a> / <a href="${edit_link}">Edit</a> /
<a href="#${element_id}_xqa-modal" onclick="javascript:getlog('${element_id}', { <a href="#${element_id}_xqa-modal" onclick="javascript:getlog('${element_id}', {
...@@ -9,7 +10,7 @@ ${module_content} ...@@ -9,7 +10,7 @@ ${module_content}
'user': '${user}' 'user': '${user}'
})" id="${element_id}_xqa_log">QA</a> })" id="${element_id}_xqa_log">QA</a>
</div> </div>
% endif % endif
<div><a href="#${element_id}_debug" id="${element_id}_trig">Staff Debug Info</a></div> <div><a href="#${element_id}_debug" id="${element_id}_trig">Staff Debug Info</a></div>
<section id="${element_id}_xqa-modal" class="modal xqa-modal" style="width:80%; left:20%; height:80%; overflow:auto" > <section id="${element_id}_xqa-modal" class="modal xqa-modal" style="width:80%; left:20%; height:80%; overflow:auto" >
...@@ -74,3 +75,5 @@ category = ${category | h} ...@@ -74,3 +75,5 @@ category = ${category | h}
'user': '${user}' 'user': '${user}'
}); });
</script> </script>
%endif
...@@ -14,17 +14,17 @@ ...@@ -14,17 +14,17 @@
<img src="${static.url('images/jobs.jpeg')}"> <img src="${static.url('images/jobs.jpeg')}">
</div> </div>
<header> <header>
<h2>Our mission is to transform learning.</h2> <h2>Our mission is to transform learning.</h2>
<blockquote> <blockquote>
<p>&ldquo;EdX represents a unique opportunity to improve education on our campuses through online learning, while simultaneously creating a bold new educational path for millions of learners worldwide.&rdquo;</p> <p>&ldquo;EdX represents a unique opportunity to improve education on our campuses through online learning, while simultaneously creating a bold new educational path for millions of learners worldwide.&rdquo;</p>
<cite>&mdash;Rafael Reif, MIT President </cite> <cite>&mdash;Rafael Reif, MIT President </cite>
</blockquote> </blockquote>
<blockquote> <blockquote>
<p>&ldquo;EdX gives Harvard and MIT an unprecedented opportunity to dramatically extend our collective reach by conducting groundbreaking research into effective education and by extending online access to quality higher education.&rdquo;</p> <p>&ldquo;EdX gives Harvard and MIT an unprecedented opportunity to dramatically extend our collective reach by conducting groundbreaking research into effective education and by extending online access to quality higher education.&rdquo;</p>
<cite>&mdash;Drew Faust, Harvard President</cite> <cite>&mdash;Drew Faust, Harvard President</cite>
</blockquote> </blockquote>
</header> </header>
</div> </div>
</section> </section>
...@@ -34,25 +34,45 @@ ...@@ -34,25 +34,45 @@
<section class="jobs-wrapper"> <section class="jobs-wrapper">
<section class="jobs-listing"> <section class="jobs-listing">
<article id="" class="job"> <article id="intro" class="job">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h3>EdX is looking to add new talent to our team! </h3> <h3>EdX is looking to add new talent to our team! </h3>
<p align="center"><em>Our mission is to give a world-class education to everyone, everywhere, regardless of gender, income or social status</em></p> <p align="center"><em>Our mission is to give a world-class education to everyone, everywhere, regardless of gender, income or social status</em></p>
<p>Today, EdX.org, a not-for-profit provides hundreds of thousands of people from around the globe with access to free education.&nbsp; We offer amazing quality classes by the best professors from the best schools. We enable our members to uncover a new passion that will transform their lives and their communities.</p> <p>Today, EdX.org, a not-for-profit provides hundreds of thousands of people from around the globe with access to free education.&nbsp; We offer amazing quality classes by the best professors from the best schools. We enable our members to uncover a new passion that will transform their lives and their communities.</p>
<p>Around the world-from coast to coast, in over 192 countries, people are making the decision to take one or several of our courses. As we continue to grow our operations, we are looking for talented, passionate people with great ideas to join the edX team. We aim to create an environment that is supportive, diverse, and as fun as our brand. If you're results-oriented, dedicated, and ready to contribute to an unparalleled member experience for our community, we really want you to apply.</p> <p>Around the world-from coast to coast, in over 192 countries, people are making the decision to take one or several of our courses. As we continue to grow our operations, we are looking for talented, passionate people with great ideas to join the edX team. We aim to create an environment that is supportive, diverse, and as fun as our brand. If you&rsquo;re results-oriented, dedicated, and ready to contribute to an unparalleled member experience for our community, we really want you to apply.</p>
<p>As part of the edX team, you&rsquo;ll receive:</p> <p>As part of the edX team, you&rsquo;ll receive:</p>
<ul> <ul>
<li>Competitive compensation</li> <li>Competitive compensation</li>
<li>Generous benefits package</li> <li>Generous benefits package</li>
<li>Free lunch every day</li> <li>Free lunch every day</li>
<li>A great working experience where everyone cares</li> <li>A great working experience where everyone cares and wants to change the world (no, we’re not kidding)</li>
</ul> </ul>
<p>While we appreciate every applicant's interest, only those under consideration will be contacted. We regret that phone calls will not be accepted.</p> <p>While we appreciate every applicant&rsquo;s interest, only those under consideration will be contacted. We regret that phone calls will not be accepted. Equal opportunity employer.</p>
</div> </div>
</article> </article>
<article id="associate-legal-counsel" class="job"> <!-- Template
<article id="SOME-JOB" class="job">
<div class="inner-wrapper">
<h3><strong>TITLE</strong></h3>
<p>INTRO</p>
<p><strong>Responsibilities:</strong></p>
<ul>
<li>A LIST</li>
</ul>
<p><strong>Qualifications:</strong></p>
<ul>
<li>A LIST</li>
</ul>
<p>TEXT</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>
</article>
-->
<article id="associate-legal-counsel" class="job">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h3><strong>ASSOCIATE LEGAL COUNSEL</strong></h3> <h3><strong>ASSOCIATE LEGAL COUNSEL</strong></h3>
...@@ -67,133 +87,375 @@ ...@@ -67,133 +87,375 @@
</ul> </ul>
<p><strong>Requirements:</strong></p> <p><strong>Requirements:</strong></p>
<li>JD from an accredited law school</li>
<li>Massachusetts bar admission required</li>
<li>2-3 years of transactional experience at a major law firm and/or as an in-house counselor</li>
<li>Substantial IP licensing experience</li>
<li>Knowledge of copyright, trademark and patent law</li>
<li>Experience with open source content and open source software preferred</li>
<li>Outstanding communications skills (written and oral)</li>
<li>Experience with drafting and legal review of internet privacy policies and terms of use.</li>
<li>Understanding of how to balance legal risks with business objectives</li>
<li>Ability to develop an innovative approach to legal issues in support of strategic business initiatives</li>
<li>An internal business and customer focused proactive attitude with ability to prioritize effectively</li>
<li>Experience with higher education preferred but not required</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> <ul>
<li>JD from an accredited law school</li>
<li>Massachusetts bar admission required</li>
<li>2-3 years of transactional experience at a major law firm and/or as an in-house counselor</li>
<li>Substantial IP licensing experience</li>
<li>Knowledge of copyright, trademark and patent law</li>
<li>Experience with open source content and open source software preferred</li>
<li>Outstanding communications skills (written and oral)</li>
<li>Experience with drafting and legal review of Internet privacy policies and terms of use.</li>
<li>Understanding of how to balance legal risks with business objectives</li>
<li>Ability to develop an innovative approach to legal issues in support of strategic business initiatives</li>
<li>An internal business and customer focused proactive attitude with ability to prioritize effectively</li>
<li>Experience with higher education preferred but not required</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="director-of-education-services" class="job">
<div class="inner-wrapper">
<h3><strong>DIRECTOR OF EDUCATIONAL SERVICES</strong></h3>
<p>The edX Director of Education Services reporting to the VP of Engineering and Educational Services is responsible for:</p>
<ol>
<li>Delivering 20 new courses in 2013 in collaboration with the partner Universities
<ul>
<li><p>Reporting to the Director of Educational Services are the Video production team, responsible for post-production of Course Video. The Director must understand how to balance artistic quality and learning objectives, and reduce production time so that video capabilities are readily accessible and at reasonable costs.</p>
<li>Reporting to the Director are a small team of Program Managers, who are responsible for managing the day to day of course production and operations. The Director must be experienced in capacity planning and operations, understand how to deploy lean collaboration and able to build alliances inside edX and the University. In conjunction with the Program Managers, the Director of Educational Services will supervise the collection of research, the retrospectives with Professors and the assembly of best practices in course production and operations. The three key deliverables are the use of a well-defined lean process for onboarding Professors, the development of tracking tools, and assessment of effectiveness of Best Practices.
<li> Also reporting to the Director of Education Services are content engineers and Course Fellows, skilled in the development of edX assessments. The Director of Educational Services will also be responsible for communicating to the VP of Engineering requirements for new types of course assessments. Course Fellows are extremely talented Ph.D.’s who work directly with the Professors to define and develop assessments and course curriculum.</li>
</ul>
</li>
<li>Training and Onboarding of 30 Partner Universities and Affiliates
<ul>
<li>The edX Director of Educational Services is responsible for building out the Training capabilities and delivery mechanisms for onboarding Professors at partner Universities. The edX Director must build out both the Training Team and the curriculum. Training will be delivered in both online courses, self-paced formats, and workshops. The training must cover a curriculum that enables partner institutions to be completely independent. Additionally, partner institutions should be engaged to contribute to the curriculum and partner with edX in the delivery of the material. The curriculum must exemplify the best in online learning, so the Universities are inspired to offer the kind of learning they have experienced in their edX Training.</li>
<li>Expand and extend the education goals of the partner Universities by operationalizing best practices.</li>
<li>Engage with University Boards to design and define the success that the technology makes possible.</li>
</ul>
</li>
<li>Growing the Team, Growing the Business
<ul>
<li>The edX Director will be responsible for working with Business Development to identify revenue opportunities and build profitable plans to grow the business and grow the team.</li>
<li>Maintain for-profit nimbleness in an organization committed to non-profit ideals.</li>
<li>Design scalable solutions to opportunities revealed by technical innovations</li>
</ul>
</li>
<li>Integrating a Strong Team within Strong Organization
<ul>
<li>Connect organization’s management and University leadership with consistent and high quality expectations and deployment</li>
<li>Integrate with a highly collaborative leadership team to maximize talents of the organization</li>
<li>Successfully escalate issues within and beyond the organization to ensure the best possible educational outcome for students and Universities</li>
</ul>
</li>
</ol>
<p><strong>Skills:</strong></p>
<ul>
<li>Ability to lead simultaneous initiatives in an entrepreneurial culture</li>
<li>Self-starter, challenger, strategic planner, analytical thinker</li>
<li>Excellent written and verbal skills</li>
<li>Strong, proactive leadership</li>
<li>Experience with deploying educational technologies on a large scale</li>
<li>Develop team skills in a ferociously intelligent group</li>
<li>Fan the enthusiasm of the partner Universities when the enormity of the transition they are facing becomes intimidating</li>
<li>Encourage creativity to allow the technology to provoke pedagogical possibilities that brick and mortar classes have precluded.</li>
<li>Lean and Agile thinking and training. Experienced in scrum or kanban.</li>
<li>Design and deliver hiring/development plans which meet rapidly changing skill needs.</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="manager-of-training-services" class="job">
<div class="inner-wrapper">
<h3><strong>MANAGER OF TRAINING SERVICES</strong></h3>
<p>The Manager of Training Services is an integral member of the edX team, a leader who is also a doer, working hands-on in the development and delivery of edX&rsquo;s training portfolio. Reporting to the Director of Educational Services, the manager will be a strategic thinker, providing leadership and vision in the development of world-class training solutions tailored to meet the diverse needs of edX Universities, partners and stakeholders</p>
<p><strong>Responsibilities:</strong></p>
<ul>
<li>Working with the Director of Educational Services, create and manage a world-class training program that includes in-person workshops and online formats such as self-paced courses, and webinars.</li>
<li>Work across a talented team of product developers, video producers and content experts to identify training needs and proactively develop training curricula for new products and services as they are deployed.</li>
<li>Develop the means for sharing and showcasing edX best practices for both internal and external audiences.</li>
<li>Apply sound instructional design theory and practice in the development of all edX training resources. </li>
<li>Work with program managers to develop training benchmarks and Key Performance Indicators. Monitor progress and proactively make adjustments as necessary.</li>
<li>Collaborate with product development on creating documentation and user guides. </li>
<li>Provide on-going evaluation of the effectiveness of edX training programs.</li>
<li>Assist in the revision/refinement of training curricula and resources. </li>
<li>Grow a train-the-trainer organization with edX partners, identifying expert edX users to provide on-site peer assistance. </li>
<li>Deliver internal and external trainings.</li>
<li>Coordinate with internal teams to ensure appropriate preparation for trainings, and follow-up after delivery.</li>
<li>Maintain training reporting database and training records.</li>
<li>Produce training evaluation reports, training support plans, and training improvement plans. </li>
<li>Quickly become an expert on edX’s standards, procedures and tools.</li>
<li>Stay current on emerging trends in eLearning, platform support and implementation strategy.</li>
</ul>
<p><strong>Requirements:</strong></p>
<ul>
<li>Minimum of 5-7 years experience developing and delivering educational training, preferably in an educational technology organization. </li>
<li>Lean and Agile thinking and training. Experienced in Scrum or kanban.</li>
<li>Excellent interpersonal skills including proven presentation and facilitation skills.</li>
<li>Strong oral and written communication skills.</li>
<li>Proven experience with production and delivery of online training programs that utilize asychronous and synchronous delivery mechanisms. </li>
<li>Flexibility to work on a variety of initiatives; prior startup experience preferred.</li>
<li>Outstanding work ethic, results-oriented, and creative/innovative style.</li>
<li>Proactive, optimistic approach to problem solving.</li>
<li>Commitment to constant personal and organizational improvement.</li>
<li>Willingness to travel to partner sites as needed. </li>
<li>Bachelors required, Master’s in Education, organizational learning, or other related field preferred. </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>
<article id="instructional-designer" class="job"> <article id="instructional-designer" class="job">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h3><strong>INSTRUCTIONAL DESIGNER</strong> &mdash; CONTRACT OPPORTUNITY</h3> <h3><strong>INSTRUCTIONAL DESIGNER</strong></h3>
<p>The Instructional Designer will work collaboratively with the edX content and engineering teams to plan, develop and deliver highly engaging and media rich online courses. The Instructional Designer will be a flexible thinker, able to determine and apply sound pedagogical strategies to unique situations and a diverse set of academic disciplines.</p> <p>The Instructional Designer will work collaboratively with the edX content and engineering teams to plan, develop and deliver highly engaging and media rich online courses. The Instructional Designer will be a flexible thinker, able to determine and apply sound pedagogical strategies to unique situations and a diverse set of academic disciplines.</p>
<p><strong>Responsibilities:</strong></p> <p><strong>Responsibilities:</strong></p>
<ul> <ul>
<li>Work with the video production team, product managers and course staff on the implementation of instructional design approaches in the development of media and other course materials.</li> <li>Work with the video production team, product managers and course staff on the implementation of instructional design approaches in the development of media and other course materials.</li>
<li>Based on course staff and faculty input, articulate learning objectives and align them to design strategies and assessments.</li> <li>Based on course staff and faculty input, articulate learning objectives and align them to design strategies and assessments.</li>
<li>Develop flipped classroom instructional strategies in coordination with community college faculty. </li> <li>Develop flipped classroom instructional strategies in coordination with community college faculty.</li>
<li>Produce clear and instructionally effective copy, instructional text, and audio and video scripts</li> <li>Produce clear and instructionally effective copy, instructional text, and audio and video scripts</li>
<li>Identify and deploy instructional design best practices for edX course staff and faculty as needed.</li> <li>Identify and deploy instructional design best practices for edX course staff and faculty as needed.</li>
<li>Create course communication style guides. Train and coach teaching staff on best practices for communication and discussion management.</li> <li>Create course communication style guides. Train and coach teaching staff on best practices for communication and discussion management.</li>
<li>Serve as a liaison to instructional design teams based at X universities.</li> <li>Serve as a liaison to instructional design teams based at our partner Universities.</li>
<li>Consult on peer review processes to be used by learners in selected courses.</li> <li>Consult on peer review processes to be used by learners in selected courses.</li>
<li>Ability to apply game-based learning theory and design into selected courses as appropriate.</li> <li>Ability to apply game-based learning theory and design into selected courses as appropriate.</li>
<li>Use learning analytics and metrics to inform course design and revision process.</li> <li>Use learning analytics and metrics to inform course design and revision process.</li>
<li>Collaborate with key research and learning sciences stakeholders at edX and partner institutions for the development of best practices for MOOC teaching and learning and course design.</li> <li>Collaborate with key research and learning sciences stakeholders at edX and partner institutions for the development of best practices for MOOC teaching and learning and course design.</li>
<li>Support the development of pilot courses and modules used for sponsored research initiatives.</li> <li>Support the development of pilot courses and modules used for sponsored research initiatives.</li>
</ul> </ul>
<p><strong>Qualifications:</strong></p> <p><strong>Qualifications:</strong></p>
<ul> <ul>
<li>Master's Degree in Educational Technology, Instructional Design or related field. Experience in higher education with additional experience in a start-up or research environment preferable.</li> <li>Master's Degree in Educational Technology, Instructional Design or related field. Experience in higher education with additional experience in a start-up or research environment preferable.</li>
<li>Excellent interpersonal and communication (written and verbal), project management, problem-solving and time management skills. The ability to be flexible with projects and to work on multiple courses essential. Ability to meet deadlines and manage expectations of constituents.</li> <li>Excellent interpersonal and communication (written and verbal), project management, problem-solving and time management skills. The ability to be flexible with projects and to work on multiple courses essential.</li> Ability to meet deadlines and manage expectations of constituents.
<li>Capacity to develop new and relevant technology skills. Experience using game theory design and learning analytics to inform instructional design decisions and strategy.</li> <li>Capacity to develop new and relevant technology skills. Experience using game theory design and learning analytics to inform instructional design decisions and strategy.</li>
<li>Technical Skills: Video and screencasting experience. LMS Platform experience, xml, HTML, CSS, Adobe Design Suite, Camtasia or Captivate experience. Experience with web 2.0 collaboration tools.</li> <li>Technical Skills: Video and screencasting experience. LMS Platform experience, xml, HTML, CSS, Adobe Design Suite, Camtasia or Captivate experience. Experience with web 2.0 collaboration tools.</li>
</ul> </ul>
<p>Eligible candidates will be invited to respond to an Instructional Design task based on current or future edX course development needs.</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>Eligible candidates will be invited to respond to an Instructional Design task based on current or future edX course development needs.</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="member-services-manager" class="job">
<article id="program-manager" class="job">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h3><strong>MEMBER SERVICES MANAGER </strong></h3> <h3><strong>PROGRAM MANAGER</strong></h3>
<p>The edX Member Services Manager is responsible for both defining support best practices and directly supporting edX members by handling or routing issues that come in from our websites, email and social media tools.&nbsp; We are looking for a passionate person to help us define and own this experience. While this is a Manager level position, we see this candidate quickly moving through the ranks, leading a larger team of employees over time. This staff member will be running our fast growth support organization.</p> <p>edX Program Managers (PM) lead the edX's course production process. They are systems thinkers who manage the creation of a course from start to finish. PMs work with University Professors and course staff to help them take advantage of edX services to create world class online learning offerings and encourage the exploration of an emerging form of higher education.</p>
<p><strong>Responsibilities:</strong></p> <p><strong>Responsibilities:</strong></p>
<ul> <ul>
<li>Define and rollout leading technology, best practices and policies to support a growing team of member care representatives.</li> <li>Create and execute the course production cycle. PMs are able to examine and explain what they do in great detail and able to think abstractly about people, time, and processes. They coordinate the efforts of multiple</li> teams engaged in the production of the courses assigned to them.
<li>Provide reports and visibility into member care metrics.</li> <li>Train partners and drive best practices adoption. PMs train course staff from partner institutions and help them adopt best practices for workflow and tools. </li>
<li>Identify a staffing plan that mirrors growth and work to grow the team with passionate, member-first focused staff.</li> <li>Build capacity. Mentor staff at partner institutions, train the trainers that help them scale their course production ability.</li>
<li>Manage member services staff to predefined service levels. </li> <li>Create visibility. PMs are responsible for making the state of the course production system accessible and comprehensible to all stakeholders. They are capable of training Course development teams in Scrum and</li> Kanban, and are Lean thinkers and educators.
<li>Resolve issues according to edX policies; escalates non-routine issues.</li> <li>Improve workflows. PMs are responsible for carefully assessing the methods and outputs of each course and adjusting them to take best advantage of available resources.</li>
<li>Educate members on edX policies and getting started</li> <li>Encourage innovation. Spark creativity in course teams to build new courses that could never be produced in brick and mortar settings.</li>
<li>May assist new members with edX procedures and processing registration issues.</li> </ul>
<li>Provides timely follow-up and resolution to issues.</li> <p><strong>Qualifications:</strong></p>
<li>A passion for doing the right thing - at edX the member is always our top priority<br> <ul>
</li> <li>Bachelor's Degree. Master's Degree preferred.</li>
<li>At least 2 years of experience working with University faculty and administrators.</li>
<li>Proven record of successful Scrum or Kanban project management, including use of project management tools. </li>
<li>Ability to create processes that systematically provide solutions to open ended challenges.</li>
<li>Excellent interpersonal and communication (written and verbal) skills, the ability to define and solve technical, process and organizational problems, and time management skills.</li>
<li>Proactive, optimistic approach to problem solving.</li>
<li>Commitment to constant personal and organizational improvement.</li>
</ul>
<p><strong>Preferred qualifications</strong></p>
<ul>
<li>Some teaching experience, </li>
<li>Online course design and development experience.</li>
<li>Experience with Lean and Agile thinking and processes.</li>
<li>Experience with online collaboration tools</li>
<li>Familiarity with video production.</li>
<li>Basic HTML, XML, programming skills.</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="project-manager-pmo" class="job">
<div class="inner-wrapper">
<h3><strong>PROJECT MANAGER (PMO)</strong></h3>
<p>As a fast paced, rapidly growing organization serving the evolving online higher education market, edX maximizes its talents and resources. To help make the most of this unapologetically intelligent and dedicated team, we seek a project manager to increase the accuracy of our resource and schedule estimates and our stakeholder satisfaction.</p>
<p><strong>Responsibilities:</strong></p>
<ul>
<li>Coordinate multiple projects to bring Courses, Software Product and Marketing initiatives to market, all of which are related, which have both dedicated and shared resources.</li>
<li>Provide, at a moment’s notice, the state of development, so that priorities can be enforced or reset, so that future expectations can be set accurately.</li>
<li>Develop lean processes that supports a wide variety of efforts which draw on a shared resource pool.</li>
<li>Develop metrics on resource use that support the leadership team in optimizing how they respond to unexpected challenges and new opportunities.</li>
<li>Accurately and clearly escalate only those issues which need escalation for productive resolution. Assist in establishing consensus for all other issues.</li>
<li>Advise the team on best practices, whether developed internally or as industry standards.</li>
<li>Recommend to the leadership team how to re-deploy key resources to better match stated priorities.</li>
<li>Help the organization deliver on its commitments with more consistency and efficiency. Allow the organization to respond to new opportunities with more certainty in its ability to forecast resource needs.</li>
<li>Select and maintain project management tools for Scrum and Kanban that can serve as the standard for those we use with our partners.</li>
<li>Forecast future resource needs given the strategic direction of the organization.</li>
</ul>
<p><strong>Skills:</strong></p>
<ul>
<li>Bachelor’s degree or higher</li>
<li>Exquisite communication skills, especially listening</li>
<li>Inexhaustible attention to detail with the ability to let go of perfection</li>
<li>Deep commitment to Lean project management, including a dedication to its best intentions not just its rituals</li>
<li>Sense of humor and humility</li>
<li>Ability to hold on to the important in the face of the urgent</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="director-of-product-management" class="job">
<div class="inner-wrapper">
<h3><strong>DIRECTOR, PRODUCT MANAGEMENT</strong></h3>
<p>When the power of edX is at its fullest, individuals become the students they had always hoped to be, Professors teach the courses they had always imagined and Universities offer educational opportunities never before seen. None of that happens by accident, so edX is seeking a Product Manager who can keep their eyes on the future and their heart and hands with a team of ferociously intelligent and dedicated technologists.
</p>
<p>The responsibility of a Product Manager is first and foremost to provide evidence to the development team that what they build will succeed in the marketplace. It is the responsibility of the Product Manager to define the product backlog and the team to build the backlog. The Product Manager is one of the most highly leveraged individuals in the Engineering organization. They work to bring a deep knowledge of the Customer – Students, Professors and Course Staff to the product roadmap. The Product Manager is well-versed in the data and sets the KPI’s that drives the team, the Product Scorecard and the Company Scorecard. They are expected to become experts in the business of online learning, familiar with blended models, MOOC’s and University and Industry needs and the competition. The Product Manager must be able to understand the edX stakeholders.
</p>
<p><strong>Responsibilities:</strong></p>
<ul>
<li>Assess users’ needs, whether students, Professors or Universities.</li>
<li>Research markets and competitors to provide data driven decisions.</li>
<li>Work with multiple engineering teams, through consensus and with data-backed arguments, in order to provide technology which defines the state of the art for online courses.</li>
<li>Repeatedly build and launch new products and services, complete with the training, documentation and metrics needed to enhance the already impressive brands of the edX partner institutions.</li>
<li>Establish the vision and future direction of the product with input from edX leadership and guidance from partner organizations.</li>
<li>Work in a lean organization, committed to Scrum and Kanban.</li>
</ul> </ul>
<p><strong>Qualifications:</strong></p> <p><strong>Qualifications:</strong></p>
<ul> <ul>
<li>5-8 years in a call center or support team management</li> <li>Bachelor’s degree or higher in a Technical Area</li>
<li>Exemplary customer service skills</li> <li>MBA or Masters in Design preferred</li>
<li>Experience in creating and rolling out support/service best practices </li> <li>Proven ability to develop and implement strategy</li>
<li>Solid computer skills &ndash; must be fluent with desktop applications and have a basic understanding of web technologies (i.e. basic HTML)</li> <li>Exquisite organizational skills</li>
<li>Problem solving - the individual identifies and resolves problems in a timely manner, gathers and analyzes information skillfully and maintains confidentiality.</li> <li>Deep analytical skills</li>
<li>Interpersonal skills - the individual maintains confidentiality, remains open to others' ideas and exhibits willingness to try new things.</li> <li>Social finesse and business sense</li>
<li>Oral communication - the individual speaks clearly and persuasively in positive or negative situations and demonstrates group presentation skills.</li> <li>Scrum, Kanban</li>
<li>Written communication &ndash; the individual edits work for spelling and grammar, presents numerical data effectively and is able to read and interpret written information.</li> <li>Infatuation with technology, in all its frustrating and fragile complexity</li>
<li>Adaptability - the individual adapts to changes in the work environment, manages competing demands and is able to deal with frequent change, delays or unexpected events.</li> <li>Top flight communication skills, oral and written, with teams which are centrally located and spread all over the world.</li>
<li>Dependability - the individual is consistently at work and on time, follows instructions, responds to management direction and solicits feedback to improve performance.</li> <li>Personal commitment and experience of the transformational possibilities of higher education</li>
<li>College degree</li>
</ul> </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> <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="director_of_pr_and_communications" class="job">
<article id="content-engineer" class="job">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h3><strong>DIRECTOR OF PR AND COMMUNICATIONS</strong></h3> <h3><strong>CONTENT ENGINEER</strong></h3>
<p>The edX Director of PR & Communications is responsible for creating and executing all PR strategy and providing company-wide leadership to help create and refine the edX core messages and identity as the revolutionary global leader in both on-campus and worldwide education. The Director will design and direct a communications program that conveys cohesive and compelling information about edX's mission, activities, personnel and products while establishing a distinct identity for edX as the leader in online education for both students and learning institutions.</p> <p>Content engineers help create the technology for specific courses. The tasks include:</p>
<ul>
<li>Developing of course-specific user-facing elements, such as the circuit editor and simulator. </li>
<li>Integrating course materials into courses</li>
<li>Creating programs to grade questions designed with complex technical features</li>
<li>Knowledge of Python, XML, and/or JavaScript is desired. Strong interest and background in pedagogy and education is desired as well.</li>
<li>Building course components in straight XML or through our course authoring tool, edX Studio.</li>
<li>Assisting University teams and in house staff take advantage of new course software, including designing and developing technical refinements for implementation.</li>
<li>Pushing content to production servers predictably and cleanly.</li>
<li>Sending high volumes of course email adhering to email engine protocols.</li>
</ul>
<p><strong>Qualifications:</strong></p>
<ul>
<li>Bachelor’s degree or higher</li>
<li>Thorough knowledge of Python, DJango, XML,HTML, CSS , Javascript and backbone.js</li>
<li>Ability to work on multiple projects simultaneously without splintering</li>
<li>Tactfully escalate conflicting deadlines or priorities only when needed. Otherwise help the team members negotiate a solution.</li>
<li>Unfailing attention to detail, especially the details the course teams have seen so often they don’t notice them anymore.</li>
<li>Readily zoom from the big picture to the smallest course component to notice when typos, inconsistencies or repetitions have unknowingly crept in</li>
<li>Curiosity to step into the shoes of an online student working to master the course content.</li>
<li>Solid interpersonal skills, especially good listening</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="director-engineering-open-source" class="job">
<div class="inner-wrapper">
<h3><strong>DIRECTOR ENGINEERING, OPEN SOURCE COMMUNITY MANAGER</strong></h3>
<p>In edX courses, students make (and break) electronic circuits, they manipulate molecules on the fly and they do it all at once, in their tens of thousands. We have great Professors and great Universities. But we can’t possibly keep up with all the great ideas out there, so we’re making our platform open source, to turn up the volume on great education. To do that well, we’ll need a Director of Engineering who can lead our Open Source Community efforts.</p>
<p><strong>Responsibilities:</strong></p> <p><strong>Responsibilities:</strong></p>
<ul> <ul>
<li>Develop and execute goals and strategy for a comprehensive external and internal communications program focused on driving student engagement around courses and institutional adoption of the edX learning platform.</li> <li>Define and implement software design standards that make the open source community most welcome and productive.</li>
<li>Work with media, either directly or through our agency of record, to establish edX as the industry leader in global learning.</li> <li>Work with others to establish the governance standards for the edX Open Source Platform, establish the infrastructure, and manage the team to deliver releases and leverage our University partners and stakeholders to</li> make the edX platform the world’s best learning platform.
<li>Work with key influencers including government officials on a global scale to ensure the edX mission, content and tools are embraced and supported worldwide.</li> <li>Help the organization recognize the benefits and limitations inherent in open source solutions.</li>
<li>Work with marketing colleagues to co-develop and/or monitor and evaluate the content and delivery of all communications messages and collateral.</li> <li>Establish best practices and key tool usage, especially those based on industry standards.</li>
<li>Initiate and/or plan thought leadership events developed to heighten target-audience awareness; participate in meetings and trade shows</li> <li>Provide visibility for the leadership team into the concerns and challenges faced by the open source community.</li>
<li>Conduct periodic research to determine communications benchmarks</li> <li>Foster a thriving community by providing the communication, documentation and feedback that they need to be enthusiastic.</li>
<li>Inform employees about edX's vision, values, policies, and strategies to enable them to perform their jobs efficiently and drive morale.</li> <li>Maximize the good code design coming from the open source community.</li>
<li>Work with and manage existing communications team to effectively meet strategic goals.</li> <li>Provide the wit and firmness that the community needs to channel their energy productively.</li>
<li>Tactfully balance the internal needs of the organization to pursue new opportunities with the community’s need to participate in the platform’s evolution.</li>
<li>Shorten lines of communication and build trust across entire team</li>
</ul> </ul>
<p><strong>Qualifications:</strong></p> <p><strong>Qualifications:</strong></p>
<ul> <ul>
<li>Ten years of experience in PR and communications</li>
<li>Ability to work creatively and provide company-wide leadership in a fast-paced, dynamic start-up environment required</li> <li>Bachelors, preferably Masters in Computer Science</li>
<li>Adaptability - the individual adapts to changes in the work environment, manages competing demands and is able to deal with frequent change, delays or unexpected events.</li> <li>Solid communication skills, especially written</li>
<li>Experience in working in successful consumer-focused startups preferred</li> <li>Committed to Agile practice, Scrum and Kanban</li>
<li>PR agency experience in setting strategy for complex multichannel, multinational organizations a plus.</li> <li>Charm and humor</li>
<li>Extensive writing experience and simply amazing oral, written, and interpersonal communications skills</li> <li>Deep familiarity with Open Source, participant and contributor</li>
<li>B.A./B.S. in communications or related field</li> <li>Python, Django, Javascript</li>
<li>Commitment to support your technical recommendations, both within and beyond the organization.</li>
</ul> </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> <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="software-engineer" class="job">
<div class="inner-wrapper">
<h3><strong>SOFTWARE ENGINEER</strong></h3>
<p>edX is looking for engineers who can contribute to its Open Source learning platform. We are a small team with a startup, lean culture, committed to building open-source software that scales and dramatically changes the face of education. Our ideal candidates are hands on developers who understand how to build scalable, service based systems, preferably in Python and have a proven track record of bringing their ideas to market. We are looking for engineers with all levels of experience, but you must be a proven leader and outstanding developer to work at edX.</p>
<p>There are a number of projects for which we are recruiting engineers:<br>
<p><strong>Learning Management System</strong>: We are developing an Open Source Standard that allows for the creation of instructional plug-ins and assessments in our platform. You must have a deep interest in semantics of learning, and able to build services at scale.</p>
<p><strong>Forums</strong>: We are building our own Forums software because we believe that education requires a forums platform capable of supporting learning communities. We are analytics driven. The ideal Forums candidates are focused on metrics and key performance indicators, understand how to build on top of a service based architecture and are wedded to quick iterations and user feedback.
<p><strong>Analytics</strong>: We are looking for a platform engineer who has deep MongoDB or no SQL database experience. Our data infrastructure needs to scale to multiple terabytes. Researchers from Harvard, MIT, Berkeley and edX Universities will use our analytics platform to research and examine the fundamentals of learning. The analytics engineer will be responsible for both building out an analytics platform and a pub-sub and real-time pipeline processing architecture. Together they will allow researchers, students and Professors access to never before seen analytics.
<p><strong>Course Development Authoring Tools</strong>: We are committed to making it easy for Professors to develop and publish their courses online. So we are building the tools that allow them to readily convert their vision to an online course ready for thousands of students. </p>
<p><strong>Requirements:</strong></p>
<ul>
<li>Real-world experience with Python or other dynamic development languages.</li>
<li>Able to code front to back, including HTML, CSS, Javascript, Django, Python</li>
<li>You must be committed to an agile development practices, in Scrum or Kanban</li>
<li>Demonstrated skills in building Service based architecture</li>
<li>Test Driven Development</li>
<li>Committed to Documentation best practices so your code can be consumed in an open source environment</li>
<li>Contributor to or consumer of Open Source Frameworks</li>
<li>BS in Computer Science from top-tier institution</li>
<li>Acknowledged by peers as a technology leader </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>
<section class="jobs-sidebar"> <section class="jobs-sidebar">
<h2>Positions</h2> <h2>Positions</h2>
<nav> <nav>
<a href="#associate-legal-counsel">Associate Legal Counsel</a>
<a href="#director-of-education-services">Director of Education Services</a>
<a href="#manager-of-training-services">Manager of Training Services</a>
<a href="#instructional-designer">Instructional Designer</a> <a href="#instructional-designer">Instructional Designer</a>
<a href="#member-services-manager">Member Services Manager</a> <a href="#program-manager">Program Manager</a>
<a href="#director_of_pr_and_communications">Director of PR & Communications</a> <a href="#project-manager-pmo">Project Manager (PMO)</a>
<a href="#director-of-product-management">Director of Product Management</a>
<a href="#content-engineer">Content Engineer</a>
<a href="#director-engineering-open-source">Director Engineering, Open Source Community Manager</a>
<a href="#software-engineer">Software Engineer</a>
</nav> </nav>
<h2>How to Apply</h2> <h2>How to Apply</h2>
<p>E-mail your resume, coverletter 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>
<h2>Our Location</h2> <h2>Our Location</h2>
<p>11 Cambridge Center <br> <p>11 Cambridge Center <br>
Cambridge, MA 02142</p> Cambridge, MA 02142</p>
</section> </section>
</section> </section>
</section> </section>
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