Commit be369938 by Rocky Duan

Merge branch 'master' of github.com:MITx/mitx

parents 2b89a82a 703c7a95
......@@ -39,7 +39,7 @@ import responsetypes
# dict of tagname, Response Class -- this should come from auto-registering
response_tag_dict = dict([(x.response_tag,x) for x in responsetypes.__all__])
entry_types = ['textline', 'schematic', 'choicegroup', 'textbox', 'imageinput', 'optioninput']
entry_types = ['textline', 'schematic', 'textbox', 'imageinput', 'optioninput', 'choicegroup', 'radiogroup', 'checkboxgroup']
solution_types = ['solution'] # extra things displayed after "show answers" is pressed
response_properties = ["responseparam", "answer"] # these get captured as student responses
......@@ -118,6 +118,9 @@ class LoncapaProblem(object):
# the dict has keys = xml subtree of Response, values = Response instance
self._preprocess_problem(self.tree)
if not self.student_answers: # True when student_answers is an empty dict
self.set_initial_display()
def do_reset(self):
'''
Reset internal state to unfinished, with no answers
......@@ -126,6 +129,14 @@ class LoncapaProblem(object):
self.correct_map = CorrectMap()
self.done = False
def set_initial_display(self):
initial_answers = dict()
for responder in self.responders.values():
if hasattr(responder,'get_initial_display'):
initial_answers.update(responder.get_initial_display())
self.student_answers = initial_answers
def __unicode__(self):
return u"LoncapaProblem ({0})".format(self.problem_id)
......@@ -180,14 +191,31 @@ class LoncapaProblem(object):
return {'score': correct,
'total': self.get_max_score()}
def update_score(self, score_msg):
newcmap = CorrectMap()
def update_score(self, score_msg, queuekey):
'''
Deliver grading response (e.g. from async code checking) to
the specific ResponseType that requested grading
Returns an updated CorrectMap
'''
cmap = CorrectMap()
cmap.update(self.correct_map)
for responder in self.responders.values():
if hasattr(responder,'update_score'): # Is this the best way to implement 'update_score' for CodeResponse?
results = responder.update_score(score_msg)
newcmap.update(results)
self.correct_map = newcmap
return newcmap
if hasattr(responder,'update_score'):
# Each LoncapaResponse will update the specific entries of 'cmap' that it's responsible for
cmap = responder.update_score(score_msg, cmap, queuekey)
self.correct_map.set_dict(cmap.get_dict())
return cmap
def is_queued(self):
'''
Returns True if any part of the problem has been submitted to an external queue
'''
queued = False
for answer_id in self.correct_map:
if self.correct_map.is_queued(answer_id):
queued = True
return queued
def grade_answers(self, answers):
'''
......@@ -457,7 +485,7 @@ class LoncapaProblem(object):
self.responder_answers = {}
for response in self.responders.keys():
try:
self.responder_answers[response] = responder.get_answers()
self.responder_answers[response] = self.responders[response].get_answers()
except:
log.debug('responder %s failed to properly return get_answers()' % self.responders[response]) # FIXME
raise
......
......@@ -14,6 +14,7 @@ class CorrectMap(object):
- msg : string (may have HTML) giving extra message response (displayed below textline or textbox)
- hint : string (may have HTML) giving optional hint (displayed below textline or textbox, above msg)
- hintmode : one of (None,'on_request','always') criteria for displaying hint
- queuekey : a random integer for xqueue_callback verification
Behaves as a dict.
'''
......@@ -29,13 +30,14 @@ class CorrectMap(object):
def __iter__(self):
return self.cmap.__iter__()
def set(self, answer_id=None, correctness=None, npoints=None, msg='', hint='', hintmode=None):
def set(self, answer_id=None, correctness=None, npoints=None, msg='', hint='', hintmode=None, queuekey=None):
if answer_id is not None:
self.cmap[answer_id] = {'correctness': correctness,
'npoints': npoints,
'msg': msg,
'hint' : hint,
'hintmode' : hintmode,
'queuekey' : queuekey,
}
def __repr__(self):
......@@ -63,6 +65,12 @@ class CorrectMap(object):
if answer_id in self.cmap: return self.cmap[answer_id]['correctness'] == 'correct'
return None
def is_queued(self,answer_id):
return answer_id in self.cmap and self.cmap[answer_id]['queuekey'] is not None
def is_right_queuekey(self, answer_id, test_key):
return answer_id in self.cmap and self.cmap[answer_id]['queuekey'] == test_key
def get_npoints(self,answer_id):
if self.is_correct(answer_id):
npoints = self.cmap[answer_id].get('npoints',1) # default to 1 point if correct
......
......@@ -8,7 +8,9 @@ Module containing the problem elements which render into input objects
- textline
- textbox (change this to textarea?)
- schemmatic
- choicegroup (for multiplechoice: checkbox, radio, or select option)
- choicegroup
- radiogroup
- checkboxgroup
- imageinput (for clickable image)
- optioninput (for option list)
......@@ -132,7 +134,8 @@ def optioninput(element, value, status, render_template, msg=''):
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
osetdict = [(oset[x],oset[x]) for x in range(len(oset)) ] # make ordered list with (key,value) same
# TODO: allow ordering to be randomized
context={'id':eid,
'value':value,
......@@ -145,6 +148,9 @@ def optioninput(element, value, status, render_template, msg=''):
return etree.XML(html)
#-----------------------------------------------------------------------------
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function
def choicegroup(element, value, status, render_template, msg=''):
'''
......@@ -160,7 +166,7 @@ def choicegroup(element, value, status, render_template, msg=''):
type="checkbox"
else:
type="radio"
choices={}
choices=[]
for choice in element:
if not choice.tag=='choice':
raise Exception("[courseware.capa.inputtypes.choicegroup] Error only <choice> tags should be immediate children of a <choicegroup>, found %s instead" % choice.tag)
......@@ -168,8 +174,66 @@ def choicegroup(element, value, status, render_template, msg=''):
ctext += ''.join([etree.tostring(x) for x in choice]) # TODO: what if choice[0] has math tags in it?
if choice.text is not None:
ctext += choice.text # TODO: fix order?
choices[choice.get("name")] = ctext
context={'id':eid, 'value':value, 'state':status, 'type':type, 'choices':choices}
choices.append((choice.get("name"),ctext))
context={'id':eid, 'value':value, 'state':status, 'input_type':type, 'choices':choices, 'inline':True, 'name_array_suffix':''}
html = render_template("choicegroup.html", context)
return etree.XML(html)
#-----------------------------------------------------------------------------
def extract_choices(element):
'''
Extracts choices for a few input types, such as radiogroup and
checkboxgroup.
TODO: allow order of choices to be randomized, following lon-capa spec. Use "location" attribute,
ie random, top, bottom.
'''
choices = []
for choice in element:
if not choice.tag=='choice':
raise Exception("[courseware.capa.inputtypes.extract_choices] \
Expected a <choice> tag; got %s instead"
% choice.tag)
choice_text = ''.join([etree.tostring(x) for x in choice])
choices.append((choice.get("name"), choice_text))
return choices
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function
def radiogroup(element, value, status, render_template, msg=''):
'''
Radio button inputs: (multiple choice)
'''
eid=element.get('id')
choices = extract_choices(element)
context = { 'id':eid, 'value':value, 'state':status, 'input_type': 'radio', 'choices':choices, 'inline': False, 'name_array_suffix': '[]' }
html = render_template("choicegroup.html", context)
return etree.XML(html)
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function
def checkboxgroup(element, value, status, render_template, msg=''):
'''
Checkbox inputs: (select one or more choices)
'''
eid=element.get('id')
choices = extract_choices(element)
context = { 'id':eid, 'value':value, 'state':status, 'input_type': 'checkbox', 'choices':choices, 'inline': False, 'name_array_suffix': '[]' }
html = render_template("choicegroup.html", context)
return etree.XML(html)
......
......@@ -8,6 +8,7 @@ Used by capa_problem.py
'''
# standard library imports
import hashlib
import inspect
import json
import logging
......@@ -16,9 +17,9 @@ import numpy
import random
import re
import requests
import time
import traceback
import abc
import time
# specific library imports
from calc import evaluator, UndefinedVariable
......@@ -266,6 +267,94 @@ class LoncapaResponse(object):
return u'LoncapaProblem Response %s' % self.xml.tag
#-----------------------------------------------------------------------------
class ChoiceResponse(LoncapaResponse):
'''
This Response type is used when the student chooses from a discrete set of
choices. Currently, to be marked correct, all "correct" choices must be
supplied by the student, and no extraneous choices may be included.
This response type allows for two inputtypes: radiogroups and checkbox
groups. radiogroups are used when the student should select a single answer,
and checkbox groups are used when the student may supply 0+ answers.
Note: it is suggested to include a "None of the above" choice when no
answer is correct for a checkboxgroup inputtype; this ensures that a student
must actively mark something to get credit.
If two choices are marked as correct with a radiogroup, the student will
have no way to get the answer right.
TODO: Allow for marking choices as 'optional' and 'required', which would
not penalize a student for including optional answers and would also allow
for questions in which the student can supply one out of a set of correct
answers.This would also allow for survey-style questions in which all
answers are correct.
Example:
<choiceresponse>
<radiogroup>
<choice correct="false">
<text>This is a wrong answer.</text>
</choice>
<choice correct="true">
<text>This is the right answer.</text>
</choice>
<choice correct="false">
<text>This is another wrong answer.</text>
</choice>
</radiogroup>
</choiceresponse>
In the above example, radiogroup can be replaced with checkboxgroup to allow
the student to select more than one choice.
'''
response_tag = 'choiceresponse'
max_inputfields = 1
allowed_inputfields = ['checkboxgroup', 'radiogroup']
def setup_response(self):
self.assign_choice_names()
correct_xml = self.xml.xpath('//*[@id=$id]//choice[@correct="true"]',
id=self.xml.get('id'))
self.correct_choices = set([choice.get('name') for choice in correct_xml])
def assign_choice_names(self):
'''
Initialize name attributes in <choice> tags for this response.
'''
for index, choice in enumerate(self.xml.xpath('//*[@id=$id]//choice',
id=self.xml.get('id'))):
choice.set("name", "choice_"+str(index))
def get_score(self, student_answers):
student_answer = student_answers.get(self.answer_id, [])
if not isinstance(student_answer, list):
student_answer = [student_answer]
student_answer = set(student_answer)
required_selected = len(self.correct_choices - student_answer) == 0
no_extra_selected = len(student_answer - self.correct_choices) == 0
correct = required_selected & no_extra_selected
if correct:
return CorrectMap(self.answer_id,'correct')
else:
return CorrectMap(self.answer_id,'incorrect')
def get_answers(self):
return { self.answer_id : self.correct_choices }
#-----------------------------------------------------------------------------
class MultipleChoiceResponse(LoncapaResponse):
# TODO: handle direction and randomize
......@@ -469,13 +558,13 @@ class CustomResponse(LoncapaResponse):
or in a <script>...</script>
'''
snippets = [{'snippet': '''<customresponse>
<startouttext/>
<text>
<br/>
Suppose that \(I(t)\) rises from \(0\) to \(I_S\) at a time \(t_0 \neq 0\)
In the space provided below write an algebraic expression for \(I(t)\).
<br/>
<textline size="5" correct_answer="IS*u(t-t0)" />
<endouttext/>
</text>
<answer type="loncapa/python">
correct=['correct']
try:
......@@ -696,15 +785,19 @@ class SymbolicResponse(CustomResponse):
class CodeResponse(LoncapaResponse):
'''
Grade student code using an external server
Grade student code using an external server, called 'xqueue'
In contrast to ExternalResponse, CodeResponse has following behavior:
1) Goes through a queueing system
2) Does not do external request for 'get_answers'
'''
response_tag = 'coderesponse'
allowed_inputfields = ['textline', 'textbox']
max_inputfields = 1
def setup_response(self):
xml = self.xml
self.url = xml.get('url') or "http://ec2-50-16-59-149.compute-1.amazonaws.com/xqueue/submit/" # FIXME -- hardcoded url
self.url = xml.get('url', "http://ec2-50-16-59-149.compute-1.amazonaws.com/xqueue/submit/") # FIXME -- hardcoded url
answer = xml.find('answer')
if answer is not None:
......@@ -713,33 +806,52 @@ class CodeResponse(LoncapaResponse):
self.code = self.system.filesystem.open('src/'+answer_src).read()
else:
self.code = answer.text
else: # no <answer> stanza; get code from <script>
else: # no <answer> stanza; get code from <script>
self.code = self.context['script_code']
if not self.code:
msg = '%s: Missing answer script code for externalresponse' % unicode(self)
msg = '%s: Missing answer script code for coderesponse' % unicode(self)
msg += "\nSee XML source line %s" % getattr(self.xml,'sourceline','<unavailable>')
raise LoncapaProblemError(msg)
self.tests = xml.get('tests')
# Extract 'answer' and 'initial_display' from XML. Note that the code to be exec'ed here is:
# (1) Internal edX code, i.e. NOT student submissions, and
# (2) The code should only define the strings 'initial_display', 'answer', 'preamble', 'test_program'
# following the 6.01 problem definition convention
penv = {}
penv['__builtins__'] = globals()['__builtins__']
try:
exec(self.code,penv,penv)
except Exception as err:
log.error('Error in CodeResponse %s: Error in problem reference code' % err)
raise Exception(err)
try:
self.answer = penv['answer']
self.initial_display = penv['initial_display']
except Exception as err:
log.error("Error in CodeResponse %s: Problem reference code does not define 'answer' and/or 'initial_display' in <answer>...</answer>" % err)
raise Exception(err)
def get_score(self, student_answers):
idset = sorted(self.answer_ids)
try:
submission = [student_answers[k] for k in idset]
submission = [student_answers[self.answer_id]]
except Exception as err:
log.error('Error in CodeResponse %s: cannot get student answer for %s; student_answers=%s' % (err, self.answer_ids, student_answers))
log.error('Error in CodeResponse %s: cannot get student answer for %s; student_answers=%s' % (err, self.answer_id, student_answers))
raise Exception(err)
self.context.update({'submission': submission})
extra_payload = {'edX_student_response': json.dumps(submission)}
# Should do something -- like update the problem state -- based on the queue response
r = self._send_to_queue(extra_payload)
return CorrectMap()
r, queuekey = self._send_to_queue(extra_payload) # TODO: Perform checks on the xqueue response
def update_score(self, score_msg):
# Non-null CorrectMap['queuekey'] indicates that the problem has been submitted
cmap = CorrectMap()
cmap.set(self.answer_id, queuekey=queuekey, msg='Submitted to queue')
return cmap
def update_score(self, score_msg, oldcmap, queuekey):
# Parse 'score_msg' as XML
try:
rxml = etree.fromstring(score_msg)
......@@ -748,51 +860,47 @@ class CodeResponse(LoncapaResponse):
raise Exception(err)
# The following process is lifted directly from ExternalResponse
idset = sorted(self.answer_ids)
cmap = CorrectMap()
ad = rxml.find('awarddetail').text
admap = {'EXACT_ANS':'correct', # TODO: handle other loncapa responses
admap = {'EXACT_ANS': 'correct', # TODO: handle other loncapa responses
'WRONG_FORMAT': 'incorrect',
}
self.context['correct'] = ['correct']
if ad in admap:
self.context['correct'][0] = admap[ad]
# create CorrectMap
for key in idset:
idx = idset.index(key)
msg = rxml.find('message').text.replace('&nbsp;','&#160;') if idx==0 else None
cmap.set(key, self.context['correct'][idx], msg=msg)
return cmap
# Replace 'oldcmap' with new grading results if queuekey matches.
# If queuekey does not match, we keep waiting for the score_msg that will match
if oldcmap.is_right_queuekey(self.answer_id, queuekey):
msg = rxml.find('message').text.replace('&nbsp;','&#160;')
oldcmap.set(self.answer_id, correctness=self.context['correct'][0], msg=msg, queuekey=None) # Queuekey is consumed
else:
log.debug('CodeResponse: queuekey %d does not match for answer_id=%s.' % (queuekey, self.answer_id))
return oldcmap
# CodeResponse differentiates from ExternalResponse in the behavior of 'get_answers'. CodeResponse.get_answers
# does NOT require a queue submission, and the answer is computed (extracted from problem XML) locally.
def get_answers(self):
# Extract the CodeResponse answer from XML
penv = {}
penv['__builtins__'] = globals()['__builtins__']
try:
exec(self.code,penv,penv)
except Exception as err:
log.error('Error in CodeResponse %s: Error in problem reference code' % err)
raise Exception(err)
try:
ans = penv['answer']
except Exception as err:
log.error('Error in CodeResponse %s: Problem reference code does not define answer in <answer>...</answer>' % err)
raise Exception(err)
anshtml = '<font color="blue"><span class="code-answer"><br/><pre>%s</pre><br/></span></font>' % ans
return dict(zip(self.answer_ids,[anshtml]))
anshtml = '<font color="blue"><span class="code-answer"><br/><pre>%s</pre><br/></span></font>' % self.answer
return { self.answer_id: anshtml }
def get_initial_display(self):
return { self.answer_id: self.initial_display }
# CodeResponse._send_to_queue implements the same interface as defined for ExternalResponse's 'get_score'
def _send_to_queue(self, extra_payload):
# Prepare payload
xmlstr = etree.tostring(self.xml, pretty_print=True)
header = { 'return_url': self.system.xqueue_callback_url }
header.update({'timestamp': time.time()})
payload = {'xqueue_header': json.dumps(header), # 'xqueue_header' should eventually be derived from xqueue.queue_common.HEADER_TAG or something similar
# Queuekey generation
h = hashlib.md5()
h.update(str(self.system.seed))
h.update(str(time.time()))
queuekey = int(h.hexdigest(),16)
header.update({'queuekey': queuekey})
payload = {'xqueue_header': json.dumps(header), # TODO: 'xqueue_header' should eventually be derived from a config file
'xml': xmlstr,
'edX_cmd': 'get_score',
'edX_tests': self.tests,
......@@ -808,7 +916,7 @@ class CodeResponse(LoncapaResponse):
log.error(msg)
raise Exception(msg)
return r
return r, queuekey
#-----------------------------------------------------------------------------
......@@ -1191,5 +1299,5 @@ class ImageResponse(LoncapaResponse):
# TEMPORARY: List of all response subclasses
# FIXME: To be replaced by auto-registration
__all__ = [ CodeResponse, NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse, StringResponse ]
__all__ = [ CodeResponse, NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse, StringResponse, ChoiceResponse, MultipleChoiceResponse, TrueFalseResponse ]
<form class="multiple-choice">
<form class="choicegroup">
% for choice_id, choice_description in choices.items():
<label for="input_${id}_${choice_id}"> <input type="${type}" name="input_${id}" id="input_${id}_${choice_id}" value="${choice_id}"
% for choice_id, choice_description in choices:
<label for="input_${id}_${choice_id}"> <input type="${input_type}" name="input_${id}${name_array_suffix}" id="input_${id}_${choice_id}" value="${choice_id}"
% if choice_id in value:
checked="true"
% endif
/> ${choice_description} </label>
% if not inline:
<br/>
% endif
% endfor
<span id="answer_${id}"></span>
......
......@@ -2,7 +2,7 @@
<select name="input_${id}" id="input_${id}" >
<option value="option_${id}_dummy_default"> </option>
% for option_id, option_description in options.items():
% for option_id, option_description in options:
<option value="${option_id}"
% if (option_id==value):
selected="true"
......
......@@ -13,6 +13,7 @@ import numpy
import xmodule
import capa.calc as calc
import capa.capa_problem as lcp
from capa.correctmap import CorrectMap
from xmodule import graders, x_module
from xmodule.graders import Score, aggregate_scores
from xmodule.progress import Progress
......@@ -271,6 +272,86 @@ class StringResponseWithHintTest(unittest.TestCase):
self.assertEquals(cmap.get_correctness('1_2_1'), 'incorrect')
self.assertTrue('St. Paul' in cmap.get_hint('1_2_1'))
class CodeResponseTest(unittest.TestCase):
'''
Test CodeResponse
'''
def test_update_score(self):
problem_file = os.path.dirname(__file__)+"/test_files/coderesponse.xml"
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
# CodeResponse requires internal CorrectMap state. Build it now in the 'queued' state
old_cmap = CorrectMap()
answer_ids = sorted(test_lcp.get_question_answers().keys())
numAnswers = len(answer_ids)
for i in range(numAnswers):
old_cmap.update(CorrectMap(answer_id=answer_ids[i], queuekey=1000+i))
# Message format inherited from ExternalResponse
correct_score_msg = "<edxgrade><awarddetail>EXACT_ANS</awarddetail><message>MESSAGE</message></edxgrade>"
incorrect_score_msg = "<edxgrade><awarddetail>WRONG_FORMAT</awarddetail><message>MESSAGE</message></edxgrade>"
xserver_msgs = {'correct': correct_score_msg,
'incorrect': incorrect_score_msg,
}
# Incorrect queuekey, state should not be updated
for correctness in ['correct', 'incorrect']:
test_lcp.correct_map = CorrectMap()
test_lcp.correct_map.update(old_cmap) # Deep copy
test_lcp.update_score(xserver_msgs[correctness], queuekey=0)
self.assertEquals(test_lcp.correct_map.get_dict(), old_cmap.get_dict()) # Deep comparison
for i in range(numAnswers):
self.assertTrue(test_lcp.correct_map.is_queued(answer_ids[i])) # Should be still queued, since message undelivered
# Correct queuekey, state should be updated
for correctness in ['correct', 'incorrect']:
for i in range(numAnswers): # Target specific answer_id's
test_lcp.correct_map = CorrectMap()
test_lcp.correct_map.update(old_cmap)
new_cmap = CorrectMap()
new_cmap.update(old_cmap)
new_cmap.set(answer_id=answer_ids[i], correctness=correctness, msg='MESSAGE', queuekey=None)
test_lcp.update_score(xserver_msgs[correctness], queuekey=1000+i)
self.assertEquals(test_lcp.correct_map.get_dict(), new_cmap.get_dict())
for j in range(numAnswers):
if j == i:
self.assertFalse(test_lcp.correct_map.is_queued(answer_ids[j])) # Should be dequeued, message delivered
else:
self.assertTrue(test_lcp.correct_map.is_queued(answer_ids[j])) # Should be queued, message undelivered
class ChoiceResponseTest(unittest.TestCase):
def test_cr_rb_grade(self):
problem_file = os.path.dirname(__file__)+"/test_files/choiceresponse_radio.xml"
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
correct_answers = {'1_2_1':'choice_2',
'1_3_1':['choice_2', 'choice_3']}
test_answers = {'1_2_1':'choice_2',
'1_3_1':'choice_2',
}
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct')
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_1'), 'incorrect')
def test_cr_cb_grade(self):
problem_file = os.path.dirname(__file__)+"/test_files/choiceresponse_checkbox.xml"
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
correct_answers = {'1_2_1':'choice_2',
'1_3_1':['choice_2', 'choice_3'],
'1_4_1':['choice_2', 'choice_3']}
test_answers = {'1_2_1':'choice_2',
'1_3_1':'choice_2',
'1_4_1':['choice_2', 'choice_3'],
}
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct')
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_1'), 'incorrect')
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_4_1'), 'correct')
#-----------------------------------------------------------------------------
# Grading tests
......
<problem>
<choiceresponse>
<checkboxgroup>
<choice correct="false">
<startouttext />This is foil One.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Two.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Three.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Four.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Five.<endouttext />
</choice>
</checkboxgroup>
</choiceresponse>
<choiceresponse>
<checkboxgroup>
<choice correct="false">
<startouttext />This is foil One.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Two.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Three.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Four.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Five.<endouttext />
</choice>
</checkboxgroup>
</choiceresponse>
<choiceresponse>
<checkboxgroup>
<choice correct="false">
<startouttext />This is foil One.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Two.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Three.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Four.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Five.<endouttext />
</choice>
</checkboxgroup>
</choiceresponse>
</problem>
<problem>
<choiceresponse>
<radiogroup>
<choice correct="false">
<startouttext />This is foil One.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Two.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Three.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Four.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Five.<endouttext />
</choice>
</radiogroup>
</choiceresponse>
<choiceresponse>
<radiogroup>
<choice correct="false">
<startouttext />This is foil One.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Two.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Three.<endouttext />
</choice>
<choice correct="true">
<startouttext />This is foil Four.<endouttext />
</choice>
<choice correct="false">
<startouttext />This is foil Five.<endouttext />
</choice>
</radiogroup>
</choiceresponse>
</problem>
<problem>
<text>
<h2>Code response</h2>
<p>
</p>
<text>
Write a program to compute the square of a number
<coderesponse tests="repeat:2,generate">
<textbox rows="10" cols="70" mode="python"/>
<answer><![CDATA[
initial_display = """
def square(n):
"""
answer = """
def square(n):
return n**2
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testSquare(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: square(%d)'%n
return str(square(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testSquare(0))
elif test == 2: f.write(testSquare(1))
else: f.write(testSquare())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
</coderesponse>
</text>
<text>
Write a program to compute the cube of a number
<coderesponse tests="repeat:2,generate">
<textbox rows="10" cols="70" mode="python"/>
<answer><![CDATA[
initial_display = """
def cube(n):
"""
answer = """
def cube(n):
return n**3
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testCube(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: cube(%d)'%n
return str(cube(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testCube(0))
elif test == 2: f.write(testCube(1))
else: f.write(testCube())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
</coderesponse>
</text>
</text>
</problem>
......@@ -323,8 +323,18 @@ class CapaModule(XModule):
raise self.system.exception404
def update_score(self, get):
"""
Delivers grading response (e.g. from asynchronous code checking) to
the capa problem, so its score can be updated
'get' must have a field 'response' which is a string that contains the
grader's response
No ajax return is needed. Return empty dict.
"""
queuekey = get['queuekey']
score_msg = get['response']
self.lcp.update_score(score_msg)
self.lcp.update_score(score_msg, queuekey)
return dict() # No AJAX return is needed
......@@ -361,7 +371,16 @@ class CapaModule(XModule):
for key in get:
# e.g. input_resistor_1 ==> resistor_1
_, _, name = key.partition('_')
answers[name] = get[key]
# This allows for answers which require more than one value for
# the same form input (e.g. checkbox inputs). The convention is that
# if the name ends with '[]' (which looks like an array), then the
# answer will be an array.
if not name.endswith('[]'):
answers[name] = get[key]
else:
name = name[:-2]
answers[name] = get.getlist(key)
return answers
......@@ -424,7 +443,8 @@ class CapaModule(XModule):
if not correct_map.is_correct(answer_id):
success = 'incorrect'
# log this in the track_function
# NOTE: We are logging both full grading and queued-grading submissions. In the latter,
# 'success' will always be incorrect
event_info['correct_map'] = correct_map.get_dict()
event_info['success'] = success
self.system.track_function('save_problem_check', event_info)
......
......@@ -81,7 +81,7 @@ class SequenceModule(XModule):
# of script, even if it occurs mid-string. Do this after json.dumps()ing
# so that we can be sure of the quotations being used
import re
params = {'items': re.sub(r'</(script)', r'\u003c/\1', json.dumps(contents), flags=re.IGNORECASE),
params = {'items': re.sub(r'(?i)</(script)', r'\u003c/\1', json.dumps(contents)), # ?i = re.IGNORECASE for py2.6 compatability
'element_id': self.location.html_id(),
'item_id': self.id,
'position': self.position,
......
......@@ -4,8 +4,10 @@ import logging
from django.conf import settings
from django.http import Http404
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from functools import wraps
from django.contrib.auth.models import User
from xmodule.modulestore.django import modulestore
from mitxmako.shortcuts import render_to_string
from models import StudentModule, StudentModuleCache
......@@ -33,6 +35,8 @@ class I4xSystem(object):
Create a closure around the system environment.
ajax_url - the url where ajax calls to the encapsulating module go.
xqueue_callback_url - the url where external queueing system (e.g. for grading)
returns its response
track_function - function of (event_type, event), intended for logging
or otherwise tracking the event.
TODO: Not used, and has inconsistent args in different
......@@ -208,7 +212,7 @@ def get_module(user, request, location, student_module_cache, position=None):
# Setup system context for module instance
ajax_url = settings.MITX_ROOT_URL + '/modx/' + descriptor.location.url() + '/'
xqueue_callback_url = settings.MITX_ROOT_URL + '/xqueue/' + user.username + '/' + descriptor.location.url() + '/'
xqueue_callback_url = settings.MITX_ROOT_URL + '/xqueue/' + str(user.id) + '/' + descriptor.location.url() + '/'
def _get_module(location):
(module, _, _, _) = get_module(user, request, location, student_module_cache, position)
......@@ -324,11 +328,9 @@ def add_histogram(module):
module.get_html = get_html
return module
# THK: TEMPORARY BYPASS OF AUTH!
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.models import User
# TODO: TEMPORARY BYPASS OF AUTH!
@csrf_exempt
def xqueue_callback(request, username, id, dispatch):
def xqueue_callback(request, userid, id, dispatch):
# Parse xqueue response
get = request.POST.copy()
try:
......@@ -336,12 +338,9 @@ def xqueue_callback(request, username, id, dispatch):
except Exception as err:
msg = "Error in xqueue_callback %s: Invalid return format" % err
raise Exception(msg)
# Should proceed only when the request timestamp is more recent than problem timestamp
timestamp = header['timestamp']
# Retrieve target StudentModule
user = User.objects.get(username=username)
user = User.objects.get(id=userid)
student_module_cache = StudentModuleCache(user, modulestore().get_item(id))
instance, instance_module, shared_module, module_type = get_module(request.user, request, id, student_module_cache)
......@@ -354,6 +353,10 @@ def xqueue_callback(request, username, id, dispatch):
oldgrade = instance_module.grade
old_instance_state = instance_module.state
# Transfer 'queuekey' from xqueue response header to 'get'. This is required to
# use the interface defined by 'handle_ajax'
get.update({'queuekey': header['queuekey']})
# We go through the "AJAX" path
# So far, the only dispatch from xqueue will be 'score_update'
try:
......
body {
margin: 0;
padding: 0; }
.wrapper, .subpage, section.copyright, section.tos, section.privacy-policy, section.honor-code, header.announcement div, section.index-content, footer {
margin: 0;
overflow: hidden; }
div#enroll form {
display: none; }
/*
html5doctor.com Reset Stylesheet
v1.6.1
Last Updated: 2010-09-17
Author: Richard Clark - http://richclarkdesign.com
Twitter: @rich_clark
*/
html, body, div, span, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
abbr, address, cite, code,
del, dfn, em, img, ins, kbd, q, samp,
small, strong, var,
b, i,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure,
footer, header, hgroup, menu, nav, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 100%;
vertical-align: baseline;
background: transparent; }
body {
line-height: 1; }
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block; }
nav ul {
list-style: none; }
blockquote, q {
quotes: none; }
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none; }
a {
margin: 0;
padding: 0;
font-size: 100%;
vertical-align: baseline;
background: transparent; }
/* change colours to suit your needs */
ins {
background-color: #ff9;
color: #000;
text-decoration: none; }
/* change colours to suit your needs */
mark {
background-color: #ff9;
color: #000;
font-style: italic;
font-weight: bold; }
del {
text-decoration: line-through; }
abbr[title], dfn[title] {
border-bottom: 1px dotted;
cursor: help; }
table {
border-collapse: collapse;
border-spacing: 0; }
/* change border colour to suit your needs */
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #cccccc;
margin: 1em 0;
padding: 0; }
input, select {
vertical-align: middle; }
/* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 25, 2012 05:06:34 PM America/New_York */
@font-face {
font-family: 'Open Sans';
src: url("../fonts/OpenSans-Regular-webfont.eot");
src: url("../fonts/OpenSans-Regular-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Regular-webfont.woff") format("woff"), url("../fonts/OpenSans-Regular-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular") format("svg");
font-weight: 600;
font-style: normal; }
@font-face {
font-family: 'Open Sans';
src: url("../fonts/OpenSans-Italic-webfont.eot");
src: url("../fonts/OpenSans-Italic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Italic-webfont.woff") format("woff"), url("../fonts/OpenSans-Italic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic") format("svg");
font-weight: 400;
font-style: italic; }
@font-face {
font-family: 'Open Sans';
src: url("../fonts/OpenSans-Bold-webfont.eot");
src: url("../fonts/OpenSans-Bold-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-Bold-webfont.woff") format("woff"), url("../fonts/OpenSans-Bold-webfont.ttf") format("truetype"), url("../fonts/OpenSans-Bold-webfont.svg#OpenSansBold") format("svg");
font-weight: 700;
font-style: normal; }
@font-face {
font-family: 'Open Sans';
src: url("../fonts/OpenSans-BoldItalic-webfont.eot");
src: url("../fonts/OpenSans-BoldItalic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-BoldItalic-webfont.woff") format("woff"), url("../fonts/OpenSans-BoldItalic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic") format("svg");
font-weight: 700;
font-style: italic; }
@font-face {
font-family: 'Open Sans';
src: url("../fonts/OpenSans-ExtraBold-webfont.eot");
src: url("../fonts/OpenSans-ExtraBold-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-ExtraBold-webfont.woff") format("woff"), url("../fonts/OpenSans-ExtraBold-webfont.ttf") format("truetype"), url("../fonts/OpenSans-ExtraBold-webfont.svg#OpenSansExtrabold") format("svg");
font-weight: 800;
font-style: normal; }
@font-face {
font-family: 'Open Sans';
src: url("../fonts/OpenSans-ExtraBoldItalic-webfont.eot");
src: url("../fonts/OpenSans-ExtraBoldItalic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/OpenSans-ExtraBoldItalic-webfont.woff") format("woff"), url("../fonts/OpenSans-ExtraBoldItalic-webfont.ttf") format("truetype"), url("../fonts/OpenSans-ExtraBoldItalic-webfont.svg#OpenSansExtraboldItalic") format("svg");
font-weight: 800;
font-style: italic; }
.wrapper, .subpage, section.copyright, section.tos, section.privacy-policy, section.honor-code, header.announcement div, footer, section.index-content {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin: 0 auto;
max-width: 1400px;
padding: 25.888px;
width: 100%; }
.subpage > div, section.copyright > div, section.tos > div, section.privacy-policy > div, section.honor-code > div {
padding-left: 34.171%; }
@media screen and (max-width: 940px) {
.subpage > div, section.copyright > div, section.tos > div, section.privacy-policy > div, section.honor-code > div {
padding-left: 0; } }
.subpage > div p, section.copyright > div p, section.tos > div p, section.privacy-policy > div p, section.honor-code > div p {
margin-bottom: 25.888px;
line-height: 25.888px; }
.subpage > div h1, section.copyright > div h1, section.tos > div h1, section.privacy-policy > div h1, section.honor-code > div h1 {
margin-bottom: 12.944px; }
.subpage > div h2, section.copyright > div h2, section.tos > div h2, section.privacy-policy > div h2, section.honor-code > div h2 {
font: 18px "Open Sans", Helvetica, Arial, sans-serif;
color: #000;
margin-bottom: 12.944px; }
.subpage > div ul, section.copyright > div ul, section.tos > div ul, section.privacy-policy > div ul, section.honor-code > div ul {
list-style: disc outside none; }
.subpage > div ul li, section.copyright > div ul li, section.tos > div ul li, section.privacy-policy > div ul li, section.honor-code > div ul li {
list-style: disc outside none;
line-height: 25.888px; }
.subpage > div dl, section.copyright > div dl, section.tos > div dl, section.privacy-policy > div dl, section.honor-code > div dl {
margin-bottom: 25.888px; }
.subpage > div dl dd, section.copyright > div dl dd, section.tos > div dl dd, section.privacy-policy > div dl dd, section.honor-code > div dl dd {
margin-bottom: 12.944px; }
.clearfix:after, .subpage:after, section.copyright:after, section.tos:after, section.privacy-policy:after, section.honor-code:after, header.announcement div section:after, footer:after, section.index-content:after, section.index-content section:after, section.index-content section.about section:after, div.leanModal_box#enroll ol:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden; }
.button, header.announcement div section.course section a, section.index-content section.course a, section.index-content section.staff a, section.index-content section.about-course section.cta a.enroll {
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
border-radius: 3px;
display: -moz-inline-box;
-moz-box-orient: vertical;
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto;
-webkit-transition-property: all;
-moz-transition-property: all;
-ms-transition-property: all;
-o-transition-property: all;
transition-property: all;
-webkit-transition-duration: 0.15s;
-moz-transition-duration: 0.15s;
-ms-transition-duration: 0.15s;
-o-transition-duration: 0.15s;
transition-duration: 0.15s;
-webkit-transition-timing-function: ease-out;
-moz-transition-timing-function: ease-out;
-ms-transition-timing-function: ease-out;
-o-transition-timing-function: ease-out;
transition-timing-function: ease-out;
-webkit-transition-delay: 0;
-moz-transition-delay: 0;
-ms-transition-delay: 0;
-o-transition-delay: 0;
transition-delay: 0;
background-color: #993333;
border: 1px solid #732626;
color: #fff;
margin: 25.888px 0 12.944px;
padding: 6.472px 12.944px;
text-decoration: none;
font-style: normal;
-webkit-box-shadow: inset 0 1px 0 #b83d3d;
-moz-box-shadow: inset 0 1px 0 #b83d3d;
box-shadow: inset 0 1px 0 #b83d3d;
-webkit-font-smoothing: antialiased; }
.button:hover, header.announcement div section.course section a:hover, section.index-content section.course a:hover, section.index-content section.staff a:hover, section.index-content section.about-course section.cta a.enroll:hover {
background-color: #732626;
border-color: #4d1919; }
.button span, header.announcement div section.course section a span, section.index-content section.course a span, section.index-content section.staff a span, section.index-content section.about-course section.cta a.enroll span {
font-family: Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", serif;
font-style: italic; }
p.ie-warning {
display: block !important;
line-height: 1.3em;
background: yellow;
margin-bottom: 25.888px;
padding: 25.888px; }
body {
background-color: #fff;
color: #444;
font: 16px Georgia, serif; }
body :focus {
outline-color: #ccc; }
body h1 {
font: 800 24px "Open Sans", Helvetica, Arial, sans-serif; }
body li {
margin-bottom: 25.888px; }
body em {
font-style: italic; }
body a {
color: #993333;
font-style: italic;
text-decoration: none; }
body a:hover, body a:focus {
color: #732626; }
body input[type="email"], body input[type="number"], body input[type="password"], body input[type="search"], body input[type="tel"], body input[type="text"], body input[type="url"], body input[type="color"], body input[type="date"], body input[type="datetime"], body input[type="datetime-local"], body input[type="month"], body input[type="time"], body input[type="week"], body textarea {
-webkit-box-shadow: 0 -1px 0 white;
-moz-box-shadow: 0 -1px 0 white;
box-shadow: 0 -1px 0 white;
background-color: #eeeeee;
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, white));
background-image: -webkit-linear-gradient(top, #eeeeee, white);
background-image: -moz-linear-gradient(top, #eeeeee, white);
background-image: -ms-linear-gradient(top, #eeeeee, white);
background-image: -o-linear-gradient(top, #eeeeee, white);
background-image: linear-gradient(top, #eeeeee, white);
border: 1px solid #999;
font: 16px Georgia, serif;
padding: 4px;
width: 100%; }
body input[type="email"]:focus, body input[type="number"]:focus, body input[type="password"]:focus, body input[type="search"]:focus, body input[type="tel"]:focus, body input[type="text"]:focus, body input[type="url"]:focus, body input[type="color"]:focus, body input[type="date"]:focus, body input[type="datetime"]:focus, body input[type="datetime-local"]:focus, body input[type="month"]:focus, body input[type="time"]:focus, body input[type="week"]:focus, body textarea:focus {
border-color: #993333; }
header.announcement {
-webkit-background-size: cover;
-moz-background-size: cover;
-ms-background-size: cover;
-o-background-size: cover;
background-size: cover;
background: #333;
border-bottom: 1px solid #000;
color: #fff;
-webkit-font-smoothing: antialiased; }
header.announcement.home {
background: #e3e3e3 url("../images/marketing/shot-5-medium.jpg"); }
@media screen and (min-width: 1200px) {
header.announcement.home {
background: #e3e3e3 url("../images/marketing/shot-5-large.jpg"); } }
header.announcement.home div {
padding: 258.88px 25.888px 77.664px; }
@media screen and (max-width:780px) {
header.announcement.home div {
padding: 64.72px 25.888px 51.776px; } }
header.announcement.home div nav h1 {
margin-right: 0; }
header.announcement.home div nav a.login {
display: none; }
header.announcement.course {
background: #e3e3e3 url("../images/marketing/course-bg-small.jpg"); }
@media screen and (min-width: 1200px) {
header.announcement.course {
background: #e3e3e3 url("../images/marketing/course-bg-large.jpg"); } }
@media screen and (max-width: 1199px) and (min-width: 700px) {
header.announcement.course {
background: #e3e3e3 url("../images/marketing/course-bg-medium.jpg"); } }
header.announcement.course div {
padding: 103.552px 25.888px 51.776px; }
@media screen and (max-width:780px) {
header.announcement.course div {
padding: 64.72px 25.888px 51.776px; } }
header.announcement div {
position: relative; }
header.announcement div nav {
position: absolute;
top: 0;
right: 25.888px;
-webkit-border-radius: 0 0 3px 3px;
-moz-border-radius: 0 0 3px 3px;
-ms-border-radius: 0 0 3px 3px;
-o-border-radius: 0 0 3px 3px;
border-radius: 0 0 3px 3px;
background: #333;
background: rgba(0, 0, 0, 0.7);
padding: 12.944px 25.888px; }
header.announcement div nav h1 {
display: -moz-inline-box;
-moz-box-orient: vertical;
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto;
margin-right: 12.944px; }
header.announcement div nav h1 a {
font: italic 800 18px "Open Sans", Helvetica, Arial, sans-serif;
color: #fff;
text-decoration: none; }
header.announcement div nav h1 a:hover, header.announcement div nav h1 a:focus {
color: #999; }
header.announcement div nav a.login {
text-decoration: none;
color: #fff;
font-size: 12px;
font-style: normal;
font-family: "Open Sans", Helvetica, Arial, sans-serif; }
header.announcement div nav a.login:hover, header.announcement div nav a.login:focus {
color: #999; }
header.announcement div section {
background: #993333;
display: -moz-inline-box;
-moz-box-orient: vertical;
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto;
margin-left: 34.171%;
padding: 25.888px 38.832px; }
@media screen and (max-width: 780px) {
header.announcement div section {
margin-left: 0; } }
header.announcement div section h1 {
font-family: "Open Sans";
font-size: 30px;
font-weight: 800;
display: -moz-inline-box;
-moz-box-orient: vertical;
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto;
line-height: 1.2em;
margin: 0 25.888px 0 0; }
header.announcement div section h2 {
font-family: "Open Sans";
font-size: 24px;
font-weight: 400;
display: -moz-inline-box;
-moz-box-orient: vertical;
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto;
line-height: 1.2em; }
header.announcement div section.course section {
float: left;
margin-left: 0;
margin-right: 3.817%;
padding: 0;
width: 48.092%; }
@media screen and (max-width: 780px) {
header.announcement div section.course section {
float: none;
width: 100%;
margin-right: 0; } }
header.announcement div section.course section a {
background-color: #4d1919;
border-color: #260d0d;
-webkit-box-shadow: inset 0 1px 0 #732626, 0 1px 0 #ac3939;
-moz-box-shadow: inset 0 1px 0 #732626, 0 1px 0 #ac3939;
box-shadow: inset 0 1px 0 #732626, 0 1px 0 #ac3939;
display: block;
padding: 12.944px 25.888px;
text-align: center; }
header.announcement div section.course section a:hover {
background-color: #732626;
border-color: #4d1919; }
header.announcement div section.course p {
width: 48.092%;
line-height: 25.888px;
float: left; }
@media screen and (max-width: 780px) {
header.announcement div section.course p {
float: none;
width: 100%; } }
footer {
padding-top: 0; }
footer div.footer-wrapper {
border-top: 1px solid #e5e5e5;
padding: 25.888px 0;
background: url("../images/marketing/mit-logo.png") right center no-repeat; }
@media screen and (max-width: 780px) {
footer div.footer-wrapper {
background-position: left bottom;
padding-bottom: 77.664px; } }
footer div.footer-wrapper a {
color: #888;
text-decoration: none;
-webkit-transition-property: all;
-moz-transition-property: all;
-ms-transition-property: all;
-o-transition-property: all;
transition-property: all;
-webkit-transition-duration: 0.15s;
-moz-transition-duration: 0.15s;
-ms-transition-duration: 0.15s;
-o-transition-duration: 0.15s;
transition-duration: 0.15s;
-webkit-transition-timing-function: ease-out;
-moz-transition-timing-function: ease-out;
-ms-transition-timing-function: ease-out;
-o-transition-timing-function: ease-out;
transition-timing-function: ease-out;
-webkit-transition-delay: 0;
-moz-transition-delay: 0;
-ms-transition-delay: 0;
-o-transition-delay: 0;
transition-delay: 0; }
footer div.footer-wrapper a:hover, footer div.footer-wrapper a:focus {
color: #666; }
footer div.footer-wrapper p {
display: -moz-inline-box;
-moz-box-orient: vertical;
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto;
margin-right: 25.888px; }
footer div.footer-wrapper ul {
display: -moz-inline-box;
-moz-box-orient: vertical;
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto; }
@media screen and (max-width: 780px) {
footer div.footer-wrapper ul {
margin-top: 25.888px; } }
footer div.footer-wrapper ul li {
display: -moz-inline-box;
-moz-box-orient: vertical;
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto;
margin-bottom: 0; }
footer div.footer-wrapper ul li:after {
content: ' |';
display: inline;
color: #ccc; }
footer div.footer-wrapper ul li:last-child:after {
content: none; }
footer div.footer-wrapper ul.social {
float: right;
margin-right: 60px;
position: relative;
top: -5px; }
@media screen and (max-width: 780px) {
footer div.footer-wrapper ul.social {
float: none; } }
footer div.footer-wrapper ul.social li {
float: left;
margin-right: 12.944px; }
footer div.footer-wrapper ul.social li:after {
content: none;
display: none; }
footer div.footer-wrapper ul.social li a {
display: block;
height: 29px;
width: 28px;
text-indent: -9999px; }
footer div.footer-wrapper ul.social li a:hover {
opacity: .8; }
footer div.footer-wrapper ul.social li.twitter a {
background: url("../images/marketing/twitter.png") 0 0 no-repeat; }
footer div.footer-wrapper ul.social li.facebook a {
background: url("../images/marketing/facebook.png") 0 0 no-repeat; }
footer div.footer-wrapper ul.social li.linkedin a {
background: url("../images/marketing/linkedin.png") 0 0 no-repeat; }
section.index-content section {
float: left; }
@media screen and (max-width: 780px) {
section.index-content section {
float: none;
width: auto;
margin-right: 0; } }
section.index-content section h1 {
font-size: 800 24px "Open Sans";
margin-bottom: 25.888px; }
section.index-content section p {
line-height: 25.888px;
margin-bottom: 25.888px; }
section.index-content section ul {
margin: 0; }
section.index-content section.about {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
border-right: 1px solid #e5e5e5;
margin-right: 2.513%;
padding-right: 1.256%;
width: 65.829%; }
@media screen and (max-width: 780px) {
section.index-content section.about {
width: 100%;
border-right: 0;
margin-right: 0;
padding-right: 0; } }
section.index-content section.about section {
margin-bottom: 25.888px; }
section.index-content section.about section p {
width: 48.092%;
float: left; }
@media screen and (max-width: 780px) {
section.index-content section.about section p {
float: none;
width: auto; } }
section.index-content section.about section p:nth-child(odd) {
margin-right: 3.817%; }
@media screen and (max-width: 780px) {
section.index-content section.about section p:nth-child(odd) {
margin-right: 0; } }
section.index-content section.about section.intro section {
margin-bottom: 0; }
section.index-content section.about section.intro section.intro-text {
margin-right: 3.817%;
width: 48.092%; }
@media screen and (max-width: 780px) {
section.index-content section.about section.intro section.intro-text {
margin-right: 0;
width: auto; } }
section.index-content section.about section.intro section.intro-text p {
margin-right: 0;
width: auto;
float: none; }
section.index-content section.about section.intro section.intro-video {
width: 48.092%; }
@media screen and (max-width: 780px) {
section.index-content section.about section.intro section.intro-video {
width: auto; } }
section.index-content section.about section.intro section.intro-video a {
display: block;
width: 100%; }
section.index-content section.about section.intro section.intro-video a img {
width: 100%; }
section.index-content section.about section.intro section.intro-video a span {
display: none; }
section.index-content section.about section.features {
border-top: 1px solid #E5E5E5;
padding-top: 25.888px;
margin-bottom: 0; }
section.index-content section.about section.features h2 {
text-transform: uppercase;
letter-spacing: 1px;
color: #888;
margin-bottom: 25.888px;
font-weight: normal;
font-size: 14px; }
section.index-content section.about section.features h2 span {
text-transform: none; }
section.index-content section.about section.features p {
width: auto;
clear: both; }
section.index-content section.about section.features p strong {
font-family: "Open sans";
font-weight: 800; }
section.index-content section.about section.features p a {
color: #993333;
text-decoration: none;
-webkit-transition-property: all;
-moz-transition-property: all;
-ms-transition-property: all;
-o-transition-property: all;
transition-property: all;
-webkit-transition-duration: 0.15s;
-moz-transition-duration: 0.15s;
-ms-transition-duration: 0.15s;
-o-transition-duration: 0.15s;
transition-duration: 0.15s;
-webkit-transition-timing-function: ease-out;
-moz-transition-timing-function: ease-out;
-ms-transition-timing-function: ease-out;
-o-transition-timing-function: ease-out;
transition-timing-function: ease-out;
-webkit-transition-delay: 0;
-moz-transition-delay: 0;
-ms-transition-delay: 0;
-o-transition-delay: 0;
transition-delay: 0; }
section.index-content section.about section.features p a:hover, section.index-content section.about section.features p a:focus {
color: #602020; }
section.index-content section.about section.features ul {
margin-bottom: 0; }
section.index-content section.about section.features ul li {
line-height: 25.888px;
width: 48.092%;
float: left;
margin-bottom: 12.944px; }
@media screen and (max-width: 780px) {
section.index-content section.about section.features ul li {
width: auto;
float: none; } }
section.index-content section.about section.features ul li:nth-child(odd) {
margin-right: 3.817%; }
@media screen and (max-width: 780px) {
section.index-content section.about section.features ul li:nth-child(odd) {
margin-right: 0; } }
section.index-content section.course, section.index-content section.staff {
width: 31.658%; }
@media screen and (max-width: 780px) {
section.index-content section.course, section.index-content section.staff {
width: auto; } }
section.index-content section.course h1, section.index-content section.staff h1 {
color: #888;
font: normal 16px Georgia, serif;
font-size: 14px;
letter-spacing: 1px;
margin-bottom: 25.888px;
text-transform: uppercase; }
section.index-content section.course h2, section.index-content section.staff h2 {
font: 800 24px "Open Sans", Helvetica, Arial, sans-serif; }
section.index-content section.course h3, section.index-content section.staff h3 {
font: 400 18px "Open Sans", Helvetica, Arial, sans-serif; }
section.index-content section.course a span.arrow, section.index-content section.staff a span.arrow {
color: rgba(255, 255, 255, 0.6);
font-style: normal;
display: -moz-inline-box;
-moz-box-orient: vertical;
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto;
padding-left: 10px; }
section.index-content section.course ul, section.index-content section.staff ul {
list-style: none; }
section.index-content section.course ul li img, section.index-content section.staff ul li img {
float: left;
margin-right: 12.944px; }
section.index-content section.course h2 {
padding-top: 129.44px;
background: url("../images/marketing/circuits-bg.jpg") 0 0 no-repeat;
-webkit-background-size: contain;
-moz-background-size: contain;
-ms-background-size: contain;
-o-background-size: contain;
background-size: contain; }
@media screen and (max-width: 998px) and (min-width: 781px) {
section.index-content section.course h2 {
background: url("../images/marketing/circuits-medium-bg.jpg") 0 0 no-repeat; } }
@media screen and (max-width: 780px) {
section.index-content section.course h2 {
padding-top: 129.44px;
background: url("../images/marketing/circuits-bg.jpg") 0 0 no-repeat; } }
@media screen and (min-width: 500px) and (max-width: 781px) {
section.index-content section.course h2 {
padding-top: 207.104px; } }
section.index-content section.course div.announcement p.announcement-button a {
margin-top: 0; }
section.index-content section.course div.announcement img {
max-width: 100%;
margin-bottom: 25.888px; }
section.index-content section.about-course {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
border-right: 1px solid #e5e5e5;
margin-right: 2.513%;
padding-right: 1.256%;
width: 65.829%; }
@media screen and (max-width: 780px) {
section.index-content section.about-course {
width: auto;
border-right: 0;
margin-right: 0;
padding-right: 0; } }
section.index-content section.about-course section {
width: 48.092%; }
@media screen and (max-width: 780px) {
section.index-content section.about-course section {
width: auto; } }
section.index-content section.about-course section.about-info {
margin-right: 3.817%; }
@media screen and (max-width: 780px) {
section.index-content section.about-course section.about-info {
margin-right: 0; } }
section.index-content section.about-course section.requirements {
clear: both;
width: 100%;
border-top: 1px solid #E5E5E5;
padding-top: 25.888px;
margin-bottom: 0; }
section.index-content section.about-course section.requirements p {
float: left;
width: 48.092%;
margin-right: 3.817%; }
@media screen and (max-width: 780px) {
section.index-content section.about-course section.requirements p {
margin-right: 0;
float: none;
width: auto; } }
section.index-content section.about-course section.requirements p:nth-child(odd) {
margin-right: 0; }
section.index-content section.about-course section.cta {
width: 100%;
text-align: center; }
section.index-content section.about-course section.cta a.enroll {
padding: 12.944px 51.776px;
display: -moz-inline-box;
-moz-box-orient: vertical;
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto;
text-align: center;
font: 800 18px "Open Sans", Helvetica, Arial, sans-serif; }
section.index-content section.staff h1 {
margin-top: 25.888px; }
#lean_overlay {
background: #000;
display: none;
height: 100%;
left: 0px;
position: fixed;
top: 0px;
width: 100%;
z-index: 100; }
div.leanModal_box {
background: #fff;
border: none;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: 0 0 6px black;
-moz-box-shadow: 0 0 6px black;
box-shadow: 0 0 6px black;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
display: none;
padding: 51.776px;
text-align: left; }
div.leanModal_box a.modal_close {
color: #aaa;
display: block;
font-style: normal;
height: 14px;
position: absolute;
right: 12px;
top: 12px;
width: 14px;
z-index: 2; }
div.leanModal_box a.modal_close:hover {
color: #993333;
text-decoration: none; }
div.leanModal_box h1 {
border-bottom: 1px solid #eee;
font-size: 24px;
margin-bottom: 25.888px;
margin-top: 0;
padding-bottom: 25.888px;
text-align: left; }
div.leanModal_box#enroll {
max-width: 600px; }
div.leanModal_box#enroll ol {
padding-top: 25.888px; }
div.leanModal_box#enroll ol li.terms, div.leanModal_box#enroll ol li.honor-code {
float: none;
width: auto; }
div.leanModal_box#enroll ol li div.tip {
display: none; }
div.leanModal_box#enroll ol li:hover div.tip {
background: #333;
color: #fff;
display: block;
font-size: 16px;
line-height: 25.888px;
margin: 0 0 0 -10px;
padding: 10px;
position: absolute;
-webkit-font-smoothing: antialiased;
width: 500px; }
div.leanModal_box form {
text-align: left; }
div.leanModal_box form div#enroll_error, div.leanModal_box form div#login_error, div.leanModal_box form div#pwd_error {
background-color: #333333;
border: black;
color: #fff;
font-family: "Open sans";
font-weight: bold;
letter-spacing: 1px;
margin: -25.888px -25.888px 25.888px;
padding: 12.944px;
text-shadow: 0 1px 0 #1a1a1a;
-webkit-font-smoothing: antialiased; }
div.leanModal_box form div#enroll_error:empty, div.leanModal_box form div#login_error:empty, div.leanModal_box form div#pwd_error:empty {
padding: 0; }
div.leanModal_box form ol {
list-style: none;
margin-bottom: 25.888px; }
div.leanModal_box form ol li {
margin-bottom: 12.944px; }
div.leanModal_box form ol li.terms, div.leanModal_box form ol li.remember {
border-top: 1px solid #eee;
clear: both;
float: none;
padding-top: 25.888px;
width: auto; }
div.leanModal_box form ol li.honor-code {
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 {
-webkit-box-sizing: border-box;
-moz-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 {
list-style: disc outside none;
margin: 12.944px 0 25.888px 25.888px; }
div.leanModal_box form ol li ul li {
color: #666;
float: none;
font-size: 14px;
list-style: disc outside none;
margin-bottom: 12.944px; }
div.leanModal_box form input[type="button"], div.leanModal_box form input[type="submit"] {
border: 1px solid #691b1b;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: inset 0 1px 0 0 #bc5c5c;
-moz-box-shadow: inset 0 1px 0 0 #bc5c5c;
box-shadow: inset 0 1px 0 0 #bc5c5c;
color: white;
display: inline;
font-size: 11px;
font-weight: bold;
background-color: #993333;
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #993333), color-stop(100%, #761e1e));
background-image: -webkit-linear-gradient(top, #993333, #761e1e);
background-image: -moz-linear-gradient(top, #993333, #761e1e);
background-image: -ms-linear-gradient(top, #993333, #761e1e);
background-image: -o-linear-gradient(top, #993333, #761e1e);
background-image: linear-gradient(top, #993333, #761e1e);
padding: 6px 18px 7px;
text-shadow: 0 1px 0 #5d1414;
-webkit-background-clip: padding-box;
font-size: 18px;
padding: 12.944px; }
div.leanModal_box form input[type="button"]:hover, div.leanModal_box form input[type="submit"]:hover {
-webkit-box-shadow: inset 0 1px 0 0 #a44141;
-moz-box-shadow: inset 0 1px 0 0 #a44141;
box-shadow: inset 0 1px 0 0 #a44141;
cursor: pointer;
background-color: #823030;
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #823030), color-stop(100%, #691c1c));
background-image: -webkit-linear-gradient(top, #823030, #691c1c);
background-image: -moz-linear-gradient(top, #823030, #691c1c);
background-image: -ms-linear-gradient(top, #823030, #691c1c);
background-image: -o-linear-gradient(top, #823030, #691c1c);
background-image: linear-gradient(top, #823030, #691c1c); }
div.leanModal_box form input[type="button"]:active, div.leanModal_box form input[type="submit"]:active {
border: 1px solid #691b1b;
-webkit-box-shadow: inset 0 0 8px 4px #5c1919, inset 0 0 8px 4px #5c1919, 0 1px 1px 0 #eeeeee;
-moz-box-shadow: inset 0 0 8px 4px #5c1919, inset 0 0 8px 4px #5c1919, 0 1px 1px 0 #eeeeee;
box-shadow: inset 0 0 8px 4px #5c1919, inset 0 0 8px 4px #5c1919, 0 1px 1px 0 #eeeeee; }
div#login {
min-width: 400px; }
div#login header {
border-bottom: 1px solid #ddd;
margin-bottom: 25.888px;
padding-bottom: 25.888px; }
div#login header h1 {
border-bottom: 0;
padding-bottom: 0;
margin-bottom: 6.472px; }
div#login ol li {
float: none;
width: auto; }
div.lost-password {
margin-top: 25.888px;
text-align: left; }
div.lost-password a {
color: #999; }
div.lost-password a:hover {
color: #444; }
div#pwd_reset p {
margin-bottom: 25.888px; }
div#pwd_reset input[type="email"] {
margin-bottom: 25.888px; }
div#apply_name_change,
div#change_email,
div#unenroll,
div#deactivate-account {
max-width: 700px; }
div#apply_name_change ul,
div#change_email ul,
div#unenroll ul,
div#deactivate-account ul {
list-style: none; }
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"] {
-webkit-box-sizing: border-box;
-moz-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"] {
white-space: normal; }
div#feedback_div form ol li {
float: none;
width: 100%; }
div#feedback_div form ol li textarea#feedback_message {
height: 100px; }
......@@ -95,7 +95,7 @@ if settings.COURSEWARE_ENABLED:
url(r'^masquerade/', include('masquerade.urls')),
url(r'^jumpto/(?P<probname>[^/]+)/$', 'courseware.views.jump_to'),
url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
url(r'^xqueue/(?P<username>[^/]*)/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.xqueue_callback'),
url(r'^xqueue/(?P<userid>[^/]*)/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.xqueue_callback'),
url(r'^change_setting$', 'student.views.change_setting'),
url(r'^s/(?P<template>[^/]*)$', 'static_template_view.views.auth_index'),
# url(r'^course_info/$', 'student.views.courseinfo'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment