Commit 793bbfd3 by Calen Pennington

Set up dev environment for testing xml vs mongo vs split_mongo modulestores

parents bda46c42 dff0c10b
......@@ -9,6 +9,7 @@ gfortran
liblapack-dev
libfreetype6-dev
libpng12-dev
libjpeg-dev
libxml2-dev
libxslt-dev
yui-compressor
......
import logging
from static_replace import replace_static_urls
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from lxml import etree
import re
from django.http import HttpResponseBadRequest, Http404
from django.http import Http404
def get_module_info(store, location, parent_location=None, rewrite_static_links=False):
try:
if location.revision is None:
module = store.get_item(location)
else:
module = store.get_item(location)
except ItemNotFoundError:
raise Http404
try:
if location.revision is None:
module = store.get_item(location)
else:
module = store.get_item(location)
except ItemNotFoundError:
# create a new one
template_location = Location(['i4x', 'edx', 'templates', location.category, 'Empty'])
module = store.clone_item(template_location, location)
data = module.data
if rewrite_static_links:
data = replace_static_urls(
module.data,
None,
course_namespace=Location([
module.location.tag,
module.location.org,
module.location.course,
data = module.data
if rewrite_static_links:
data = replace_static_urls(
module.data,
None,
None
])
)
course_namespace=Location([
module.location.tag,
module.location.org,
module.location.course,
None,
None
])
)
return {
'id': module.location.url(),
......@@ -41,7 +39,6 @@ def get_module_info(store, location, parent_location=None, rewrite_static_links=
def set_module_info(store, location, post_data):
module = None
isNew = False
try:
if location.revision is None:
module = store.get_item(location)
......@@ -55,7 +52,6 @@ def set_module_info(store, location, post_data):
# presume that we have an 'Empty' template
template_location = Location(['i4x', 'edx', 'templates', location.category, 'Empty'])
module = store.clone_item(template_location, location)
isNew = True
if post_data.get('data') is not None:
data = post_data['data']
......
import json
import shutil
from django.test.client import Client
from override_settings import override_settings
from django.test.utils import override_settings
from django.conf import settings
from django.core.urlresolvers import reverse
from path import path
......@@ -10,6 +10,7 @@ import json
from fs.osfs import OSFS
import copy
from mock import Mock
from json import dumps, loads
from student.models import Registration
from django.contrib.auth.models import User
......@@ -207,6 +208,15 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# check for custom_tags
self.verify_content_existence(ms, root_dir, location, 'custom_tags', 'custom_tag_template')
# check for graiding_policy.json
fs = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012')
self.assertTrue(fs.exists('grading_policy.json'))
# compare what's on disk compared to what we have in our course
with fs.open('grading_policy.json','r') as grading_policy:
on_disk = loads(grading_policy.read())
course = ms.get_item(location)
self.assertEqual(on_disk, course.definition['data']['grading_policy'])
# remove old course
delete_course(ms, cs, location)
......
......@@ -249,8 +249,7 @@ class CourseGradingTest(CourseTestCase):
altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__)
self.assertDictEqual(test_grader.__dict__, altered_grader.__dict__, "cutoff add D")
test_grader.grace_period = {'hours': '4'}
test_grader.grace_period = {'hours': 4, 'minutes': 5, 'seconds': 0}
altered_grader = CourseGradingModel.update_from_json(test_grader.__dict__)
print test_grader.__dict__
print altered_grader.__dict__
......
import json
import shutil
from django.test.client import Client
from override_settings import override_settings
from django.conf import settings
from django.core.urlresolvers import reverse
from path import path
......
......@@ -2,7 +2,6 @@ import json
import copy
from time import time
from django.test import TestCase
from override_settings import override_settings
from django.conf import settings
from student.models import Registration
......
......@@ -126,7 +126,8 @@ def index(request):
course.location.course,
course.location.name]))
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
})
......@@ -1254,6 +1255,10 @@ def edge(request):
@login_required
@expect_json
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
# 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
......
......@@ -156,13 +156,8 @@ class CourseGradingModel(object):
if 'grace_period' in graceperiodjson:
graceperiodjson = graceperiodjson['grace_period']
timedelta_kwargs = dict(
(key, float(val))
for key, val
in graceperiodjson.items()
if key in ('days', 'seconds', 'minutes', 'hours')
)
grace_rep = timedelta(**timedelta_kwargs)
# lms requires these to be in a fixed order
grace_rep = "{0[hours]:d} hours {0[minutes]:d} minutes {0[seconds]:d} seconds".format(graceperiodjson)
descriptor = get_modulestore(course_location).get_item(course_location)
descriptor.lms.graceperiod = grace_rep
......@@ -241,6 +236,7 @@ class CourseGradingModel(object):
@staticmethod
def convert_set_grace_period(descriptor):
<<<<<<< HEAD
# 5 hours 59 minutes 59 seconds => converted to iso format
rawgrace = descriptor.lms.graceperiod
if rawgrace:
......@@ -265,6 +261,14 @@ class CourseGradingModel(object):
return graceperiod
else:
return None
=======
# 5 hours 59 minutes 59 seconds => { hours: 5, minutes : 59, seconds : 59}
rawgrace = descriptor.metadata.get('graceperiod', None)
if rawgrace:
parsedgrace = {str(key): int(val) for (val, key) in re.findall('\s*(\d+)\s*(\w+)', rawgrace)}
return parsedgrace
else: return None
>>>>>>> origin/master
@staticmethod
def parse_grader(json_grader):
......
......@@ -165,13 +165,6 @@ STATICFILES_DIRS = [
# This is how you would use the textbook images locally
# ("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
TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
......
......@@ -58,6 +58,9 @@ $(document).ready(function() {
drop: onSectionReordered,
greedy: true
});
// stop clicks on drag bars from doing their thing w/o stopping drag
$('.drag-handle').click(function(e) {e.preventDefault(); });
});
......@@ -202,13 +205,17 @@ function _handleReorder(event, ui, parentIdField, childrenSelector) {
children = _.without(children, ui.draggable.data('id'));
}
// add to this parent (figure out where)
for (var i = 0; i < _els.length; i++) {
if (!ui.draggable.is(_els[i]) && ui.offset.top < $(_els[i]).offset().top) {
for (var i = 0, bump = 0; i < _els.length; i++) {
if (ui.draggable.is(_els[i])) {
bump = -1; // bump indicates that the draggable was passed in the dom but not children's list b/c
// it's not in that list
}
else if (ui.offset.top < $(_els[i]).offset().top) {
// insert at i in children and _els
ui.draggable.insertBefore($(_els[i]));
// TODO figure out correct way to have it remove the style: top:n; setting (and similar line below)
ui.draggable.attr("style", "position:relative;");
children.splice(i, 0, ui.draggable.data('id'));
children.splice(i + bump, 0, ui.draggable.data('id'));
break;
}
}
......
......@@ -227,7 +227,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
time = 0;
}
var newVal = new Date(date.getTime() + time * 1000);
if (cacheModel.get(fieldName).getTime() !== newVal.getTime()) {
if (!cacheModel.has(fieldName) || cacheModel.get(fieldName).getTime() !== newVal.getTime()) {
cacheModel.save(fieldName, newVal, { error: CMS.ServerError});
}
}
......
......@@ -107,6 +107,8 @@
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/grader-select-view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/overview.js')}"></script>
<script type="text/javascript">
$(document).ready(function() {
......
......@@ -37,7 +37,9 @@
<h1>My Courses</h1>
<article class="my-classes">
% 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">
%for course, url in courses:
<li>
......
......@@ -97,7 +97,7 @@
$cancelButton.bind('click', hideNewUserForm);
$('.new-user-button').bind('click', showNewUserForm);
$body.bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
$('body').bind('keyup', { $cancelButton: $cancelButton }, checkForCancel);
$('.remove-user').click(function() {
$.ajax({
......
......@@ -206,7 +206,7 @@ from contentstore import utils
<section class="setting-details-marketing">
<header>
<h3>Introducing Your Course</h3>
<span class="detail">Information for perspective students</span>
<span class="detail">Information for prospective students</span>
</header>
<div class="row row-col2">
......
......@@ -2,7 +2,7 @@ import django.test
from django.contrib.auth.models import User
from django.conf import settings
from override_settings import override_settings
from django.test.utils import override_settings
from course_groups.models import CourseUserGroup
from course_groups.cohorts import (get_cohort, get_course_cohorts,
......
......@@ -13,12 +13,18 @@ log = logging.getLogger(__name__)
def _url_replace_regex(prefix):
"""
Match static urls in quotes that don't end in '?raw'.
To anyone contemplating making this more complicated:
http://xkcd.com/1171/
"""
return r"""
(?x) # flags=re.VERBOSE
(?P<quote>\\?['"]) # the opening quotes
(?P<prefix>{prefix}) # theeprefix
(?P<rest>.*?) # everything else in the url
(?P=quote) # the first matching closing quote
(?x) # flags=re.VERBOSE
(?P<quote>\\?['"]) # the opening quotes
(?P<prefix>{prefix}) # the prefix
(?P<rest>.*?) # everything else in the url
(?P=quote) # the first matching closing quote
""".format(prefix=prefix)
......@@ -74,12 +80,20 @@ def replace_static_urls(text, data_directory, course_namespace=None):
quote = match.group('quote')
rest = match.group('rest')
# Don't mess with things that end in '?raw'
if rest.endswith('?raw'):
return original
# course_namespace is not None, then use studio style urls
if course_namespace is not None and not isinstance(modulestore(), XMLModuleStore):
url = StaticContent.convert_legacy_static_url(rest, course_namespace)
# In debug mode, if we can find the url as is,
elif settings.DEBUG and finders.find(rest, True):
return original
# Otherwise, look the file up in staticfiles_storage, and append the data directory if needed
else:
course_path = "/".join((data_directory, rest))
try:
if staticfiles_storage.exists(rest):
url = staticfiles_storage.url(rest)
......
from nose.tools import assert_equals
from static_replace import replace_static_urls, replace_course_urls
import re
from nose.tools import assert_equals, assert_true, assert_false
from static_replace import (replace_static_urls, replace_course_urls,
_url_replace_regex)
from mock import patch, Mock
from xmodule.modulestore import Location
from xmodule.modulestore.mongo import MongoModuleStore
......@@ -75,3 +78,34 @@ def test_data_dir_fallback(mock_storage, mock_modulestore, mock_settings):
mock_storage.exists.return_value = False
assert_equals('"/static/data_dir/file.png"', replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY))
def test_raw_static_check():
"""
Make sure replace_static_urls leaves alone things that end in '.raw'
"""
path = '"/static/foo.png?raw"'
assert_equals(path, replace_static_urls(path, DATA_DIRECTORY))
text = 'text <tag a="/static/js/capa/protex/protex.nocache.js?raw"/><div class="'
assert_equals(path, replace_static_urls(path, text))
def test_regex():
yes = ('"/static/foo.png"',
'"/static/foo.png"',
"'/static/foo.png'")
no = ('"/not-static/foo.png"',
'"/static/foo', # no matching quote
)
regex = _url_replace_regex('/static/')
for s in yes:
print 'Should match: {0!r}'.format(s)
assert_true(re.match(regex, s))
for s in no:
print 'Should not match: {0!r}'.format(s)
assert_false(re.match(regex, s))
from django.conf import settings
from django.test import TestCase
import os
from override_settings import override_settings
from django.test.utils import override_settings
from tempfile import NamedTemporaryFile
from status import get_site_status_msg
......
import datetime
import feedparser
#import itertools
import json
import logging
import random
import string
import sys
#import time
import urllib
import uuid
......@@ -16,17 +14,19 @@ from django.contrib.auth import logout, authenticate, login
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.core.cache import cache
from django.core.context_processors import csrf
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.db import IntegrityError
from django.http import HttpResponse, HttpResponseForbidden, Http404
from django.http import HttpResponse, HttpResponseRedirect, Http404
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 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,
TestCenterRegistration, TestCenterRegistrationForm,
PendingNameChange, PendingEmailChange,
......@@ -38,12 +38,15 @@ from certificates.models import CertificateStatuses, certificate_status_for_stud
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location
#from datetime import date
from collections import namedtuple
from courseware.courses import get_courses, sort_by_announcement
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
......@@ -1066,27 +1069,134 @@ def accept_name_change(request):
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
def test_center_login(request):
if not settings.MITX_FEATURES.get('ENABLE_PEARSON_HACK_TEST'):
raise Http404
# errors are returned by navigating to the error_url, adding a query parameter named "code"
# 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")
# TODO: check remaining parameters, and maybe at least log if they're not matching
# expected values....
# registration_id = request.POST.get("registrationID")
exit_url = request.POST.get("exitURL")
error_url = request.POST.get("errorURL")
# exit_url = request.POST.get("exitURL")
# 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":
user = authenticate(username=settings.PEARSON_TEST_USER,
password=settings.PEARSON_TEST_PASSWORD)
login(request, user)
return redirect('/courses/MITx/6.002x/2012_Fall/courseware/Final_Exam/Final_Exam_Fall_2012/')
else:
return HttpResponseForbidden()
time_accommodation_code = 'TESTING'
if time_accommodation_code:
timelimit_module.accommodation_code = time_accommodation_code
instance_module = get_instance_module(course_id, testcenteruser.user, timelimit_module, timelimit_module_cache)
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):
......
......@@ -18,8 +18,9 @@ def jsdate_to_time(field):
"""
if field is None:
return field
elif isinstance(field, basestring): # iso format but ignores time zone assuming it's Z
d = datetime.datetime(*map(int, re.split('[^\d]', field)[:6])) # stop after seconds. Debatable
elif isinstance(field, basestring):
# ISO format but ignores time zone assuming it's Z.
d = datetime.datetime(*map(int, re.split('[^\d]', field)[:6])) # stop after seconds. Debatable
return d.utctimetuple()
elif isinstance(field, (int, long, float)):
return time.gmtime(field / 1000)
......
......@@ -632,8 +632,12 @@ class MultipleChoiceResponse(LoncapaResponse):
# define correct choices (after calling secondary setup)
xml = self.xml
cxml = xml.xpath('//*[@id=$id]//choice[@correct="true"]', id=xml.get('id'))
self.correct_choices = [contextualize_text(choice.get('name'), self.context) for choice in cxml]
cxml = xml.xpath('//*[@id=$id]//choice', id=xml.get('id'))
# contextualize correct attribute and then select ones for which
# correct = "true"
self.correct_choices = [contextualize_text(choice.get('name'), self.context)
for choice in cxml
if contextualize_text(choice.get('correct'), self.context) == "true"]
def mc_setup_response(self):
'''
......@@ -999,7 +1003,7 @@ def sympy_check2():
self.context['debug'] = self.system.DEBUG
# exec the check function
if type(self.code) == str:
if isinstance(self.code, basestring):
try:
exec self.code in self.context['global_context'], self.context
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(
"section = xmodule.backcompat_module:SemanticSectionDescriptor",
"sequential = xmodule.seq_module:SequenceDescriptor",
"slides = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"timelimit = xmodule.timelimit_module:TimeLimitDescriptor",
"vertical = xmodule.vertical_module:VerticalDescriptor",
"video = xmodule.video_module:VideoDescriptor",
"videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor",
......
......@@ -510,7 +510,7 @@ class CourseDescriptor(SequenceDescriptor):
# utility function to get datetime objects for dates used to
# compute the is_new flag and the sorting_score
def to_datetime(timestamp):
return datetime.fromtimestamp(time.mktime(timestamp))
return datetime(*timestamp[:6])
def get_date(field):
timetuple = self._try_parse_time(field)
......@@ -660,7 +660,7 @@ class CourseDescriptor(SequenceDescriptor):
raise ValueError("First appointment date must be before last appointment date")
if self.registration_end_date > self.last_eligible_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):
"""
......@@ -716,6 +716,10 @@ class CourseDescriptor(SequenceDescriptor):
else:
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
def title(self):
return self.display_name
......
......@@ -107,12 +107,13 @@ class @HTMLEditingDescriptor
# 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.
visualEditor.startContent = visualEditor.getContent({format: "raw", no_events: 1});
visualEditor.isNotDirty = true
@focusVisualEditor(visualEditor)
@showingVisualEditor = true
focusVisualEditor: (visualEditor) =>
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?
@$mceToolbar = $(@element).find('table.mceToolbar')
......
......@@ -74,7 +74,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
# 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.
need_uniq_names = ('problem', 'sequential', 'video', 'course', 'chapter', 'videosequence')
need_uniq_names = ('problem', 'sequential', 'video', 'course', 'chapter', 'videosequence', 'timelimit')
attr = xml_data.attrib
tag = xml_data.tag
......
......@@ -2,6 +2,7 @@ import logging
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from fs.osfs import OSFS
from json import dumps
def export_to_xml(modulestore, contentstore, course_location, root_dir, course_dir):
......@@ -27,6 +28,12 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d
# export the course updates
export_extra_content(export_fs, modulestore, course_location, 'course_info', 'info', '.html')
# export the grading policy
policies_dir = export_fs.makeopendir('policies')
course_run_policy_dir = policies_dir.makeopendir(course.location.name)
with course_run_policy_dir.open('grading_policy.json', 'w') as grading_policy:
grading_policy.write(dumps(course.definition['data']['grading_policy']))
def export_extra_content(export_fs, modulestore, course_location, category_type, dirname, file_suffix=''):
query_loc = Location('i4x', course_location.org, course_location.course, category_type, None)
......
......@@ -51,9 +51,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
REQUEST_HINT = 'request_hint'
DONE = 'done'
js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]}
js_module_name = "SelfAssessment"
student_answers = List(scope=Scope.student_state, default=[])
scores = List(scope=Scope.student_state, default=[])
hints = List(scope=Scope.student_state, default=[])
......
......@@ -84,18 +84,21 @@ class ConditionalModuleTest(unittest.TestCase):
descriptor = self.modulestore.get_instance(course.id, location, depth=None)
location = descriptor.location
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)
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):
return text
test_system.replace_urls = replace_urls
test_system.get_module = inner_get_module
module = inner_get_module(location)
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()
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
from xblock.core import Float, String, Boolean, Scope
log = logging.getLogger(__name__)
class TimeLimitModule(XModule):
'''
Wrapper module which imposes a time constraint for the completion of its child.
'''
beginning_at = Float(help="The time this timer was started", scope=Scope.student_state)
ending_at = Float(help="The time this timer will end", scope=Scope.student_state)
accomodation_code = String(help="A code indicating accommodations to be given the student", scope=Scope.student_state)
time_expired_redirect_url = String(help="Url to redirect users to after the timelimit has expired", scope=Scope.settings)
duration = Float(help="The length of this timer", scope=Scope.settings)
suppress_toplevel_navigation = Boolean(help="Whether the toplevel navigation should be suppressed when viewing this module", scope=Scope.settings)
def __init__(self, *args, **kwargs):
XModule.__init__(self, *args, **kwargs)
self.rendered = False
# 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_remaining_time_in_ms(self):
return int((self.ending_at - time()) * 1000)
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
......@@ -50,7 +50,7 @@ if Backbone?
convertMath: ->
element = @$(".post-body")
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.text()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
renderResponses: ->
......
......@@ -47,7 +47,7 @@ if Backbone?
convertMath: ->
element = @$(".post-body")
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.text()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
toggleVote: (event) ->
......
......@@ -26,7 +26,7 @@ if Backbone?
convertMath: ->
body = @$el.find(".response-body")
body.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight body.html()
body.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight body.text()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, body[0]]
markAsStaff: ->
......
......@@ -30,7 +30,7 @@ if Backbone?
convertMath: ->
element = @$(".response-body")
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.html()
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.text()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]]
markAsStaff: ->
......
from django.core.urlresolvers import reverse
from override_settings import override_settings
from django.test.utils import override_settings
import xmodule.modulestore.django
......
......@@ -13,14 +13,8 @@ ASSUMPTIONS: modules have unique IDs, even across different module_types
"""
from django.db import models
#from django.core.cache import cache
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):
"""
Keeps student state for a particular module in a particular course.
......@@ -30,6 +24,7 @@ class StudentModule(models.Model):
MODULE_TYPES = (('problem', 'problem'),
('video', 'video'),
('html', 'html'),
('timelimit', 'timelimit'),
)
## These three are the key for the object
module_type = models.CharField(max_length=32, choices=MODULE_TYPES, default='problem', db_index=True)
......
......@@ -11,7 +11,7 @@ from django.test import TestCase
from django.test.client import RequestFactory
from django.conf import settings
from django.core.urlresolvers import reverse
from override_settings import override_settings
from django.test.utils import override_settings
import xmodule.modulestore.django
from xmodule.modulestore.mongo import MongoModuleStore
......
......@@ -8,7 +8,7 @@ from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
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 mitxmako.shortcuts import render_to_response, render_to_string
#from django.views.decorators.csrf import ensure_csrf_cookie
......@@ -21,8 +21,8 @@ from courseware.courses import (get_courses, get_course_with_access,
get_courses_by_university, sort_by_announcement)
import courseware.tabs as tabs
from courseware.model_data import ModelDataCache
from module_render import toc_for_course, get_module
from student.models import UserProfile
from module_render import toc_for_course, get_module_for_descriptor, get_module
from courseware.models import StudentModule
from django_comment_client.utils import get_discussion_title
......@@ -40,7 +40,6 @@ log = logging.getLogger("mitx.courseware")
template_imports = {'urllib': urllib}
def user_groups(user):
"""
TODO (vshnayder): This is not used. When we have a new plan for groups, adjust appropriately.
......@@ -161,6 +160,71 @@ def save_child_position(seq_module, child_name):
seq_module.position = position
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 = {}
# TODO (cpennington): Once we can query the course structure, replace this with such a query
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 = ModelDataCache.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 timelimit_descriptor.time_expired_redirect_url is None:
raise Http404("no time_expired_redirect_url specified at this location: {} ".format(timelimit_module.location))
context['time_expired_redirect_url'] = timelimit_descriptor.time_expired_redirect_url
# Fetch the remaining time relative to the end time as stored in the module when it was started.
# This value should be in milliseconds.
remaining_time = timelimit_module.get_remaining_time_in_ms()
context['timer_expiration_duration'] = remaining_time
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, model_data_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_duration' in dict if timer is still active, and not if timer has expired.
'''
context = {}
# determine where to go when the exam ends:
if timelimit_descriptor.time_expired_redirect_url is None:
raise Http404("No time_expired_redirect_url specified at this location: {} ".format(timelimit_module.location))
context['time_expired_redirect_url'] = timelimit_descriptor.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 timelimit_descriptor.duration is None:
raise Http404("No duration specified at this location: {} ".format(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.
timelimit_module.begin(timelimit_descriptor.duration)
# the exam has been started, either because the student is returning to the
# exam page, or because they have just visited it. Fetch the remaining time relative to the
# end time as stored in the module when it was started.
context['timer_expiration_duration'] = timelimit_module.get_remaining_time_in_ms()
# also use the timed module to determine whether top-level navigation is visible:
context['suppress_toplevel_navigation'] = timelimit_descriptor.suppress_toplevel_navigation
return context
@login_required
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
......@@ -224,7 +288,7 @@ def index(request, course_id, chapter=None, section=None,
if chapter_descriptor is not None:
save_child_position(course_module, chapter)
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)
if chapter_module is None:
......@@ -253,6 +317,20 @@ def index(request, course_id, chapter=None, section=None,
# Save where we are in the chapter
save_child_position(chapter_module, section)
# 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_duration' 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()
else:
# section is none, so display a message
......
......@@ -6,7 +6,7 @@ from django.conf import settings
from mock import Mock
from override_settings import override_settings
from django.test.utils import override_settings
import xmodule.modulestore.django
......
......@@ -15,6 +15,8 @@ from django.http import HttpResponse
from django.utils import simplejson
from django_comment_client.models import Role
from django_comment_client.permissions import check_permissions_by_view
from xmodule.modulestore.exceptions import NoPathToItem
from mitxmako import middleware
import pystache_custom as pystache
......@@ -168,6 +170,7 @@ def initialize_discussion_info(course):
# get all discussion models within this course_id
all_modules = modulestore().get_items(['i4x', course.location.org, course.location.course, 'discussion', None], course_id=course_id)
path_to_locations = {}
for module in all_modules:
skip_module = False
for key in ('id', 'discussion_category', 'for'):
......@@ -175,6 +178,14 @@ def initialize_discussion_info(course):
log.warning("Required key '%s' not in discussion %s, leaving out of category map" % (key, module.location))
skip_module = True
# cdodge: pre-compute the path_to_location. Note this can throw an exception for any
# dangling discussion modules
try:
path_to_locations[module.location] = path_to_location(modulestore(), course.id, module.location)
except NoPathToItem:
log.warning("Could not compute path_to_location for {0}. Perhaps this is an orphaned discussion module?!? Skipping...".format(module.location))
skip_module = True
if skip_module:
continue
......@@ -237,6 +248,7 @@ def initialize_discussion_info(course):
_DISCUSSIONINFO[course.id]['id_map'] = discussion_id_map
_DISCUSSIONINFO[course.id]['category_map'] = category_map
_DISCUSSIONINFO[course.id]['timestamp'] = datetime.now()
_DISCUSSIONINFO[course.id]['path_to_location'] = path_to_locations
class JsonResponse(HttpResponse):
......@@ -392,11 +404,23 @@ def get_courseware_context(content, course):
if id in id_map:
location = id_map[id]["location"].url()
title = id_map[id]["title"]
(course_id, chapter, section, position) = path_to_location(modulestore(), course.id, location)
url = reverse('courseware_position', kwargs={"course_id": course_id,
"chapter": chapter,
"section": section,
"position": position})
# cdodge: did we pre-compute, if so, then let's use that rather than recomputing
if 'path_to_location' in _DISCUSSIONINFO[course.id] and location in _DISCUSSIONINFO[course.id]['path_to_location']:
(course_id, chapter, section, position) = _DISCUSSIONINFO[course.id]['path_to_location'][location]
else:
try:
(course_id, chapter, section, position) = path_to_location(modulestore(), course.id, location)
except NoPathToItem:
# Object is not in the graph any longer, let's just get path to the base of the course
# so that we can at least return something to the caller
(course_id, chapter, section, position) = path_to_location(modulestore(), course.id, course.location)
url = reverse('courseware_position', kwargs={"course_id":course_id,
"chapter":chapter,
"section":section,
"position":position})
content_info = {"courseware_url": url, "courseware_title": title}
return content_info
......
......@@ -15,7 +15,7 @@ import json
from nose import SkipTest
from mock import patch, Mock
from override_settings import override_settings
from django.test.utils import override_settings
# Need access to internal func to put users in the right group
from django.contrib.auth.models import Group
......
......@@ -386,6 +386,46 @@ def instructor_dashboard(request, course_id):
track.views.server_track(request, 'remove-instructor {0}'.format(user), {}, page='idashboard')
#----------------------------------------
# DataDump
elif 'Download CSV of all student profile data' in action:
enrolled_students = User.objects.filter(courseenrollment__course_id=course_id).order_by('username').select_related("profile")
profkeys = ['name', 'language', 'location', 'year_of_birth', 'gender', 'level_of_education',
'mailing_address', 'goals']
datatable = {'header': ['username', 'email'] + profkeys}
def getdat(u):
p = u.profile
return [u.username, u.email] + [getattr(p,x,'') for x in profkeys]
datatable['data'] = [getdat(u) for u in enrolled_students]
datatable['title'] = 'Student profile data for course %s' % course_id
return return_csv('profiledata_%s.csv' % course_id, datatable)
elif 'Download CSV of all responses to problem' in action:
problem_to_dump = request.POST.get('problem_to_dump','')
if problem_to_dump[-4:]==".xml":
problem_to_dump=problem_to_dump[:-4]
try:
(org, course_name, run)=course_id.split("/")
module_state_key="i4x://"+org+"/"+course_name+"/problem/"+problem_to_dump
smdat = StudentModule.objects.filter(course_id=course_id,
module_state_key=module_state_key)
smdat = smdat.order_by('student')
msg+="Found module to reset. "
except Exception as err:
msg+="<font color='red'>Couldn't find module with that urlname. </font>"
msg += "<pre>%s</pre>" % escape(err)
smdat = []
if smdat:
datatable = {'header': ['username', 'state']}
datatable['data'] = [ [x.student.username, x.state] for x in smdat ]
datatable['title'] = 'Student state for problem %s' % problem_to_dump
return return_csv('student_state_from_%s.csv' % problem_to_dump, datatable)
#----------------------------------------
# Group management
elif 'List beta testers' in action:
......
......@@ -22,7 +22,7 @@ from mitxmako.shortcuts import render_to_string
import logging
log = logging.getLogger(__name__)
from override_settings import override_settings
from django.test.utils import override_settings
from django.http import QueryDict
......
......@@ -53,7 +53,6 @@ with open(ENV_ROOT / CONFIG_PREFIX + "env.json") as env_file:
SITE_NAME = ENV_TOKENS['SITE_NAME']
SESSION_COOKIE_DOMAIN = ENV_TOKENS.get('SESSION_COOKIE_DOMAIN')
CSRF_COOKIE_DOMAIN = ENV_TOKENS.get('SESSION_COOKIE_DOMAIN')
BOOK_URL = ENV_TOKENS['BOOK_URL']
MEDIA_URL = ENV_TOKENS['MEDIA_URL']
......
......@@ -238,8 +238,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
###### PMathML input ######
# convert mathml answer to formula
try:
if dynamath:
mmlans = dynamath[0]
mmlans = dynamath[0] if dynamath else None
except Exception, err:
mmlans = None
if not mmlans:
......
......@@ -22,7 +22,7 @@
@import 'course/courseware/sidebar';
@import 'course/courseware/amplifier';
@import 'course/layout/calculator';
@import 'course/layout/timer';
// 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
......
......@@ -67,7 +67,7 @@ section.course-index {
}
.chapter {
width: 100%;
width: 100% !important;
@include box-sizing(border-box);
padding: 11px 14px;
@include linear-gradient(top, rgba(255, 255, 255, .6), rgba(255, 255, 255, 0));
......@@ -99,6 +99,8 @@ section.course-index {
@include border-radius(0);
margin: 0;
padding: 9px 0 9px 9px;
overflow: auto;
width: 100%;
li {
border-bottom: 0;
......
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,76 @@
});
});
</script>
% if timer_expiration_duration:
<script type="text/javascript">
var timer = {
timer_inst : null,
end_time : null,
get_remaining_secs : function(endTime) {
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(self.end_time);
if (remaining_secs <= 0) {
self.end(self);
}
$('#exam_timer').text(self.get_time_string(remaining_secs));
},
start : function() { var that = this;
// set the end time when the template is rendered.
// This value should be UTC time as number of milliseconds since epoch.
this.end_time = new Date((new Date()).getTime() + ${timer_expiration_duration});
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>
<%include file="/courseware/course_navigation.html" args="active_page='courseware'" />
% if timer_expiration_duration:
<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">
<div class="course-wrapper">
% if accordion:
<section aria-label="Course Navigation" class="course-index">
<header id="open_close_accordion">
<a href="#">close</a>
......@@ -76,6 +140,7 @@
</nav>
</div>
</section>
% endif
<section class="course-content">
${content}
......
......@@ -64,6 +64,7 @@ function goto( mode)
<a href="#" onclick="goto('Admin');" class="${modeflag.get('Admin')}">Admin</a> |
<a href="#" onclick="goto('Forum Admin');" class="${modeflag.get('Forum Admin')}">Forum Admin</a> |
<a href="#" onclick="goto('Enrollment');" class="${modeflag.get('Enrollment')}">Enrollment</a> |
<a href="#" onclick="goto('Data');" class="${modeflag.get('Data')}">DataDump</a> |
<a href="#" onclick="goto('Manage Groups');" class="${modeflag.get('Manage Groups')}">Manage Groups</a> ]
</h2>
......@@ -269,6 +270,20 @@ function goto( mode)
##-----------------------------------------------------------------------------
%if modeflag.get('Data'):
<hr width="40%" style="align:left">
<p>
<input type="submit" name="action" value="Download CSV of all student profile data">
</p>
<p> Problem urlname:
<input type="text" name="problem_to_dump" size="40">
<input type="submit" name="action" value="Download CSV of all responses to problem">
</p>
<hr width="40%" style="align:left">
%endif
##-----------------------------------------------------------------------------
%if modeflag.get('Manage Groups'):
%if instructor_access:
<hr width="40%" style="align:left">
......
......@@ -29,13 +29,18 @@
<body class="<%block name='bodyclass'/>">
% if not suppress_toplevel_navigation:
<%include file="navigation.html" />
% endif
<section class="content-wrapper">
${self.body()}
<%block name="bodyextra"/>
</section>
% if not suppress_toplevel_navigation:
<%include file="footer.html" />
% endif
<%static:js group='application'/>
<%static:js group='module-js'/>
......
${module_content}
%if edit_link:
%if location.category in ['problem','video','html']:
% if edit_link:
<div>
<a href="${edit_link}">Edit</a> /
<a href="#${element_id}_xqa-modal" onclick="javascript:getlog('${element_id}', {
......@@ -9,7 +10,7 @@ ${module_content}
'user': '${user}'
})" id="${element_id}_xqa_log">QA</a>
</div>
% endif
% endif
<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" >
......@@ -82,3 +83,5 @@ category = ${category | h}
);
});
</script>
%endif
......@@ -14,17 +14,17 @@
<img src="${static.url('images/jobs.jpeg')}">
</div>
<header>
<h2>Our mission is to transform learning.</h2>
<h2>Our mission is to transform learning.</h2>
<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>
<cite>&mdash;Rafael Reif, MIT President </cite>
</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>
<cite>&mdash;Rafael Reif, MIT President </cite>
</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>
<cite>&mdash;Drew Faust, Harvard President</cite>
</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>
<cite>&mdash;Drew Faust, Harvard President</cite>
</blockquote>
</header>
</div>
</section>
......@@ -34,25 +34,45 @@
<section class="jobs-wrapper">
<section class="jobs-listing">
<article id="" class="job">
<article id="intro" class="job">
<div class="inner-wrapper">
<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>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>
<ul>
<li>Competitive compensation</li>
<li>Generous benefits package</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>
<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>
</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">
<h3><strong>ASSOCIATE LEGAL COUNSEL</strong></h3>
......@@ -67,133 +87,375 @@
</ul>
<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 id="instructional-designer" class="job">
<div class="inner-wrapper">
<h3><strong>INSTRUCTIONAL DESIGNER</strong> &mdash; CONTRACT OPPORTUNITY</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><strong>Responsibilities:</strong></p>
<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>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>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>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>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>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>Support the development of pilot courses and modules used for sponsored research initiatives.</li>
</ul>
<p><strong>Qualifications:</strong></p>
<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>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>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>
</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>
<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><strong>Responsibilities:</strong></p>
<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>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>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>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 our partner Universities.</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>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>Support the development of pilot courses and modules used for sponsored research initiatives.</li>
</ul>
<p><strong>Qualifications:</strong></p>
<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>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>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>
<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>
</article>
<article id="member-services-manager" class="job">
<article id="program-manager" class="job">
<div class="inner-wrapper">
<h3><strong>MEMBER SERVICES 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>
<h3><strong>PROGRAM MANAGER</strong></h3>
<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>
<ul>
<li>Define and rollout leading technology, best practices and policies to support a growing team of member care representatives.</li>
<li>Provide reports and visibility into member care metrics.</li>
<li>Identify a staffing plan that mirrors growth and work to grow the team with passionate, member-first focused staff.</li>
<li>Manage member services staff to predefined service levels. </li>
<li>Resolve issues according to edX policies; escalates non-routine issues.</li>
<li>Educate members on edX policies and getting started</li>
<li>May assist new members with edX procedures and processing registration issues.</li>
<li>Provides timely follow-up and resolution to issues.</li>
<li>A passion for doing the right thing - at edX the member is always our top priority<br>
</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>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>Build capacity. Mentor staff at partner institutions, train the trainers that help them scale their course production ability.</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>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>Encourage innovation. Spark creativity in course teams to build new courses that could never be produced in brick and mortar settings.</li>
</ul>
<p><strong>Qualifications:</strong></p>
<ul>
<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>
<p><strong>Qualifications:</strong></p>
<ul>
<li>5-8 years in a call center or support team management</li>
<li>Exemplary customer service skills</li>
<li>Experience in creating and rolling out support/service best practices </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>Problem solving - the individual identifies and resolves problems in a timely manner, gathers and analyzes information skillfully and maintains confidentiality.</li>
<li>Interpersonal skills - the individual maintains confidentiality, remains open to others' ideas and exhibits willingness to try new things.</li>
<li>Oral communication - the individual speaks clearly and persuasively in positive or negative situations and demonstrates group presentation skills.</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>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>Dependability - the individual is consistently at work and on time, follows instructions, responds to management direction and solicits feedback to improve performance.</li>
<li>College degree</li>
<li>Bachelor’s degree or higher in a Technical Area</li>
<li>MBA or Masters in Design preferred</li>
<li>Proven ability to develop and implement strategy</li>
<li>Exquisite organizational skills</li>
<li>Deep analytical skills</li>
<li>Social finesse and business sense</li>
<li>Scrum, Kanban</li>
<li>Infatuation with technology, in all its frustrating and fragile complexity</li>
<li>Top flight communication skills, oral and written, with teams which are centrally located and spread all over the world.</li>
<li>Personal commitment and experience of the transformational possibilities of higher education</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_pr_and_communications" class="job">
<article id="content-engineer" class="job">
<div class="inner-wrapper">
<h3><strong>DIRECTOR OF PR AND COMMUNICATIONS</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>
<h3><strong>CONTENT ENGINEER</strong></h3>
<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>
<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>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 key influencers including government officials on a global scale to ensure the edX mission, content and tools are embraced and supported worldwide.</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>Initiate and/or plan thought leadership events developed to heighten target-audience awareness; participate in meetings and trade shows</li>
<li>Conduct periodic research to determine communications benchmarks</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>Work with and manage existing communications team to effectively meet strategic goals.</li>
<li>Define and implement software design standards that make the open source community most welcome and productive.</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>Help the organization recognize the benefits and limitations inherent in open source solutions.</li>
<li>Establish best practices and key tool usage, especially those based on industry standards.</li>
<li>Provide visibility for the leadership team into the concerns and challenges faced by the open source community.</li>
<li>Foster a thriving community by providing the communication, documentation and feedback that they need to be enthusiastic.</li>
<li>Maximize the good code design coming from the open source community.</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>
<p><strong>Qualifications:</strong></p>
<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>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>Experience in working in successful consumer-focused startups preferred</li>
<li>PR agency experience in setting strategy for complex multichannel, multinational organizations a plus.</li>
<li>Extensive writing experience and simply amazing oral, written, and interpersonal communications skills</li>
<li>B.A./B.S. in communications or related field</li>
<li>Bachelors, preferably Masters in Computer Science</li>
<li>Solid communication skills, especially written</li>
<li>Committed to Agile practice, Scrum and Kanban</li>
<li>Charm and humor</li>
<li>Deep familiarity with Open Source, participant and contributor</li>
<li>Python, Django, Javascript</li>
<li>Commitment to support your technical recommendations, both within and beyond the organization.</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="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 class="jobs-sidebar">
<h2>Positions</h2>
<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="#member-services-manager">Member Services Manager</a>
<a href="#director_of_pr_and_communications">Director of PR & Communications</a>
<a href="#program-manager">Program Manager</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>
<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>
<p>11 Cambridge Center <br>
Cambridge, MA 02142</p>
Cambridge, MA 02142</p>
</section>
</section>
</section>
......@@ -24,7 +24,6 @@ django_nose
nosexcover==1.0.7
rednose==0.3.3
GitPython==0.3.2.RC1
django-override-settings==1.2
mock==0.8.0
PyYAML==3.10
South==0.7.6
......
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