Merge branch 'master' of github.com:MITx/mitx
This commit is contained in:
@@ -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
|
||||
@@ -265,6 +266,94 @@ class LoncapaResponse(object):
|
||||
def __unicode__(self):
|
||||
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):
|
||||
@@ -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,63 +806,19 @@ 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')
|
||||
|
||||
def get_score(self, student_answers):
|
||||
idset = sorted(self.answer_ids)
|
||||
|
||||
try:
|
||||
submission = [student_answers[k] for k in idset]
|
||||
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))
|
||||
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()
|
||||
|
||||
def update_score(self, score_msg):
|
||||
# Parse 'score_msg' as XML
|
||||
try:
|
||||
rxml = etree.fromstring(score_msg)
|
||||
except Exception as err:
|
||||
msg = 'Error in CodeResponse %s: cannot parse response from xworker r.text=%s' % (err, score_msg)
|
||||
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
|
||||
'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(' ',' ') if idx==0 else None
|
||||
cmap.set(key, self.context['correct'][idx], msg=msg)
|
||||
|
||||
return cmap
|
||||
|
||||
# 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
|
||||
# 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:
|
||||
@@ -778,21 +827,80 @@ class CodeResponse(LoncapaResponse):
|
||||
log.error('Error in CodeResponse %s: Error in problem reference code' % err)
|
||||
raise Exception(err)
|
||||
try:
|
||||
ans = penv['answer']
|
||||
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 in <answer>...</answer>' % 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)
|
||||
|
||||
anshtml = '<font color="blue"><span class="code-answer"><br/><pre>%s</pre><br/></span></font>' % ans
|
||||
return dict(zip(self.answer_ids,[anshtml]))
|
||||
def get_score(self, student_answers):
|
||||
try:
|
||||
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_id, student_answers))
|
||||
raise Exception(err)
|
||||
|
||||
self.context.update({'submission': submission})
|
||||
extra_payload = {'edX_student_response': json.dumps(submission)}
|
||||
|
||||
r, queuekey = self._send_to_queue(extra_payload) # TODO: Perform checks on the xqueue response
|
||||
|
||||
# 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)
|
||||
except Exception as err:
|
||||
msg = 'Error in CodeResponse %s: cannot parse response from xworker r.text=%s' % (err, score_msg)
|
||||
raise Exception(err)
|
||||
|
||||
# The following process is lifted directly from ExternalResponse
|
||||
ad = rxml.find('awarddetail').text
|
||||
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]
|
||||
|
||||
# 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(' ',' ')
|
||||
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):
|
||||
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 ]
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<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
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<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>
|
||||
40
common/lib/xmodule/tests/test_files/choiceresponse_radio.xml
Normal file
40
common/lib/xmodule/tests/test_files/choiceresponse_radio.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<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>
|
||||
101
common/lib/xmodule/tests/test_files/coderesponse.xml
Normal file
101
common/lib/xmodule/tests/test_files/coderesponse.xml
Normal file
@@ -0,0 +1,101 @@
|
||||
<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:
|
||||
|
||||
10
lms/static/sass/marketing-ie.css
Executable file
10
lms/static/sass/marketing-ie.css
Executable file
@@ -0,0 +1,10 @@
|
||||
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; }
|
||||
1017
lms/static/sass/marketing.css
Executable file
1017
lms/static/sass/marketing.css
Executable file
@@ -0,0 +1,1017 @@
|
||||
/*
|
||||
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'),
|
||||
|
||||
Reference in New Issue
Block a user