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"> + + + + + + + + <%include file="navigation.html" args="active_page=''" />
-

Gradebook

- % for s in students: -

${s['username']}

- % 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'] + %> + + + + + %for section in templateSummary: + %if 'subscores' in section: + %for subsection in section['subscores']: + + %endfor + + %else: + + %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" + %> + + + + %for student in students: + + + %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 +
Student${subsection['label']}${section['totallabel']}${section['category']}
${ "{0:.0%}".format( percentage ) }
${student['username']}
+ %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 + + +<%include file="navigation.html" args="active_page=''" /> +
+
+
+

Gradebook

+
    + % for s in students: +
  1. +

    ${s['username']}

    +
    +
  2. + % 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 @@
- -
-
Suffixes:
-
%kMGTcmunp
-
Operations:
-
^ * / + - ()
-
Functions:
-
sin, cos, tan, sqrt, log10, log2, ln, arccos, arcsin, arctan, abs
-
Constants
-
e, pi
+
+ - -
+ + +
+ @@ -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 <%! @@ -10,7 +12,7 @@