diff --git a/.gitignore b/.gitignore
index 54cc7a27cb..f98fdf7bf9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,5 @@ courseware/static/js/mathjax/*
db.newaskbot
db.oldaskbot
flushdb.sh
+build
+\#*\#
\ No newline at end of file
diff --git a/ci/build.sh b/ci/build.sh
deleted file mode 100755
index fc8c5bf7f3..0000000000
--- a/ci/build.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#! /bin/bash
-
-set -x
-set -e
-
-#sass sass:static/css -r templates/sass/bourbon/lib/bourbon.rb --style :compressed
-
-if [ -z "${GIT_COMMIT}" ]; then
- GIT_COMMIT=$(git rev-parse HEAD)
-fi
-
-if [ -z "${GIT_BRANCH}" ]; then
- GIT_BRANCH=$(git symbolic-ref -q HEAD)
- GIT_BRANCH=${GIT_BRANCH##refs/heads/}
- GIT_BRANCH=${GIT_BRANCH:-HEAD}
-fi
-GIT_BRANCH=${GIT_BRANCH##origin/}
-GIT_BRANCH=${GIT_BRANCH//\//_}
-
-if [ -z "${BUILD_NUMBER}" ]; then
- BUILD_NUMBER=dev
-fi
-
-ID=mitx-${GIT_BRANCH}-${BUILD_NUMBER}-${GIT_COMMIT}
-REPO_ROOT=$(dirname $0)/..
-BUILD_DIR=${REPO_ROOT}/build
-
-mkdir -p ${BUILD_DIR}
-tar --exclude=.git --exclude=build --transform="s#^#mitx/#" -czf ${BUILD_DIR}/${ID}.tgz ${REPO_ROOT}
diff --git a/djangoapps/courseware/capa/calc.py b/djangoapps/courseware/capa/calc.py
index 63c5c9de01..fb64c58139 100644
--- a/djangoapps/courseware/capa/calc.py
+++ b/djangoapps/courseware/capa/calc.py
@@ -5,6 +5,7 @@ import operator
import re
import numpy
+import numbers
import scipy.constants
from pyparsing import Word, alphas, nums, oneOf, Literal
@@ -121,7 +122,7 @@ def evaluator(variables, functions, string, cs=False):
def number_parse_action(x): # [ '7' ] -> [ 7 ]
return [super_float("".join(x))]
def exp_parse_action(x): # [ 2 ^ 3 ^ 2 ] -> 512
- x = [e for e in x if type(e) in [float, numpy.float64, numpy.complex]] # Ignore ^
+ x = [e for e in x if isinstance(e, numbers.Number)] # Ignore ^
x.reverse()
x=reduce(lambda a,b:b**a, x)
return x
@@ -130,7 +131,7 @@ def evaluator(variables, functions, string, cs=False):
return x[0]
if 0 in x:
return float('nan')
- x = [1./e for e in x if type(e) == float] # Ignore ^
+ x = [1./e for e in x if isinstance(e, numbers.Number)] # Ignore ||
return 1./sum(x)
def sum_parse_action(x): # [ 1 + 2 - 3 ] -> 0
total = 0.0
@@ -217,4 +218,7 @@ if __name__=='__main__':
print evaluator({},{}, "-(7+5)")
print evaluator({},{}, "-0.33")
print evaluator({},{}, "-.33")
+ print evaluator({},{}, "5+1*j")
+ print evaluator({},{}, "j||1")
+ print evaluator({},{}, "e^(j*pi)")
print evaluator({},{}, "5+7 QWSEKO")
diff --git a/djangoapps/courseware/capa/inputtypes.py b/djangoapps/courseware/capa/inputtypes.py
index 2ede94ad8d..789887243d 100644
--- a/djangoapps/courseware/capa/inputtypes.py
+++ b/djangoapps/courseware/capa/inputtypes.py
@@ -8,7 +8,8 @@ class textline(object):
def render(element, value, state):
eid=element.get('id')
count = int(eid.split('_')[-2])-1 # HACK
- context = {'id':eid, 'value':value, 'state':state, 'count':count}
+ size = element.get('size')
+ context = {'id':eid, 'value':value, 'state':state, 'count':count, 'size': size}
html=render_to_string("textinput.html", context)
return etree.XML(html)
diff --git a/djangoapps/courseware/capa/responsetypes.py b/djangoapps/courseware/capa/responsetypes.py
index 56ea51ddb9..c173eb6faf 100644
--- a/djangoapps/courseware/capa/responsetypes.py
+++ b/djangoapps/courseware/capa/responsetypes.py
@@ -1,5 +1,6 @@
import json
import math
+import numbers
import numpy
import random
import scipy
@@ -37,7 +38,7 @@ class numericalresponse(object):
def __init__(self, xml, context):
self.xml = xml
self.correct_answer = contextualize_text(xml.get('answer'), context)
- self.correct_answer = float(self.correct_answer)
+ self.correct_answer = complex(self.correct_answer)
self.tolerance_xml = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default',
id=xml.get('id'))[0]
self.tolerance = contextualize_text(self.tolerance_xml, context)
@@ -49,7 +50,10 @@ class numericalresponse(object):
student_answer = student_answers[self.answer_id]
try:
correct = compare_with_tolerance (evaluator(dict(),dict(),student_answer), self.correct_answer, self.tolerance)
- except:
+ # We should catch this explicitly.
+ # I think this is just pyparsing.ParseException, calc.UndefinedVariable:
+ # But we'd need to confirm
+ except:
raise StudentInputError('Invalid input -- please use a number only')
if correct:
@@ -141,7 +145,7 @@ class formularesponse(object):
except:
#traceback.print_exc()
raise StudentInputError("Error in formula")
- if math.isnan(student_result) or math.isinf(student_result):
+ if numpy.isnan(student_result) or numpy.isinf(student_result):
return {self.answer_id:"incorrect"}
if not compare_with_tolerance(student_result, instructor_result, self.tolerance):
return {self.answer_id:"incorrect"}
@@ -153,9 +157,9 @@ class formularesponse(object):
keys and all non-numeric values stripped out. All values also
converted to float. Used so we can safely use Python contexts.
'''
- d=dict([(k, float(d[k])) for k in d if type(k)==str and \
+ d=dict([(k, numpy.complex(d[k])) for k in d if type(k)==str and \
k.isalnum() and \
- (type(d[k]) == float or type(d[k]) == int) ])
+ isinstance(d[k], numbers.Number)])
return d
def get_answers(self):
diff --git a/djangoapps/courseware/grades.py b/djangoapps/courseware/grades.py
index 43ed0c8155..75b24cebcc 100644
--- a/djangoapps/courseware/grades.py
+++ b/djangoapps/courseware/grades.py
@@ -1,23 +1,26 @@
-import logging
-import urllib
-from lxml import etree
-
import courseware.content_parser as content_parser
-from models import StudentModule
-from django.conf import settings
import courseware.modules
+import logging
+import random
+import urllib
+from collections import namedtuple
+from django.conf import settings
+from lxml import etree
+from models import StudentModule
from student.models import UserProfile
log = logging.getLogger("mitx.courseware")
+Score = namedtuple("Score", "earned possible graded section")
+
def get_grade(user, problem, cache):
## HACK: assumes max score is fixed per problem
id = problem.get('id')
correct = 0
# If the ID is not in the cache, add the item
- if id not in cache:
+ if id not in cache:
module = StudentModule(module_type = 'problem', # TODO: Move into StudentModule.__init__?
module_id = id,
student = user,
@@ -44,6 +47,17 @@ def get_grade(user, problem, cache):
return (correct, total)
def grade_sheet(student):
+ """
+ This pulls a summary of all problems in the course. It returns a dictionary with two datastructures:
+
+ - courseware_summary is a summary of all sections with problems in the course. It is organized as an array of chapters,
+ each containing an array of sections, each containing an array of scores. This contains information for graded and ungraded
+ problems, and is good for displaying a course summary with due dates, etc.
+
+ - grade_summary is a summary of how the final grade breaks down. It is an array of "sections". Each section can either be
+ a conglomerate of scores (like labs or homeworks) which has subscores and a totalscore, or a section can be all from one assignment
+ (such as a midterm or final) and only has a totalscore. Each section has a weight that shows how it contributes to the total grade.
+ """
dom=content_parser.course_file(student)
course = dom.xpath('//course/@name')[0]
xmlChapters = dom.xpath('//course[@name=$course]/chapter', course=course)
@@ -54,11 +68,14 @@ def grade_sheet(student):
response_by_id[response.module_id] = response
- total_scores = {}
+
+ totaled_scores = {}
chapters=[]
for c in xmlChapters:
sections = []
chname=c.get('name')
+
+
for s in dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section',
course=course, chname=chname):
problems=dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section[@name=$section]//problem',
@@ -84,22 +101,26 @@ def grade_sheet(student):
else:
correct = total
- scores.append((int(correct),total, graded ))
+ scores.append( Score(int(correct),total, graded, s.get("name")) )
+
+ section_total = Score(sum([score.earned for score in scores]),
+ sum([score.possible for score in scores]),
+ False,
+ p.get("id"))
- section_total = (sum([score[0] for score in scores]),
- sum([score[1] for score in scores]))
+ graded_total = Score(sum([score.earned for score in scores if score.graded]),
+ sum([score.possible for score in scores if score.graded]),
+ True,
+ p.get("id"))
- graded_total = (sum([score[0] for score in scores if score[2]]),
- sum([score[1] for score in scores if score[2]]))
-
- #Add the graded total to total_scores
+ #Add the graded total to totaled_scores
format = s.get('format') if s.get('format') else ""
subtitle = s.get('subtitle') if s.get('subtitle') else format
if format and graded_total[1] > 0:
- format_scores = total_scores[ format ] if format in total_scores else []
- format_scores.append( graded_total + (s.get("name"),) )
- total_scores[ format ] = format_scores
+ format_scores = totaled_scores.get(format, [])
+ format_scores.append( graded_total )
+ totaled_scores[ format ] = format_scores
score={'section':s.get("name"),
'scores':scores,
@@ -114,7 +135,20 @@ def grade_sheet(student):
chapters.append({'course':course,
'chapter' : c.get("name"),
'sections' : sections,})
-
+
+
+ grade_summary = grade_summary_6002x(totaled_scores)
+
+ return {'courseware_summary' : chapters,
+ 'grade_summary' : grade_summary}
+
+
+def grade_summary_6002x(totaled_scores):
+ """
+ This function takes the a dictionary of (graded) section scores, and applies the course grading rules to create
+ the grade_summary. For 6.002x this means homeworks and labs all have equal weight, with the lowest 2 of each
+ being dropped. There is one midterm and one final.
+ """
def totalWithDrops(scores, drop_count):
#Note that this key will sort the list descending
@@ -131,12 +165,12 @@ def grade_sheet(student):
return aggregate_score, dropped_indices
#Figure the homework scores
- homework_scores = total_scores['Homework'] if 'Homework' in total_scores else []
+ homework_scores = totaled_scores['Homework'] if 'Homework' in totaled_scores else []
homework_percentages = []
for i in range(12):
if i < len(homework_scores):
- percentage = homework_scores[i][0] / float(homework_scores[i][1])
- summary = "Homework {0} - {1} - {2:.0%} ({3:g}/{4:g})".format( i + 1, homework_scores[i][2] , percentage, homework_scores[i][0], homework_scores[i][1] )
+ percentage = homework_scores[1].earned / float(homework_scores[i].possible)
+ summary = "Homework {0} - {1} - {2:.0%} ({3:g}/{4:g})".format( i + 1, homework_scores[i].section , percentage, homework_scores[i].earned, homework_scores[i].possible )
else:
percentage = 0
summary = "Unreleased Homework {0} - 0% (?/?)".format(i + 1)
@@ -153,13 +187,12 @@ def grade_sheet(student):
homework_total, homework_dropped_indices = totalWithDrops(homework_percentages, 2)
#Figure the lab scores
- lab_scores = total_scores['Lab'] if 'Lab' in total_scores else []
+ lab_scores = totaled_scores['Lab'] if 'Lab' in totaled_scores else []
lab_percentages = []
- log.debug("lab_scores: {0}".format(lab_scores))
for i in range(12):
if i < len(lab_scores):
- percentage = lab_scores[i][0] / float(lab_scores[i][1])
- summary = "Lab {0} - {1} - {2:.0%} ({3:g}/{4:g})".format( i + 1, lab_scores[i][2] , percentage, lab_scores[i][0], lab_scores[i][1] )
+ percentage = lab_scores[i].earned / float(lab_scores[i].possible)
+ summary = "Lab {0} - {1} - {2:.0%} ({3:g}/{4:g})".format( i + 1, lab_scores[i].section , percentage, lab_scores[i].earned, lab_scores[i].possible )
else:
percentage = 0
summary = "Unreleased Lab {0} - 0% (?/?)".format(i + 1)
@@ -177,18 +210,18 @@ def grade_sheet(student):
#TODO: Pull this data about the midterm and final from the databse. It should be exactly similar to above, but we aren't sure how exams will be done yet.
- midterm_score = ('?', '?')
+ midterm_score = Score('?', '?', True, "?")
midterm_percentage = 0
- final_score = ('?', '?')
+ final_score = Score('?', '?', True, "?")
final_percentage = 0
if settings.GENERATE_PROFILE_SCORES:
- midterm_score = (random.randrange(50, 150), 150)
- midterm_percentage = midterm_score[0] / float(midterm_score[1])
+ midterm_score = Score(random.randrange(50, 150), 150, True, "?")
+ midterm_percentage = midterm_score.earned / float(midterm_score.possible)
- final_score = (random.randrange(100, 300), 300)
- final_percentage = final_score[0] / float(final_score[1])
+ final_score = Score(random.randrange(100, 300), 300, True, "?")
+ final_percentage = final_score.earned / float(final_score.possible)
grade_summary = [
@@ -196,7 +229,8 @@ def grade_sheet(student):
'category': 'Homework',
'subscores' : homework_percentages,
'dropped_indices' : homework_dropped_indices,
- 'totalscore' : {'score' : homework_total, 'summary' : "Homework Average - {0:.0%}".format(homework_total)},
+ 'totalscore' : homework_total,
+ 'totalscore_summary' : "Homework Average - {0:.0%}".format(homework_total),
'totallabel' : 'HW Avg',
'weight' : 0.15,
},
@@ -204,25 +238,25 @@ def grade_sheet(student):
'category': 'Labs',
'subscores' : lab_percentages,
'dropped_indices' : lab_dropped_indices,
- 'totalscore' : {'score' : lab_total, 'summary' : "Lab Average - {0:.0%}".format(lab_total)},
+ 'totalscore' : lab_total,
+ 'totalscore_summary' : "Lab Average - {0:.0%}".format(lab_total),
'totallabel' : 'Lab Avg',
'weight' : 0.15,
},
{
'category': 'Midterm',
- 'totalscore' : {'score' : midterm_percentage, 'summary' : "Midterm - {0:.0%} ({1}/{2})".format(midterm_percentage, midterm_score[0], midterm_score[1])},
+ 'totalscore' : midterm_percentage,
+ 'totalscore_summary' : "Midterm - {0:.0%} ({1}/{2})".format(midterm_percentage, midterm_score.earned, midterm_score.possible),
'totallabel' : 'Midterm',
'weight' : 0.30,
},
{
'category': 'Final',
- 'totalscore' : {'score' : final_percentage, 'summary' : "Final - {0:.0%} ({1}/{2})".format(final_percentage, final_score[0], final_score[1])},
+ 'totalscore' : final_percentage,
+ 'totalscore_summary' : "Final - {0:.0%} ({1}/{2})".format(final_percentage, final_score.earned, final_score.possible),
'totallabel' : 'Final',
'weight' : 0.40,
}
]
- return {'grade_summary' : grade_summary,
- 'chapters':chapters}
-
-
+ return grade_summary
diff --git a/djangoapps/courseware/tests.py b/djangoapps/courseware/tests.py
index b0d7cad19f..2b2b354177 100644
--- a/djangoapps/courseware/tests.py
+++ b/djangoapps/courseware/tests.py
@@ -20,7 +20,7 @@ class ModelsTest(unittest.TestCase):
variables={'R1':2.0, 'R3':4.0}
functions={'sin':numpy.sin, 'cos':numpy.cos}
- self.assertEqual(calc.evaluator(variables, functions, "10000||sin(7+5)-6k"), 4000.0)
+ self.assertTrue(abs(calc.evaluator(variables, functions, "10000||sin(7+5)+0.5356"))<0.01)
self.assertEqual(calc.evaluator({'R1': 2.0, 'R3':4.0}, {}, "13"), 13)
self.assertEqual(calc.evaluator(variables, functions, "13"), 13)
self.assertEqual(calc.evaluator({'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, {}, "5"), 5)
@@ -30,6 +30,8 @@ class ModelsTest(unittest.TestCase):
self.assertEqual(calc.evaluator(variables, functions, "R1*R3"), 8.0)
self.assertTrue(abs(calc.evaluator(variables, functions, "sin(e)-0.41"))<0.01)
self.assertTrue(abs(calc.evaluator(variables, functions, "k*T/q-0.025"))<0.001)
+ self.assertTrue(abs(calc.evaluator(variables, functions, "e^(j*pi)")+1)<0.00001)
+ self.assertTrue(abs(calc.evaluator(variables, functions, "j||1")-0.5-0.5j)<0.00001)
exception_happened = False
try:
calc.evaluator({},{}, "5+7 QWSEKO")
diff --git a/pre-requirements.txt b/pre-requirements.txt
new file mode 100644
index 0000000000..24ce15ab7e
--- /dev/null
+++ b/pre-requirements.txt
@@ -0,0 +1 @@
+numpy
diff --git a/rakefile b/rakefile
new file mode 100644
index 0000000000..bf0ce2431d
--- /dev/null
+++ b/rakefile
@@ -0,0 +1,78 @@
+require 'rake/clean'
+require 'tempfile'
+
+# Build Constants
+REPO_ROOT = File.dirname(__FILE__)
+BUILD_DIR = File.join(REPO_ROOT, "build")
+
+# Packaging constants
+DEPLOY_DIR = "/opt/wwc"
+PACKAGE_NAME = "mitx"
+LINK_PATH = File.join(DEPLOY_DIR, PACKAGE_NAME)
+VERSION = "0.1"
+COMMIT = (ENV["GIT_COMMIT"] || `git rev-parse HEAD`).chomp()[0, 10]
+BRANCH = (ENV["GIT_BRANCH"] || `git symbolic-ref -q HEAD`).chomp().gsub('refs/heads/', '').gsub('origin/', '').gsub('/', '_')
+BUILD_NUMBER = (ENV["BUILD_NUMBER"] || "dev").chomp()
+
+if BRANCH == "master"
+ DEPLOY_NAME = "#{PACKAGE_NAME}-#{BUILD_NUMBER}-#{COMMIT}"
+else
+ DEPLOY_NAME = "#{PACKAGE_NAME}-#{BRANCH}-#{BUILD_NUMBER}-#{COMMIT}"
+end
+INSTALL_DIR_PATH = File.join(DEPLOY_DIR, DEPLOY_NAME)
+PACKAGE_REPO = "packages@gp.mitx.mit.edu:/opt/pkgrepo.incoming"
+
+
+# Set up the clean and clobber tasks
+CLOBBER.include('build')
+CLEAN.include("#{BUILD_DIR}/*.deb", "#{BUILD_DIR}/util")
+
+
+task :package do
+ FileUtils.mkdir_p(BUILD_DIR)
+
+ Dir.chdir(BUILD_DIR) do
+
+ postinstall = Tempfile.new('postinstall')
+ postinstall.write <<-POSTINSTALL.gsub(/^\s*/, '')
+ #! /bin/sh
+ set -e
+ set -x
+
+ service gunicorn stop
+ rm #{LINK_PATH}
+ ln -s #{INSTALL_DIR_PATH} #{LINK_PATH}
+ service gunicorn start
+ POSTINSTALL
+ postinstall.close()
+ FileUtils.chmod(0755, postinstall.path)
+
+ args = ["fakeroot", "fpm", "-s", "dir", "-t", "deb",
+ "--after-install=#{postinstall.path}",
+ "--prefix=#{INSTALL_DIR_PATH}",
+ "-C", "#{REPO_ROOT}",
+ "--depends=python-mysqldb",
+ "--depends=python-django",
+ "--depends=python-pip",
+ "--depends=python-flup",
+ "--depends=python-numpy",
+ "--depends=python-scipy",
+ "--depends=python-matplotlib",
+ "--depends=python-libxml2",
+ "--depends=python2.7-dev",
+ "--depends=libxml2-dev",
+ "--depends=libxslt-dev",
+ "--depends=python-markdown",
+ "--depends=python-pygments",
+ "--depends=mysql-client",
+ "--name=#{DEPLOY_NAME}",
+ "--version=#{VERSION}",
+ "-a", "all",
+ "."]
+ system(*args) || raise("fpm failed to build the .deb")
+ end
+end
+
+task :publish => :package do
+ sh("scp #{BUILD_DIR}/#{DEPLOY_NAME}_#{VERSION}-1_all.deb #{PACKAGE_REPO}")
+end
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000..44eab85dc8
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,14 @@
+django
+pip
+flup
+scipy
+matplotlib
+markdown
+pygments
+django-mako
+django-ses
+lxml
+boto
+mako
+python-memcached
+django-celery
diff --git a/settings.py b/settings.py
index 0e9285f296..c019bed1a9 100644
--- a/settings.py
+++ b/settings.py
@@ -174,7 +174,9 @@ CACHES = {
}
# Make sure we execute correctly regardless of where we're called from
-execfile(os.path.join(BASE_DIR, "settings.py"))
+override_settings = os.path.join(BASE_DIR, "settings.py")
+if os.path.isfile(override_settings):
+ execfile(override_settings)
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
@@ -238,7 +240,7 @@ LOGGING = {
},
'loggers' : {
'django' : {
- 'handlers' : handlers + ['mail_admins'],
+ 'handlers' : handlers, # + ['mail_admins'],
'propagate' : True,
'level' : 'INFO'
},
diff --git a/static/css/application.css b/static/css/application.css
index 7b5a82fc3a..8cc48307f1 100644
--- a/static/css/application.css
+++ b/static/css/application.css
@@ -117,7 +117,7 @@ input, select {
font-weight: 800;
font-style: italic; }
-.clearfix:after, .topbar:after, nav.sequence-nav:after, div.book-wrapper section.book nav:after, div.wiki-wrapper section.wiki-body header:after, html body section.main-content:after, html body section.outside-app:after, div.header-wrapper header:after, div.header-wrapper header hgroup:after, div.header-wrapper header nav ul:after, footer:after, li.calc-main div#calculator_wrapper form:after, div.leanModal_box#enroll ol:after, div.course-wrapper section.course-content .problem-set:after, div.course-wrapper section.course-content section.problems-wrapper:after, div.course-wrapper section.course-content div#seq_content:after, div.course-wrapper section.course-content ol.vert-mod > li:after, section.course-content div.video-subtitles div.video-wrapper section.video-controls:after, section.course-content div.video-subtitles div.video-wrapper section.video-controls div#slider:after, section.course-content nav.sequence-bottom ul:after, div#graph-container:after, div#schematic-container:after, div.book-wrapper section.book nav ul:after, div.info-wrapper section.updates > ol > li:after, div.info-wrapper section.handouts ol li:after, div.profile-wrapper section.course-info header:after, div.profile-wrapper section.course-info > ol > li:after, div#wiki_panel div#wiki_create_form:after, div.wiki-wrapper section.wiki-body:after, ul.badge-list li.badge:after {
+.clearfix:after, .topbar:after, nav.sequence-nav:after, div.book-wrapper section.book nav:after, div.wiki-wrapper section.wiki-body header:after, html body section.main-content:after, html body section.outside-app:after, div.header-wrapper header:after, div.header-wrapper header hgroup:after, div.header-wrapper header nav ul:after, footer:after, li.calc-main div#calculator_wrapper form:after, li.calc-main div#calculator_wrapper form div.input-wrapper:after, div.leanModal_box#enroll ol:after, div.course-wrapper section.course-content .problem-set:after, div.course-wrapper section.course-content section.problems-wrapper:after, div.course-wrapper section.course-content div#seq_content:after, div.course-wrapper section.course-content ol.vert-mod > li:after, section.course-content div.video-subtitles div.video-wrapper section.video-controls:after, section.course-content div.video-subtitles div.video-wrapper section.video-controls div#slider:after, section.course-content nav.sequence-bottom ul:after, div#graph-container:after, div#schematic-container:after, div.book-wrapper section.book nav ul:after, div.info-wrapper section.updates > ol > li:after, div.info-wrapper section.handouts ol li:after, div.profile-wrapper section.course-info header:after, div.profile-wrapper section.course-info > ol > li:after, div#wiki_panel div#wiki_create_form:after, div.wiki-wrapper section.wiki-body:after, ul.badge-list li.badge:after {
content: ".";
display: block;
height: 0;
@@ -449,8 +449,7 @@ html body section.main-content, html body section.outside-app {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
- margin-top: 3px;
- overflow: hidden; }
+ margin-top: 3px; }
@media print {
html body section.main-content, html body section.outside-app {
border-bottom: 0;
@@ -665,7 +664,7 @@ footer nav ul.social li.linkedin a {
background: url("/static/images/linkedin.png") 0 0 no-repeat; }
li.calc-main {
- bottom: 0;
+ bottom: -36px;
left: 0;
position: fixed;
width: 100%; }
@@ -692,32 +691,20 @@ li.calc-main a.calc {
*vertical-align: auto;
padding: 8px 12px;
width: 16px;
- height: 20px; }
+ height: 20px;
+ position: relative;
+ top: -36px; }
li.calc-main a.calc:hover {
opacity: .8; }
li.calc-main a.calc.closed {
background-image: url("/static/images/close-calc-icon.png"); }
li.calc-main div#calculator_wrapper {
background: rgba(17, 17, 17, 0.9);
+ position: relative;
+ top: -36px;
clear: both; }
li.calc-main div#calculator_wrapper form {
padding: 22.652px; }
-li.calc-main div#calculator_wrapper form input#calculator_input {
- border: none;
- -webkit-box-shadow: none;
- -moz-box-shadow: none;
- box-shadow: none;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- font-size: 16px;
- padding: 10px;
- width: 61.741%;
- margin: 0;
- float: left; }
-li.calc-main div#calculator_wrapper form input#calculator_input:focus {
- outline: none;
- border: none; }
li.calc-main div#calculator_wrapper form input#calculator_button {
background: #111;
border: 1px solid #000;
@@ -758,14 +745,82 @@ li.calc-main div#calculator_wrapper form input#calculator_output {
margin: 1px 0 0;
padding: 10px;
width: 31.984%; }
-li.calc-main div#calculator_wrapper dl {
- display: none; }
-li.calc-main div#calculator_wrapper dl dt {
+li.calc-main div#calculator_wrapper form div.input-wrapper {
+ position: relative;
+ width: 61.741%;
+ margin: 0;
+ float: left; }
+li.calc-main div#calculator_wrapper form div.input-wrapper input#calculator_input {
+ border: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ font-size: 16px;
+ padding: 10px;
+ width: 100%; }
+li.calc-main div#calculator_wrapper form div.input-wrapper input#calculator_input:focus {
+ outline: none;
+ border: none; }
+li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper {
+ position: absolute;
+ right: 8px;
+ top: 15px; }
+li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper a {
+ text-indent: -9999px;
+ overflow: hidden;
+ display: block;
+ width: 17px;
+ height: 17px;
+ background: url("/static/images/info-icon.png") center center no-repeat; }
+li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper dl {
+ background: #fff;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ -ms-border-radius: 3px;
+ -o-border-radius: 3px;
+ border-radius: 3px;
+ -webkit-box-shadow: 0 0 3px #999999;
+ -moz-box-shadow: 0 0 3px #999999;
+ box-shadow: 0 0 3px #999999;
+ color: #333;
+ opacity: 0;
+ padding: 10px;
+ position: absolute;
+ right: -40px;
+ top: -110px;
+ width: 500px;
+ -webkit-transition-property: all;
+ -moz-transition-property: all;
+ -ms-transition-property: all;
+ -o-transition-property: all;
+ transition-property: all;
+ -webkit-transition-duration: 0.15s;
+ -moz-transition-duration: 0.15s;
+ -ms-transition-duration: 0.15s;
+ -o-transition-duration: 0.15s;
+ transition-duration: 0.15s;
+ -webkit-transition-timing-function: ease-out;
+ -moz-transition-timing-function: ease-out;
+ -ms-transition-timing-function: ease-out;
+ -o-transition-timing-function: ease-out;
+ transition-timing-function: ease-out;
+ -webkit-transition-delay: 0;
+ -moz-transition-delay: 0;
+ -ms-transition-delay: 0;
+ -o-transition-delay: 0;
+ transition-delay: 0; }
+li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper dl.shown {
+ opacity: 1;
+ top: -115px; }
+li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper dl dt {
clear: both;
float: left;
font-weight: bold;
padding-right: 11.326px; }
-li.calc-main div#calculator_wrapper dl dd {
+li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper dl dd {
float: left; }
#lean_overlay {
@@ -2900,14 +2955,25 @@ section.course-content div.video-subtitles.closed ol.subtitles {
height: 0; }
nav.sequence-nav {
- margin-bottom: 22.652px; }
-nav.sequence-nav ol {
- display: table-row;
- float: left;
- width: 90.611%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ margin-bottom: 22.652px;
position: relative; }
+nav.sequence-nav ol {
+ border-bottom: 1px solid #e4d080;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ display: table;
+ padding-right: 8.696%;
+ width: 100%; }
nav.sequence-nav ol li {
- display: table-cell; }
+ border-left: 1px solid #e4d080;
+ display: table-cell;
+ min-width: 20px; }
+nav.sequence-nav ol li:first-child {
+ border-left: none; }
nav.sequence-nav ol li .inactive, nav.sequence-nav ol li a.seq_video_inactive, nav.sequence-nav ol li a.seq_other_inactive, nav.sequence-nav ol li a.seq_vertical_inactive, nav.sequence-nav ol li a.seq_problem_inactive {
background-repeat: no-repeat; }
nav.sequence-nav ol li .inactive:hover, nav.sequence-nav ol li a.seq_video_inactive:hover, nav.sequence-nav ol li a.seq_other_inactive:hover, nav.sequence-nav ol li a.seq_vertical_inactive:hover, nav.sequence-nav ol li a.seq_problem_inactive:hover {
@@ -2922,25 +2988,21 @@ nav.sequence-nav ol li .visited:hover, nav.sequence-nav ol li a.seq_video_visite
background-color: #f6efd4;
background-position: center center; }
nav.sequence-nav ol li .active, nav.sequence-nav ol div.header-wrapper header nav.courseware li.courseware a, div.header-wrapper header nav.courseware nav.sequence-nav ol li.courseware a, nav.sequence-nav ol div.header-wrapper header nav.book li.book a, div.header-wrapper header nav.book nav.sequence-nav ol li.book a, nav.sequence-nav ol div.header-wrapper header nav.info li.info a, div.header-wrapper header nav.info nav.sequence-nav ol li.info a, nav.sequence-nav ol div.header-wrapper header nav.discussion li.discussion a, div.header-wrapper header nav.discussion nav.sequence-nav ol li.discussion a, nav.sequence-nav ol div.header-wrapper header nav.wiki li.wiki a, div.header-wrapper header nav.wiki nav.sequence-nav ol li.wiki a, nav.sequence-nav ol div.header-wrapper header nav.profile li.profile a, div.header-wrapper header nav.profile nav.sequence-nav ol li.profile a, nav.sequence-nav ol li section.course-index div#accordion h3.ui-accordion-header.ui-state-active, section.course-index div#accordion nav.sequence-nav ol li h3.ui-accordion-header.ui-state-active, nav.sequence-nav ol li section.course-index div#accordion div#wiki_panel input.ui-accordion-header.ui-state-active[type="button"], section.course-index div#accordion div#wiki_panel nav.sequence-nav ol li input.ui-accordion-header.ui-state-active[type="button"], nav.sequence-nav ol li div#wiki_panel section.course-index div#accordion input.ui-accordion-header.ui-state-active[type="button"], div#wiki_panel section.course-index div#accordion nav.sequence-nav ol li input.ui-accordion-header.ui-state-active[type="button"], nav.sequence-nav ol li a.seq_video_active, nav.sequence-nav ol li a.seq_other_active, nav.sequence-nav ol li a.seq_vertical_active, nav.sequence-nav ol li a.seq_problem_active {
+ background-color: #fff;
+ background-repeat: no-repeat;
-webkit-box-shadow: 0 1px 0 white;
-moz-box-shadow: 0 1px 0 white;
- box-shadow: 0 1px 0 white;
- background-color: #fff;
- background-repeat: no-repeat; }
+ box-shadow: 0 1px 0 white; }
nav.sequence-nav ol li .active:hover, nav.sequence-nav ol div.header-wrapper header nav.courseware li.courseware a:hover, div.header-wrapper header nav.courseware nav.sequence-nav ol li.courseware a:hover, nav.sequence-nav ol div.header-wrapper header nav.book li.book a:hover, div.header-wrapper header nav.book nav.sequence-nav ol li.book a:hover, nav.sequence-nav ol div.header-wrapper header nav.info li.info a:hover, div.header-wrapper header nav.info nav.sequence-nav ol li.info a:hover, nav.sequence-nav ol div.header-wrapper header nav.discussion li.discussion a:hover, div.header-wrapper header nav.discussion nav.sequence-nav ol li.discussion a:hover, nav.sequence-nav ol div.header-wrapper header nav.wiki li.wiki a:hover, div.header-wrapper header nav.wiki nav.sequence-nav ol li.wiki a:hover, nav.sequence-nav ol div.header-wrapper header nav.profile li.profile a:hover, div.header-wrapper header nav.profile nav.sequence-nav ol li.profile a:hover, nav.sequence-nav ol li section.course-index div#accordion h3.ui-accordion-header.ui-state-active:hover, section.course-index div#accordion nav.sequence-nav ol li h3.ui-accordion-header.ui-state-active:hover, nav.sequence-nav ol li section.course-index div#accordion div#wiki_panel input.ui-accordion-header.ui-state-active[type="button"]:hover, section.course-index div#accordion div#wiki_panel nav.sequence-nav ol li input.ui-accordion-header.ui-state-active[type="button"]:hover, nav.sequence-nav ol li div#wiki_panel section.course-index div#accordion input.ui-accordion-header.ui-state-active[type="button"]:hover, div#wiki_panel section.course-index div#accordion nav.sequence-nav ol li input.ui-accordion-header.ui-state-active[type="button"]:hover, nav.sequence-nav ol li a.seq_video_active:hover, nav.sequence-nav ol li a.seq_other_active:hover, nav.sequence-nav ol li a.seq_vertical_active:hover, nav.sequence-nav ol li a.seq_problem_active:hover {
background-color: #fff;
background-position: center; }
nav.sequence-nav ol li a {
- -webkit-box-shadow: 1px 0 0 white;
- -moz-box-shadow: 1px 0 0 white;
- box-shadow: 1px 0 0 white;
background-position: center center;
border: none;
- border-right: 1px solid #eddfaa;
cursor: pointer;
- padding: 15px 4px 14px;
- width: 28px;
+ display: block;
height: 17px;
+ padding: 15px 0 14px;
-webkit-transition-property: all;
-moz-transition-property: all;
-ms-transition-property: all;
@@ -2960,7 +3022,8 @@ nav.sequence-nav ol li a {
-moz-transition-delay: 0;
-ms-transition-delay: 0;
-o-transition-delay: 0;
- transition-delay: 0; }
+ transition-delay: 0;
+ width: 100%; }
nav.sequence-nav ol li a.seq_video_inactive {
background-image: url("/static/images/sequence-nav/video-icon-normal.png");
background-position: center; }
@@ -3020,8 +3083,8 @@ nav.sequence-nav ol li p {
white-space: pre-wrap;
z-index: 99; }
nav.sequence-nav ol li p.shown {
- opacity: 1;
- margin-top: 4px; }
+ margin-top: 4px;
+ opacity: 1; }
nav.sequence-nav ol li p:empty {
background: none; }
nav.sequence-nav ol li p:empty::after {
@@ -3031,9 +3094,9 @@ nav.sequence-nav ol li p::after {
content: " ";
display: block;
height: 10px;
+ left: 18px;
position: absolute;
top: -5px;
- left: 18px;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
@@ -3041,30 +3104,33 @@ nav.sequence-nav ol li p::after {
transform: rotate(45deg);
width: 10px; }
nav.sequence-nav ul {
- float: right;
margin-right: 1px;
- width: 8.696%;
- display: table-row; }
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: 8.696%; }
nav.sequence-nav ul li {
- display: table-cell; }
+ float: left;
+ width: 50%; }
nav.sequence-nav ul li.prev a, nav.sequence-nav ul li.next a {
- -webkit-box-shadow: inset 1px 0 0 #faf7e9;
- -moz-box-shadow: inset 1px 0 0 #faf7e9;
- box-shadow: inset 1px 0 0 #faf7e9;
background-color: #f2e7bf;
background-position: center center;
background-repeat: no-repeat;
border-left: 1px solid #e4d080;
+ -webkit-box-shadow: inset 1px 0 0 #faf7e9;
+ -moz-box-shadow: inset 1px 0 0 #faf7e9;
+ box-shadow: inset 1px 0 0 #faf7e9;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
cursor: pointer;
- padding: 0 4px;
- text-indent: -9999px;
- width: 38px;
- display: block; }
+ display: block;
+ text-indent: -9999px; }
nav.sequence-nav ul li.prev a:hover, nav.sequence-nav ul li.next a:hover {
- text-decoration: none;
+ background-color: none;
color: #7e691a;
text-decoration: none;
- background-color: none; }
+ text-decoration: none; }
nav.sequence-nav ul li.prev a.disabled, nav.sequence-nav ul li.next a.disabled {
cursor: normal;
opacity: .4; }
@@ -3077,16 +3143,13 @@ nav.sequence-nav ul li.next a {
nav.sequence-nav ul li.next a:hover {
background-color: none; }
-section.course-content {
- position: relative; }
section.course-content div#seq_content {
margin-bottom: 60px; }
section.course-content nav.sequence-bottom {
- position: absolute;
- bottom: 0;
- right: 50%;
- margin-right: -53px; }
+ bottom: -22.652px;
+ position: relative; }
section.course-content nav.sequence-bottom ul {
+ background-color: #f2e7bf;
background-color: #f2e7bf;
border: 1px solid #e4d080;
border-bottom: 0;
@@ -3095,12 +3158,12 @@ section.course-content nav.sequence-bottom ul {
-ms-border-radius: 3px 3px 0 0;
-o-border-radius: 3px 3px 0 0;
border-radius: 3px 3px 0 0;
- overflow: hidden;
- width: 106px;
- background-color: #f2e7bf;
-webkit-box-shadow: inset 0 0 0 1px #faf7e9;
-moz-box-shadow: inset 0 0 0 1px #faf7e9;
- box-shadow: inset 0 0 0 1px #faf7e9; }
+ box-shadow: inset 0 0 0 1px #faf7e9;
+ margin: 0 auto;
+ overflow: hidden;
+ width: 106px; }
section.course-content nav.sequence-bottom ul li {
float: left; }
section.course-content nav.sequence-bottom ul li.prev, section.course-content nav.sequence-bottom ul li.next {
@@ -3110,10 +3173,9 @@ section.course-content nav.sequence-bottom ul li.prev a, section.course-content
background-repeat: no-repeat;
border-bottom: none;
display: block;
+ display: block;
padding: 16.989px 4px;
text-indent: -9999px;
- width: 45px;
- display: block;
-webkit-transition-property: all;
-moz-transition-property: all;
-ms-transition-property: all;
@@ -3133,7 +3195,8 @@ section.course-content nav.sequence-bottom ul li.prev a, section.course-content
-moz-transition-delay: 0;
-ms-transition-delay: 0;
-o-transition-delay: 0;
- transition-delay: 0; }
+ transition-delay: 0;
+ width: 45px; }
section.course-content nav.sequence-bottom ul li.prev a:hover, section.course-content nav.sequence-bottom ul li.next a:hover {
background-color: #eddfaa;
color: #7e691a;
@@ -3287,6 +3350,11 @@ div.info-wrapper section.updates > ol > li {
padding-bottom: 11.326px;
margin-bottom: 11.326px;
border-bottom: 1px solid #e3e3e3; }
+div.info-wrapper section.updates > ol > li:first-child {
+ padding: 11.326px;
+ margin: 0 -11.326px 22.652px;
+ background: #f6efd4;
+ border-bottom: 1px solid #eddfaa; }
div.info-wrapper section.updates > ol > li h2 {
float: left;
width: 20.109%;
diff --git a/static/images/info-icon.png b/static/images/info-icon.png
new file mode 100644
index 0000000000..736b2f2374
Binary files /dev/null and b/static/images/info-icon.png differ
diff --git a/templates/gradebook.html b/templates/gradebook.html
index 340b0da696..99d3af7515 100644
--- a/templates/gradebook.html
+++ b/templates/gradebook.html
@@ -1,22 +1,76 @@
<%inherit file="main.html" />
+
+<%block name="headextra">
+
+
+
+
+
+
+%block>
+
<%include file="navigation.html" args="active_page=''" />
- Gradebook
- % for s in students:
-
- % for c in s['grade_info']['grade_summary']:
- ${c['category']}
-
- % if 'subscores' in c:
- % for ss in c['subscores']:
-
${ss['summary']}
- % endfor
- % endif
-
- % endfor
- % endfor
+ Gradebook
+
+ %if len(students) > 0:
+
+ <%
+ templateSummary = students[0]['grade_info']['grade_summary']
+ %>
+
+
+
+ | Student |
+ %for section in templateSummary:
+ %if 'subscores' in section:
+ %for subsection in section['subscores']:
+ ${subsection['label']} |
+ %endfor
+ ${section['totallabel']} |
+ %else:
+ ${section['category']} |
+ %endif
+ %endfor
+
+
+ <%def name="percent_data(percentage)">
+ <%
+ data_class = "grade_none"
+ if percentage > .87:
+ data_class = "grade_a"
+ elif percentage > .70:
+ data_class = "grade_b"
+ elif percentage > .6:
+ data_class = "grade_c"
+ %>
+ ${ "{0:.0%}".format( percentage ) } |
+ %def>
+
+ %for student in students:
+
+ | ${student['username']} |
+ %for section in student['grade_info']['grade_summary']:
+ %if 'subscores' in section:
+ %for subsection in section['subscores']:
+ ${percent_data( subsection['percentage'] )}
+ %endfor
+ ${percent_data( section['totalscore'] )}
+ %else:
+ ${percent_data( section['totalscore'] )}
+ %endif
+ %endfor
+
+ %endfor
+
+ %endif
-
+
\ No newline at end of file
diff --git a/templates/gradebook_profilegraphs.html b/templates/gradebook_profilegraphs.html
new file mode 100644
index 0000000000..22a763a410
--- /dev/null
+++ b/templates/gradebook_profilegraphs.html
@@ -0,0 +1,30 @@
+<%inherit file="main.html" />
+<%namespace name="profile_graphs" file="profile_graphs.js"/>
+
+<%block name="headextra">
+
+
+
+ % for s in students:
+
+ %endfor
+%block>
+
+<%include file="navigation.html" args="active_page=''" />
+
+
+
+ Gradebook
+
+ % for s in students:
+ -
+
+
+
+ % endfor
+
+
+
+
diff --git a/templates/main.html b/templates/main.html
index fa0fcd462e..07e1d75695 100644
--- a/templates/main.html
+++ b/templates/main.html
@@ -57,21 +57,27 @@
@@ -128,11 +134,20 @@ $(function() {
$("#calculator_wrapper").hide();
$(".calc").click(function(){
- $("#calculator_wrapper").slideToggle();
+ $("#calculator_wrapper").slideToggle("fast");
$("#calculator_wrapper #calculator_input").focus();
$(this).toggleClass("closed");
+ return false;
});
+ $("div.help-wrapper a").hover(function(){
+ $(".help").toggleClass("shown");
+
+ });
+
+ $("div.help-wrapper a").click(function(){
+ return false;
+ });
$("form#calculator").submit(function(e){
e.preventDefault();
$.getJSON("/calculate", {"equation":$("#calculator_input").attr("value")},
diff --git a/templates/profile.html b/templates/profile.html
index 60a01f502e..6d15a14444 100644
--- a/templates/profile.html
+++ b/templates/profile.html
@@ -1,4 +1,6 @@
<%inherit file="main.html" />
+<%namespace name="profile_graphs" file="profile_graphs.js"/>
+
<%block name="title">Profile - MITx 6.002x%block>
<%!
@@ -10,7 +12,7 @@