Commit a4dae577 by Lyla Fischer

merge with master

parent 7f92606e
"""
Course settings module. The settings are based of django.conf. All settings in
courseware.global_course_settings are first applied, and then any settings
in the settings.DATA_DIR/course_settings.py are applied. A setting must be
in ALL_CAPS.
Settings are used by calling
from courseware import course_settings
Note that courseware.course_settings is not a module -- it's an object. So
importing individual settings is not possible:
from courseware.course_settings import GRADER # This won't work.
"""
import imp
import logging
import sys
import types
from django.conf import settings
from courseware import global_course_settings
from courseware import graders
_log = logging.getLogger("mitx.courseware")
class Settings(object):
def __init__(self):
# update this dict from global settings (but only for ALL_CAPS settings)
for setting in dir(global_course_settings):
if setting == setting.upper():
setattr(self, setting, getattr(global_course_settings, setting))
data_dir = settings.DATA_DIR
fp = None
try:
fp, pathname, description = imp.find_module("course_settings", [data_dir])
mod = imp.load_module("course_settings", fp, pathname, description)
except Exception as e:
_log.exception("Unable to import course settings file from " + data_dir + ". Error: " + str(e))
mod = types.ModuleType('course_settings')
finally:
if fp:
fp.close()
for setting in dir(mod):
if setting == setting.upper():
setting_value = getattr(mod, setting)
setattr(self, setting, setting_value)
# Here is where we should parse any configurations, so that we can fail early
self.GRADER = graders.grader_from_conf(self.GRADER)
course_settings = Settings()
\ No newline at end of file
......@@ -53,13 +53,12 @@ html_problem_semantics = ["responseparam", "answer", "script"]
html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text"]
class LoncapaProblem(object):
def __init__(self, filename, id, state=None, seed=None):
def __init__(self, fileobject, id, state=None, seed=None):
## Initialize class variables from state
self.seed = None
self.student_answers = dict()
self.correct_map = dict()
self.done = False
self.filename = filename
self.problem_id = id
if seed != None:
......@@ -75,16 +74,12 @@ class LoncapaProblem(object):
if 'done' in state:
self.done = state['done']
# print self.seed
# TODO: Does this deplete the Linux entropy pool? Is this fast enough?
if not self.seed:
self.seed=struct.unpack('i', os.urandom(4))[0]
# print filename, self.seed, seed
## Parse XML file
#log.debug(u"LoncapaProblem() opening file {0}".format(filename))
file_text = open(filename).read()
file_text = fileobject.read()
# Convert startouttext and endouttext to proper <text></text>
# TODO: Do with XML operations
file_text = re.sub("startouttext\s*/","text",file_text)
......
......@@ -177,6 +177,8 @@ def course_xml_process(tree):
propogate_downward_tag(tree, "due")
propogate_downward_tag(tree, "graded")
propogate_downward_tag(tree, "graceperiod")
propogate_downward_tag(tree, "showanswer")
propogate_downward_tag(tree, "rerandomize")
return tree
def course_file(user):
......
GRADER = [
{
'type' : "Homework",
'min_count' : 12,
'drop_count' : 2,
'short_label' : "HW",
'weight' : 0.15,
},
{
'type' : "Lab",
'min_count' : 12,
'drop_count' : 2,
'category' : "Labs",
'weight' : 0.15
},
{
'type' : "Midterm",
'name' : "Midterm Exam",
'short_label' : "Midterm",
'weight' : 0.3,
},
{
'type' : "Final",
'name' : "Final Exam",
'short_label' : "Final",
'weight' : 0.4,
}
]
......@@ -18,8 +18,12 @@ from django.http import HttpResponse
from django.shortcuts import redirect
from django.template import Context
from django.template import Context, loader
from fs.osfs import OSFS
from mitxmako.shortcuts import render_to_response, render_to_string
from models import StudentModule
from student.models import UserProfile
import track.views
......@@ -30,6 +34,14 @@ import courseware.modules
log = logging.getLogger("mitx.courseware")
class I4xSystem(object):
def __init__(self, ajax_url, track_function, render_function, filestore=None):
self.ajax_url = ajax_url
self.track_function = track_function
self.filestore = OSFS(settings.DATA_DIR)
self.render_function = render_function
self.exception404 = Http404
def object_cache(cache, user, module_type, module_id):
# We don't look up on user -- all queries include user
# Additional lookup would require a DB hit the way Django
......@@ -76,12 +88,15 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
xml = content_parser.module_xml(request.user, module, 'id', id)
# Create the module
instance=courseware.modules.get_module_class(module)(xml,
system = I4xSystem(track_function = make_track_function(request),
render_function = None,
ajax_url = ajax_url,
filestore = None
)
instance=courseware.modules.get_module_class(module)(system,
xml,
id,
ajax_url=ajax_url,
state=oldstate,
track_function = make_track_function(request),
render_function = None)
state=oldstate)
# Let the module handle the AJAX
post_data=""
if request.raw_post_data:
......@@ -131,12 +146,15 @@ def render_x_module(user, request, xml_module, module_object_preload):
# Create a new instance
ajax_url = '/modx/'+module_type+'/'+module_id+'/'
instance=module_class(etree.tostring(xml_module),
system = I4xSystem(track_function = make_track_function(request),
render_function = lambda x: render_module(user, request, x, module_object_preload),
ajax_url = ajax_url,
filestore = None
)
instance=module_class(system,
etree.tostring(xml_module),
module_id,
ajax_url=ajax_url,
state=state,
track_function = make_track_function(request),
render_function = lambda x: render_module(user, request, x, module_object_preload))
state=state)
# If instance wasn't already in the database, create it
if not smod:
......
......@@ -16,9 +16,7 @@ import traceback
from lxml import etree
## TODO: Abstract out from Django
from django.conf import settings
from mitxmako.shortcuts import render_to_response, render_to_string
from django.http import Http404
from mitxmako.shortcuts import render_to_string
from x_module import XModule
from courseware.capa.capa_problem import LoncapaProblem, StudentInputError
......@@ -92,7 +90,6 @@ class Module(XModule):
# User submitted a problem, and hasn't reset. We don't want
# more submissions.
if self.lcp.done and self.rerandomize == "always":
#print "!"
check_button = False
save_button = False
......@@ -131,8 +128,8 @@ class Module(XModule):
return html
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None, meta = None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
self.attempts = 0
self.max_attempts = None
......@@ -185,15 +182,14 @@ class Module(XModule):
if state!=None and 'attempts' in state:
self.attempts=state['attempts']
self.filename=content_parser.item(dom2.xpath('/problem/@filename'))
filename=settings.DATA_DIR+"/problems/"+self.filename+".xml"
self.filename="problems/"+content_parser.item(dom2.xpath('/problem/@filename'))+".xml"
self.name=content_parser.item(dom2.xpath('/problem/@name'))
self.weight=content_parser.item(dom2.xpath('/problem/@weight'))
if self.rerandomize == 'never':
seed = 1
else:
seed = None
self.lcp=LoncapaProblem(filename, self.item_id, state, seed = seed)
self.lcp=LoncapaProblem(self.filestore.open(self.filename), self.item_id, state, seed = seed)
def handle_ajax(self, dispatch, get):
if dispatch=='problem_get':
......@@ -242,16 +238,15 @@ class Module(XModule):
if self.show_answer == 'closed' and not self.closed():
return False
print "aa", self.show_answer
raise Http404
raise self.system.exception404 #TODO: Not 404
def get_answer(self, get):
if not self.answer_available():
raise Http404
raise self.system.exception404
else:
return json.dumps(self.lcp.get_question_answers(),
cls=ComplexEncoder)
# Figure out if we should move these to capa_problem?
def get_problem(self, get):
''' Same as get_problem_html -- if we want to reconfirm we
......@@ -276,33 +271,27 @@ class Module(XModule):
if self.closed():
event_info['failure']='closed'
self.tracker('save_problem_check_fail', event_info)
print "cp"
raise Http404
raise self.system.exception404
# Problem submitted. Student should reset before checking
# again.
if self.lcp.done and self.rerandomize == "always":
event_info['failure']='unreset'
self.tracker('save_problem_check_fail', event_info)
print "cpdr"
raise Http404
raise self.system.exception404
try:
old_state = self.lcp.get_state()
lcp_id = self.lcp.problem_id
filename = self.lcp.filename
correct_map = self.lcp.grade_answers(answers)
except StudentInputError as inst:
self.lcp = LoncapaProblem(filename, id=lcp_id, state=old_state)
self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state)
traceback.print_exc()
# print {'error':sys.exc_info(),
# 'answers':answers,
# 'seed':self.lcp.seed,
# 'filename':self.lcp.filename}
return json.dumps({'success':inst.message})
except:
self.lcp = LoncapaProblem(filename, id=lcp_id, state=old_state)
self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state)
traceback.print_exc()
raise
return json.dumps({'success':'Unknown Error'})
......@@ -380,8 +369,8 @@ class Module(XModule):
self.lcp.questions=dict() # Detailed info about questions in problem instance. TODO: Should be by id and not lid.
self.lcp.seed=None
filename=settings.DATA_DIR+"problems/"+self.filename+".xml"
self.lcp=LoncapaProblem(filename, self.item_id, self.lcp.get_state())
filename="problems/"+self.filename+".xml"
self.lcp=LoncapaProblem(self.filestore.open(filename), self.item_id, self.lcp.get_state())
event_info['new_state']=self.lcp.get_state()
self.tracker('reset_problem', event_info)
......
import json
## TODO: Abstract out from Django
from django.conf import settings
from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule
......@@ -24,13 +22,13 @@ class Module(XModule):
textlist=[i for i in textlist if type(i)==str]
return "".join(textlist)
try:
filename=settings.DATA_DIR+"html/"+self.filename
return open(filename).read()
filename="html/"+self.filename
return self.filestore.open(filename).read()
except: # For backwards compatibility. TODO: Remove
return render_to_string(self.filename, {'id': self.item_id})
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
xmltree=etree.fromstring(xml)
self.filename = None
filename_l=xmltree.xpath("/html/@filename")
......
......@@ -19,6 +19,6 @@ class Module(XModule):
def get_html(self):
return '<input type="hidden" class="schematic" name="{item_id}" height="480" width="640">'.format(item_id=self.item_id)
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, render_function = None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, render_function)
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
......@@ -2,9 +2,6 @@ import json
from lxml import etree
## TODO: Abstract out from Django
from django.http import Http404
from django.conf import settings
from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule
......@@ -38,12 +35,10 @@ class Module(XModule):
return self.destroy_js
def handle_ajax(self, dispatch, get):
print "GET", get
print "DISPATCH", dispatch
if dispatch=='goto_position':
self.position = int(get['position'])
return json.dumps({'success':True})
raise Http404()
raise self.system.exception404
def render(self):
if self.rendered:
......@@ -107,9 +102,8 @@ class Module(XModule):
self.rendered = True
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
self.xmltree=etree.fromstring(xml)
self.position = 1
......
......@@ -14,16 +14,16 @@ class Module(XModule):
@classmethod
def get_xml_tags(c):
## TODO: Abstract out from filesystem
tags = os.listdir(settings.DATA_DIR+'/custom_tags')
return tags
def get_html(self):
return self.html
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
xmltree = etree.fromstring(xml)
filename = xmltree.tag
params = dict(xmltree.items())
# print params
self.html = render_to_string(filename, params, namespace = 'custom_tags')
import json
## TODO: Abstract out from Django
from django.conf import settings
from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule
......@@ -26,8 +24,9 @@ class Module(XModule):
def get_destroy_js(self):
return self.destroy_js_text
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
xmltree=etree.fromstring(xml)
self.contents=[(e.get("name"),self.render_function(e)) \
for e in xmltree]
......
......@@ -3,8 +3,6 @@ import logging
from lxml import etree
## TODO: Abstract out from Django
from django.conf import settings
from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule
......@@ -58,8 +56,8 @@ class Module(XModule):
def get_destroy_js(self):
return "videoDestroy(\"{0}\");".format(self.item_id)+self.annotations_destroy
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None):
XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
xmltree=etree.fromstring(xml)
self.youtube = xmltree.get('youtube')
self.name = xmltree.get('name')
......
......@@ -45,13 +45,14 @@ class XModule(object):
get is a dictionary-like object '''
return ""
def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None):
def __init__(self, system, xml, item_id, track_url=None, state=None):
''' In most cases, you must pass state or xml'''
self.xml = xml
self.item_id = item_id
self.ajax_url = ajax_url
self.track_url = track_url
self.state = state
self.tracker = track_function
self.render_function = render_function
self.ajax_url = system.ajax_url
self.tracker = system.track_function
self.filestore = system.filestore
self.render_function = system.render_function
self.system = system
......@@ -84,7 +84,7 @@ def render_accordion(request,course,chapter,section):
parameter. Returns (initialization_javascript, content)'''
if not course:
course = "6.002 Spring 2012"
toc=content_parser.toc_from_xml(content_parser.course_file(request.user), chapter, section)
active_chapter=1
for i in range(len(toc)):
......@@ -96,8 +96,7 @@ def render_accordion(request,course,chapter,section):
['format_url_params',content_parser.format_url_params],
['csrf',csrf(request)['csrf_token']]] + \
template_imports.items())
return {'init_js':render_to_string('accordion_init.js',context),
'content':render_to_string('accordion.html',context)}
return render_to_string('accordion.html',context)
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def render_section(request, section):
......@@ -124,8 +123,8 @@ def render_section(request, section):
if 'init_js' not in module:
module['init_js']=''
context={'init':accordion['init_js']+module['init_js'],
'accordion':accordion['content'],
context={'init':module['init_js'],
'accordion':accordion,
'content':module['content'],
'csrf':csrf(request)['csrf_token']}
......@@ -179,8 +178,8 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
if 'init_js' not in module:
module['init_js']=''
context={'init':accordion['init_js']+module['init_js'],
'accordion':accordion['content'],
context={'init':module['init_js'],
'accordion':accordion,
'content':module['content'],
'csrf':csrf(request)['csrf_token']}
......
#! /usr/bin/env python
import sys
import json
import random
import copy
from collections import defaultdict
from argparse import ArgumentParser, FileType
from datetime import datetime
def generate_user(user_number):
return {
......@@ -51,7 +54,6 @@ def generate_user(user_number):
}
def parse_args(args=sys.argv[1:]):
parser = ArgumentParser()
parser.add_argument('-d', '--data', type=FileType('r'), default=sys.stdin)
......@@ -59,6 +61,7 @@ def parse_args(args=sys.argv[1:]):
parser.add_argument('count', type=int)
return parser.parse_args(args)
def main(args=sys.argv[1:]):
args = parse_args(args)
......@@ -79,6 +82,8 @@ def main(args=sys.argv[1:]):
sample = random.choice(answers)
data = copy.deepcopy(sample)
data["fields"]["student"] = student_id + 1
data["fields"]["created"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
data["fields"]["modified"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
data["pk"] = out_pk
out_pk += 1
out_data.append(data)
......
......@@ -11,7 +11,7 @@ PACKAGE_NAME = "mitx"
LINK_PATH = "/opt/wwc/mitx"
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('/', '_').downcase()
BRANCH = (ENV["GIT_BRANCH"] || `git symbolic-ref -q HEAD`).chomp().gsub('refs/heads/', '').gsub('origin/', '')
BUILD_NUMBER = (ENV["BUILD_NUMBER"] || "dev").chomp()
if BRANCH == "master"
......@@ -19,9 +19,10 @@ if BRANCH == "master"
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"
NORMALIZED_DEPLOY_NAME = DEPLOY_NAME.downcase().gsub(/[_\/]/, '-')
INSTALL_DIR_PATH = File.join(DEPLOY_DIR, NORMALIZED_DEPLOY_NAME)
# Set up the clean and clobber tasks
CLOBBER.include('build')
......@@ -58,7 +59,7 @@ task :package do
"--prefix=#{INSTALL_DIR_PATH}",
"-C", "#{REPO_ROOT}",
"--provides=#{PACKAGE_NAME}",
"--name=#{DEPLOY_NAME}",
"--name=#{NORMALIZED_DEPLOY_NAME}",
"--version=#{VERSION}",
"-a", "all",
"."]
......@@ -67,5 +68,5 @@ task :package do
end
task :publish => :package do
sh("scp #{BUILD_DIR}/#{DEPLOY_NAME}_#{VERSION}-1_all.deb #{PACKAGE_REPO}")
sh("scp #{BUILD_DIR}/#{NORMALIZED_DEPLOY_NAME}_#{VERSION}*.deb #{PACKAGE_REPO}")
end
......@@ -147,6 +147,7 @@ INSTALLED_APPS = (
'perfstats',
'util',
'masquerade',
'django_jasmine',
# Uncomment the next line to enable the admin:
# 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
......@@ -347,6 +348,7 @@ PROJECT_ROOT = os.path.dirname(__file__)
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
'django.core.context_processors.static',
'askbot.context.application_settings',
#'django.core.context_processors.i18n',
'askbot.user_messages.context_processors.user_messages',#must be before auth
......@@ -683,3 +685,5 @@ if MAKO_MODULE_DIR == None:
djcelery.setup_loader()
# Jasmine Settings
JASMINE_TEST_DIRECTORY = PROJECT_DIR+'/templates/coffee'
This source diff could not be displayed because it is too large. You can view the blob instead.
// Generated by CoffeeScript 1.3.2-pre
(function() {
window.Calculator = (function() {
function Calculator() {}
Calculator.bind = function() {
var calculator;
calculator = new Calculator;
$('.calc').click(calculator.toggle);
$('form#calculator').submit(calculator.calculate).submit(function(e) {
return e.preventDefault();
});
return $('div.help-wrapper a').hover(calculator.helpToggle).click(function(e) {
return e.preventDefault();
});
};
Calculator.prototype.toggle = function() {
$('li.calc-main').toggleClass('open');
$('#calculator_wrapper #calculator_input').focus();
return $('.calc').toggleClass('closed');
};
Calculator.prototype.helpToggle = function() {
return $('.help').toggleClass('shown');
};
Calculator.prototype.calculate = function() {
return $.getJSON('/calculate', {
equation: $('#calculator_input').val()
}, function(data) {
return $('#calculator_output').val(data.result);
});
};
return Calculator;
})();
window.Courseware = (function() {
function Courseware() {}
Courseware.bind = function() {
return this.Navigation.bind();
};
Courseware.Navigation = (function() {
function Navigation() {}
Navigation.bind = function() {
var active, navigation;
if ($('#accordion').length) {
navigation = new Navigation;
active = $('#accordion ul:has(li.active)').index('#accordion ul');
$('#accordion').bind('accordionchange', navigation.log).accordion({
active: active >= 0 ? active : 1,
header: 'h3',
autoHeight: false
});
return $('#open_close_accordion a').click(navigation.toggle);
}
};
Navigation.prototype.log = function(event, ui) {
return log_event('accordion', {
newheader: ui.newHeader.text(),
oldheader: ui.oldHeader.text()
});
};
Navigation.prototype.toggle = function() {
return $('.course-wrapper').toggleClass('closed');
};
return Navigation;
})();
return Courseware;
}).call(this);
window.FeedbackForm = (function() {
function FeedbackForm() {}
FeedbackForm.bind = function() {
return $('#feedback_button').click(function() {
var data;
data = {
subject: $('#feedback_subject').val(),
message: $('#feedback_message').val(),
url: window.location.href
};
return $.post('/send_feedback', data, function() {
return $('#feedback_div').html('Feedback submitted. Thank you');
}, 'json');
});
};
return FeedbackForm;
})();
$(function() {
$.ajaxSetup({
headers: {
'X-CSRFToken': $.cookie('csrftoken')
}
});
Calculator.bind();
Courseware.bind();
FeedbackForm.bind();
return $("a[rel*=leanModal]").leanModal();
});
}).call(this);
......@@ -18,6 +18,14 @@ This set of questions and answers accompanies MIT&rsquo;s February 13,
6.002x: Circuits and Electronics.
</p>
<h2> How do I register? </h2>
<p> We will have a link to a form where you can sign up for our database and mailing list shortly. Please check back in the next two weeks to this website for further instruction. </p>
<h2> Where can I find a list of courses available? When do the next classes begin? </h2>
<p> Courses will begin again in the Fall Semester (September). We anticipate offering 4-5 courses this Fall, one of which will be 6.002x again. The additional classes will be announced in early summer. </p>
<h2> I tried to register for the course, but it says the username
is already taken.</h2>
......
$("#accordion").accordion({
active: ${ active_chapter },
header: 'h3',
autoHeight: false,
});
$("#open_close_accordion a").click(function(){
if ($(".course-wrapper").hasClass("closed")){
$(".course-wrapper").removeClass("closed");
} else {
$(".course-wrapper").addClass("closed");
}
});
$('.ui-accordion').bind('accordionchange', function(event, ui) {
var event_data = {'newheader':ui.newHeader.text(),
'oldheader':ui.oldHeader.text()};
log_event('accordion', event_data);
});
CoffeeScript
============
This folder contains the CoffeeScript file that will be compiled to the static
directory. By default, we're compile and merge all the files ending `.coffee`
into `static/js/application.js`.
Install the Compiler
--------------------
CoffeeScript compiler are written in JavaScript. You'll need to install Node and
npm (Node Package Manager) to be able to install the CoffeeScript compiler.
### Mac OS X
Install Node via Homebrew, then use npm:
brew install node
curl http://npmjs.org/install.sh | sh
npm install -g git://github.com/jashkenas/coffee-script.git
(Note that we're using the edge version of CoffeeScript for now, as there was
some issue with directory watching in 1.3.1.)
Try to run `coffee` and make sure you get a coffee prompt.
### Debian/Ubuntu
Conveniently, you can install Node via `apt-get`, then use npm:
sudo apt-get install nodejs npm &&
sudo npm install -g git://github.com/jashkenas/coffee-script.git
Compiling
---------
Run this command in the `mitx` directory to easily make the compiler watch for
changes in your file, and join the result into `application.js`:
coffee -j static/js/application.js -cw templates/coffee/src
Please note that the compiler will not be able to detect the file that get added
after you've ran the command, so you'll need to restart the compiler if there's
a new CoffeeScript file.
Testing
=======
We're also using Jasmine to unit-testing the JavaScript files. All the specs are
written in CoffeeScript for the consistency. Because of the limitation of
`django-jasmine` plugin, we'll need to also running another compiler to compile
the test file.
Using this command to compile the test files:
coffee -cw templates/coffee/spec/*.coffee
Then start the server in debug mode, navigate to http://127.0.0.1:8000/_jasmine
to see the test result.
{
"js_files": [
"/static/js/jquery-1.6.2.min.js",
"/static/js/jquery-ui-1.8.16.custom.min.js",
"/static/js/jquery.leanModal.js"
],
"static_files": [
"js/application.js"
]
}
<div class="course-wrapper">
<header id="open_close_accordion">
<a href="#">close</a>
</header>
<div id="accordion"></div>
</div>
<ul>
<li class="calc-main">
<a href="#" class="calc">Calculator</a>
<div id="calculator_wrapper">
<form id="calculator">
<div class="input-wrapper">
<input type="text" id="calculator_input" />
<div class="help-wrapper">
<a href="#">Hints</a>
<dl class="help"></dl>
</div>
</div>
<input id="calculator_button" type="submit" value="="/>
<input type="text" id="calculator_output" readonly />
</form>
</div>
</li>
</ul>
<div id="feedback_div">
<form>
<label>Subject:</label> <input type="text" id="feedback_subject">
<label>Feedback: </label><textarea id="feedback_message"></textarea>
<input id="feedback_button" type="button" value="Submit">
</form>
</div>
describe 'Calculator', ->
beforeEach ->
loadFixtures 'calculator.html'
@calculator = new Calculator
describe 'bind', ->
beforeEach ->
Calculator.bind()
it 'bind the calculator button', ->
expect($('.calc')).toHandleWith 'click', @calculator.toggle
it 'bind the help button', ->
# These events are bind by $.hover()
expect($('div.help-wrapper a')).toHandleWith 'mouseenter', @calculator.helpToggle
expect($('div.help-wrapper a')).toHandleWith 'mouseleave', @calculator.helpToggle
it 'prevent default behavior on help button', ->
$('div.help-wrapper a').click (e) ->
expect(e.isDefaultPrevented()).toBeTruthy()
$('div.help-wrapper a').click()
it 'bind the calculator submit', ->
expect($('form#calculator')).toHandleWith 'submit', @calculator.calculate
it 'prevent default behavior on form submit', ->
$('form#calculator').submit (e) ->
expect(e.isDefaultPrevented()).toBeTruthy()
e.preventDefault()
$('form#calculator').submit()
describe 'toggle', ->
it 'toggle the calculator and focus the input', ->
spyOn $.fn, 'focus'
@calculator.toggle()
expect($('li.calc-main')).toHaveClass('open')
expect($('#calculator_wrapper #calculator_input').focus).toHaveBeenCalled()
it 'toggle the close button on the calculator button', ->
@calculator.toggle()
expect($('.calc')).toHaveClass('closed')
@calculator.toggle()
expect($('.calc')).not.toHaveClass('closed')
describe 'helpToggle', ->
it 'toggle the help overlay', ->
@calculator.helpToggle()
expect($('.help')).toHaveClass('shown')
@calculator.helpToggle()
expect($('.help')).not.toHaveClass('shown')
describe 'calculate', ->
beforeEach ->
$('#calculator_input').val '1+2'
spyOn($, 'getJSON').andCallFake (url, data, callback) ->
callback({ result: 3 })
@calculator.calculate()
it 'send data to /calculate', ->
expect($.getJSON).toHaveBeenCalledWith '/calculate',
equation: '1+2'
, jasmine.any(Function)
it 'update the calculator output', ->
expect($('#calculator_output').val()).toEqual('3')
// Generated by CoffeeScript 1.3.2-pre
(function() {
describe('Calculator', function() {
beforeEach(function() {
loadFixtures('calculator.html');
return this.calculator = new Calculator;
});
describe('bind', function() {
beforeEach(function() {
return Calculator.bind();
});
it('bind the calculator button', function() {
return expect($('.calc')).toHandleWith('click', this.calculator.toggle);
});
it('bind the help button', function() {
expect($('div.help-wrapper a')).toHandleWith('mouseenter', this.calculator.helpToggle);
return expect($('div.help-wrapper a')).toHandleWith('mouseleave', this.calculator.helpToggle);
});
it('prevent default behavior on help button', function() {
$('div.help-wrapper a').click(function(e) {
return expect(e.isDefaultPrevented()).toBeTruthy();
});
return $('div.help-wrapper a').click();
});
it('bind the calculator submit', function() {
return expect($('form#calculator')).toHandleWith('submit', this.calculator.calculate);
});
return it('prevent default behavior on form submit', function() {
$('form#calculator').submit(function(e) {
expect(e.isDefaultPrevented()).toBeTruthy();
return e.preventDefault();
});
return $('form#calculator').submit();
});
});
describe('toggle', function() {
it('toggle the calculator and focus the input', function() {
spyOn($.fn, 'focus');
this.calculator.toggle();
expect($('li.calc-main')).toHaveClass('open');
return expect($('#calculator_wrapper #calculator_input').focus).toHaveBeenCalled();
});
return it('toggle the close button on the calculator button', function() {
this.calculator.toggle();
expect($('.calc')).toHaveClass('closed');
this.calculator.toggle();
return expect($('.calc')).not.toHaveClass('closed');
});
});
describe('helpToggle', function() {
return it('toggle the help overlay', function() {
this.calculator.helpToggle();
expect($('.help')).toHaveClass('shown');
this.calculator.helpToggle();
return expect($('.help')).not.toHaveClass('shown');
});
});
return describe('calculate', function() {
beforeEach(function() {
$('#calculator_input').val('1+2');
spyOn($, 'getJSON').andCallFake(function(url, data, callback) {
return callback({
result: 3
});
});
return this.calculator.calculate();
});
it('send data to /calculate', function() {
return expect($.getJSON).toHaveBeenCalledWith('/calculate', {
equation: '1+2'
}, jasmine.any(Function));
});
return it('update the calculator output', function() {
return expect($('#calculator_output').val()).toEqual('3');
});
});
});
}).call(this);
describe 'Courseware', ->
describe 'bind', ->
it 'bind the navigation', ->
spyOn Courseware.Navigation, 'bind'
Courseware.bind()
expect(Courseware.Navigation.bind).toHaveBeenCalled()
describe 'Navigation', ->
beforeEach ->
loadFixtures 'accordion.html'
@navigation = new Courseware.Navigation
describe 'bind', ->
describe 'when the #accordion exists', ->
describe 'when there is an active section', ->
it 'activate the accordion with correct active section', ->
spyOn $.fn, 'accordion'
$('#accordion').append('<ul><li></li></ul><ul><li class="active"></li></ul>')
Courseware.Navigation.bind()
expect($('#accordion').accordion).toHaveBeenCalledWith
active: 1
header: 'h3'
autoHeight: false
describe 'when there is no active section', ->
it 'activate the accordian with section 1 as active', ->
spyOn $.fn, 'accordion'
$('#accordion').append('<ul><li></li></ul><ul><li></li></ul>')
Courseware.Navigation.bind()
expect($('#accordion').accordion).toHaveBeenCalledWith
active: 1
header: 'h3'
autoHeight: false
it 'binds the accordionchange event', ->
Courseware.Navigation.bind()
expect($('#accordion')).toHandleWith 'accordionchange', @navigation.log
it 'bind the navigation toggle', ->
Courseware.Navigation.bind()
expect($('#open_close_accordion a')).toHandleWith 'click', @navigation.toggle
describe 'when the #accordion does not exists', ->
beforeEach ->
$('#accordion').remove()
it 'does not activate the accordion', ->
spyOn $.fn, 'accordion'
Courseware.Navigation.bind()
expect($('#accordion').accordion).wasNotCalled()
describe 'toggle', ->
it 'toggle closed class on the wrapper', ->
$('.course-wrapper').removeClass('closed')
@navigation.toggle()
expect($('.course-wrapper')).toHaveClass('closed')
@navigation.toggle()
expect($('.course-wrapper')).not.toHaveClass('closed')
describe 'log', ->
beforeEach ->
window.log_event = ->
spyOn window, 'log_event'
it 'submit event log', ->
@navigation.log {}, {
newHeader:
text: -> "new"
oldHeader:
text: -> "old"
}
expect(window.log_event).toHaveBeenCalledWith 'accordion',
newheader: 'new'
oldheader: 'old'
// Generated by CoffeeScript 1.3.2-pre
(function() {
describe('Courseware', function() {
describe('bind', function() {
return it('bind the navigation', function() {
spyOn(Courseware.Navigation, 'bind');
Courseware.bind();
return expect(Courseware.Navigation.bind).toHaveBeenCalled();
});
});
return describe('Navigation', function() {
beforeEach(function() {
loadFixtures('accordion.html');
return this.navigation = new Courseware.Navigation;
});
describe('bind', function() {
describe('when the #accordion exists', function() {
describe('when there is an active section', function() {
return it('activate the accordion with correct active section', function() {
spyOn($.fn, 'accordion');
$('#accordion').append('<ul><li></li></ul><ul><li class="active"></li></ul>');
Courseware.Navigation.bind();
return expect($('#accordion').accordion).toHaveBeenCalledWith({
active: 1,
header: 'h3',
autoHeight: false
});
});
});
describe('when there is no active section', function() {
return it('activate the accordian with section 1 as active', function() {
spyOn($.fn, 'accordion');
$('#accordion').append('<ul><li></li></ul><ul><li></li></ul>');
Courseware.Navigation.bind();
return expect($('#accordion').accordion).toHaveBeenCalledWith({
active: 1,
header: 'h3',
autoHeight: false
});
});
});
it('binds the accordionchange event', function() {
Courseware.Navigation.bind();
return expect($('#accordion')).toHandleWith('accordionchange', this.navigation.log);
});
return it('bind the navigation toggle', function() {
Courseware.Navigation.bind();
return expect($('#open_close_accordion a')).toHandleWith('click', this.navigation.toggle);
});
});
return describe('when the #accordion does not exists', function() {
beforeEach(function() {
return $('#accordion').remove();
});
return it('does not activate the accordion', function() {
spyOn($.fn, 'accordion');
Courseware.Navigation.bind();
return expect($('#accordion').accordion).wasNotCalled();
});
});
});
describe('toggle', function() {
return it('toggle closed class on the wrapper', function() {
$('.course-wrapper').removeClass('closed');
this.navigation.toggle();
expect($('.course-wrapper')).toHaveClass('closed');
this.navigation.toggle();
return expect($('.course-wrapper')).not.toHaveClass('closed');
});
});
return describe('log', function() {
beforeEach(function() {
window.log_event = function() {};
return spyOn(window, 'log_event');
});
return it('submit event log', function() {
this.navigation.log({}, {
newHeader: {
text: function() {
return "new";
}
},
oldHeader: {
text: function() {
return "old";
}
}
});
return expect(window.log_event).toHaveBeenCalledWith('accordion', {
newheader: 'new',
oldheader: 'old'
});
});
});
});
});
}).call(this);
describe 'FeedbackForm', ->
beforeEach ->
loadFixtures 'feedback_form.html'
describe 'bind', ->
beforeEach ->
FeedbackForm.bind()
spyOn($, 'post').andCallFake (url, data, callback, format) ->
callback()
it 'binds to the #feedback_button', ->
expect($('#feedback_button')).toHandle 'click'
it 'post data to /send_feedback on click', ->
$('#feedback_subject').val 'Awesome!'
$('#feedback_message').val 'This site is really good.'
$('#feedback_button').click()
expect($.post).toHaveBeenCalledWith '/send_feedback', {
subject: 'Awesome!'
message: 'This site is really good.'
url: window.location.href
}, jasmine.any(Function), 'json'
it 'replace the form with a thank you message', ->
$('#feedback_button').click()
expect($('#feedback_div').html()).toEqual 'Feedback submitted. Thank you'
// Generated by CoffeeScript 1.3.2-pre
(function() {
describe('FeedbackForm', function() {
beforeEach(function() {
return loadFixtures('feedback_form.html');
});
return describe('bind', function() {
beforeEach(function() {
FeedbackForm.bind();
return spyOn($, 'post').andCallFake(function(url, data, callback, format) {
return callback();
});
});
it('binds to the #feedback_button', function() {
return expect($('#feedback_button')).toHandle('click');
});
it('post data to /send_feedback on click', function() {
$('#feedback_subject').val('Awesome!');
$('#feedback_message').val('This site is really good.');
$('#feedback_button').click();
return expect($.post).toHaveBeenCalledWith('/send_feedback', {
subject: 'Awesome!',
message: 'This site is really good.',
url: window.location.href
}, jasmine.any(Function), 'json');
});
return it('replace the form with a thank you message', function() {
$('#feedback_button').click();
return expect($('#feedback_div').html()).toEqual('Feedback submitted. Thank you');
});
});
});
}).call(this);
jasmine.getFixtures().fixturesPath = "/_jasmine/fixtures/"
// Generated by CoffeeScript 1.3.2-pre
(function() {
jasmine.getFixtures().fixturesPath = "/_jasmine/fixtures/";
}).call(this);
class window.Calculator
@bind: ->
calculator = new Calculator
$('.calc').click calculator.toggle
$('form#calculator').submit(calculator.calculate).submit (e) ->
e.preventDefault()
$('div.help-wrapper a').hover(calculator.helpToggle).click (e) ->
e.preventDefault()
toggle: ->
$('li.calc-main').toggleClass 'open'
$('#calculator_wrapper #calculator_input').focus()
$('.calc').toggleClass 'closed'
helpToggle: ->
$('.help').toggleClass 'shown'
calculate: ->
$.getJSON '/calculate', { equation: $('#calculator_input').val() }, (data) ->
$('#calculator_output').val(data.result)
class window.Courseware
@bind: ->
@Navigation.bind()
class @Navigation
@bind: ->
if $('#accordion').length
navigation = new Navigation
active = $('#accordion ul:has(li.active)').index('#accordion ul')
$('#accordion').bind('accordionchange', navigation.log).accordion
active: if active >= 0 then active else 1
header: 'h3'
autoHeight: false
$('#open_close_accordion a').click navigation.toggle
log: (event, ui) ->
log_event 'accordion',
newheader: ui.newHeader.text()
oldheader: ui.oldHeader.text()
toggle: ->
$('.course-wrapper').toggleClass('closed')
class window.FeedbackForm
@bind: ->
$('#feedback_button').click ->
data =
subject: $('#feedback_subject').val()
message: $('#feedback_message').val()
url: window.location.href
$.post '/send_feedback', data, ->
$('#feedback_div').html 'Feedback submitted. Thank you'
,'json'
$ ->
$.ajaxSetup
headers : { 'X-CSRFToken': $.cookie 'csrftoken' }
Calculator.bind()
Courseware.bind()
FeedbackForm.bind()
$("a[rel*=leanModal]").leanModal()
......@@ -8,7 +8,8 @@
<style type="text/css">
.grade_a {color:green;}
.grade_b {color:Chocolate;}
.grade_c {color:DimGray;}
.grade_c {color:DarkSlateGray;}
.grade_f {color:DimGray;}
.grade_none {color:LightGray;}
</style>
......@@ -29,16 +30,10 @@
<tr> <!-- Header Row -->
<th>Student</th>
%for section in templateSummary:
%if 'subscores' in section:
%for subsection in section['subscores']:
<th>${subsection['label']}</th>
%endfor
<th>${section['totallabel']}</th>
%else:
<th>${section['category']}</th>
%endif
%for section in templateSummary['section_breakdown']:
<th>${section['label']}</th>
%endfor
<th>Total</th>
</tr>
<%def name="percent_data(percentage)">
......@@ -50,6 +45,8 @@
data_class = "grade_b"
elif percentage > .6:
data_class = "grade_c"
elif percentage > 0:
data_class = "grade_f"
%>
<td class="${data_class}">${ "{0:.0%}".format( percentage ) }</td>
</%def>
......@@ -57,16 +54,10 @@
%for student in students:
<tr>
<td><a href="/profile/${student['id']}/">${student['username']}</a></td>
%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
%for section in student['grade_info']['grade_summary']['section_breakdown']:
${percent_data( section['percent'] )}
%endfor
<th>${percent_data( student['grade_info']['grade_summary']['percent'])}</th>
</tr>
%endfor
</table>
......
......@@ -9,6 +9,7 @@
<script type="text/javascript" src="${ settings.LIB_URL }jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="${ settings.LIB_URL }jquery-ui-1.8.16.custom.min.js"></script>
<script type="text/javascript" src="${ settings.LIB_URL }swfobject/swfobject.js"></script>
<script type="text/javascript" src="/static/js/application.js"></script>
<!--[if lt IE 9]>
<script src="/static/js/html5shiv.js"></script>
......@@ -90,20 +91,18 @@
</nav>
</footer>
<div id="feedback_div" class="leanModal_box">
<h1>Feedback for MITx</h1>
<p>Found a bug? Got an idea for improving our system? Let us know.</p>
<form>
<ol>
<li><label>Subject:</label> <input type="text" id="feedback_subject"></li>
<li><label>Feedback: </label><textarea id="feedback_message"></textarea></li>
<li><input id="feedback_button" type="button" value="Submit"></li>
</ol>
</form>
</div>
<div id="feedback_div" class="leanModal_box">
<h1>Feedback for MITx</h1>
<p>Found a bug? Got an idea for improving our system? Let us know.</p>
<form>
<ol>
<li><label>Subject:</label> <input type="text" id="feedback_subject"></li>
<li><label>Feedback: </label><textarea id="feedback_message"></textarea></li>
<li><input id="feedback_button" type="button" value="Submit"></li>
</ol>
</form>
</div>
<script type="text/javascript" src="${ settings.LIB_URL }jquery.treeview.js"></script>
<script type="text/javascript" src="/static/js/jquery.leanModal.min.js"></script>
......@@ -112,52 +111,7 @@
<script type="text/javascript" src="/static/js/video_player.js"></script>
<script type="text/javascript" src="/static/js/schematic.js"></script>
<script type="text/javascript" src="/static/js/cktsim.js"></script>
<script>
// Feedback form
$(function() {
$("#feedback_button").click(function(){
postJSON("/send_feedback", {"subject":$("#feedback_subject").attr("value"),
"url":document.URL,
"message":$("#feedback_message").attr("value")},
function(data){
$("#feedback_subject").attr("value","");
$("#feedback_message").attr("value","");
$("#feedback_div").html("Feedback submitted. Thank you");
});
});
// Calculator
$(".calc").click(function(){
$("li.calc-main").toggleClass('open');
$("#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")},
function(data){
$("#calculator_output").attr("value",data.result);
});
});
$("a[rel*=leanModal]").leanModal();
});
</script>
<%block name="js_extra"/>
<%block name="js_extra"/>
</body>
</html>
......@@ -150,11 +150,11 @@ $(function() {
<%
earned = section['section_total'].earned
total = section['section_total'].possible
percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 else ""
percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else ""
%>
<h3><a href="${reverse('courseware_section', args=format_url_params([chapter['course'], chapter['chapter'], section['section']])) }">
${ section['section'] }</a> ${"({0:g}/{1:g}) {2}".format( earned, total, percentageString )}</h3>
${ section['section'] }</a> ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}</h3>
${section['subtitle']}
%if 'due' in section and section['due']!="":
due ${section['due']}
......@@ -164,7 +164,7 @@ $(function() {
<ol class="scores">
${ "Problem Scores: " if section['graded'] else "Practice Scores: "}
%for score in section['scores']:
<li class="score">${"{0:g}/{1:g}".format(score.earned,score.possible)}</li>
<li class="score">${"{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible))}</li>
%endfor
</ol>
%endif
......
......@@ -9,7 +9,7 @@ $(function () {
position: 'absolute',
display: 'none',
top: y + 5,
left: x + 5,
left: x + 15,
border: '1px solid #000',
padding: '4px 6px',
color: '#fff',
......@@ -19,96 +19,81 @@ $(function () {
}
/* -------------------------------- Grade detail bars -------------------------------- */
<%
colors = ["#b72121", "#600101", "#666666", "#333333"]
categories = {}
tickIndex = 1
sectionSpacer = 0.5
sectionSpacer = 0.25
sectionIndex = 0
series = []
ticks = [] #These are the indices and x-axis labels for the data
bottomTicks = [] #Labels on the bottom
detail_tooltips = {} #This an dictionary mapping from 'section' -> array of detail_tooltips
droppedScores = [] #These are the datapoints to indicate assignments which aren't factored into the total score
droppedScores = [] #These are the datapoints to indicate assignments which are not factored into the total score
dropped_score_tooltips = []
for section in grade_summary:
if 'subscores' in section: ##This is for sections like labs or homeworks, with several smaller components and a total
series.append({
'label' : section['category'],
'data' : [[i + tickIndex, score['percentage']] for i,score in enumerate(section['subscores'])],
'color' : colors[sectionIndex]
})
ticks += [[i + tickIndex, score['label'] ] for i,score in enumerate(section['subscores'])]
bottomTicks.append( [tickIndex + len(section['subscores'])/2, section['category']] )
detail_tooltips[ section['category'] ] = [score['summary'] for score in section['subscores']]
droppedScores += [[tickIndex + index, 0.05] for index in section['dropped_indices']]
dropExplanation = "The lowest {0} {1} scores are dropped".format( len(section['dropped_indices']), section['category'] )
dropped_score_tooltips += [dropExplanation] * len(section['dropped_indices'])
tickIndex += len(section['subscores']) + sectionSpacer
category_total_label = section['category'] + " Total"
series.append({
'label' : category_total_label,
'data' : [ [tickIndex, section['totalscore']] ],
'color' : colors[sectionIndex]
})
ticks.append( [tickIndex, section['totallabel']] )
detail_tooltips[category_total_label] = [section['totalscore_summary']]
else:
series.append({
'label' : section['category'],
'data' : [ [tickIndex, section['totalscore']] ],
'color' : colors[sectionIndex]
})
ticks.append( [tickIndex, section['totallabel']] )
detail_tooltips[section['category']] = [section['totalscore_summary']]
for section in grade_summary['section_breakdown']:
if section.get('prominent', False):
tickIndex += sectionSpacer
if section['category'] not in categories:
colorIndex = len(categories) % len(colors)
categories[ section['category'] ] = {'label' : section['category'],
'data' : [],
'color' : colors[colorIndex]}
categoryData = categories[ section['category'] ]
categoryData['data'].append( [tickIndex, section['percent']] )
ticks.append( [tickIndex, section['label'] ] )
if section['category'] in detail_tooltips:
detail_tooltips[ section['category'] ].append( section['detail'] )
else:
detail_tooltips[ section['category'] ] = [ section['detail'], ]
if 'mark' in section:
droppedScores.append( [tickIndex, 0.05] )
dropped_score_tooltips.append( section['mark']['detail'] )
tickIndex += 1 + sectionSpacer
sectionIndex += 1
detail_tooltips['Dropped Scores'] = dropped_score_tooltips
tickIndex += 1
if section.get('prominent', False):
tickIndex += sectionSpacer
## ----------------------------- Grade overviewew bar ------------------------- ##
totalWeight = 0.0
sectionIndex = 0
totalScore = 0.0
tickIndex += sectionSpacer
series = categories.values()
overviewBarX = tickIndex
for section in grade_summary:
weighted_score = section['totalscore'] * section['weight']
summary_text = "{0} - {1:.1%} of a possible {2:.0%}".format(section['category'], weighted_score, section['weight'])
weighted_category_label = section['category'] + " - Weighted"
if section['totalscore'] > 0:
extraColorIndex = len(categories) #Keeping track of the next color to use for categories not in categories[]
for section in grade_summary['grade_breakdown']:
if section['percent'] > 0:
if section['category'] in categories:
color = categories[ section['category'] ]['color']
else:
color = colors[ extraColorIndex % len(colors) ]
extraColorIndex += 1
series.append({
'label' : weighted_category_label,
'data' : [ [overviewBarX, weighted_score] ],
'color' : colors[sectionIndex]
'label' : section['category'] + "-grade_breakdown",
'data' : [ [overviewBarX, section['percent']] ],
'color' : color
})
detail_tooltips[weighted_category_label] = [ summary_text ]
sectionIndex += 1
totalWeight += section['weight']
totalScore += section['totalscore'] * section['weight']
detail_tooltips[section['category'] + "-grade_breakdown"] = [ section['detail'] ]
ticks += [ [overviewBarX, "Total"] ]
tickIndex += 1 + sectionSpacer
totalScore = grade_summary['percent']
detail_tooltips['Dropped Scores'] = dropped_score_tooltips
%>
var series = ${ json.dumps(series) };
var series = ${ json.dumps( series ) };
var ticks = ${ json.dumps(ticks) };
var bottomTicks = ${ json.dumps(bottomTicks) };
var detail_tooltips = ${ json.dumps(detail_tooltips) };
......@@ -132,7 +117,7 @@ $(function () {
var $grade_detail_graph = $("#${graph_div_id}");
if ($grade_detail_graph.length > 0) {
var plot = $.plot($grade_detail_graph, series, options);
//We need to put back the plotting of the percent here
var o = plot.pointOffset({x: ${overviewBarX} , y: ${totalScore}});
$grade_detail_graph.append('<div style="position:absolute;left:' + (o.left - 12) + 'px;top:' + (o.top - 20) + 'px">${"{totalscore:.0%}".format(totalscore=totalScore)}</div>');
}
......
......@@ -84,84 +84,83 @@ section.tool-wrapper {
width: flex-grid(4.5, 9);
div.graph-controls {
padding: 0 0 lh();
margin-bottom: lh();
border-bottom: 1px solid darken(#073642, 5%);
@include box-shadow(0 1px 0 lighten(#073642, 2%));
@extend .clearfix;
div.music-wrapper {
margin-right: flex-gutter(4.5);
width: flex-grid(1.5, 4.5);
float: left;
padding: 0 0 lh();
margin-bottom: lh();
border-bottom: 1px solid darken(#073642, 10%);
@include box-shadow(0 1px 0 lighten(#073642, 2%));
@extend .clearfix;
input#playButton {
display: block;
@include button(simple, lighten( #586e75, 5% ));
font: bold 14px $body-font-family;
border-color: darken(#002b36, 6%);
float: right;
&:active {
@include box-shadow(none);
}
&[value="Stop"] {
@include button(simple, darken(#268bd2, 30%));
font: bold 14px $body-font-family;
&:active {
@include box-shadow(none);
}
}
}
}
div.inputs-wrapper {
padding-top: lh(.5);
width: flex-grid(3, 4.5);
float: left;
@include clearfix;
margin-bottom: lh();
padding: 0 0 lh();
margin-bottom: lh();
border-bottom: 1px solid darken(#073642, 10%);
@include box-shadow(0 1px 0 lighten(#073642, 2%));
@extend .clearfix;
}
select#musicTypeSelect {
display: block;
margin-bottom: lh(.5);
font: 16px $body-font-family;
width: 100%;
p {
@include inline-block();
margin: 0;
-webkit-font-smoothing: antialiased;
font-weight: bold;
text-shadow: 0 -1px 0 darken(#073642, 10%);
}
div#graph-output, div#graph-listen {
display: block;
margin-bottom: lh(.5);
text-align: right;
p {
@include inline-block();
margin: 0;
}
ul {
@include inline-block();
margin-bottom: 0;
ul {
li {
@include inline-block();
margin-bottom: 0;
li {
@include inline-block();
margin-bottom: 0;
input {
margin-right: 5px;
}
input {
margin-right: 5px;
}
}
}
input#playButton {
div#graph-listen {
margin-top: 8px;
margin-right: 20px;
display: block;
@include button(simple, #dc322f);
font: bold 14px $body-font-family;
color: #47221a;
text-shadow: 0 1px 0 lighten(#dc322f, 5%);
@include box-shadow(inset 0 1px 0 lighten(#dc322f, 10%));
&:active {
@include box-shadow(none);
}
&[value="Stop"] {
@include button(simple, darken(#268bd2, 30%));
font: bold 14px $body-font-family;
&:active {
@include box-shadow(none);
}
}
text-align: right;
float: left;
margin-bottom: 0;
}
}
label {
@include border-radius(2px);
font-weight: bold;
padding: 3px;
color: #fff;
padding: 3px;
-webkit-font-smoothing: antialiased;
}
......@@ -190,6 +189,29 @@ section.tool-wrapper {
div.schematic-sliders {
div.top-sliders {
padding: 0 0 lh();
margin-bottom: lh();
border-bottom: 1px solid darken(#073642, 10%);
@include box-shadow(0 1px 0 lighten(#073642, 2%));
@extend .clearfix;
select#musicTypeSelect {
@include inline-block();
font: 16px $body-font-family;
margin-bottom: 0;
}
p {
@include inline-block();
-webkit-font-smoothing: antialiased;
text-shadow: 0 -1px 0 darken(#073642, 10%);
margin: 0 lh(.5) lh() 0;
font-weight: bold;
}
}
div.slider-label {
margin-bottom: lh(0.5);
font-weight: bold;
......@@ -208,11 +230,13 @@ section.tool-wrapper {
}
.ui-slider-handle {
background-color: #dc322f;
background: lighten( #586e75, 5% ) url('/static/images/amplifier-slider-handle.png') center no-repeat;
border: 1px solid darken(#002b36, 8%);
@include box-shadow(inset 0 1px 0 lighten( #586e75, 20% ));
margin-top: -.3em;
&:hover, &:active {
background-color: lighten(#dc322f, 5%);
background-color: lighten( #586e75, 10% );
}
}
}
......
......@@ -172,7 +172,7 @@ div.course-wrapper {
header {
@extend h1.top-header;
margin-bottom: 0;
margin-bottom: -16px;
h1 {
margin: 0;
......@@ -193,10 +193,18 @@ div.course-wrapper {
padding-bottom: 0;
}
ul {
list-style: disc outside none;
padding-left: 1em;
}
nav.sequence-bottom {
ul {
list-style: none;
padding: 0;
}
}
}
}
......
......@@ -42,16 +42,6 @@ div.answer-block {
padding-top: 20px;
width: 100%;
div.official-stamp {
background: $mit-red;
color: #fff;
font-size: 12px;
margin-top: 10px;
padding: 2px 5px;
text-align: center;
margin-left: -1px;
}
img.answer-img-accept {
margin: 10px 0px 10px 16px;
}
......
div.question-header {
div.official-stamp {
background: $mit-red;
color: #fff;
font-size: 12px;
margin-top: 10px;
padding: 2px 5px;
text-align: center;
margin-left: -1px;
}
div.vote-buttons {
display: inline-block;
float: left;
......
......@@ -77,6 +77,10 @@ if settings.ASKBOT_ENABLED:
# url(r'^robots.txt$', include('robots.urls')),
)
if settings.DEBUG:
## Jasmine
urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),)
urlpatterns = patterns(*urlpatterns)
if settings.DEBUG:
......
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