Merge branch 'master' into specific-error-handling
Conflicts: djangoapps/courseware/views.py
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,6 +4,9 @@
|
||||
*.swp
|
||||
*.orig
|
||||
*.DS_Store
|
||||
:2e_*
|
||||
:2e#
|
||||
.AppleDouble
|
||||
database.sqlite
|
||||
courseware/static/js/mathjax/*
|
||||
db.newaskbot
|
||||
|
||||
@@ -25,7 +25,7 @@ from mako.template import Template
|
||||
|
||||
from util import contextualize_text
|
||||
import inputtypes
|
||||
from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, StudentInputError, TrueFalseResponse, ExternalResponse,ImageResponse
|
||||
from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, StudentInputError, TrueFalseResponse, ExternalResponse,ImageResponse,OptionResponse
|
||||
|
||||
import calc
|
||||
import eia
|
||||
@@ -40,8 +40,9 @@ response_types = {'numericalresponse':NumericalResponse,
|
||||
'multiplechoiceresponse':MultipleChoiceResponse,
|
||||
'truefalseresponse':TrueFalseResponse,
|
||||
'imageresponse':ImageResponse,
|
||||
'optionresponse':OptionResponse,
|
||||
}
|
||||
entry_types = ['textline', 'schematic', 'choicegroup','textbox','imageinput']
|
||||
entry_types = ['textline', 'schematic', 'choicegroup','textbox','imageinput','optioninput']
|
||||
solution_types = ['solution'] # extra things displayed after "show answers" is pressed
|
||||
response_properties = ["responseparam", "answer"] # these get captured as student responses
|
||||
|
||||
@@ -186,6 +187,13 @@ class LoncapaProblem(object):
|
||||
if answer:
|
||||
answer_map[entry.get('id')] = contextualize_text(answer, self.context)
|
||||
|
||||
# include solutions from <solution>...</solution> stanzas
|
||||
# Tentative merge; we should figure out how we want to handle hints and solutions
|
||||
for entry in self.tree.xpath("//"+"|//".join(solution_types)):
|
||||
answer = etree.tostring(entry)
|
||||
if answer:
|
||||
answer_map[entry.get('id')] = answer
|
||||
|
||||
return answer_map
|
||||
|
||||
# ======= Private ========
|
||||
@@ -241,7 +249,24 @@ class LoncapaProblem(object):
|
||||
if self.student_answers and problemid in self.student_answers:
|
||||
value = self.student_answers[problemid]
|
||||
|
||||
return getattr(inputtypes, problemtree.tag)(problemtree, value, status) #TODO
|
||||
#### This code is a hack. It was merged to help bring two branches
|
||||
#### in sync, but should be replaced. msg should be passed in a
|
||||
#### response_type
|
||||
# prepare the response message, if it exists in correct_map
|
||||
if 'msg' in self.correct_map:
|
||||
msg = self.correct_map['msg']
|
||||
elif ('msg_%s' % problemid) in self.correct_map:
|
||||
msg = self.correct_map['msg_%s' % problemid]
|
||||
else:
|
||||
msg = ''
|
||||
|
||||
#if settings.DEBUG:
|
||||
# print "[courseware.capa.capa_problem.extract_html] msg = ",msg
|
||||
|
||||
# do the rendering
|
||||
#render_function = html_special_response[problemtree.tag]
|
||||
render_function = getattr(inputtypes, problemtree.tag)
|
||||
return render_function(problemtree, value, status, msg) # render the special response (textline, schematic,...)
|
||||
|
||||
tree=Element(problemtree.tag)
|
||||
for item in problemtree:
|
||||
@@ -287,6 +312,7 @@ class LoncapaProblem(object):
|
||||
answer_id = 1
|
||||
for entry in tree.xpath("|".join(['//'+response.tag+'[@id=$id]//'+x for x in (entry_types + solution_types)]),
|
||||
id=response_id_str):
|
||||
# assign one answer_id for each entry_type or solution_type
|
||||
entry.attrib['response_id'] = str(response_id)
|
||||
entry.attrib['answer_id'] = str(answer_id)
|
||||
entry.attrib['id'] = "%s_%i_%i"%(self.problem_id, response_id, answer_id)
|
||||
|
||||
@@ -6,11 +6,16 @@
|
||||
Module containing the problem elements which render into input objects
|
||||
|
||||
- textline
|
||||
- textbox (change this to textarea?)
|
||||
- textbox (change this to textarea?)
|
||||
- schemmatic
|
||||
- choicegroup (for multiplechoice: checkbox, radio, or select option)
|
||||
- imageinput (for clickable image)
|
||||
- optioninput (for option list)
|
||||
|
||||
These are matched by *.html files templates/*.html which are mako templates with the actual html.
|
||||
|
||||
Each input type takes the xml tree as 'element', the previous answer as 'value', and the graded status as 'status'
|
||||
|
||||
'''
|
||||
|
||||
# TODO: rename "state" to "status" for all below
|
||||
@@ -18,6 +23,7 @@ These are matched by *.html files templates/*.html which are mako templates with
|
||||
# but it will turn into a dict containing both the answer and any associated message for the problem ID for the input element.
|
||||
|
||||
import re
|
||||
import shlex # for splitting quoted strings
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
@@ -27,9 +33,42 @@ from lxml import etree
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
#takes the xml tree as 'element', the student's previous answer as 'value', and the graded status as 'state'
|
||||
|
||||
def choicegroup(element, value, state, msg=""):
|
||||
def optioninput(element, value, status, msg=''):
|
||||
'''
|
||||
Select option input type.
|
||||
|
||||
Example:
|
||||
|
||||
<optioninput options="('Up','Down')" correct="Up"/><text>The location of the sky</text>
|
||||
'''
|
||||
eid=element.get('id')
|
||||
options = element.get('options')
|
||||
if not options:
|
||||
raise Exception,"[courseware.capa.inputtypes.optioninput] Missing options specification in " + etree.tostring(element)
|
||||
oset = shlex.shlex(options[1:-1])
|
||||
oset.quotes = "'"
|
||||
oset.whitespace = ","
|
||||
oset = [x[1:-1] for x in list(oset)]
|
||||
|
||||
# osetdict = dict([('option_%s_%s' % (eid,x),oset[x]) for x in range(len(oset)) ]) # make dict with IDs
|
||||
osetdict = dict([(oset[x],oset[x]) for x in range(len(oset)) ]) # make dict with key,value same
|
||||
if settings.DEBUG:
|
||||
print '[courseware.capa.inputtypes.optioninput] osetdict=',osetdict
|
||||
|
||||
context={'id':eid,
|
||||
'value':value,
|
||||
'state':status,
|
||||
'msg':msg,
|
||||
'options':osetdict,
|
||||
}
|
||||
|
||||
html=render_to_string("optioninput.html", context)
|
||||
return etree.XML(html)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def choicegroup(element, value, status, msg=''):
|
||||
'''
|
||||
Radio button inputs: multiple choice or true/false
|
||||
|
||||
@@ -47,7 +86,7 @@ def choicegroup(element, value, state, msg=""):
|
||||
for choice in element:
|
||||
assert choice.tag =="choice", "only <choice> tags should be immediate children of a <choicegroup>"
|
||||
choices[choice.get("name")] = etree.tostring(choice[0]) # TODO: what if choice[0] has math tags in it?
|
||||
context={'id':eid, 'value':value, 'state':state, 'type':type, 'choices':choices}
|
||||
context={'id':eid, 'value':value, 'state':status, 'type':type, 'choices':choices}
|
||||
html=render_to_string("choicegroup.html", context)
|
||||
return etree.XML(html)
|
||||
|
||||
@@ -60,9 +99,9 @@ def textline(element, value, state, msg=""):
|
||||
return etree.XML(html)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# TODO: Make a wrapper for <formulainput>
|
||||
# TODO: Make an AJAX loop to confirm equation is okay in real-time as user types
|
||||
def jstextline(element, value, state, msg=""):
|
||||
|
||||
def js_textline(element, value, status, msg=''):
|
||||
## TODO: Code should follow PEP8 (4 spaces per indentation level)
|
||||
'''
|
||||
textline is used for simple one-line inputs, like formularesponse and symbolicresponse.
|
||||
'''
|
||||
@@ -72,7 +111,7 @@ def jstextline(element, value, state, msg=""):
|
||||
dojs = element.get('dojs') # dojs is used for client-side javascript display & return
|
||||
# when dojs=='math', a <span id=display_eid>`{::}`</span>
|
||||
# and a hidden textarea with id=input_eid_fromjs will be output
|
||||
context = {'id':eid, 'value':value, 'state':state, 'count':count, 'size': size,
|
||||
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size,
|
||||
'dojs':dojs,
|
||||
'msg':msg,
|
||||
}
|
||||
@@ -81,7 +120,7 @@ def jstextline(element, value, state, msg=""):
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
## TODO: Make a wrapper for <codeinput>
|
||||
def textbox(element, value, state, msg=''):
|
||||
def textbox(element, value, status, msg=''):
|
||||
'''
|
||||
The textbox is used for code input. The message is the return HTML string from
|
||||
evaluating the code, eg error messages, and output from the code tests.
|
||||
@@ -91,12 +130,12 @@ def textbox(element, value, state, msg=''):
|
||||
eid=element.get('id')
|
||||
count = int(eid.split('_')[-2])-1 # HACK
|
||||
size = element.get('size')
|
||||
context = {'id':eid, 'value':value, 'state':state, 'count':count, 'size': size, 'msg':msg}
|
||||
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg':msg}
|
||||
html=render_to_string("textbox.html", context)
|
||||
return etree.XML(html)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def schematic(element, value, state):
|
||||
def schematic(element, value, status, msg=''):
|
||||
eid = element.get('id')
|
||||
height = element.get('height')
|
||||
width = element.get('width')
|
||||
@@ -120,7 +159,7 @@ def schematic(element, value, state):
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
### TODO: Move out of inputtypes
|
||||
def math(element, value, state, msg=''):
|
||||
def math(element, value, status, msg=''):
|
||||
'''
|
||||
This is not really an input type. It is a convention from Lon-CAPA, used for
|
||||
displaying a math equation.
|
||||
@@ -134,21 +173,27 @@ def math(element, value, state, msg=''):
|
||||
|
||||
TODO: use shorter tags (but this will require converting problem XML files!)
|
||||
'''
|
||||
mathstr = element.text[1:-1]
|
||||
if '\\displaystyle' in mathstr:
|
||||
isinline = False
|
||||
mathstr = mathstr.replace('\\displaystyle','')
|
||||
else:
|
||||
isinline = True
|
||||
mathstr = re.sub('\$(.*)\$','[mathjaxinline]\\1[/mathjaxinline]',element.text)
|
||||
mtag = 'mathjax'
|
||||
if not '\\displaystyle' in mathstr: mtag += 'inline'
|
||||
else: mathstr = mathstr.replace('\\displaystyle','')
|
||||
mathstr = mathstr.replace('mathjaxinline]','%s]'%mtag)
|
||||
|
||||
html=render_to_string("mathstring.html",{'mathstr':mathstr,'isinline':isinline,'tail':element.tail})
|
||||
#if '\\displaystyle' in mathstr:
|
||||
# isinline = False
|
||||
# mathstr = mathstr.replace('\\displaystyle','')
|
||||
#else:
|
||||
# isinline = True
|
||||
# html=render_to_string("mathstring.html",{'mathstr':mathstr,'isinline':isinline,'tail':element.tail})
|
||||
|
||||
html = '<html><html>%s</html><html>%s</html></html>' % (mathstr,element.tail)
|
||||
xhtml = etree.XML(html)
|
||||
# xhtml.tail = element.tail # don't forget to include the tail!
|
||||
return xhtml
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def solution(element, value, state, msg=''):
|
||||
def solution(element, value, status, msg=''):
|
||||
'''
|
||||
This is not really an input type. It is just a <span>...</span> which is given an ID,
|
||||
that is used for displaying an extended answer (a problem "solution") after "show answers"
|
||||
@@ -159,7 +204,7 @@ def solution(element, value, state, msg=''):
|
||||
size = element.get('size')
|
||||
context = {'id':eid,
|
||||
'value':value,
|
||||
'state':state,
|
||||
'state':status,
|
||||
'size': size,
|
||||
'msg':msg,
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful So
|
||||
import calc
|
||||
import eia
|
||||
|
||||
from util import contextualize_text
|
||||
|
||||
def compare_with_tolerance(v1, v2, tol):
|
||||
''' Compare v1 to v2 with maximum tolerance tol
|
||||
tol is relative if it ends in %; otherwise, it is absolute
|
||||
@@ -61,6 +63,8 @@ class GenericResponse(object):
|
||||
|
||||
#Every response type needs methods "grade" and "get_answers"
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class MultipleChoiceResponse(GenericResponse):
|
||||
'''
|
||||
Example:
|
||||
@@ -84,6 +88,7 @@ class MultipleChoiceResponse(GenericResponse):
|
||||
self.correct_choices = [choice.get('name') for choice in self.correct_choices]
|
||||
self.context = context
|
||||
|
||||
self.answer_field = xml.find('choicegroup') # assumes only ONE choicegroup within this response
|
||||
self.answer_id = xml.xpath('//*[@id=$id]//choicegroup/@id',
|
||||
id=xml.get('id'))
|
||||
if not len(self.answer_id) == 1:
|
||||
@@ -100,9 +105,14 @@ class MultipleChoiceResponse(GenericResponse):
|
||||
return {self.answer_id:self.correct_choices}
|
||||
|
||||
def preprocess_response(self):
|
||||
'''
|
||||
Initialize name attributes in <choice> stanzas in the <choicegroup> in this response.
|
||||
'''
|
||||
i=0
|
||||
for response in self.xml.xpath("choicegroup"):
|
||||
response.set("type", "MultipleChoice")
|
||||
rtype = response.get('type')
|
||||
if rtype not in ["MultipleChoice"]:
|
||||
response.set("type", "MultipleChoice") # force choicegroup to be MultipleChoice if not valid
|
||||
for choice in list(response):
|
||||
if choice.get("name") == None:
|
||||
choice.set("name", "choice_"+str(i))
|
||||
@@ -131,6 +141,42 @@ class TrueFalseResponse(MultipleChoiceResponse):
|
||||
|
||||
return {self.answer_id : 'incorrect'}
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class OptionResponse(GenericResponse):
|
||||
'''
|
||||
Example:
|
||||
|
||||
<optionresponse direction="vertical" randomize="yes">
|
||||
<optioninput options="('Up','Down')" correct="Up"><text>The location of the sky</text></optioninput>
|
||||
<optioninput options="('Up','Down')" correct="Down"><text>The location of the earth</text></optioninput>
|
||||
</optionresponse>
|
||||
|
||||
TODO: handle direction and randomize
|
||||
|
||||
'''
|
||||
def __init__(self, xml, context):
|
||||
self.xml = xml
|
||||
self.answer_fields = xml.findall('optioninput')
|
||||
if settings.DEBUG:
|
||||
print '[courseware.capa.responsetypes.OR.init] answer_fields=%s' % (self.answer_fields)
|
||||
self.context = context
|
||||
|
||||
def grade(self, student_answers):
|
||||
cmap = {}
|
||||
amap = self.get_answers()
|
||||
for aid in amap:
|
||||
if aid in student_answers and student_answers[aid]==amap[aid]:
|
||||
cmap[aid] = 'correct'
|
||||
else:
|
||||
cmap[aid] = 'incorrect'
|
||||
return cmap
|
||||
|
||||
def get_answers(self):
|
||||
amap = dict([(af.get('id'),af.get('correct')) for af in self.answer_fields])
|
||||
return amap
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class NumericalResponse(GenericResponse):
|
||||
def __init__(self, xml, context):
|
||||
@@ -219,43 +265,153 @@ def sympy_check2():
|
||||
self.answer_ids = xml.xpath('//*[@id=$id]//textline/@id',
|
||||
id=xml.get('id'))
|
||||
self.context = context
|
||||
answer_list = xml.xpath('//*[@id=$id]//answer',
|
||||
id=xml.get('id'))
|
||||
if len(answer_list):
|
||||
answer=answer_list[0]
|
||||
else:
|
||||
raise Exception("Invalid custom response -- no checker code")
|
||||
|
||||
answer_src = answer.get('src')
|
||||
if answer_src != None:
|
||||
self.code = open(settings.DATA_DIR+'src/'+answer_src).read()
|
||||
else:
|
||||
self.code = answer.text
|
||||
# if <customresponse> has an "expect" attribute then save that
|
||||
self.expect = xml.get('expect')
|
||||
self.myid = xml.get('id')
|
||||
|
||||
# the <answer>...</answer> stanza should be local to the current <customresponse>. So try looking there first.
|
||||
self.code = None
|
||||
answer = None
|
||||
try:
|
||||
answer = xml.xpath('//*[@id=$id]//answer',id=xml.get('id'))[0]
|
||||
except IndexError,err:
|
||||
# print "xml = ",etree.tostring(xml,pretty_print=True)
|
||||
|
||||
# if we have a "cfn" attribute then look for the function specified by cfn, in the problem context
|
||||
# ie the comparison function is defined in the <script>...</script> stanza instead
|
||||
cfn = xml.get('cfn')
|
||||
if cfn:
|
||||
if settings.DEBUG: print "[courseware.capa.responsetypes] cfn = ",cfn
|
||||
if cfn in context:
|
||||
self.code = context[cfn]
|
||||
else:
|
||||
print "can't find cfn in context = ",context
|
||||
|
||||
if not self.code:
|
||||
if not answer:
|
||||
# raise Exception,"[courseware.capa.responsetypes.customresponse] missing code checking script! id=%s" % self.myid
|
||||
print "[courseware.capa.responsetypes.customresponse] missing code checking script! id=%s" % self.myid
|
||||
self.code = ''
|
||||
else:
|
||||
answer_src = answer.get('src')
|
||||
if answer_src != None:
|
||||
self.code = open(settings.DATA_DIR+'src/'+answer_src).read()
|
||||
else:
|
||||
self.code = answer.text
|
||||
|
||||
def grade(self, student_answers):
|
||||
'''
|
||||
student_answers is a dict with everything from request.POST, but with the first part
|
||||
of each key removed (the string before the first "_").
|
||||
'''
|
||||
from capa_problem import global_context
|
||||
submission = [student_answers[k] for k in sorted(self.answer_ids)]
|
||||
self.context.update({'submission':submission})
|
||||
exec self.code in global_context, self.context
|
||||
return zip(sorted(self.answer_ids), self.context['correct'])
|
||||
|
||||
def getkey2(dict,key,default):
|
||||
"""utilify function: get dict[key] if key exists, or return default"""
|
||||
if dict.has_key(key):
|
||||
return dict[key]
|
||||
return default
|
||||
|
||||
idset = sorted(self.answer_ids) # ordered list of answer id's
|
||||
submission = [student_answers[k] for k in idset] # ordered list of answers
|
||||
fromjs = [ getkey2(student_answers,k+'_fromjs',None) for k in idset ] # ordered list of fromjs_XXX responses (if exists)
|
||||
|
||||
# if there is only one box, and it's empty, then don't evaluate
|
||||
if len(idset)==1 and not submission[0]:
|
||||
return {idset[0]:'no_answer_entered'}
|
||||
|
||||
gctxt = self.context['global_context']
|
||||
|
||||
correct = ['unknown'] * len(idset)
|
||||
messages = [''] * len(idset)
|
||||
|
||||
# put these in the context of the check function evaluator
|
||||
# note that this doesn't help the "cfn" version - only the exec version
|
||||
self.context.update({'xml' : self.xml, # our subtree
|
||||
'response_id' : self.myid, # my ID
|
||||
'expect': self.expect, # expected answer (if given as attribute)
|
||||
'submission':submission, # ordered list of student answers from entry boxes in our subtree
|
||||
'idset':idset, # ordered list of ID's of all entry boxes in our subtree
|
||||
'fromjs':fromjs, # ordered list of all javascript inputs in our subtree
|
||||
'answers':student_answers, # dict of student's responses, with keys being entry box IDs
|
||||
'correct':correct, # the list to be filled in by the check function
|
||||
'messages':messages, # the list of messages to be filled in by the check function
|
||||
'testdat':'hello world',
|
||||
})
|
||||
|
||||
# exec the check function
|
||||
if type(self.code)==str:
|
||||
try:
|
||||
exec self.code in self.context['global_context'], self.context
|
||||
except Exception,err:
|
||||
print "oops in customresponse (code) error %s" % err
|
||||
print "context = ",self.context
|
||||
print traceback.format_exc()
|
||||
else: # self.code is not a string; assume its a function
|
||||
|
||||
# this is an interface to the Tutor2 check functions
|
||||
fn = self.code
|
||||
try:
|
||||
answer_given = submission[0] if (len(idset)==1) else submission
|
||||
if fn.func_code.co_argcount>=4: # does it want four arguments (the answers dict, myname)?
|
||||
ret = fn(self.expect,answer_given,student_answers,self.answer_ids[0])
|
||||
elif fn.func_code.co_argcount>=3: # does it want a third argument (the answers dict)?
|
||||
ret = fn(self.expect,answer_given,student_answers)
|
||||
else:
|
||||
ret = fn(self.expect,answer_given)
|
||||
except Exception,err:
|
||||
print "oops in customresponse (cfn) error %s" % err
|
||||
# print "context = ",self.context
|
||||
print traceback.format_exc()
|
||||
if settings.DEBUG: print "[courseware.capa.responsetypes.customresponse.grade] ret = ",ret
|
||||
if type(ret)==dict:
|
||||
correct[0] = 'correct' if ret['ok'] else 'incorrect'
|
||||
msg = ret['msg']
|
||||
|
||||
if 1:
|
||||
# try to clean up message html
|
||||
msg = '<html>'+msg+'</html>'
|
||||
msg = etree.tostring(fromstring_bs(msg),pretty_print=True)
|
||||
msg = msg.replace(' ','')
|
||||
#msg = re.sub('<html>(.*)</html>','\\1',msg,flags=re.M|re.DOTALL) # python 2.7
|
||||
msg = re.sub('(?ms)<html>(.*)</html>','\\1',msg)
|
||||
|
||||
messages[0] = msg
|
||||
else:
|
||||
correct[0] = 'correct' if ret else 'incorrect'
|
||||
|
||||
# build map giving "correct"ness of the answer(s)
|
||||
#correct_map = dict(zip(idset, self.context['correct']))
|
||||
correct_map = {}
|
||||
for k in range(len(idset)):
|
||||
correct_map[idset[k]] = correct[k]
|
||||
correct_map['msg_%s' % idset[k]] = messages[k]
|
||||
return correct_map
|
||||
|
||||
def get_answers(self):
|
||||
# Since this is explicitly specified in the problem, this will
|
||||
# be handled by capa_problem
|
||||
'''
|
||||
Give correct answer expected for this response.
|
||||
|
||||
capa_problem handles correct_answers from entry objects like textline, and that
|
||||
is what should be used when this response has multiple entry objects.
|
||||
|
||||
but for simplicity, if an "expect" attribute was given by the content author
|
||||
ie <customresponse expect="foo" ...> then return it now.
|
||||
'''
|
||||
if len(self.answer_ids)>1:
|
||||
return {}
|
||||
if self.expect:
|
||||
return {self.answer_ids[0] : self.expect}
|
||||
return {}
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class ExternalResponse(GenericResponse):
|
||||
'''
|
||||
"""
|
||||
Grade the student's input using an external server.
|
||||
|
||||
Typically used by coding problems.
|
||||
'''
|
||||
"""
|
||||
def __init__(self, xml, context):
|
||||
self.xml = xml
|
||||
self.answer_ids = xml.xpath('//*[@id=$id]//textbox/@id|//*[@id=$id]//textline/@id',
|
||||
@@ -471,10 +627,6 @@ class ImageResponse(GenericResponse):
|
||||
raise Exception,'[capamodule.capa.responsetypes.imageinput] error grading %s (input=%s)' % (err,aid,given)
|
||||
(gx,gy) = [int(x) for x in m.groups()]
|
||||
|
||||
if settings.DEBUG:
|
||||
print "[capamodule.capa.responsetypes.imageinput] llx,lly,urx,ury=",(llx,lly,urx,ury)
|
||||
print "[capamodule.capa.responsetypes.imageinput] gx,gy=",(gx,gy)
|
||||
|
||||
# answer is correct if (x,y) is within the specified rectangle
|
||||
if (llx <= gx <= urx) and (lly <= gy <= ury):
|
||||
correct_map[aid] = 'correct'
|
||||
|
||||
@@ -24,7 +24,9 @@ try: # This lets us do __name__ == ='__main__'
|
||||
from student.models import UserTestGroup
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
from util.cache import cache
|
||||
from multicourse import multicourse_settings
|
||||
except:
|
||||
print "Could not import/content_parser"
|
||||
settings = None
|
||||
|
||||
''' This file will eventually form an abstraction layer between the
|
||||
@@ -181,7 +183,7 @@ def course_xml_process(tree):
|
||||
propogate_downward_tag(tree, "rerandomize")
|
||||
return tree
|
||||
|
||||
def course_file(user):
|
||||
def course_file(user,coursename=None):
|
||||
''' Given a user, return course.xml'''
|
||||
|
||||
if user.is_authenticated():
|
||||
@@ -189,6 +191,11 @@ def course_file(user):
|
||||
else:
|
||||
filename = 'guest_course.xml'
|
||||
|
||||
# if a specific course is specified, then use multicourse to get the right path to the course XML directory
|
||||
if coursename and settings.ENABLE_MULTICOURSE:
|
||||
xp = multicourse_settings.get_course_xmlpath(coursename)
|
||||
filename = xp + filename # prefix the filename with the path
|
||||
|
||||
groups = user_groups(user)
|
||||
options = {'dev_content':settings.DEV_CONTENT,
|
||||
'groups' : groups}
|
||||
@@ -210,13 +217,24 @@ def course_file(user):
|
||||
|
||||
return tree
|
||||
|
||||
def section_file(user, section):
|
||||
''' Given a user and the name of a section, return that section
|
||||
def section_file(user, section, coursename=None, dironly=False):
|
||||
'''
|
||||
Given a user and the name of a section, return that section.
|
||||
This is done specific to each course.
|
||||
If dironly=True then return the sections directory.
|
||||
'''
|
||||
filename = section+".xml"
|
||||
|
||||
if filename not in os.listdir(settings.DATA_DIR + '/sections/'):
|
||||
print filename+" not in "+str(os.listdir(settings.DATA_DIR + '/sections/'))
|
||||
# if a specific course is specified, then use multicourse to get the right path to the course XML directory
|
||||
xp = ''
|
||||
if coursename and settings.ENABLE_MULTICOURSE: xp = multicourse_settings.get_course_xmlpath(coursename)
|
||||
|
||||
dirname = settings.DATA_DIR + xp + '/sections/'
|
||||
|
||||
if dironly: return dirname
|
||||
|
||||
if filename not in os.listdir(dirname):
|
||||
print filename+" not in "+str(os.listdir(dirname))
|
||||
return None
|
||||
|
||||
options = {'dev_content':settings.DEV_CONTENT,
|
||||
@@ -226,7 +244,7 @@ def section_file(user, section):
|
||||
return tree
|
||||
|
||||
|
||||
def module_xml(user, module, id_tag, module_id):
|
||||
def module_xml(user, module, id_tag, module_id, coursename=None):
|
||||
''' Get XML for a module based on module and module_id. Assumes
|
||||
module occurs once in courseware XML file or hidden section. '''
|
||||
# Sanitize input
|
||||
@@ -239,14 +257,15 @@ def module_xml(user, module, id_tag, module_id):
|
||||
id_tag=id_tag,
|
||||
id=module_id)
|
||||
#result_set=doc.xpathEval(xpath_search)
|
||||
doc = course_file(user)
|
||||
section_list = (s[:-4] for s in os.listdir(settings.DATA_DIR+'/sections') if s[-4:]=='.xml')
|
||||
doc = course_file(user,coursename)
|
||||
sdirname = section_file(user,'',coursename,True) # get directory where sections information is stored
|
||||
section_list = (s[:-4] for s in os.listdir(sdirname) if s[-4:]=='.xml')
|
||||
|
||||
result_set=doc.xpath(xpath_search)
|
||||
if len(result_set)<1:
|
||||
for section in section_list:
|
||||
try:
|
||||
s = section_file(user, section)
|
||||
s = section_file(user, section, coursename)
|
||||
except etree.XMLSyntaxError:
|
||||
ex= sys.exc_info()
|
||||
raise ContentException("Malformed XML in " + section+ "("+str(ex[1].msg)+")")
|
||||
|
||||
@@ -67,7 +67,7 @@ course_settings = Settings()
|
||||
|
||||
|
||||
|
||||
def grade_sheet(student):
|
||||
def grade_sheet(student,coursename=None):
|
||||
"""
|
||||
This pulls a summary of all problems in the course. It returns a dictionary with two datastructures:
|
||||
|
||||
@@ -77,7 +77,7 @@ def grade_sheet(student):
|
||||
|
||||
- grade_summary is the output from the course grader. More information on the format is in the docstring for CourseGrader.
|
||||
"""
|
||||
dom=content_parser.course_file(student)
|
||||
dom=content_parser.course_file(student,coursename)
|
||||
course = dom.xpath('//course/@name')[0]
|
||||
xmlChapters = dom.xpath('//course[@name=$course]/chapter', course=course)
|
||||
|
||||
@@ -103,7 +103,7 @@ def grade_sheet(student):
|
||||
scores=[]
|
||||
if len(problems)>0:
|
||||
for p in problems:
|
||||
(correct,total) = get_score(student, p, response_by_id)
|
||||
(correct,total) = get_score(student, p, response_by_id, coursename=coursename)
|
||||
|
||||
if settings.GENERATE_PROFILE_SCORES:
|
||||
if total > 1:
|
||||
@@ -167,7 +167,7 @@ def aggregate_scores(scores, section_name = "summary"):
|
||||
return all_total, graded_total
|
||||
|
||||
|
||||
def get_score(user, problem, cache):
|
||||
def get_score(user, problem, cache, coursename=None):
|
||||
## HACK: assumes max score is fixed per problem
|
||||
id = problem.get('id')
|
||||
correct = 0.0
|
||||
@@ -196,7 +196,7 @@ def get_score(user, problem, cache):
|
||||
## HACK 1: We shouldn't specifically reference capa_module
|
||||
## HACK 2: Backwards-compatibility: This should be written when a grade is saved, and removed from the system
|
||||
from module_render import I4xSystem
|
||||
system = I4xSystem(None, None, None)
|
||||
system = I4xSystem(None, None, None, coursename=coursename)
|
||||
total=float(courseware.modules.capa_module.Module(system, etree.tostring(problem), "id").max_score())
|
||||
response.max_grade = total
|
||||
response.save()
|
||||
|
||||
@@ -22,6 +22,11 @@ import courseware.modules
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
|
||||
class I4xSystem(object):
|
||||
'''
|
||||
This is an abstraction such that x_modules can function independent
|
||||
of the courseware (e.g. import into other types of courseware, LMS,
|
||||
or if we want to have a sandbox server for user-contributed content)
|
||||
'''
|
||||
def __init__(self, ajax_url, track_function, render_function, filestore=None):
|
||||
self.ajax_url = ajax_url
|
||||
self.track_function = track_function
|
||||
@@ -29,6 +34,10 @@ class I4xSystem(object):
|
||||
self.filestore = OSFS(settings.DATA_DIR)
|
||||
self.render_function = render_function
|
||||
self.exception404 = Http404
|
||||
def __repr__(self):
|
||||
return repr(self.__dict__)
|
||||
def __str__(self):
|
||||
return str(self.__dict__)
|
||||
|
||||
def object_cache(cache, user, module_type, module_id):
|
||||
# We don't look up on user -- all queries include user
|
||||
@@ -50,6 +59,7 @@ def make_track_function(request):
|
||||
def f(event_type, event):
|
||||
return track.views.server_track(request, event_type, event, page='x_module')
|
||||
return f
|
||||
|
||||
def grade_histogram(module_id):
|
||||
''' Print out a histogram of grades on a given problem.
|
||||
Part of staff member debug info.
|
||||
@@ -83,6 +93,10 @@ def render_x_module(user, request, xml_module, module_object_preload):
|
||||
else:
|
||||
state = smod.state
|
||||
|
||||
# get coursename if stored
|
||||
if 'coursename' in request.session: coursename = request.session['coursename']
|
||||
else: coursename = None
|
||||
|
||||
# Create a new instance
|
||||
ajax_url = settings.MITX_ROOT_URL + '/modx/'+module_type+'/'+module_id+'/'
|
||||
system = I4xSystem(track_function = make_track_function(request),
|
||||
@@ -104,6 +118,7 @@ def render_x_module(user, request, xml_module, module_object_preload):
|
||||
state=instance.get_state())
|
||||
smod.save()
|
||||
module_object_preload.append(smod)
|
||||
|
||||
# Grab content
|
||||
content = instance.get_html()
|
||||
init_js = instance.get_init_js()
|
||||
|
||||
@@ -21,6 +21,7 @@ from mitxmako.shortcuts import render_to_string
|
||||
from x_module import XModule
|
||||
from courseware.capa.capa_problem import LoncapaProblem, StudentInputError
|
||||
import courseware.content_parser as content_parser
|
||||
from multicourse import multicourse_settings
|
||||
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
|
||||
@@ -115,18 +116,19 @@ class Module(XModule):
|
||||
if len(explain) == 0:
|
||||
explain = False
|
||||
|
||||
html=render_to_string('problem.html',
|
||||
{'problem' : content,
|
||||
'id' : self.item_id,
|
||||
'check_button' : check_button,
|
||||
'reset_button' : reset_button,
|
||||
'save_button' : save_button,
|
||||
'answer_available' : self.answer_available(),
|
||||
'ajax_url' : self.ajax_url,
|
||||
'attempts_used': self.attempts,
|
||||
'attempts_allowed': self.max_attempts,
|
||||
'explain': explain
|
||||
})
|
||||
context = {'problem' : content,
|
||||
'id' : self.item_id,
|
||||
'check_button' : check_button,
|
||||
'reset_button' : reset_button,
|
||||
'save_button' : save_button,
|
||||
'answer_available' : self.answer_available(),
|
||||
'ajax_url' : self.ajax_url,
|
||||
'attempts_used': self.attempts,
|
||||
'attempts_allowed': self.max_attempts,
|
||||
'explain': explain,
|
||||
}
|
||||
|
||||
html=render_to_string('problem.html', context)
|
||||
if encapsulate:
|
||||
html = '<div id="main_{id}">'.format(id=self.item_id)+html+"</div>"
|
||||
|
||||
@@ -193,7 +195,12 @@ class Module(XModule):
|
||||
seed = 1
|
||||
else:
|
||||
seed = None
|
||||
self.lcp=LoncapaProblem(self.filestore.open(self.filename), self.item_id, state, seed = seed)
|
||||
try:
|
||||
fp = self.filestore.open(self.filename)
|
||||
except Exception,err:
|
||||
print '[courseware.capa.capa_module.Module.init] error %s: cannot open file %s' % (err,self.filename)
|
||||
raise Exception,err
|
||||
self.lcp=LoncapaProblem(fp, self.item_id, state, seed = seed)
|
||||
|
||||
def handle_ajax(self, dispatch, get):
|
||||
'''
|
||||
@@ -306,7 +313,7 @@ class Module(XModule):
|
||||
except:
|
||||
self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state)
|
||||
traceback.print_exc()
|
||||
raise
|
||||
raise Exception,"error in capa_module"
|
||||
return json.dumps({'success':'Unknown Error'})
|
||||
|
||||
self.attempts = self.attempts + 1
|
||||
|
||||
63
djangoapps/courseware/test_files/optionresponse.xml
Normal file
63
djangoapps/courseware/test_files/optionresponse.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<problem>
|
||||
<text>
|
||||
<p>
|
||||
Why do bicycles benefit from having larger wheels when going up a bump as shown in the picture? <br/>
|
||||
Assume that for both bicycles:<br/>
|
||||
1.) The tires have equal air pressure.<br/>
|
||||
2.) The bicycles never leave the contact with the bump.<br/>
|
||||
3.) The bicycles have the same mass. The bicycle tires (regardless of size) have the same mass.<br/>
|
||||
</p>
|
||||
</text>
|
||||
<optionresponse texlayout="horizontal" max="10" randomize="yes">
|
||||
<ul>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels have more time to go over the bump. This decreases the magnitude of the force needed to lift the bicycle.</p>
|
||||
</text>
|
||||
<optioninput name="Foil1" location="random" options="('True','False')" correct="True">
|
||||
</optioninput>
|
||||
</li>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels always have a smaller vertical displacement regardless of speed.</p>
|
||||
</text>
|
||||
<optioninput name="Foil2" location="random" options="('True','False')" correct="False">
|
||||
</optioninput>
|
||||
</li>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels experience a force backward with less magnitude for the same amount of time.</p>
|
||||
</text>
|
||||
<optioninput name="Foil3" location="random" options="('True','False')" correct="False">
|
||||
</optioninput>
|
||||
</li>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels experience a force backward with less magnitude for a greater amount of time.</p>
|
||||
</text>
|
||||
<optioninput name="Foil4" location="random" options="('True','False')" correct="True">
|
||||
</optioninput>
|
||||
</li>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels have more kinetic energy turned into gravitational potential energy.</p>
|
||||
</text>
|
||||
<optioninput name="Foil5" location="random" options="('True','False')" correct="False">
|
||||
</optioninput>
|
||||
</li>
|
||||
<li>
|
||||
<text>
|
||||
<p>The bicycles with larger wheels have more rotational kinetic energy, so the horizontal velocity of the biker changes less.</p>
|
||||
</text>
|
||||
<optioninput name="Foil6" location="random" options="('True','False')" correct="False">
|
||||
</optioninput>
|
||||
</li>
|
||||
</ul>
|
||||
<hintgroup showoncorrect="no">
|
||||
<text>
|
||||
<br/>
|
||||
<br/>
|
||||
</text>
|
||||
</hintgroup>
|
||||
</optionresponse>
|
||||
</problem>
|
||||
@@ -63,6 +63,9 @@ class ModelsTest(unittest.TestCase):
|
||||
exception_happened = True
|
||||
self.assertTrue(exception_happened)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# tests of capa_problem inputtypes
|
||||
|
||||
class MultiChoiceTest(unittest.TestCase):
|
||||
def test_MC_grade(self):
|
||||
multichoice_file = os.path.dirname(__file__)+"/test_files/multichoice.xml"
|
||||
@@ -93,6 +96,38 @@ class MultiChoiceTest(unittest.TestCase):
|
||||
self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect')
|
||||
false_answers = {'1_2_1':['choice_foil1', 'choice_foil2', 'choice_foil3']}
|
||||
self.assertEquals(test_lcp.grade_answers(false_answers)['1_2_1'], 'incorrect')
|
||||
|
||||
class ImageResponseTest(unittest.TestCase):
|
||||
def test_ir_grade(self):
|
||||
imageresponse_file = os.path.dirname(__file__)+"/test_files/imageresponse.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(imageresponse_file), '1')
|
||||
correct_answers = {'1_2_1':'(490,11)-(556,98)',
|
||||
'1_2_2':'(242,202)-(296,276)'}
|
||||
test_answers = {'1_2_1':'[500,20]',
|
||||
'1_2_2':'[250,300]',
|
||||
}
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_1'], 'correct')
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_2'], 'incorrect')
|
||||
|
||||
class OptionResponseTest(unittest.TestCase):
|
||||
'''
|
||||
Run this with
|
||||
|
||||
python manage.py test courseware.OptionResponseTest
|
||||
'''
|
||||
def test_or_grade(self):
|
||||
optionresponse_file = os.path.dirname(__file__)+"/test_files/optionresponse.xml"
|
||||
test_lcp = lcp.LoncapaProblem(open(optionresponse_file), '1')
|
||||
correct_answers = {'1_2_1':'True',
|
||||
'1_2_2':'False'}
|
||||
test_answers = {'1_2_1':'True',
|
||||
'1_2_2':'True',
|
||||
}
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_1'], 'correct')
|
||||
self.assertEquals(test_lcp.grade_answers(test_answers)['1_2_2'], 'incorrect')
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Grading tests
|
||||
|
||||
class GradesheetTest(unittest.TestCase):
|
||||
|
||||
@@ -118,7 +153,7 @@ class GradesheetTest(unittest.TestCase):
|
||||
all, graded = aggregate_scores(scores)
|
||||
self.assertAlmostEqual(all, Score(earned=5, possible=15, graded=False, section="summary"))
|
||||
self.assertAlmostEqual(graded, Score(earned=5, possible=10, graded=True, section="summary"))
|
||||
|
||||
|
||||
class GraderTest(unittest.TestCase):
|
||||
|
||||
empty_gradesheet = {
|
||||
|
||||
@@ -18,6 +18,7 @@ from module_render import render_module, make_track_function, I4xSystem
|
||||
from models import StudentModule
|
||||
from student.models import UserProfile
|
||||
from util.errors import record_exception
|
||||
from multicourse import multicourse_settings
|
||||
|
||||
import courseware.content_parser as content_parser
|
||||
import courseware.modules
|
||||
@@ -35,11 +36,16 @@ template_imports={'urllib':urllib}
|
||||
def gradebook(request):
|
||||
if 'course_admin' not in content_parser.user_groups(request.user):
|
||||
raise Http404
|
||||
|
||||
# TODO: This should be abstracted out. We repeat this logic many times.
|
||||
if 'coursename' in request.session: coursename = request.session['coursename']
|
||||
else: coursename = None
|
||||
|
||||
student_objects = User.objects.all()[:100]
|
||||
student_info = [{'username' :s.username,
|
||||
'id' : s.id,
|
||||
'email': s.email,
|
||||
'grade_info' : grades.grade_sheet(s),
|
||||
'grade_info' : grades.grade_sheet(s,coursename),
|
||||
'realname' : UserProfile.objects.get(user = s).name
|
||||
} for s in student_objects]
|
||||
|
||||
@@ -61,6 +67,9 @@ def profile(request, student_id = None):
|
||||
|
||||
user_info = UserProfile.objects.get(user=student) # request.user.profile_cache #
|
||||
|
||||
if 'coursename' in request.session: coursename = request.session['coursename']
|
||||
else: coursename = None
|
||||
|
||||
context={'name':user_info.name,
|
||||
'username':student.username,
|
||||
'location':user_info.location,
|
||||
@@ -69,7 +78,7 @@ def profile(request, student_id = None):
|
||||
'format_url_params' : content_parser.format_url_params,
|
||||
'csrf':csrf(request)['csrf_token']
|
||||
}
|
||||
context.update(grades.grade_sheet(student))
|
||||
context.update(grades.grade_sheet(student,coursename))
|
||||
|
||||
return render_to_response('profile.html', context)
|
||||
|
||||
@@ -79,7 +88,7 @@ def render_accordion(request,course,chapter,section):
|
||||
if not course:
|
||||
course = "6.002 Spring 2012"
|
||||
|
||||
toc=content_parser.toc_from_xml(content_parser.course_file(request.user), chapter, section)
|
||||
toc=content_parser.toc_from_xml(content_parser.course_file(request.user,course), chapter, section)
|
||||
active_chapter=1
|
||||
for i in range(len(toc)):
|
||||
if toc[i]['active']:
|
||||
@@ -100,8 +109,11 @@ def render_section(request, section):
|
||||
if not settings.COURSEWARE_ENABLED:
|
||||
return redirect('/')
|
||||
|
||||
if 'coursename' in request.session: coursename = request.session['coursename']
|
||||
else: coursename = None
|
||||
|
||||
try:
|
||||
dom = content_parser.section_file(user, section)
|
||||
dom = content_parser.section_file(user, section, coursename)
|
||||
except:
|
||||
record_exception(log, "Unable to parse courseware xml")
|
||||
return render_to_response('courseware-error.html', {})
|
||||
@@ -139,13 +151,21 @@ def render_section(request, section):
|
||||
|
||||
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
def index(request, course="6.002 Spring 2012", chapter="Using the System", section="Hints"):
|
||||
def index(request, course=None, chapter="Using the System", section="Hints"):
|
||||
''' Displays courseware accordion, and any associated content.
|
||||
'''
|
||||
user = request.user
|
||||
if not settings.COURSEWARE_ENABLED:
|
||||
return redirect('/')
|
||||
|
||||
if course==None:
|
||||
if not settings.ENABLE_MULTICOURSE:
|
||||
course = "6.002 Spring 2012"
|
||||
elif 'coursename' in request.session:
|
||||
course = request.session['coursename']
|
||||
else:
|
||||
course = settings.COURSE_DEFAULT
|
||||
|
||||
# Fixes URLs -- we don't get funny encoding characters from spaces
|
||||
# so they remain readable
|
||||
## TODO: Properly replace underscores
|
||||
@@ -153,13 +173,15 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
|
||||
chapter=chapter.replace("_"," ")
|
||||
section=section.replace("_"," ")
|
||||
|
||||
# HACK: Force course to 6.002 for now
|
||||
# Without this, URLs break
|
||||
if course!="6.002 Spring 2012":
|
||||
# use multicourse module to determine if "course" is valid
|
||||
#if course!=settings.COURSE_NAME.replace('_',' '):
|
||||
if not multicourse_settings.is_valid_course(course):
|
||||
return redirect('/')
|
||||
|
||||
request.session['coursename'] = course # keep track of current course being viewed in django's request.session
|
||||
|
||||
try:
|
||||
dom = content_parser.course_file(user)
|
||||
dom = content_parser.course_file(user,course) # also pass course to it, for course-specific XML path
|
||||
except:
|
||||
record_exception(log, "Unable to parse courseware xml")
|
||||
return render_to_response('courseware-error.html', {})
|
||||
@@ -184,6 +206,7 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'accordion': render_accordion(request, course, chapter, section)
|
||||
'COURSE_TITLE':multicourse_settings.get_course_title(course),
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -226,9 +249,13 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
|
||||
|
||||
ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/'
|
||||
|
||||
# get coursename if stored
|
||||
if 'coursename' in request.session: coursename = request.session['coursename']
|
||||
else: coursename = None
|
||||
|
||||
# Grab the XML corresponding to the request from course.xml
|
||||
try:
|
||||
xml = content_parser.module_xml(request.user, module, 'id', id)
|
||||
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
|
||||
except:
|
||||
record_exception(log, "Unable to load module during ajax call")
|
||||
if 'text/html' in request.accepted_types:
|
||||
@@ -267,3 +294,98 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
|
||||
s.save()
|
||||
# Return whatever the module wanted to return to the client/caller
|
||||
return HttpResponse(ajax_return)
|
||||
|
||||
def quickedit(request, id=None):
|
||||
'''
|
||||
quick-edit capa problem.
|
||||
|
||||
Maybe this should be moved into capa/views.py
|
||||
Or this should take a "module" argument, and the quickedit moved into capa_module.
|
||||
'''
|
||||
print "WARNING: UNDEPLOYABLE CODE. FOR DEV USE ONLY."
|
||||
print "In deployed use, this will only edit on one server"
|
||||
print "We need a setting to disable for production where there is"
|
||||
print "a load balanacer"
|
||||
if not request.user.is_staff():
|
||||
return redirect('/')
|
||||
|
||||
# get coursename if stored
|
||||
if 'coursename' in request.session: coursename = request.session['coursename']
|
||||
else: coursename = None
|
||||
|
||||
def get_lcp(coursename,id):
|
||||
# Grab the XML corresponding to the request from course.xml
|
||||
module = 'problem'
|
||||
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
|
||||
|
||||
ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/'
|
||||
|
||||
# Create the module (instance of capa_module.Module)
|
||||
system = I4xSystem(track_function = make_track_function(request),
|
||||
render_function = None,
|
||||
ajax_url = ajax_url,
|
||||
filestore = None,
|
||||
coursename = coursename,
|
||||
role = 'staff' if request.user.is_staff else 'student', # TODO: generalize this
|
||||
)
|
||||
instance=courseware.modules.get_module_class(module)(system,
|
||||
xml,
|
||||
id,
|
||||
state=None)
|
||||
lcp = instance.lcp
|
||||
pxml = lcp.tree
|
||||
pxmls = etree.tostring(pxml,pretty_print=True)
|
||||
|
||||
return instance, pxmls
|
||||
|
||||
instance, pxmls = get_lcp(coursename,id)
|
||||
|
||||
# if there was a POST, then process it
|
||||
msg = ''
|
||||
if 'qesubmit' in request.POST:
|
||||
action = request.POST['qesubmit']
|
||||
if "Revert" in action:
|
||||
msg = "Reverted to original"
|
||||
elif action=='Change Problem':
|
||||
key = 'quickedit_%s' % id
|
||||
if not key in request.POST:
|
||||
msg = "oops, missing code key=%s" % key
|
||||
else:
|
||||
newcode = request.POST[key]
|
||||
|
||||
# see if code changed
|
||||
if str(newcode)==str(pxmls) or '<?xml version="1.0"?>\n'+str(newcode)==str(pxmls):
|
||||
msg = "No changes"
|
||||
else:
|
||||
# check new code
|
||||
isok = False
|
||||
try:
|
||||
newxml = etree.fromstring(newcode)
|
||||
isok = True
|
||||
except Exception,err:
|
||||
msg = "Failed to change problem: XML error \"<font color=red>%s</font>\"" % err
|
||||
|
||||
if isok:
|
||||
filename = instance.lcp.fileobject.name
|
||||
fp = open(filename,'w') # TODO - replace with filestore call?
|
||||
fp.write(newcode)
|
||||
fp.close()
|
||||
msg = "<font color=green>Problem changed!</font> (<tt>%s</tt>)" % filename
|
||||
instance, pxmls = get_lcp(coursename,id)
|
||||
|
||||
lcp = instance.lcp
|
||||
|
||||
# get the rendered problem HTML
|
||||
phtml = instance.get_problem_html()
|
||||
|
||||
context = {'id':id,
|
||||
'msg' : msg,
|
||||
'lcp' : lcp,
|
||||
'filename' : lcp.fileobject.name,
|
||||
'pxmls' : pxmls,
|
||||
'phtml' : phtml,
|
||||
'init_js':instance.get_init_js(),
|
||||
}
|
||||
|
||||
result = render_to_response('quickedit.html', context)
|
||||
return result
|
||||
|
||||
73
djangoapps/multicourse/multicourse_settings.py
Normal file
73
djangoapps/multicourse/multicourse_settings.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# multicourse/multicourse_settings.py
|
||||
#
|
||||
# central module for providing fixed settings (course name, number, title)
|
||||
# for multiple courses. Loads this information from django.conf.settings
|
||||
#
|
||||
# Allows backward compatibility with settings configurations without
|
||||
# multiple courses specified.
|
||||
#
|
||||
# The central piece of configuration data is the dict COURSE_SETTINGS, with
|
||||
# keys being the COURSE_NAME (spaces ok), and the value being a dict of
|
||||
# parameter,value pairs. The required parameters are:
|
||||
#
|
||||
# - number : course number (used in the simplewiki pages)
|
||||
# - title : humanized descriptive course title
|
||||
#
|
||||
# Optional parameters:
|
||||
#
|
||||
# - xmlpath : path (relative to data directory) for this course (defaults to "")
|
||||
#
|
||||
# If COURSE_SETTINGS does not exist, then fallback to 6.002_Spring_2012 default,
|
||||
# for now.
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# load course settings
|
||||
|
||||
if hasattr(settings,'COURSE_SETTINGS'): # in the future, this could be replaced by reading an XML file
|
||||
COURSE_SETTINGS = settings.COURSE_SETTINGS
|
||||
|
||||
elif hasattr(settings,'COURSE_NAME'): # backward compatibility
|
||||
COURSE_SETTINGS = {settings.COURSE_NAME: {'number': settings.COURSE_NUMBER,
|
||||
'title': settings.COURSE_TITLE,
|
||||
},
|
||||
}
|
||||
else: # default to 6.002_Spring_2012
|
||||
COURSE_SETTINGS = {'6.002_Spring_2012': {'number': '6.002x',
|
||||
'title': 'Circuits and Electronics',
|
||||
},
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# wrapper functions around course settings
|
||||
|
||||
def get_course_settings(coursename):
|
||||
if not coursename:
|
||||
if hasattr(settings,'COURSE_DEFAULT'):
|
||||
coursename = settings.COURSE_DEFAULT
|
||||
else:
|
||||
coursename = '6.002_Spring_2012'
|
||||
if coursename in COURSE_SETTINGS: return COURSE_SETTINGS[coursename]
|
||||
coursename = coursename.replace(' ','_')
|
||||
if coursename in COURSE_SETTINGS: return COURSE_SETTINGS[coursename]
|
||||
return None
|
||||
|
||||
def is_valid_course(coursename):
|
||||
return not (get_course_settings==None)
|
||||
|
||||
def get_course_property(coursename,property):
|
||||
cs = get_course_settings(coursename)
|
||||
if not cs: return '' # raise exception instead?
|
||||
if property in cs: return cs[property]
|
||||
return '' # default
|
||||
|
||||
def get_course_xmlpath(coursename):
|
||||
return get_course_property(coursename,'xmlpath')
|
||||
|
||||
def get_course_title(coursename):
|
||||
return get_course_property(coursename,'title')
|
||||
|
||||
def get_course_number(coursename):
|
||||
return get_course_property(coursename,'number')
|
||||
|
||||
1
djangoapps/multicourse/views.py
Normal file
1
djangoapps/multicourse/views.py
Normal file
@@ -0,0 +1 @@
|
||||
# multicourse/views.py
|
||||
@@ -9,6 +9,8 @@ from django.utils import simplejson
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
|
||||
from multicourse import multicourse_settings
|
||||
|
||||
from models import Revision, Article, CreateArticleForm, RevisionFormWithTitle, RevisionForm
|
||||
import wiki_settings
|
||||
|
||||
@@ -17,6 +19,11 @@ def view(request, wiki_url):
|
||||
if err:
|
||||
return err
|
||||
|
||||
if 'coursename' in request.session: coursename = request.session['coursename']
|
||||
else: coursename = None
|
||||
|
||||
course_number = multicourse_settings.get_course_number(coursename)
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True, check_deleted=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
@@ -25,7 +32,7 @@ def view(request, wiki_url):
|
||||
'wiki_write': article.can_write_l(request.user),
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
'wiki_current_revision_deleted' : not (article.current_revision.deleted == 0),
|
||||
'wiki_title' : article.title + " - MITX 6.002x Wiki"
|
||||
'wiki_title' : article.title + " - MITX %s Wiki" % course_number
|
||||
}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_view.html', d)
|
||||
|
||||
0
djangoapps/ssl_auth/__init__.py
Normal file
0
djangoapps/ssl_auth/__init__.py
Normal file
281
djangoapps/ssl_auth/ssl_auth.py
Executable file
281
djangoapps/ssl_auth/ssl_auth.py
Executable file
@@ -0,0 +1,281 @@
|
||||
"""
|
||||
User authentication backend for ssl (no pw required)
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth.models import User, check_password
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from django.contrib.auth.middleware import RemoteUserMiddleware
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
import os, string, re
|
||||
from random import choice
|
||||
|
||||
from student.models import UserProfile
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def ssl_dn_extract_info(dn):
|
||||
'''
|
||||
Extract username, email address (may be anyuser@anydomain.com) and full name
|
||||
from the SSL DN string. Return (user,email,fullname) if successful, and None
|
||||
otherwise.
|
||||
'''
|
||||
ss = re.search('/emailAddress=(.*)@([^/]+)',dn)
|
||||
if ss:
|
||||
user = ss.group(1)
|
||||
email = "%s@%s" % (user,ss.group(2))
|
||||
else:
|
||||
return None
|
||||
ss = re.search('/CN=([^/]+)/',dn)
|
||||
if ss:
|
||||
fullname = ss.group(1)
|
||||
else:
|
||||
return None
|
||||
return (user,email,fullname)
|
||||
|
||||
def check_nginx_proxy(request):
|
||||
'''
|
||||
Check for keys in the HTTP header (META) to se if we are behind an ngix reverse proxy.
|
||||
If so, get user info from the SSL DN string and return that, as (user,email,fullname)
|
||||
'''
|
||||
m = request.META
|
||||
if m.has_key('HTTP_X_REAL_IP'): # we're behind a nginx reverse proxy, which has already done ssl auth
|
||||
if not m.has_key('HTTP_SSL_CLIENT_S_DN'):
|
||||
return None
|
||||
dn = m['HTTP_SSL_CLIENT_S_DN']
|
||||
return ssl_dn_extract_info(dn)
|
||||
return None
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def get_ssl_username(request):
|
||||
x = check_nginx_proxy(request)
|
||||
if x:
|
||||
return x[0]
|
||||
env = request._req.subprocess_env
|
||||
if env.has_key('SSL_CLIENT_S_DN_Email'):
|
||||
email = env['SSL_CLIENT_S_DN_Email']
|
||||
user = email[:email.index('@')]
|
||||
return user
|
||||
return None
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class NginxProxyHeaderMiddleware(RemoteUserMiddleware):
|
||||
'''
|
||||
Django "middleware" function for extracting user information from HTTP request.
|
||||
|
||||
'''
|
||||
# this field is generated by nginx's reverse proxy
|
||||
header = 'HTTP_SSL_CLIENT_S_DN' # specify the request.META field to use
|
||||
|
||||
def process_request(self, request):
|
||||
# AuthenticationMiddleware is required so that request.user exists.
|
||||
if not hasattr(request, 'user'):
|
||||
raise ImproperlyConfigured(
|
||||
"The Django remote user auth middleware requires the"
|
||||
" authentication middleware to be installed. Edit your"
|
||||
" MIDDLEWARE_CLASSES setting to insert"
|
||||
" 'django.contrib.auth.middleware.AuthenticationMiddleware'"
|
||||
" before the RemoteUserMiddleware class.")
|
||||
|
||||
#raise ImproperlyConfigured('[ProxyHeaderMiddleware] request.META=%s' % repr(request.META))
|
||||
|
||||
try:
|
||||
username = request.META[self.header] # try the nginx META key first
|
||||
except KeyError:
|
||||
try:
|
||||
env = request._req.subprocess_env # else try the direct apache2 SSL key
|
||||
if env.has_key('SSL_CLIENT_S_DN'):
|
||||
username = env['SSL_CLIENT_S_DN']
|
||||
else:
|
||||
raise ImproperlyConfigured('no ssl key, env=%s' % repr(env))
|
||||
username = ''
|
||||
except:
|
||||
# If specified header doesn't exist then return (leaving
|
||||
# request.user set to AnonymousUser by the
|
||||
# AuthenticationMiddleware).
|
||||
return
|
||||
# If the user is already authenticated and that user is the user we are
|
||||
# getting passed in the headers, then the correct user is already
|
||||
# persisted in the session and we don't need to continue.
|
||||
|
||||
#raise ImproperlyConfigured('[ProxyHeaderMiddleware] username=%s' % username)
|
||||
|
||||
if request.user.is_authenticated():
|
||||
if request.user.username == self.clean_username(username, request):
|
||||
#raise ImproperlyConfigured('%s already authenticated (%s)' % (username,request.user.username))
|
||||
return
|
||||
# We are seeing this user for the first time in this session, attempt
|
||||
# to authenticate the user.
|
||||
#raise ImproperlyConfigured('calling auth.authenticate, remote_user=%s' % username)
|
||||
user = auth.authenticate(remote_user=username)
|
||||
if user:
|
||||
# User is valid. Set request.user and persist user in the session
|
||||
# by logging the user in.
|
||||
request.user = user
|
||||
if settings.DEBUG: print "[ssl_auth.ssl_auth.NginxProxyHeaderMiddleware] logging in user=%s" % user
|
||||
auth.login(request, user)
|
||||
|
||||
def clean_username(self,username,request):
|
||||
'''
|
||||
username is the SSL DN string - extract the actual username from it and return
|
||||
'''
|
||||
info = ssl_dn_extract_info(username)
|
||||
if not info:
|
||||
return None
|
||||
(username,email,fullname) = info
|
||||
return username
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class SSLLoginBackend(ModelBackend):
|
||||
'''
|
||||
Django authentication back-end which auto-logs-in a user based on having
|
||||
already authenticated with an MIT certificate (SSL).
|
||||
'''
|
||||
def authenticate(self, username=None, password=None, remote_user=None):
|
||||
|
||||
# remote_user is from the SSL_DN string. It will be non-empty only when
|
||||
# the user has already passed the server authentication, which means
|
||||
# matching with the certificate authority.
|
||||
if not remote_user:
|
||||
# no remote_user, so check username (but don't auto-create user)
|
||||
if not username:
|
||||
return None
|
||||
return None # pass on to another authenticator backend
|
||||
#raise ImproperlyConfigured("in SSLLoginBackend, username=%s, remote_user=%s" % (username,remote_user))
|
||||
try:
|
||||
user = User.objects.get(username=username) # if user already exists don't create it
|
||||
return user
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
return None
|
||||
|
||||
#raise ImproperlyConfigured("in SSLLoginBackend, username=%s, remote_user=%s" % (username,remote_user))
|
||||
#if not os.environ.has_key('HTTPS'):
|
||||
# return None
|
||||
#if not os.environ.get('HTTPS')=='on': # only use this back-end if HTTPS on
|
||||
# return None
|
||||
|
||||
def GenPasswd(length=8, chars=string.letters + string.digits):
|
||||
return ''.join([choice(chars) for i in range(length)])
|
||||
|
||||
# convert remote_user to user, email, fullname
|
||||
info = ssl_dn_extract_info(remote_user)
|
||||
#raise ImproperlyConfigured("[SSLLoginBackend] looking up %s" % repr(info))
|
||||
if not info:
|
||||
#raise ImproperlyConfigured("[SSLLoginBackend] remote_user=%s, info=%s" % (remote_user,info))
|
||||
return None
|
||||
(username,email,fullname) = info
|
||||
|
||||
try:
|
||||
user = User.objects.get(username=username) # if user already exists don't create it
|
||||
except User.DoesNotExist:
|
||||
raise "User does not exist. Not creating user; potential schema consistency issues"
|
||||
#raise ImproperlyConfigured("[SSLLoginBackend] creating %s" % repr(info))
|
||||
user = User(username=username, password=GenPasswd()) # create new User
|
||||
user.is_staff = False
|
||||
user.is_superuser = False
|
||||
# get first, last name from fullname
|
||||
name = fullname
|
||||
if not name.count(' '):
|
||||
user.first_name = " "
|
||||
user.last_name = name
|
||||
mn = ''
|
||||
else:
|
||||
user.first_name = name[:name.find(' ')]
|
||||
ml = name[name.find(' '):].strip()
|
||||
if ml.count(' '):
|
||||
user.last_name = ml[ml.rfind(' '):]
|
||||
mn = ml[:ml.rfind(' ')]
|
||||
else:
|
||||
user.last_name = ml
|
||||
mn = ''
|
||||
# set email
|
||||
user.email = email
|
||||
# cleanup last name
|
||||
user.last_name = user.last_name.strip()
|
||||
# save
|
||||
user.save()
|
||||
|
||||
# auto-create user profile
|
||||
up = UserProfile(user=user)
|
||||
up.name = fullname
|
||||
up.save()
|
||||
|
||||
#tui = user.get_profile()
|
||||
#tui.middle_name = mn
|
||||
#tui.role = 'Misc'
|
||||
#tui.section = None # no section assigned at first
|
||||
#tui.save()
|
||||
# return None
|
||||
return user
|
||||
|
||||
def get_user(self, user_id):
|
||||
#if not os.environ.has_key('HTTPS'):
|
||||
# return None
|
||||
#if not os.environ.get('HTTPS')=='on': # only use this back-end if HTTPS on
|
||||
# return None
|
||||
try:
|
||||
return User.objects.get(pk=user_id)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# OLD!
|
||||
|
||||
class AutoLoginBackend:
|
||||
def authenticate(self, username=None, password=None):
|
||||
raise ImproperlyConfigured("in AutoLoginBackend, username=%s" % username)
|
||||
if not os.environ.has_key('HTTPS'):
|
||||
return None
|
||||
if not os.environ.get('HTTPS')=='on':# only use this back-end if HTTPS on
|
||||
return None
|
||||
|
||||
def GenPasswd(length=8, chars=string.letters + string.digits):
|
||||
return ''.join([choice(chars) for i in range(length)])
|
||||
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
user = User(username=username, password=GenPasswd())
|
||||
user.is_staff = False
|
||||
user.is_superuser = False
|
||||
# get first, last name
|
||||
name = os.environ.get('SSL_CLIENT_S_DN_CN').strip()
|
||||
if not name.count(' '):
|
||||
user.first_name = " "
|
||||
user.last_name = name
|
||||
mn = ''
|
||||
else:
|
||||
user.first_name = name[:name.find(' ')]
|
||||
ml = name[name.find(' '):].strip()
|
||||
if ml.count(' '):
|
||||
user.last_name = ml[ml.rfind(' '):]
|
||||
mn = ml[:ml.rfind(' ')]
|
||||
else:
|
||||
user.last_name = ml
|
||||
mn = ''
|
||||
# get email
|
||||
user.email = os.environ.get('SSL_CLIENT_S_DN_Email')
|
||||
# save
|
||||
user.save()
|
||||
tui = user.get_profile()
|
||||
tui.middle_name = mn
|
||||
tui.role = 'Misc'
|
||||
tui.section = None# no section assigned at first
|
||||
tui.save()
|
||||
# return None
|
||||
return user
|
||||
|
||||
def get_user(self, user_id):
|
||||
if not os.environ.has_key('HTTPS'):
|
||||
return None
|
||||
if not os.environ.get('HTTPS')=='on':# only use this back-end if HTTPS on
|
||||
return None
|
||||
try:
|
||||
return User.objects.get(pk=user_id)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
11
envs/aws.py
11
envs/aws.py
@@ -8,7 +8,8 @@ Common traits:
|
||||
"""
|
||||
import json
|
||||
|
||||
from common import *
|
||||
from envs.logsettings import get_logger_config
|
||||
from envs.common import *
|
||||
|
||||
############################### ALWAYS THE SAME ################################
|
||||
DEBUG = False
|
||||
@@ -31,10 +32,10 @@ LOG_DIR = ENV_TOKENS['LOG_DIR']
|
||||
|
||||
CACHES = ENV_TOKENS['CACHES']
|
||||
|
||||
LOGGING = logsettings.get_logger_config(LOG_DIR,
|
||||
logging_env=ENV_TOKENS['LOGGING_ENV'],
|
||||
syslog_addr=(ENV_TOKENS['SYSLOG_SERVER'], 514),
|
||||
debug=False)
|
||||
LOGGING = get_logger_config(LOG_DIR,
|
||||
logging_env=ENV_TOKENS['LOGGING_ENV'],
|
||||
syslog_addr=(ENV_TOKENS['SYSLOG_SERVER'], 514),
|
||||
debug=False)
|
||||
|
||||
############################## SECURE AUTH ITEMS ###############################
|
||||
# Secret things: passwords, access keys, etc.
|
||||
|
||||
@@ -24,8 +24,7 @@ import tempfile
|
||||
import djcelery
|
||||
from path import path
|
||||
|
||||
from askbotsettings import * # this is where LIVESETTINGS_OPTIONS comes from
|
||||
import logsettings
|
||||
from envs.askbotsettings import * # this is where LIVESETTINGS_OPTIONS comes from
|
||||
|
||||
################################### FEATURES ###################################
|
||||
COURSEWARE_ENABLED = True
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
These are debug machines used for content creators, so they're kind of a cross
|
||||
between dev machines and AWS machines.
|
||||
"""
|
||||
from aws import *
|
||||
from envs.aws import *
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = True
|
||||
|
||||
11
envs/dev.py
11
envs/dev.py
@@ -7,15 +7,16 @@ sessions. Assumes structure:
|
||||
/mitx # The location of this repo
|
||||
/log # Where we're going to write log files
|
||||
"""
|
||||
from common import *
|
||||
from envs.common import *
|
||||
from envs.logsettings import get_logger_config
|
||||
|
||||
DEBUG = False
|
||||
TEMPLATE_DEBUG = False
|
||||
|
||||
LOGGING = logsettings.get_logger_config(ENV_ROOT / "log",
|
||||
logging_env="dev",
|
||||
tracking_filename="tracking.log",
|
||||
debug=True)
|
||||
LOGGING = get_logger_config(ENV_ROOT / "log",
|
||||
logging_env="dev",
|
||||
tracking_filename="tracking.log",
|
||||
debug=True)
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
|
||||
@@ -13,7 +13,7 @@ Dir structure:
|
||||
/log # Where we're going to write log files
|
||||
|
||||
"""
|
||||
from dev import *
|
||||
from envs.dev import *
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
|
||||
@@ -7,14 +7,15 @@ sessions. Assumes structure:
|
||||
/mitx # The location of this repo
|
||||
/log # Where we're going to write log files
|
||||
"""
|
||||
from common import *
|
||||
from envs.common import *
|
||||
from envs.logsettings import get_logger_config
|
||||
|
||||
STATIC_GRAB = True
|
||||
|
||||
LOGGING = logsettings.get_logger_config(ENV_ROOT / "log",
|
||||
logging_env="dev",
|
||||
tracking_filename="tracking.log",
|
||||
debug=False)
|
||||
LOGGING = get_logger_config(ENV_ROOT / "log",
|
||||
logging_env="dev",
|
||||
tracking_filename="tracking.log",
|
||||
debug=False)
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
|
||||
14
envs/test.py
14
envs/test.py
@@ -7,7 +7,8 @@ sessions. Assumes structure:
|
||||
/mitx # The location of this repo
|
||||
/log # Where we're going to write log files
|
||||
"""
|
||||
from common import *
|
||||
from envs.common import *
|
||||
from envs.logsettings import get_logger_config
|
||||
import os
|
||||
|
||||
INSTALLED_APPS = [
|
||||
@@ -25,7 +26,8 @@ for app in os.listdir(PROJECT_ROOT / 'djangoapps'):
|
||||
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
|
||||
|
||||
# Local Directories
|
||||
COURSES_ROOT = PROJECT_ROOT / "test_data"
|
||||
TEST_ROOT = path("test_root")
|
||||
COURSES_ROOT = TEST_ROOT / "data"
|
||||
DATA_DIR = COURSES_ROOT
|
||||
MAKO_TEMPLATES['course'] = [DATA_DIR]
|
||||
MAKO_TEMPLATES['sections'] = [DATA_DIR / 'sections']
|
||||
@@ -34,10 +36,10 @@ MAKO_TEMPLATES['main'] = [PROJECT_ROOT / 'templates',
|
||||
DATA_DIR / 'info',
|
||||
DATA_DIR / 'problems']
|
||||
|
||||
LOGGING = logsettings.get_logger_config(PROJECT_ROOT / "log",
|
||||
logging_env="dev",
|
||||
tracking_filename="tracking.log",
|
||||
debug=True)
|
||||
LOGGING = get_logger_config(TEST_ROOT / "log",
|
||||
logging_env="dev",
|
||||
tracking_filename="tracking.log",
|
||||
debug=True)
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
|
||||
3
lib/loncapa/__init__.py
Normal file
3
lib/loncapa/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
from loncapa_check import *
|
||||
17
lib/loncapa/loncapa_check.py
Normal file
17
lib/loncapa/loncapa_check.py
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# File: mitx/lib/loncapa/loncapa_check.py
|
||||
#
|
||||
# Python functions which duplicate the standard comparison functions available to LON-CAPA problems.
|
||||
# Used in translating LON-CAPA problems to i4x problem specification language.
|
||||
|
||||
import random
|
||||
|
||||
def lc_random(lower,upper,stepsize):
|
||||
'''
|
||||
like random.randrange but lower and upper can be non-integer
|
||||
'''
|
||||
nstep = int((upper-lower)/(1.0*stepsize))
|
||||
choices = [lower+x*stepsize for x in range(nstep)]
|
||||
return random.choice(choices)
|
||||
|
||||
@@ -34,6 +34,9 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'):
|
||||
context_dictionary.update(d)
|
||||
if context:
|
||||
context_dictionary.update(context)
|
||||
## HACK
|
||||
## We should remove this, and possible set COURSE_TITLE in the middleware from the session.
|
||||
if 'COURSE_TITLE' not in context_dictionary: context_dictionary['COURSE_TITLE'] = ''
|
||||
# fetch and render template
|
||||
template = middleware.lookup[namespace].get_template(template_name)
|
||||
return template.render(**context_dictionary)
|
||||
|
||||
@@ -2,7 +2,6 @@ import datetime
|
||||
import json
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.context_processors import csrf
|
||||
@@ -61,3 +60,9 @@ def send_feedback(request):
|
||||
def info(request):
|
||||
''' Info page (link from main header) '''
|
||||
return render_to_response("info.html", {})
|
||||
|
||||
def mitxhome(request):
|
||||
''' Home page (link from main header). List of courses. '''
|
||||
if settings.ENABLE_MULTICOURSE:
|
||||
return render_to_response("mitxhome.html", {})
|
||||
return info(request)
|
||||
|
||||
10
rakefile
10
rakefile
@@ -29,12 +29,17 @@ INSTALL_DIR_PATH = File.join(DEPLOY_DIR, NORMALIZED_DEPLOY_NAME)
|
||||
CLOBBER.include(BUILD_DIR, REPORT_DIR, 'cover*', '.coverage')
|
||||
CLEAN.include("#{BUILD_DIR}/*.deb", "#{BUILD_DIR}/util")
|
||||
|
||||
def select_executable(*cmds)
|
||||
cmds.find_all{ |cmd| system("which #{cmd} > /dev/null 2>&1") }[0] || fail("No executables found from #{cmds.join(', ')}")
|
||||
end
|
||||
|
||||
|
||||
task :default => [:pep8, :pylint, :test]
|
||||
|
||||
directory REPORT_DIR
|
||||
|
||||
task :pep8 => REPORT_DIR do
|
||||
sh("pep8 djangoapps | tee #{REPORT_DIR}/pep8.report")
|
||||
sh("pep8 --ignore=E501 djangoapps | tee #{REPORT_DIR}/pep8.report")
|
||||
end
|
||||
|
||||
task :pylint => REPORT_DIR do
|
||||
@@ -47,7 +52,8 @@ end
|
||||
|
||||
task :test => REPORT_DIR do
|
||||
ENV['NOSE_XUNIT_FILE'] = File.join(REPORT_DIR, "nosetests.xml")
|
||||
sh("django-admin.py test --settings=envs.test --pythonpath=. $(ls djangoapps)")
|
||||
django_admin = ENV['DJANGO_ADMIN_PATH'] || select_executable('django-admin.py', 'django-admin')
|
||||
sh("#{django_admin} test --settings=envs.test --pythonpath=. $(ls djangoapps)")
|
||||
end
|
||||
|
||||
task :package do
|
||||
|
||||
22
settings.py
22
settings.py
@@ -8,6 +8,7 @@ import djcelery
|
||||
### Dark code. Should be enabled in local settings for devel.
|
||||
|
||||
ENABLE_MULTICOURSE = False # set to False to disable multicourse display (see lib.util.views.mitxhome)
|
||||
QUICKEDIT = False
|
||||
|
||||
###
|
||||
|
||||
@@ -20,19 +21,11 @@ COURSE_TITLE = "Circuits and Electronics"
|
||||
|
||||
COURSE_DEFAULT = '6.002_Spring_2012'
|
||||
|
||||
COURSE_LIST = {'6.002_Spring_2012': {'number' : '6.002x',
|
||||
'title' : 'Circuits and Electronics',
|
||||
'datapath': '6002x/',
|
||||
},
|
||||
'8.02_Spring_2013': {'number' : '8.02x',
|
||||
'title' : 'Electricity & Magnetism',
|
||||
'datapath': '802x/',
|
||||
},
|
||||
'8.01_Spring_2013': {'number' : '8.01x',
|
||||
'title' : 'Mechanics',
|
||||
'datapath': '801x/',
|
||||
},
|
||||
}
|
||||
COURSE_SETTINGS = {'6.002_Spring_2012': {'number' : '6.002x',
|
||||
'title' : 'Circuits and Electronics',
|
||||
'xmlpath': '6002x/',
|
||||
}
|
||||
}
|
||||
|
||||
ROOT_URLCONF = 'urls'
|
||||
|
||||
@@ -151,6 +144,7 @@ MIDDLEWARE_CLASSES = (
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'track.middleware.TrackMiddleware',
|
||||
'mitxmako.middleware.MakoMiddleware',
|
||||
#'ssl_auth.ssl_auth.NginxProxyHeaderMiddleware', # ssl authentication behind nginx proxy
|
||||
#'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
|
||||
# Uncommenting the following will prevent csrf token from being re-set if you
|
||||
@@ -180,6 +174,8 @@ INSTALLED_APPS = (
|
||||
'util',
|
||||
'masquerade',
|
||||
'django_jasmine',
|
||||
#'ssl_auth', ## Broken. Disabled for now.
|
||||
'multicourse', # multiple courses
|
||||
# Uncomment the next line to enable the admin:
|
||||
# 'django.contrib.admin',
|
||||
# Uncomment the next line to enable admin documentation:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -735,14 +735,14 @@ section.index-content section.staff h1 {
|
||||
margin-top: 25.888px; }
|
||||
|
||||
#lean_overlay {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: #000;
|
||||
display: none; }
|
||||
display: none;
|
||||
height: 100%;
|
||||
left: 0px;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
z-index: 100; }
|
||||
|
||||
div.leanModal_box {
|
||||
background: #fff;
|
||||
@@ -772,8 +772,8 @@ div.leanModal_box a.modal_close {
|
||||
width: 14px;
|
||||
z-index: 2; }
|
||||
div.leanModal_box a.modal_close:hover {
|
||||
text-decoration: none;
|
||||
color: #993333; }
|
||||
color: #993333;
|
||||
text-decoration: none; }
|
||||
div.leanModal_box h1 {
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 24px;
|
||||
@@ -786,8 +786,8 @@ div.leanModal_box#enroll {
|
||||
div.leanModal_box#enroll ol {
|
||||
padding-top: 25.888px; }
|
||||
div.leanModal_box#enroll ol li.terms, div.leanModal_box#enroll ol li.honor-code {
|
||||
width: auto;
|
||||
float: none; }
|
||||
float: none;
|
||||
width: auto; }
|
||||
div.leanModal_box#enroll ol li div.tip {
|
||||
display: none; }
|
||||
div.leanModal_box#enroll ol li:hover div.tip {
|
||||
@@ -828,16 +828,16 @@ div.leanModal_box form ol li.terms, div.leanModal_box form ol li.remember {
|
||||
padding-top: 25.888px;
|
||||
width: auto; }
|
||||
div.leanModal_box form ol li.honor-code {
|
||||
width: auto;
|
||||
float: none; }
|
||||
float: none;
|
||||
width: auto; }
|
||||
div.leanModal_box form ol li label {
|
||||
display: block;
|
||||
font-weight: bold; }
|
||||
div.leanModal_box form ol li input[type="email"], div.leanModal_box form ol li input[type="number"], div.leanModal_box form ol li input[type="password"], div.leanModal_box form ol li input[type="search"], div.leanModal_box form ol li input[type="tel"], div.leanModal_box form ol li input[type="text"], div.leanModal_box form ol li input[type="url"], div.leanModal_box form ol li input[type="color"], div.leanModal_box form ol li input[type="date"], div.leanModal_box form ol li input[type="datetime"], div.leanModal_box form ol li input[type="datetime-local"], div.leanModal_box form ol li input[type="month"], div.leanModal_box form ol li input[type="time"], div.leanModal_box form ol li input[type="week"], div.leanModal_box form ol li textarea {
|
||||
width: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box; }
|
||||
box-sizing: border-box;
|
||||
width: 100%; }
|
||||
div.leanModal_box form ol li input[type="checkbox"] {
|
||||
margin-right: 10px; }
|
||||
div.leanModal_box form ol li ul {
|
||||
@@ -904,12 +904,12 @@ div#login header h1 {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 6.472px; }
|
||||
div#login ol li {
|
||||
width: auto;
|
||||
float: none; }
|
||||
float: none;
|
||||
width: auto; }
|
||||
|
||||
div.lost-password {
|
||||
text-align: left;
|
||||
margin-top: 25.888px; }
|
||||
margin-top: 25.888px;
|
||||
text-align: left; }
|
||||
div.lost-password a {
|
||||
color: #999; }
|
||||
div.lost-password a:hover {
|
||||
@@ -927,11 +927,11 @@ div#apply_name_change ul, div#change_email ul, div#unenroll ul, div#deactivate-a
|
||||
div#apply_name_change ul li, div#change_email ul li, div#unenroll ul li, div#deactivate-account ul li {
|
||||
margin-bottom: 12.944px; }
|
||||
div#apply_name_change ul li textarea, div#apply_name_change ul li input[type="email"], div#apply_name_change ul li input[type="number"], div#apply_name_change ul li input[type="password"], div#apply_name_change ul li input[type="search"], div#apply_name_change ul li input[type="tel"], div#apply_name_change ul li input[type="text"], div#apply_name_change ul li input[type="url"], div#apply_name_change ul li input[type="color"], div#apply_name_change ul li input[type="date"], div#apply_name_change ul li input[type="datetime"], div#apply_name_change ul li input[type="datetime-local"], div#apply_name_change ul li input[type="month"], div#apply_name_change ul li input[type="time"], div#apply_name_change ul li input[type="week"], div#change_email ul li textarea, div#change_email ul li input[type="email"], div#change_email ul li input[type="number"], div#change_email ul li input[type="password"], div#change_email ul li input[type="search"], div#change_email ul li input[type="tel"], div#change_email ul li input[type="text"], div#change_email ul li input[type="url"], div#change_email ul li input[type="color"], div#change_email ul li input[type="date"], div#change_email ul li input[type="datetime"], div#change_email ul li input[type="datetime-local"], div#change_email ul li input[type="month"], div#change_email ul li input[type="time"], div#change_email ul li input[type="week"], div#unenroll ul li textarea, div#unenroll ul li input[type="email"], div#unenroll ul li input[type="number"], div#unenroll ul li input[type="password"], div#unenroll ul li input[type="search"], div#unenroll ul li input[type="tel"], div#unenroll ul li input[type="text"], div#unenroll ul li input[type="url"], div#unenroll ul li input[type="color"], div#unenroll ul li input[type="date"], div#unenroll ul li input[type="datetime"], div#unenroll ul li input[type="datetime-local"], div#unenroll ul li input[type="month"], div#unenroll ul li input[type="time"], div#unenroll ul li input[type="week"], div#deactivate-account ul li textarea, div#deactivate-account ul li input[type="email"], div#deactivate-account ul li input[type="number"], div#deactivate-account ul li input[type="password"], div#deactivate-account ul li input[type="search"], div#deactivate-account ul li input[type="tel"], div#deactivate-account ul li input[type="text"], div#deactivate-account ul li input[type="url"], div#deactivate-account ul li input[type="color"], div#deactivate-account ul li input[type="date"], div#deactivate-account ul li input[type="datetime"], div#deactivate-account ul li input[type="datetime-local"], div#deactivate-account ul li input[type="month"], div#deactivate-account ul li input[type="time"], div#deactivate-account ul li input[type="week"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box; }
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 100%; }
|
||||
div#apply_name_change ul li textarea, div#change_email ul li textarea, div#unenroll ul li textarea, div#deactivate-account ul li textarea {
|
||||
height: 60px; }
|
||||
div#apply_name_change ul li input[type="submit"], div#change_email ul li input[type="submit"], div#unenroll ul li input[type="submit"], div#deactivate-account ul li input[type="submit"] {
|
||||
|
||||
@@ -12,10 +12,6 @@
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
${init}
|
||||
|
||||
$(".sequence-nav li a").hover(function(){
|
||||
$(this).siblings().toggleClass("shown");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</%block>
|
||||
|
||||
95
templates/quickedit.html
Normal file
95
templates/quickedit.html
Normal file
@@ -0,0 +1,95 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="${ settings.LIB_URL }jquery.treeview.css" type="text/css" media="all" />
|
||||
<link rel="stylesheet" href="/static/css/codemirror.css" type="text/css" media="all" />
|
||||
|
||||
<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 }codemirror-compressed.js"></script>
|
||||
<script type="text/javascript" src="/static/js/schematic.js"></script>
|
||||
<%include file="mathjax_include.html" />
|
||||
|
||||
<script>
|
||||
function postJSON(url, data, callback) {
|
||||
$.ajax({type:'POST',
|
||||
url: url,
|
||||
dataType: 'json',
|
||||
data: data,
|
||||
success: callback,
|
||||
headers : {'X-CSRFToken':'none'} // getCookie('csrftoken')}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="/static/js/html5shiv.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<style type="text/css">
|
||||
.CodeMirror {border-style: solid;
|
||||
border-width: 1px;}
|
||||
.CodeMirror-scroll {
|
||||
height: 500;
|
||||
width: 100%
|
||||
}
|
||||
</style>
|
||||
|
||||
## -----------------------------------------------------------------------------
|
||||
## information and i4x PSL code
|
||||
|
||||
<hr width="100%">
|
||||
<h2>QuickEdit</h2>
|
||||
<hr width="100%">
|
||||
<ul>
|
||||
<li>File = ${filename}</li>
|
||||
<li>ID = ${id}</li>
|
||||
</ul>
|
||||
|
||||
<form method="post">
|
||||
<textarea rows="40" cols="160" name="quickedit_${id}" id="quickedit_${id}">${pxmls|h}</textarea>
|
||||
<br/>
|
||||
<input type="submit" value="Change Problem" name="qesubmit" />
|
||||
<input type="submit" value="Revert to original" name="qesubmit" />
|
||||
</form>
|
||||
|
||||
<span>${msg|n}</span>
|
||||
|
||||
## -----------------------------------------------------------------------------
|
||||
## rendered problem display
|
||||
|
||||
<script>
|
||||
// height: auto;
|
||||
// overflow-y: hidden;
|
||||
// overflow-x: auto;
|
||||
|
||||
$(function(){
|
||||
var cm = CodeMirror.fromTextArea(document.getElementById("quickedit_${id}"),
|
||||
{ 'mode': {name: "xml", alignCDATA: true},
|
||||
lineNumbers: true
|
||||
});
|
||||
|
||||
// $('.my-wymeditor').wymeditor();
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<hr width="100%">
|
||||
|
||||
<script>
|
||||
${init_js}
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
.staff {display:none;}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form>
|
||||
${phtml}
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,10 +2,10 @@ section.help.main-content {
|
||||
padding: lh();
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: lh();
|
||||
padding-bottom: lh();
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin-bottom: lh();
|
||||
margin-top: 0;
|
||||
padding-bottom: lh();
|
||||
}
|
||||
|
||||
p {
|
||||
@@ -17,9 +17,9 @@ section.help.main-content {
|
||||
}
|
||||
|
||||
section.self-help {
|
||||
float: left;
|
||||
margin-bottom: lh();
|
||||
margin-right: flex-gutter();
|
||||
float: left;
|
||||
width: flex-grid(6);
|
||||
|
||||
ul {
|
||||
@@ -36,17 +36,17 @@ section.help.main-content {
|
||||
width: flex-grid(6);
|
||||
|
||||
dl {
|
||||
margin-bottom: lh();
|
||||
display: block;
|
||||
margin-bottom: lh();
|
||||
|
||||
dd {
|
||||
margin-bottom: lh();
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
clear: left;
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
width: flex-grid(2, 6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,28 +16,28 @@ div.info-wrapper {
|
||||
list-style: none;
|
||||
|
||||
> li {
|
||||
padding-bottom: lh(.5);
|
||||
margin-bottom: lh(.5);
|
||||
@extend .clearfix;
|
||||
border-bottom: 1px solid #e3e3e3;
|
||||
margin-bottom: lh(.5);
|
||||
padding-bottom: lh(.5);
|
||||
|
||||
&:first-child {
|
||||
padding: lh(.5);
|
||||
margin: 0 (-(lh(.5))) lh();
|
||||
background: $cream;
|
||||
border-bottom: 1px solid darken($cream, 10%);
|
||||
margin: 0 (-(lh(.5))) lh();
|
||||
padding: lh(.5);
|
||||
}
|
||||
|
||||
h2 {
|
||||
float: left;
|
||||
width: flex-grid(2, 9);
|
||||
margin: 0 flex-gutter() 0 0;
|
||||
width: flex-grid(2, 9);
|
||||
}
|
||||
|
||||
section.update-description {
|
||||
float: left;
|
||||
width: flex-grid(7, 9);
|
||||
margin-bottom: 0;
|
||||
width: flex-grid(7, 9);
|
||||
|
||||
li {
|
||||
margin-bottom: lh(.5);
|
||||
@@ -55,9 +55,9 @@ div.info-wrapper {
|
||||
|
||||
section.handouts {
|
||||
@extend .sidebar;
|
||||
border-left: 1px solid #d3d3d3;
|
||||
@include border-radius(0 4px 4px 0);
|
||||
border-right: 0;
|
||||
border-left: 1px solid #d3d3d3;
|
||||
|
||||
header {
|
||||
@extend .bottom-border;
|
||||
@@ -69,32 +69,32 @@ div.info-wrapper {
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
margin-bottom: 0;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
background: none;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
@include box-shadow(0 1px 0 #eee);
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
@include box-sizing(border-box);
|
||||
@extend .clearfix;
|
||||
padding: 7px lh(.75);
|
||||
background: none;
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
@include box-shadow(0 1px 0 #eee);
|
||||
@include box-sizing(border-box);
|
||||
padding: 7px lh(.75);
|
||||
position: relative;
|
||||
|
||||
&.expandable,
|
||||
&.collapsable {
|
||||
h4 {
|
||||
padding-left: 18px;
|
||||
font-style: $body-font-size;
|
||||
font-weight: normal;
|
||||
padding-left: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,10 +103,10 @@ div.info-wrapper {
|
||||
margin: 7px (-(lh(.75))) 0;
|
||||
|
||||
li {
|
||||
padding-left: 18px + lh(.75);
|
||||
@include box-shadow(inset 0 1px 0 #eee);
|
||||
border-top: 1px solid #d3d3d3;
|
||||
border-bottom: 0;
|
||||
border-top: 1px solid #d3d3d3;
|
||||
@include box-shadow(inset 0 1px 0 #eee);
|
||||
padding-left: 18px + lh(.75);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,13 +116,13 @@ div.info-wrapper {
|
||||
|
||||
div.hitarea {
|
||||
background-image: url('/static/images/treeview-default.gif');
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 20px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
left: lh(.75);
|
||||
margin-left: 0;
|
||||
max-height: 20px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
@@ -140,27 +140,27 @@ div.info-wrapper {
|
||||
|
||||
h3 {
|
||||
border-bottom: 0;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
@include box-shadow(none);
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: $body-font-size;
|
||||
letter-spacing: 0;
|
||||
margin: 0;
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
font-size: $body-font-size;
|
||||
|
||||
a {
|
||||
padding-right: 8px;
|
||||
|
||||
&:before {
|
||||
color: #ccc;
|
||||
content: "•";
|
||||
@include inline-block();
|
||||
padding-right: 8px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
@@ -173,10 +173,10 @@ div.info-wrapper {
|
||||
}
|
||||
|
||||
a {
|
||||
@include transition();
|
||||
color: lighten($text-color, 10%);
|
||||
text-decoration: none;
|
||||
@include inline-block();
|
||||
text-decoration: none;
|
||||
@include transition();
|
||||
|
||||
&:hover {
|
||||
color: $mit-red;
|
||||
|
||||
@@ -4,14 +4,14 @@ div.profile-wrapper {
|
||||
|
||||
section.user-info {
|
||||
@extend .sidebar;
|
||||
@include border-radius(0px 4px 4px 0);
|
||||
border-left: 1px solid #d3d3d3;
|
||||
@include border-radius(0px 4px 4px 0);
|
||||
border-right: 0;
|
||||
|
||||
header {
|
||||
padding: lh(.5) lh();
|
||||
margin: 0 ;
|
||||
@extend .bottom-border;
|
||||
margin: 0 ;
|
||||
padding: lh(.5) lh();
|
||||
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
@@ -20,12 +20,12 @@ div.profile-wrapper {
|
||||
}
|
||||
|
||||
a {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
right: lh(.5);
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
top: 13px;
|
||||
|
||||
&:hover {
|
||||
color: #555;
|
||||
@@ -37,14 +37,14 @@ div.profile-wrapper {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
@include transition();
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
@include box-shadow(0 1px 0 #eee);
|
||||
color: lighten($text-color, 10%);
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
@include box-shadow(0 1px 0 #eee);
|
||||
padding: 7px lh();
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
@include transition();
|
||||
|
||||
div#location_sub, div#language_sub {
|
||||
font-weight: bold;
|
||||
@@ -57,9 +57,9 @@ div.profile-wrapper {
|
||||
input {
|
||||
|
||||
&[type="text"] {
|
||||
@include box-sizing(border-box);
|
||||
margin: lh(.5) 0;
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
&[type="input"]{
|
||||
@@ -80,12 +80,12 @@ div.profile-wrapper {
|
||||
a.edit-email,
|
||||
a.name-edit,
|
||||
a.email-edit {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
right: lh(.5);
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
top: 9px;
|
||||
|
||||
&:hover {
|
||||
color: #555;
|
||||
@@ -93,10 +93,10 @@ div.profile-wrapper {
|
||||
}
|
||||
|
||||
p {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-bottom: 0;
|
||||
margin-top: 4px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
a.deactivate {
|
||||
@@ -132,10 +132,10 @@ div.profile-wrapper {
|
||||
padding: 7px lh();
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
font-size: $body-font-size;
|
||||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,14 +148,14 @@ div.profile-wrapper {
|
||||
@extend .clearfix;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
div#grade-detail-graph {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
> ol {
|
||||
|
||||
@@ -3,8 +3,8 @@ div.book-wrapper {
|
||||
|
||||
section.book-sidebar {
|
||||
@extend .sidebar;
|
||||
@include box-sizing(border-box);
|
||||
@extend .tran;
|
||||
@include box-sizing(border-box);
|
||||
|
||||
ul#booknav {
|
||||
font-size: 12px;
|
||||
@@ -22,14 +22,14 @@ div.book-wrapper {
|
||||
padding-left: 30px;
|
||||
|
||||
div.hitarea {
|
||||
margin-left: -22px;
|
||||
background-image: url('/static/images/treeview-default.gif');
|
||||
margin-left: -22px;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
filter: alpha(opacity=60);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,13 +63,13 @@ div.book-wrapper {
|
||||
|
||||
li {
|
||||
&.last {
|
||||
float: left;
|
||||
display: block;
|
||||
float: left;
|
||||
|
||||
a {
|
||||
@include box-shadow(inset -1px 0 0 lighten(#f6efd4, 5%));
|
||||
border-right: 1px solid darken(#f6efd4, 20%);
|
||||
border-left: 0;
|
||||
border-right: 1px solid darken(#f6efd4, 20%);
|
||||
@include box-shadow(inset -1px 0 0 lighten(#f6efd4, 5%));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +81,10 @@ div.book-wrapper {
|
||||
}
|
||||
|
||||
&.bottom-nav {
|
||||
margin-top: lh();
|
||||
margin-bottom: -(lh());
|
||||
border-bottom: 0;
|
||||
border-top: 1px solid #EDDFAA;
|
||||
margin-bottom: -(lh());
|
||||
margin-top: lh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,18 +110,18 @@ div.book-wrapper {
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding: 0;
|
||||
visibility: hidden;
|
||||
width: 10px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul#booknav {
|
||||
max-height: 100px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
visibility: hidden;
|
||||
width: 10px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
max-height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ img {
|
||||
}
|
||||
|
||||
#{$all-text-inputs}, textarea {
|
||||
@include box-shadow(0 -1px 0 #fff);
|
||||
@include linear-gradient(#eee, #fff);
|
||||
border: 1px solid #999;
|
||||
@include box-shadow(0 -1px 0 #fff);
|
||||
font: $body-font-size $body-font-family;
|
||||
@include linear-gradient(#eee, #fff);
|
||||
padding: 4px;
|
||||
|
||||
&:focus {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
.clearfix:after {
|
||||
clear: both;
|
||||
content: ".";
|
||||
display: block;
|
||||
height: 0;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@@ -40,27 +40,27 @@ h1.top-header {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
&:hover, &:focus {
|
||||
border: 1px solid darken(#888, 20%);
|
||||
@include box-shadow(inset 0 1px 0 lighten(#888, 20%), 0 0 3px #ccc);
|
||||
@include linear-gradient(lighten(#888, 10%), darken(#888, 5%));
|
||||
border: 1px solid darken(#888, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
.light-button, a.light-button {
|
||||
@include box-shadow(inset 0 1px 0 #fff);
|
||||
@include linear-gradient(#fff, lighten(#888, 40%));
|
||||
@include border-radius(3px);
|
||||
border: 1px solid #ccc;
|
||||
padding: 4px 8px;
|
||||
@include border-radius(3px);
|
||||
@include box-shadow(inset 0 1px 0 #fff);
|
||||
color: #666;
|
||||
font: normal $body-font-size $body-font-family;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
font: normal $body-font-size $body-font-family;
|
||||
@include linear-gradient(#fff, lighten(#888, 40%));
|
||||
padding: 4px 8px;
|
||||
text-decoration: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
&:hover, &:focus {
|
||||
@include linear-gradient(#fff, lighten(#888, 37%));
|
||||
border: 1px solid #ccc;
|
||||
@include linear-gradient(#fff, lighten(#888, 37%));
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
@@ -70,8 +70,8 @@ h1.top-header {
|
||||
color: $mit-red;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: darken($mit-red, 20%);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,13 +110,13 @@ h1.top-header {
|
||||
}
|
||||
|
||||
a {
|
||||
font-style: normal;
|
||||
border: none;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.bottom-border {
|
||||
@include box-shadow(0 1px 0 #eee);
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
@include box-shadow(0 1px 0 #eee);
|
||||
}
|
||||
|
||||
@media print {
|
||||
@@ -124,10 +124,10 @@ h1.top-header {
|
||||
}
|
||||
|
||||
h3 {
|
||||
border: none;
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
@extend .bottom-border;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
color: #000;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
@@ -172,8 +172,8 @@ h1.top-header {
|
||||
position: relative;
|
||||
|
||||
h2 {
|
||||
padding-right: 20px;
|
||||
margin: 0;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -205,10 +205,10 @@ h1.top-header {
|
||||
border-bottom: 1px solid darken($cream, 10%);
|
||||
@include box-shadow(inset 0 1px 0 #fff, inset 1px 0 0 #fff);
|
||||
font-size: 12px;
|
||||
height:46px;
|
||||
line-height: 46px;
|
||||
margin: (-$body-line-height) (-$body-line-height) $body-line-height;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
line-height: 46px;
|
||||
height:46px;
|
||||
|
||||
@media print {
|
||||
display: none;
|
||||
@@ -242,10 +242,10 @@ h1.top-header {
|
||||
}
|
||||
|
||||
p.ie-warning {
|
||||
background: yellow;
|
||||
display: block !important;
|
||||
line-height: 1.3em;
|
||||
background: yellow;
|
||||
margin-bottom: 0;
|
||||
padding: lh();
|
||||
text-align: left;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
// Flexible grid
|
||||
@function flex-grid($columns, $container-columns: $fg-max-columns) {
|
||||
$width: $columns * $fg-column + ($columns - 1) * $fg-gutter;
|
||||
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
|
||||
@return percentage($width / $container-width);
|
||||
}
|
||||
|
||||
// Flexible grid gutter
|
||||
@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) {
|
||||
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
|
||||
@return percentage($gutter / $container-width);
|
||||
}
|
||||
|
||||
// Percentage of container calculator
|
||||
@function perc($width, $container-width: $max-width) {
|
||||
@return percentage($width / $container-width);
|
||||
}
|
||||
|
||||
// Line-height
|
||||
@function lh($amount: 1) {
|
||||
@return $body-line-height * $amount;
|
||||
|
||||
@@ -1,32 +1,22 @@
|
||||
// Variables
|
||||
// ---------------------------------------- //
|
||||
|
||||
// fonts
|
||||
// Type
|
||||
$body-font-family: "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
|
||||
$body-font-size: 14px;
|
||||
|
||||
// grid
|
||||
$columns: 12;
|
||||
$column-width: 80px;
|
||||
$gutter-width: 25px;
|
||||
$max-width: ($columns * $column-width) + (($columns - 1) * $gutter-width);
|
||||
|
||||
$gw-column: perc($column-width);
|
||||
$gw-gutter: perc($gutter-width);
|
||||
$body-line-height: golden-ratio($body-font-size, 1);
|
||||
|
||||
//Flexible grid
|
||||
$fg-column: $column-width;
|
||||
$fg-gutter: $gutter-width;
|
||||
$fg-max-columns: $columns;
|
||||
// Grid
|
||||
$fg-column: 80px;
|
||||
$fg-gutter: 25px;
|
||||
$fg-max-columns: 12;
|
||||
$fg-max-width: 1400px;
|
||||
$fg-min-width: 810px;
|
||||
|
||||
// color
|
||||
// Color
|
||||
$light-gray: #ddd;
|
||||
$dark-gray: #333;
|
||||
$mit-red: #993333;
|
||||
$cream: #F6EFD4;
|
||||
|
||||
$text-color: $dark-gray;
|
||||
$border-color: $light-gray;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// JM MOSFET AMPLIFIER
|
||||
section.tool-wrapper {
|
||||
background: #073642;
|
||||
border-top: 1px solid darken(#002b36, 10%);
|
||||
border-bottom: 1px solid darken(#002b36, 10%);
|
||||
@include box-shadow(inset 0 0 0 4px darken(#094959, 2%));
|
||||
margin: lh() (-(lh())) 0;
|
||||
color: #839496;
|
||||
@extend .clearfix;
|
||||
background: #073642;
|
||||
border-bottom: 1px solid darken(#002b36, 10%);
|
||||
border-top: 1px solid darken(#002b36, 10%);
|
||||
@include box-shadow(inset 0 0 0 4px darken(#094959, 2%));
|
||||
color: #839496;
|
||||
display: table;
|
||||
margin: lh() (-(lh())) 0;
|
||||
|
||||
div#graph-container {
|
||||
background: none;
|
||||
@@ -29,24 +29,24 @@ section.tool-wrapper {
|
||||
|
||||
ul.ui-tabs-nav {
|
||||
background: darken(#073642, 2%);
|
||||
border-bottom: 1px solid darken(#073642, 8%);
|
||||
@include border-radius(0);
|
||||
margin: (-(lh())) (-(lh())) 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
width: 110%;
|
||||
@include border-radius(0);
|
||||
border-bottom: 1px solid darken(#073642, 8%);
|
||||
|
||||
li {
|
||||
margin-bottom: 0;
|
||||
background: none;
|
||||
color: #fff;
|
||||
border: none;
|
||||
@include border-radius(0);
|
||||
color: #fff;
|
||||
margin-bottom: 0;
|
||||
|
||||
&.ui-tabs-selected {
|
||||
border-right: 1px solid darken(#073642, 8%);
|
||||
border-left: 1px solid darken(#073642, 8%);
|
||||
background-color: #073642;
|
||||
border-left: 1px solid darken(#073642, 8%);
|
||||
border-right: 1px solid darken(#073642, 8%);
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
@@ -59,10 +59,10 @@ section.tool-wrapper {
|
||||
|
||||
a {
|
||||
border: none;
|
||||
font: bold 12px $body-font-family;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
color: #839496;
|
||||
font: bold 12px $body-font-family;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:hover {
|
||||
color: #eee8d5;
|
||||
@@ -86,18 +86,18 @@ section.tool-wrapper {
|
||||
div.graph-controls {
|
||||
|
||||
div.music-wrapper {
|
||||
padding: 0 0 lh();
|
||||
margin-bottom: lh();
|
||||
@extend .clearfix;
|
||||
border-bottom: 1px solid darken(#073642, 10%);
|
||||
@include box-shadow(0 1px 0 lighten(#073642, 2%));
|
||||
@extend .clearfix;
|
||||
margin-bottom: lh();
|
||||
padding: 0 0 lh();
|
||||
|
||||
input#playButton {
|
||||
display: block;
|
||||
@include button(simple, lighten( #586e75, 5% ));
|
||||
font: bold 14px $body-font-family;
|
||||
border-color: darken(#002b36, 6%);
|
||||
@include button(simple, lighten( #586e75, 5% ));
|
||||
display: block;
|
||||
float: right;
|
||||
font: bold 14px $body-font-family;
|
||||
|
||||
&:active {
|
||||
@include box-shadow(none);
|
||||
@@ -115,21 +115,21 @@ section.tool-wrapper {
|
||||
}
|
||||
|
||||
div.inputs-wrapper {
|
||||
@include clearfix;
|
||||
margin-bottom: lh();
|
||||
padding: 0 0 lh();
|
||||
margin-bottom: lh();
|
||||
@extend .clearfix;
|
||||
border-bottom: 1px solid darken(#073642, 10%);
|
||||
@include box-shadow(0 1px 0 lighten(#073642, 2%));
|
||||
@extend .clearfix;
|
||||
@include clearfix;
|
||||
margin-bottom: lh();
|
||||
margin-bottom: lh();
|
||||
padding: 0 0 lh();
|
||||
}
|
||||
|
||||
p {
|
||||
font-weight: bold;
|
||||
@include inline-block();
|
||||
margin: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 -1px 0 darken(#073642, 10%);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
ul {
|
||||
@@ -147,19 +147,19 @@ section.tool-wrapper {
|
||||
}
|
||||
|
||||
div#graph-listen {
|
||||
margin-top: 8px;
|
||||
margin-right: 20px;
|
||||
display: block;
|
||||
text-align: right;
|
||||
float: left;
|
||||
margin-bottom: 0;
|
||||
margin-right: 20px;
|
||||
margin-top: 8px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
@include border-radius(2px);
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
padding: 3px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
@@ -188,33 +188,31 @@ section.tool-wrapper {
|
||||
}
|
||||
|
||||
div.schematic-sliders {
|
||||
|
||||
div.top-sliders {
|
||||
padding: 0 0 lh();
|
||||
margin-bottom: lh();
|
||||
@extend .clearfix;
|
||||
border-bottom: 1px solid darken(#073642, 10%);
|
||||
@include box-shadow(0 1px 0 lighten(#073642, 2%));
|
||||
@extend .clearfix;
|
||||
margin-bottom: lh();
|
||||
padding: 0 0 lh();
|
||||
|
||||
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;
|
||||
}
|
||||
select#musicTypeSelect {
|
||||
font: 16px $body-font-family;
|
||||
@include inline-block();
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-weight: bold;
|
||||
@include inline-block();
|
||||
margin: 0 lh(.5) lh() 0;
|
||||
text-shadow: 0 -1px 0 darken(#073642, 10%);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
}
|
||||
|
||||
div.slider-label {
|
||||
margin-bottom: lh(0.5);
|
||||
font-weight: bold;
|
||||
margin-bottom: lh(0.5);
|
||||
text-shadow: 0 -1px 0 darken(#073642, 10%);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
@@ -223,10 +221,10 @@ section.tool-wrapper {
|
||||
margin-bottom: lh(1);
|
||||
|
||||
&.ui-slider-horizontal {
|
||||
height: 0.4em;
|
||||
background: darken(#002b36, 2%);
|
||||
border: 1px solid darken(#002b36, 8%);
|
||||
@include box-shadow(none);
|
||||
height: 0.4em;
|
||||
}
|
||||
|
||||
.ui-slider-handle {
|
||||
@@ -243,5 +241,3 @@ section.tool-wrapper {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ div.course-wrapper {
|
||||
section.course-content {
|
||||
@extend .content;
|
||||
overflow: hidden;
|
||||
@include border-top-right-radius(4px);
|
||||
@include border-bottom-right-radius(4px);
|
||||
|
||||
h1 {
|
||||
margin: 0 0 lh();
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
nav.sequence-nav {
|
||||
@extend .topbar;
|
||||
@include box-sizing(border-box);
|
||||
border-bottom: 1px solid darken($cream, 20%);
|
||||
margin-bottom: $body-line-height;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
@include border-top-right-radius(4px);
|
||||
|
||||
ol {
|
||||
border-bottom: 1px solid darken($cream, 20%);
|
||||
border-top: 1px solid darken($cream, 20%);
|
||||
@include box-sizing(border-box);
|
||||
display: table;
|
||||
height: 100%;
|
||||
padding-right: flex-grid(1, 9);
|
||||
width: 100%;
|
||||
|
||||
@@ -63,117 +62,117 @@ nav.sequence-nav {
|
||||
display: block;
|
||||
height: 17px;
|
||||
padding: 15px 0 14px;
|
||||
position: relative;
|
||||
@include transition(all, .4s, $ease-in-out-quad);
|
||||
width: 100%;
|
||||
|
||||
// @media screen and (max-width: 800px) {
|
||||
// padding: 12px 8px;
|
||||
// }
|
||||
|
||||
//video
|
||||
&.seq_video_inactive {
|
||||
@extend .inactive;
|
||||
background-image: url('/static/images/sequence-nav/video-icon-normal.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&.seq_video_visited {
|
||||
@extend .visited;
|
||||
background-image: url('/static/images/sequence-nav/video-icon-visited.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&.seq_video_active {
|
||||
@extend .active;
|
||||
background-image: url('/static/images/sequence-nav/video-icon-current.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
//other
|
||||
&.seq_other_inactive {
|
||||
@extend .inactive;
|
||||
background-image: url('/static/images/sequence-nav/document-icon-normal.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&.seq_other_visited {
|
||||
@extend .visited;
|
||||
background-image: url('/static/images/sequence-nav/document-icon-visited.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&.seq_other_active {
|
||||
@extend .active;
|
||||
background-image: url('/static/images/sequence-nav/document-icon-current.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
//vertical & problems
|
||||
&.seq_vertical_inactive, &.seq_problem_inactive {
|
||||
@extend .inactive;
|
||||
background-image: url('/static/images/sequence-nav/list-icon-normal.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&.seq_vertical_visited, &.seq_problem_visited {
|
||||
@extend .visited;
|
||||
background-image: url('/static/images/sequence-nav/list-icon-visited.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&.seq_vertical_active, &.seq_problem_active {
|
||||
@extend .active;
|
||||
background-image: url('/static/images/sequence-nav/list-icon-current.png');
|
||||
background-position: center;
|
||||
}
|
||||
//video
|
||||
&.seq_video_inactive {
|
||||
@extend .inactive;
|
||||
background-image: url('/static/images/sequence-nav/video-icon-normal.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
p {
|
||||
// display: none;
|
||||
// visibility: hidden;
|
||||
background: #333;
|
||||
color: #fff;
|
||||
line-height: lh();
|
||||
margin: 0px 0 0 -5px;
|
||||
opacity: 0;
|
||||
padding: 6px;
|
||||
position: absolute;
|
||||
text-shadow: 0 -1px 0 #000;
|
||||
@include transition(all, .6s, $ease-in-out-quart);
|
||||
white-space: pre-wrap;
|
||||
z-index: 99;
|
||||
|
||||
&.shown {
|
||||
margin-top: 4px;
|
||||
opacity: 1;
|
||||
&.seq_video_visited {
|
||||
@extend .visited;
|
||||
background-image: url('/static/images/sequence-nav/video-icon-visited.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&:empty {
|
||||
background: none;
|
||||
&.seq_video_active {
|
||||
@extend .active;
|
||||
background-image: url('/static/images/sequence-nav/video-icon-current.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
//other
|
||||
&.seq_other_inactive {
|
||||
@extend .inactive;
|
||||
background-image: url('/static/images/sequence-nav/document-icon-normal.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&.seq_other_visited {
|
||||
@extend .visited;
|
||||
background-image: url('/static/images/sequence-nav/document-icon-visited.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&.seq_other_active {
|
||||
@extend .active;
|
||||
background-image: url('/static/images/sequence-nav/document-icon-current.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
//vertical & problems
|
||||
&.seq_vertical_inactive, &.seq_problem_inactive {
|
||||
@extend .inactive;
|
||||
background-image: url('/static/images/sequence-nav/list-icon-normal.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&.seq_vertical_visited, &.seq_problem_visited {
|
||||
@extend .visited;
|
||||
background-image: url('/static/images/sequence-nav/list-icon-visited.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&.seq_vertical_active, &.seq_problem_active {
|
||||
@extend .active;
|
||||
background-image: url('/static/images/sequence-nav/list-icon-current.png');
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
p {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
display: none;
|
||||
line-height: lh();
|
||||
left: 0px;
|
||||
opacity: 0;
|
||||
padding: 6px;
|
||||
position: absolute;
|
||||
top: 48px;
|
||||
text-shadow: 0 -1px 0 #000;
|
||||
@include transition(all, .1s, $ease-in-out-quart);
|
||||
white-space: pre;
|
||||
z-index: 99;
|
||||
|
||||
&:empty {
|
||||
background: none;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
background: #333;
|
||||
content: " ";
|
||||
display: block;
|
||||
height: 10px;
|
||||
left: 18px;
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
@include transform(rotate(45deg));
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: #333;
|
||||
content: " ";
|
||||
display: block;
|
||||
height: 10px;
|
||||
left: 18px;
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
@include transform(rotate(45deg));
|
||||
width: 10px;
|
||||
&:hover {
|
||||
p {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-right: 1px;
|
||||
list-style: none !important;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
@@ -223,6 +222,7 @@ nav.sequence-nav {
|
||||
&.next {
|
||||
a {
|
||||
background-image: url('/static/images/sequence-nav/next-icon.png');
|
||||
@include border-top-right-radius(4px);
|
||||
|
||||
&:hover {
|
||||
background-color: none;
|
||||
|
||||
@@ -26,6 +26,7 @@ section.course-index {
|
||||
}
|
||||
|
||||
&.ui-state-active {
|
||||
@include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(225,225,225)));
|
||||
@extend .active;
|
||||
}
|
||||
}
|
||||
@@ -33,57 +34,97 @@ section.course-index {
|
||||
|
||||
ul.ui-accordion-content {
|
||||
@include border-radius(0);
|
||||
@include box-shadow( inset -1px 0 0 #e6e6e6);
|
||||
@include box-shadow(inset -1px 0 0 #e6e6e6);
|
||||
background: #dadada;
|
||||
border: none;
|
||||
border-bottom: 1px solid #c3c3c3;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
// overflow: visible;
|
||||
padding: 1em 1.5em;
|
||||
|
||||
li {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
@include border-radius(4px);
|
||||
margin-bottom: lh(.5);
|
||||
position: relative;
|
||||
padding: 5px 36px 5px 10px;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
color: #666;
|
||||
|
||||
p {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0;
|
||||
|
||||
span.subtitle {
|
||||
color: #666;
|
||||
font-weight: normal;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
background: transparent;
|
||||
border-top: 1px solid rgb(180,180,180);
|
||||
border-right: 1px solid rgb(180,180,180);
|
||||
content: "";
|
||||
display: block;
|
||||
height: 12px;
|
||||
margin-top: -6px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 30px;
|
||||
@include transform(rotate(45deg));
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include background-image(linear-gradient(-90deg, rgba(245,245,245, 0.4), rgba(230,230,230, 0.4)));
|
||||
border-color: rgb(200,200,200);
|
||||
|
||||
&:after {
|
||||
opacity: 1;
|
||||
right: 15px;
|
||||
@include transition(all, 0.2s, linear);
|
||||
}
|
||||
|
||||
> a p {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
@include box-shadow(inset 0 1px 14px 0 rgba(0,0,0, 0.1));
|
||||
top: 1px;
|
||||
|
||||
&:after {
|
||||
opacity: 1;
|
||||
right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgb(240,240,240);
|
||||
@include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(230,230,230)));
|
||||
border-color: rgb(200,200,200);
|
||||
font-weight: bold;
|
||||
|
||||
> a p {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
span.subtitle {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
// &:after {
|
||||
// content: " ";
|
||||
// width: 16px;
|
||||
// height: 16px;
|
||||
// position: absolute;
|
||||
// right: -35px;
|
||||
// top: 7px;
|
||||
// display: block;
|
||||
// background-color: #dadada;
|
||||
// border-top: 1px solid #c3c3c3;
|
||||
// border-right: 1px solid #c3c3c3;
|
||||
// z-index: 99;
|
||||
// @include transform(rotate(45deg));
|
||||
// }
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
margin-bottom: lh(.5);
|
||||
display: block;
|
||||
color: #000;
|
||||
|
||||
&:hover {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
|
||||
span.subtitle {
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
&:after {
|
||||
opacity: 1;
|
||||
right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,56 +14,53 @@ section.course-content {
|
||||
}
|
||||
}
|
||||
|
||||
div.video {
|
||||
}
|
||||
|
||||
div.video-subtitles {
|
||||
@include clearfix();
|
||||
padding: 6px lh();
|
||||
margin: 0 (-(lh()));
|
||||
border-top: 1px solid #e1e1e1;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
background: #f3f3f3;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
border-top: 1px solid #e1e1e1;
|
||||
@include clearfix();
|
||||
display: block;
|
||||
margin: 0 (-(lh()));
|
||||
padding: 6px lh();
|
||||
|
||||
div.video-wrapper {
|
||||
float: left;
|
||||
width: flex-grid(6, 9);
|
||||
margin-right: flex-gutter(9);
|
||||
width: flex-grid(6, 9);
|
||||
|
||||
div.video-player {
|
||||
position: relative;
|
||||
padding-bottom: 56.25%;
|
||||
padding-top: 30px;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
padding-bottom: 56.25%;
|
||||
padding-top: 30px;
|
||||
position: relative;
|
||||
|
||||
object {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
iframe#html5_player {
|
||||
border: none;
|
||||
display: none;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
section.video-controls {
|
||||
@extend .clearfix;
|
||||
background: #333;
|
||||
position: relative;
|
||||
border: 1px solid #000;
|
||||
border-top: 0;
|
||||
color: #ccc;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
ul, div {
|
||||
@@ -73,12 +70,12 @@ section.course-content {
|
||||
|
||||
div#slider {
|
||||
@extend .clearfix;
|
||||
@include border-radius(0);
|
||||
@include box-shadow(inset 0 1px 0 #eee, 0 1px 0 #555);
|
||||
background: #c2c2c2;
|
||||
border: none;
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
@include border-radius(0);
|
||||
border-top: 1px solid #000;
|
||||
@include box-shadow(inset 0 1px 0 #eee, 0 1px 0 #555);
|
||||
height: 7px;
|
||||
@include transition(height 2.0s ease-in-out);
|
||||
|
||||
@@ -96,54 +93,53 @@ section.course-content {
|
||||
font: bold 12px $body-font-family;
|
||||
margin-bottom: 6px;
|
||||
margin-right: 0;
|
||||
overflow: visible;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-shadow: 0 -1px 0 darken($mit-red, 10%);
|
||||
overflow: visible;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
background: $mit-red;
|
||||
border-bottom: 1px solid darken($mit-red, 20%);
|
||||
border-right: 1px solid darken($mit-red, 20%);
|
||||
bottom: -5px;
|
||||
content: " ";
|
||||
display: block;
|
||||
height: 7px;
|
||||
left: 50%;
|
||||
margin-left: -3px;
|
||||
position: absolute;
|
||||
@include transform(rotate(45deg));
|
||||
background: $mit-red;
|
||||
border-right: 1px solid darken($mit-red, 20%);
|
||||
border-bottom: 1px solid darken($mit-red, 20%);
|
||||
width: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
a.ui-slider-handle {
|
||||
background: $mit-red url(/static/images/slider-handle.png) center center no-repeat;
|
||||
@include background-size(50%);
|
||||
border: 1px solid darken($mit-red, 20%);
|
||||
@include border-radius(15px);
|
||||
@include box-shadow(inset 0 1px 0 lighten($mit-red, 10%));
|
||||
background: $mit-red url(/static/images/slider-handle.png) center center no-repeat;
|
||||
border: 1px solid darken($mit-red, 20%);
|
||||
cursor: pointer;
|
||||
height: 15px;
|
||||
margin-left: -7px;
|
||||
top: -4px;
|
||||
width: 15px;
|
||||
@include transition(height 2.0s ease-in-out, width 2.0s ease-in-out);
|
||||
@include background-size(50%);
|
||||
width: 15px;
|
||||
|
||||
&:focus, &:hover {
|
||||
background-color: lighten($mit-red, 10%);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ul.vcr {
|
||||
float: left;
|
||||
margin-right: lh();
|
||||
@extend .dullify;
|
||||
float: left;
|
||||
list-style: none;
|
||||
margin-right: lh();
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
@@ -151,17 +147,16 @@ section.course-content {
|
||||
margin-bottom: 0;
|
||||
|
||||
a {
|
||||
@include box-shadow(1px 0 0 #555);
|
||||
border-bottom: none;
|
||||
border-right: 1px solid #000;
|
||||
display: block;
|
||||
@include box-shadow(1px 0 0 #555);
|
||||
cursor: pointer;
|
||||
// height: 14px;
|
||||
display: block;
|
||||
line-height: 46px;
|
||||
padding: 0 lh(.75);
|
||||
text-indent: -9999px;
|
||||
width: 14px;
|
||||
@include transition();
|
||||
width: 14px;
|
||||
|
||||
&.play {
|
||||
background: url('/static/images/play-icon.png') center center no-repeat;
|
||||
@@ -178,21 +173,20 @@ section.course-content {
|
||||
background-color: #444;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
div#vidtime {
|
||||
padding-left: lh(.75);
|
||||
font-weight: bold;
|
||||
line-height: 46px; //height of play pause buttons
|
||||
padding-left: lh(.75);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.secondary-controls {
|
||||
float: right;
|
||||
@extend .dullify;
|
||||
float: right;
|
||||
|
||||
div.speeds {
|
||||
float: left;
|
||||
@@ -201,10 +195,11 @@ section.course-content {
|
||||
background: url('/static/images/closed-arrow.png') 10px center no-repeat;
|
||||
border-left: 1px solid #000;
|
||||
border-right: 1px solid #000;
|
||||
display: block;
|
||||
@include box-shadow(1px 0 0 #555, inset 1px 0 0 #555);
|
||||
@include clearfix();
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
line-height: 46px; //height of play pause buttons
|
||||
margin-right: 0;
|
||||
padding-left: 15px;
|
||||
@@ -212,30 +207,29 @@ section.course-content {
|
||||
@include transition();
|
||||
-webkit-font-smoothing: antialiased;
|
||||
width: 110px;
|
||||
color: #fff;
|
||||
|
||||
&.open {
|
||||
background: url('/static/images/open-arrow.png') 10px center no-repeat;
|
||||
|
||||
ol#video_speeds {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #999;
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
float: left;
|
||||
letter-spacing: 1px;
|
||||
padding: 0 lh(.25) 0 lh(.5);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
p.active {
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0;
|
||||
padding: 0 lh(.5) 0 0;
|
||||
}
|
||||
@@ -245,14 +239,14 @@ section.course-content {
|
||||
background-color: #444;
|
||||
border: 1px solid #000;
|
||||
@include box-shadow(inset 1px 0 0 #555, 0 3px 0 #444);
|
||||
left: -1px;
|
||||
display: none;
|
||||
left: -1px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top:0;
|
||||
@include transition();
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
@include transition();
|
||||
opacity: 0;
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #000;
|
||||
@@ -267,20 +261,20 @@ section.course-content {
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
margin-top: 0;
|
||||
@include box-shadow(none);
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #aaa;
|
||||
background-color: #666;
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background-color: #444;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,34 +293,33 @@ section.course-content {
|
||||
@include transition();
|
||||
width: 30px;
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
background-color: #444;
|
||||
}
|
||||
}
|
||||
|
||||
a.hide-subtitles {
|
||||
float: left;
|
||||
display: block;
|
||||
padding: 0 lh(.5);
|
||||
margin-left: 0;
|
||||
color: #797979;
|
||||
line-height: 46px; //height of play pause buttons
|
||||
width: 30px;
|
||||
text-indent: -9999px;
|
||||
font-weight: 800;
|
||||
background: url('/static/images/cc.png') center no-repeat;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@include transition();
|
||||
color: #797979;
|
||||
display: block;
|
||||
float: left;
|
||||
font-weight: 800;
|
||||
line-height: 46px; //height of play pause buttons
|
||||
margin-left: 0;
|
||||
opacity: 1;
|
||||
padding: 0 lh(.5);
|
||||
position: relative;
|
||||
text-indent: -9999px;
|
||||
@include transition();
|
||||
-webkit-font-smoothing: antialiased;
|
||||
width: 30px;
|
||||
|
||||
&:hover {
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
&.off {
|
||||
@@ -358,10 +351,10 @@ section.course-content {
|
||||
|
||||
ol.subtitles {
|
||||
float: left;
|
||||
width: flex-grid(3, 9);
|
||||
padding-top: 10px;
|
||||
max-height: 460px;
|
||||
overflow: hidden;
|
||||
padding-top: 10px;
|
||||
width: flex-grid(3, 9);
|
||||
|
||||
li {
|
||||
border: 0;
|
||||
@@ -398,43 +391,43 @@ section.course-content {
|
||||
}
|
||||
|
||||
ol.subtitles {
|
||||
width: 0px;
|
||||
height: 0;
|
||||
width: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&.fullscreen {
|
||||
background: rgba(#000, .95);
|
||||
border: 0;
|
||||
margin: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 999;
|
||||
overflow: hidden;
|
||||
|
||||
&.closed {
|
||||
ol.subtitles {
|
||||
width: auto;
|
||||
height: auto;
|
||||
right: -(flex-grid(4));
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
a.exit {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
color: #aaa;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-style: 12px;
|
||||
display: none;
|
||||
font-style: 12px;
|
||||
left: 20px;
|
||||
letter-spacing: 1px;
|
||||
position: absolute;
|
||||
text-transform: uppercase;
|
||||
top: 20px;
|
||||
|
||||
&::after {
|
||||
content: "✖";
|
||||
@@ -453,33 +446,33 @@ section.course-content {
|
||||
}
|
||||
|
||||
object#myytplayer, iframe {
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
section.video-controls {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 9999;
|
||||
}
|
||||
}
|
||||
|
||||
ol.subtitles {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: rgba(#000, .8);
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
background: rgba(#000, .8);
|
||||
padding: lh();
|
||||
max-width: flex-grid(3);
|
||||
max-height: 100%;
|
||||
max-width: flex-grid(3);
|
||||
padding: lh();
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
@include transition();
|
||||
|
||||
li {
|
||||
|
||||
@@ -2,10 +2,10 @@ li.calc-main {
|
||||
bottom: -126px;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
@include transition(bottom);
|
||||
z-index: 99;
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
z-index: 99;
|
||||
|
||||
&.open {
|
||||
bottom: -36px;
|
||||
@@ -16,19 +16,19 @@ li.calc-main {
|
||||
}
|
||||
|
||||
a.calc {
|
||||
@include hide-text;
|
||||
background: url("/static/images/calc-icon.png") rgba(#111, .9) no-repeat center;
|
||||
border-bottom: 0;
|
||||
@include border-radius(3px 3px 0 0);
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
@include border-radius(3px 3px 0 0);
|
||||
@include inline-block;
|
||||
padding: 8px 12px;
|
||||
width: 16px;
|
||||
height: 20px;
|
||||
@include hide-text;
|
||||
@include inline-block;
|
||||
margin-right: 10px;
|
||||
padding: 8px 12px;
|
||||
position: relative;
|
||||
top: -36px;
|
||||
width: 16px;
|
||||
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
@@ -41,15 +41,15 @@ li.calc-main {
|
||||
|
||||
div#calculator_wrapper {
|
||||
background: rgba(#111, .9);
|
||||
position: relative;
|
||||
top: -36px;
|
||||
clear: both;
|
||||
max-height: 90px;
|
||||
position: relative;
|
||||
top: -36px;
|
||||
|
||||
form {
|
||||
padding: lh();
|
||||
@extend .clearfix;
|
||||
@include box-sizing(border-box);
|
||||
padding: lh();
|
||||
|
||||
input#calculator_button {
|
||||
background: #111;
|
||||
@@ -94,20 +94,20 @@ li.calc-main {
|
||||
position: relative;
|
||||
width: flex-grid(7.5);
|
||||
|
||||
input#calculator_input {
|
||||
border: none;
|
||||
@include box-shadow(none);
|
||||
@include box-sizing(border-box);
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
input#calculator_input {
|
||||
border: none;
|
||||
@include box-shadow(none);
|
||||
@include box-sizing(border-box);
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.help-wrapper {
|
||||
position: absolute;
|
||||
@@ -115,10 +115,10 @@ li.calc-main {
|
||||
top: 15px;
|
||||
|
||||
a {
|
||||
background: url("/static/images/info-icon.png") center center no-repeat;
|
||||
height: 17px;
|
||||
@include hide-text;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
background: url("/static/images/info-icon.png") center center no-repeat;
|
||||
}
|
||||
|
||||
dl {
|
||||
@@ -126,14 +126,14 @@ li.calc-main {
|
||||
@include border-radius(3px);
|
||||
@include box-shadow(0 0 3px #999);
|
||||
color: #333;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
right: -40px;
|
||||
top: -110px;
|
||||
width: 500px;
|
||||
display: none;
|
||||
@include transition();
|
||||
width: 500px;
|
||||
|
||||
&.shown {
|
||||
opacity: 1;
|
||||
|
||||
@@ -68,11 +68,11 @@ footer {
|
||||
}
|
||||
|
||||
a {
|
||||
border-bottom: 0;
|
||||
display: block;
|
||||
height: 29px;
|
||||
width: 28px;
|
||||
text-indent: -9999px;
|
||||
border-bottom: 0;
|
||||
width: 28px;
|
||||
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
|
||||
@@ -100,12 +100,12 @@ div.header-wrapper {
|
||||
float: left;
|
||||
|
||||
a {
|
||||
border: none;
|
||||
color: #fff;
|
||||
display: block;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
padding: 10px lh() 8px;
|
||||
border: none;
|
||||
font-style: normal;
|
||||
|
||||
@media screen and (max-width: 1020px) {
|
||||
padding: 10px lh(.7) 8px;
|
||||
@@ -125,10 +125,10 @@ div.header-wrapper {
|
||||
|
||||
ul {
|
||||
li {
|
||||
padding: auto;
|
||||
display: table-cell;
|
||||
width: 16.6666666667%;
|
||||
padding: auto;
|
||||
text-align: center;
|
||||
width: 16.6666666667%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ html {
|
||||
margin-top: 0;
|
||||
|
||||
body {
|
||||
background: #f4f4f4; //#f3f1e5
|
||||
color: $dark-gray;
|
||||
font: $body-font-size $body-font-family;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
background: #f4f4f4; //#f3f1e5
|
||||
text-align: center;
|
||||
|
||||
section.main-content {
|
||||
@extend .clearfix;
|
||||
@@ -17,7 +17,7 @@ html {
|
||||
@include box-shadow(0 0 4px #dfdfdf);
|
||||
@include box-sizing(border-box);
|
||||
margin-top: 3px;
|
||||
// overflow: hidden;
|
||||
overflow: hidden;
|
||||
|
||||
@media print {
|
||||
border-bottom: 0;
|
||||
@@ -32,12 +32,13 @@ html {
|
||||
|
||||
div.qtip {
|
||||
div.ui-tooltip-content {
|
||||
border: none;
|
||||
background: #000;
|
||||
background: rgba(#000, .8);
|
||||
border: none;
|
||||
color: #fff;
|
||||
font: 12px $body-font-family;
|
||||
margin-top: -30px;
|
||||
margin-right: -20px;
|
||||
margin-top: -30px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#lean_overlay {
|
||||
position: fixed;
|
||||
z-index:100;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height:100%;
|
||||
width:100%;
|
||||
background: #000;
|
||||
display: none;
|
||||
height:100%;
|
||||
left: 0px;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
width:100%;
|
||||
z-index:100;
|
||||
}
|
||||
|
||||
div.leanModal_box {
|
||||
@@ -31,8 +31,8 @@ div.leanModal_box {
|
||||
z-index: 2;
|
||||
|
||||
&:hover{
|
||||
text-decoration: none;
|
||||
color: $mit-red;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ div.leanModal_box {
|
||||
li {
|
||||
|
||||
&.terms, &.honor-code {
|
||||
width: auto;
|
||||
float: none;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
div.tip {
|
||||
@@ -118,8 +118,8 @@ div.leanModal_box {
|
||||
}
|
||||
|
||||
&.honor-code {
|
||||
width: auto;
|
||||
float: none;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
label {
|
||||
@@ -128,8 +128,8 @@ div.leanModal_box {
|
||||
}
|
||||
|
||||
#{$all-text-inputs}, textarea {
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
@@ -176,15 +176,15 @@ div#login {
|
||||
|
||||
ol {
|
||||
li {
|
||||
width: auto;
|
||||
float: none;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.lost-password {
|
||||
text-align: left;
|
||||
margin-top: lh();
|
||||
text-align: left;
|
||||
|
||||
a {
|
||||
color: #999;
|
||||
@@ -218,9 +218,9 @@ div#deactivate-account {
|
||||
margin-bottom: lh(.5);
|
||||
|
||||
textarea, #{$all-text-inputs} {
|
||||
@include box-sizing(border-box);
|
||||
display: block;
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
textarea {
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
@import "base/reset", "base/font-face", "base/functions";
|
||||
|
||||
// pages
|
||||
@import "index/variables", "index/extends", "index/base", "index/header", "index/footer", "index/index";
|
||||
@import "marketing/variables", "marketing/extends", "marketing/base", "marketing/header", "marketing/footer", "marketing/index";
|
||||
@import "layout/leanmodal";
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
form#wiki_revision {
|
||||
float: left;
|
||||
width: flex-grid(6, 9);
|
||||
margin-right: flex-gutter(9);
|
||||
|
||||
width: flex-grid(6, 9);
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 7px ;
|
||||
}
|
||||
|
||||
|
||||
.CodeMirror-scroll {
|
||||
min-height: 550px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
@extend textarea;
|
||||
@include box-sizing(border-box);
|
||||
font-family: monospace;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
textarea {
|
||||
@include box-sizing(border-box);
|
||||
margin-bottom: 20px;
|
||||
@@ -32,25 +33,25 @@ form#wiki_revision {
|
||||
}
|
||||
|
||||
#submit_delete {
|
||||
@include box-shadow(none);
|
||||
background: none;
|
||||
border: none;
|
||||
@include box-shadow(none);
|
||||
color: #999;
|
||||
float: right;
|
||||
text-decoration: underline;
|
||||
font-weight: normal;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
input[type="submit"] {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
#wiki_edit_instructions {
|
||||
float: left;
|
||||
width: flex-grid(3, 9);
|
||||
margin-top: lh();
|
||||
color: #666;
|
||||
float: left;
|
||||
margin-top: lh();
|
||||
width: flex-grid(3, 9);
|
||||
|
||||
&:hover {
|
||||
color: #333;
|
||||
@@ -58,16 +59,14 @@ form#wiki_revision {
|
||||
|
||||
.markdown-example {
|
||||
background-color: #e3e3e3;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
|
||||
line-height: 1.0;
|
||||
margin: 5px 0 7px;
|
||||
padding: {
|
||||
top: 5px;
|
||||
right: 2px;
|
||||
bottom: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
margin: 5px 0 7px;
|
||||
line-height: 1.0;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,20 @@ div#wiki_panel {
|
||||
overflow: auto;
|
||||
|
||||
h2 {
|
||||
padding: lh(.5) lh();
|
||||
@extend .bottom-border;
|
||||
font-size: 18px;
|
||||
margin: 0 ;
|
||||
@extend .bottom-border;
|
||||
padding: lh(.5) lh();
|
||||
}
|
||||
|
||||
input[type="button"] {
|
||||
@extend h3;
|
||||
@include transition();
|
||||
color: lighten($text-color, 10%);
|
||||
font-size: $body-font-size;
|
||||
margin: 0 !important;
|
||||
padding: 7px lh();
|
||||
text-align: left;
|
||||
@include transition();
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
@@ -28,8 +28,8 @@ div#wiki_panel {
|
||||
ul {
|
||||
li {
|
||||
&.search {
|
||||
@include box-shadow(0 1px 0 #eee);
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
@include box-shadow(0 1px 0 #eee);
|
||||
padding: 7px lh();
|
||||
|
||||
label {
|
||||
@@ -49,15 +49,15 @@ div#wiki_panel {
|
||||
|
||||
div#wiki_create_form {
|
||||
@extend .clearfix;
|
||||
padding: 15px;
|
||||
background: #d6d6d6;
|
||||
border-bottom: 1px solid #bbb;
|
||||
padding: 15px;
|
||||
|
||||
input[type="text"] {
|
||||
margin-bottom: 6px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ul {
|
||||
|
||||
@@ -7,15 +7,14 @@ div.wiki-wrapper {
|
||||
@extend .content;
|
||||
position: relative;
|
||||
|
||||
|
||||
header {
|
||||
@extend .topbar;
|
||||
height:46px;
|
||||
@include box-shadow(inset 0 1px 0 white);
|
||||
height:46px;
|
||||
|
||||
&:empty {
|
||||
display: none !important;
|
||||
border-bottom: 0;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -23,10 +22,10 @@ div.wiki-wrapper {
|
||||
}
|
||||
|
||||
p {
|
||||
float: left;
|
||||
margin-bottom: 0;
|
||||
color: darken($cream, 55%);
|
||||
float: left;
|
||||
line-height: 46px;
|
||||
margin-bottom: 0;
|
||||
padding-left: lh();
|
||||
}
|
||||
|
||||
@@ -48,8 +47,8 @@ div.wiki-wrapper {
|
||||
@include box-shadow(inset 1px 0 0 lighten(#f6efd4, 5%));
|
||||
color: darken($cream, 80%);
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
letter-spacing: 1px;
|
||||
line-height: 46px;
|
||||
margin: 0;
|
||||
@@ -89,15 +88,15 @@ div.wiki-wrapper {
|
||||
width: flex-grid(2.5, 9);
|
||||
|
||||
@media screen and (max-width:900px) {
|
||||
border-right: 0;
|
||||
display: block;
|
||||
width: auto;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
border-right: 0;
|
||||
display: block;
|
||||
width: auto;
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,9 +105,9 @@ div.wiki-wrapper {
|
||||
}
|
||||
|
||||
section.results {
|
||||
border-left: 1px dashed #ddd;
|
||||
@include box-sizing(border-box);
|
||||
display: inline-block;
|
||||
border-left: 1px dashed #ddd;
|
||||
float: left;
|
||||
padding-left: 10px;
|
||||
width: flex-grid(6.5, 9);
|
||||
@@ -123,8 +122,8 @@ div.wiki-wrapper {
|
||||
|
||||
@media print {
|
||||
display: block;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
|
||||
canvas, img {
|
||||
page-break-inside: avoid;
|
||||
@@ -140,14 +139,15 @@ div.wiki-wrapper {
|
||||
}
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #eee;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
@@ -155,6 +155,5 @@ div.wiki-wrapper {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
var ${ id }contents=["",
|
||||
%for t in items:
|
||||
${t['content']} ,
|
||||
${t['content']} ,
|
||||
%endfor
|
||||
""
|
||||
];
|
||||
@@ -16,7 +16,7 @@ var ${ id }types=["",
|
||||
|
||||
var ${ id }init_functions=["",
|
||||
%for t in items:
|
||||
function(){ ${t['init_js']} },
|
||||
function(){ ${t['init_js']} },
|
||||
%endfor
|
||||
""];
|
||||
|
||||
@@ -24,12 +24,12 @@ var ${ id }titles=${titles};
|
||||
|
||||
var ${ id }destroy_functions=["",
|
||||
%for t in items:
|
||||
function(){ ${t['destroy_js']} },
|
||||
function(){ ${t['destroy_js']} },
|
||||
%endfor
|
||||
""];
|
||||
|
||||
var ${ id }loc = -1;
|
||||
function disablePrev() {
|
||||
function disablePrev() {
|
||||
var i=${ id }loc-1;
|
||||
log_event("seq_prev", {'old':${id}loc, 'new':i,'id':'${id}'});
|
||||
if (i < 1 ) {
|
||||
@@ -39,7 +39,7 @@ function disablePrev() {
|
||||
};
|
||||
}
|
||||
|
||||
function disableNext() {
|
||||
function disableNext() {
|
||||
var i=${ id }loc+1;
|
||||
log_event("seq_next", {'old':${id}loc, 'new':i,'id':'${id}'});
|
||||
|
||||
@@ -77,11 +77,11 @@ function ${ id }goto(i) {
|
||||
function ${ id }setup_click(i) {
|
||||
$('#tt_'+i).click(function(eo) { ${ id }goto(i);});
|
||||
$('#tt_'+i).addClass("seq_"+${ id }types[i]+"_inactive");
|
||||
$('#tt_'+i).parent().append("<p>" + ${ id }titles[i-1] + "</p>");
|
||||
$('#tt_'+i).append("<p>" + ${ id }titles[i-1] + "</p>");
|
||||
|
||||
}
|
||||
|
||||
function ${ id }next() {
|
||||
function ${ id }next() {
|
||||
var i=${ id }loc+1;
|
||||
log_event("seq_next", {'old':${id}loc, 'new':i,'id':'${id}'});
|
||||
if(i > ${ len(items) } ) {
|
||||
@@ -92,7 +92,7 @@ function ${ id }next() {
|
||||
}
|
||||
|
||||
|
||||
function ${ id }prev() {
|
||||
function ${ id }prev() {
|
||||
var i=${ id }loc-1;
|
||||
log_event("seq_prev", {'old':${id}loc, 'new':i,'id':'${id}'});
|
||||
if (i < 1 ) {
|
||||
@@ -105,7 +105,7 @@ function ${ id }prev() {
|
||||
|
||||
|
||||
$(function() {
|
||||
var i;
|
||||
var i;
|
||||
for(i=1; i<${ len(items)+1 }; i++) {
|
||||
${ id }setup_click(i);
|
||||
}
|
||||
|
||||
0
test_root/data/custom_tags/.git-keep
Normal file
0
test_root/data/custom_tags/.git-keep
Normal file
0
test_root/log/.git-keep
Normal file
0
test_root/log/.git-keep
Normal file
6
urls.py
6
urls.py
@@ -69,6 +69,12 @@ if settings.COURSEWARE_ENABLED:
|
||||
url(r'^calculate$', 'util.views.calculate'),
|
||||
)
|
||||
|
||||
if settings.ENABLE_MULTICOURSE:
|
||||
urlpatterns += (url(r'^mitxhome$', 'util.views.mitxhome'),)
|
||||
|
||||
if settings.QUICKEDIT:
|
||||
urlpatterns += (url(r'^quickedit/(?P<id>[^/]*)$', 'courseware.views.quickedit'),)
|
||||
|
||||
if settings.ASKBOT_ENABLED:
|
||||
urlpatterns += (url(r'^%s' % settings.ASKBOT_URL, include('askbot.urls')), \
|
||||
url(r'^admin/', include(admin.site.urls)), \
|
||||
|
||||
Reference in New Issue
Block a user