Merge remote-tracking branch 'origin/master' into dormsbee/multicourse

Conflicts:
	common/lib/xmodule/xmodule/capa_module.py
	common/lib/xmodule/xmodule/modulestore/xml.py
	lms/djangoapps/courseware/views.py
	lms/templates/index.html
	lms/templates/info.html
	lms/templates/main.html
	lms/templates/navigation.html
	requirements.txt
This commit is contained in:
Calen Pennington
2012-07-10 13:39:19 -04:00
164 changed files with 3612 additions and 713 deletions

View File

@@ -251,7 +251,7 @@ class LoncapaProblem(object):
if file is not None:
try:
ifp = self.system.filestore.open(file) # open using I4xSystem OSFS filestore
except Exception,err:
except Exception as err:
log.error('Error %s in problem xml include: %s' % (err,etree.tostring(inc,pretty_print=True)))
log.error('Cannot find file %s in %s' % (file,self.system.filestore))
if not self.system.get('DEBUG'): # if debugging, don't fail - just log error
@@ -259,7 +259,7 @@ class LoncapaProblem(object):
else: continue
try:
incxml = etree.XML(ifp.read()) # read in and convert to XML
except Exception,err:
except Exception as err:
log.error('Error %s in problem xml include: %s' % (err,etree.tostring(inc,pretty_print=True)))
log.error('Cannot parse XML in %s' % (file))
if not self.system.get('DEBUG'): # if debugging, don't fail - just log error
@@ -283,6 +283,7 @@ class LoncapaProblem(object):
context.update(global_context) # initialize context to have stuff in global_context
context['__builtins__'] = globals()['__builtins__'] # put globals there also
context['the_lcp'] = self # pass instance of LoncapaProblem in
context['script_code'] = ''
for script in tree.findall('.//script'):
stype = script.get('type')
@@ -295,10 +296,12 @@ class LoncapaProblem(object):
code = script.text
XMLESC = {"'": "'", """: '"'}
code = unescape(code, XMLESC)
context['script_code'] += code # store code source in context
try:
exec code in context, context # use "context" for global context; thus defs in code are global within code
exec code in context, context # use "context" for global context; thus defs in code are global within code
except Exception:
log.exception("Error while execing code: " + code)
log.exception("Error while execing script code: " + code)
raise responsetypes.LoncapaProblemError("Error while executing script code")
return context
def _extract_html(self, problemtree): # private
@@ -311,6 +314,10 @@ class LoncapaProblem(object):
Used by get_html.
'''
if problemtree.tag=='script' and problemtree.get('type') and 'javascript' in problemtree.get('type'):
# leave javascript intact.
return problemtree
if problemtree.tag in html_problem_semantics:
return

View File

@@ -22,10 +22,14 @@ Each input type takes the xml tree as 'element', the previous answer as 'value',
# status is currently the answer for the problem ID for the input element,
# but it will turn into a dict containing both the answer and any associated message for the problem ID for the input element.
import logging
import re
import shlex # for splitting quoted strings
from lxml import etree
import xml.sax.saxutils as saxutils
log = logging.getLogger('mitx.' + __name__)
def get_input_xml_tags():
''' Eventually, this will be for all registered input types '''
@@ -121,7 +125,7 @@ def optioninput(element, value, status, render_template, msg=''):
eid=element.get('id')
options = element.get('options')
if not options:
raise Exception,"[courseware.capa.inputtypes.optioninput] Missing options specification in " + etree.tostring(element)
raise Exception("[courseware.capa.inputtypes.optioninput] Missing options specification in " + etree.tostring(element))
oset = shlex.shlex(options[1:-1])
oset.quotes = "'"
oset.whitespace = ","
@@ -159,10 +163,11 @@ def choicegroup(element, value, status, render_template, msg=''):
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
raise Exception("[courseware.capa.inputtypes.choicegroup] Error only <choice> tags should be immediate children of a <choicegroup>, found %s instead" % choice.tag)
ctext = ""
ctext += ''.join([etree.tostring(x) for x in choice]) # TODO: what if choice[0] has math tags in it?
ctext += choice.text # TODO: fix order?
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}
html = render_template("choicegroup.html", context)
@@ -182,9 +187,18 @@ def textline(element, value, status, render_template, msg=""):
raise Exception(msg)
count = int(eid.split('_')[-2])-1 # HACK
size = element.get('size')
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg': msg}
hidden = element.get('hidden','') # if specified, then textline is hidden and id is stored in div of name given by hidden
escapedict = {'"': '&quot;'}
value = saxutils.escape(value,escapedict) # otherwise, answers with quotes in them crashes the system!
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg': msg, 'hidden': hidden}
html = render_template("textinput.html", context)
return etree.XML(html)
try:
xhtml = etree.XML(html)
except Exception as err:
if True: # TODO needs to be self.system.DEBUG - but can't access system
log.debug('[inputtypes.textline] failed to parse XML for:\n%s' % html)
raise
return xhtml
#-----------------------------------------------------------------------------
@@ -195,7 +209,6 @@ def textline_dynamath(element, value, status, render_template, msg=''):
'''
# TODO: Make a wrapper for <formulainput>
# TODO: Make an AJAX loop to confirm equation is okay in real-time as user types
## TODO: Code should follow PEP8 (4 spaces per indentation level)
'''
textline is used for simple one-line inputs, like formularesponse and symbolicresponse.
uses a <span id=display_eid>`{::}`</span>
@@ -204,8 +217,9 @@ def textline_dynamath(element, value, status, render_template, msg=''):
eid=element.get('id')
count = int(eid.split('_')[-2])-1 # HACK
size = element.get('size')
hidden = element.get('hidden','') # if specified, then textline is hidden and id is stored in div of name given by hidden
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size,
'msg':msg,
'msg':msg, 'hidden' : hidden,
}
html = render_template("textinput_dynamath.html", context)
return etree.XML(html)
@@ -225,14 +239,24 @@ def textbox(element, value, status, render_template, msg=''):
rows = element.get('rows') or '30'
cols = element.get('cols') or '80'
mode = element.get('mode') or 'python' # mode for CodeMirror, eg "python" or "xml"
hidden = element.get('hidden','') # if specified, then textline is hidden and id is stored in div of name given by hidden
linenumbers = element.get('linenumbers') # for CodeMirror
if not value: value = element.text # if no student input yet, then use the default input given by the problem
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg':msg,
'mode':mode, 'linenumbers':linenumbers,
'rows':rows, 'cols':cols,
'hidden' : hidden,
}
html = render_template("textbox.html", context)
return etree.XML(html)
try:
xhtml = etree.XML(html)
except Exception as err:
newmsg = 'error %s in rendering message' % (str(err).replace('<','&lt;'))
newmsg += '<br/>Original message: %s' % msg.replace('<','&lt;')
context['msg'] = newmsg
html = render_template("textbox.html", context)
xhtml = etree.XML(html)
return xhtml
#-----------------------------------------------------------------------------
@register_render_function
@@ -288,8 +312,18 @@ def math(element, value, status, render_template, msg=''):
# isinline = True
# html = render_template("mathstring.html",{'mathstr':mathstr,'isinline':isinline,'tail':element.tail})
html = '<html><html>%s</html><html>%s</html></html>' % (mathstr,element.tail)
xhtml = etree.XML(html)
html = '<html><html>%s</html><html>%s</html></html>' % (mathstr,saxutils.escape(element.tail))
try:
xhtml = etree.XML(html)
except Exception as err:
if False: # TODO needs to be self.system.DEBUG - but can't access system
msg = "<html><font color='red'><p>Error %s</p>" % str(err).replace('<','&lt;')
msg += '<p>Failed to construct math expression from <pre>%s</pre></p>' % html.replace('<','&lt;')
msg += "</font></html>"
log.error(msg)
return etree.XML(msg)
else:
raise
# xhtml.tail = element.tail # don't forget to include the tail!
return xhtml
@@ -338,14 +372,14 @@ def imageinput(element, value, status, render_template, msg=''):
(gx,gy) = (0,0)
context = {
'id':eid,
'value':value,
'id': eid,
'value': value,
'height': height,
'width' : width,
'src':src,
'gx':gx,
'gy':gy,
'state' : status, # to change
'width': width,
'src': src,
'gx': gx,
'gy': gy,
'state': status, # to change
'msg': msg, # to change
}
html = render_template("imageinput.html", context)

View File

@@ -193,7 +193,7 @@ class LoncapaResponse(object):
try:
self.context[hintfn](self.answer_ids, student_answers, new_cmap, old_cmap)
except Exception, err:
except Exception as err:
msg = 'Error %s in evaluating hint function %s' % (err,hintfn)
msg += "\nSee XML source line %s" % getattr(self.xml,'sourceline','<unavailable>')
raise ResponseError(msg)
@@ -556,7 +556,7 @@ def sympy_check2():
idset = sorted(self.answer_ids) # ordered list of answer id's
try:
submission = [student_answers[k] for k in idset] # ordered list of answers
except Exception, err:
except Exception as err:
msg = '[courseware.capa.responsetypes.customresponse] error getting student answer from %s' % student_answers
msg += '\n idset = %s, error = %s' % (idset,err)
log.error(msg)
@@ -567,7 +567,7 @@ def sympy_check2():
# if there is only one box, and it's empty, then don't evaluate
if len(idset)==1 and not submission[0]:
return {idset[0]:'no_answer_entered'}
return CorrectMap(idset[0],'incorrect',msg='<font color="red">No answer entered!</font>')
correct = ['unknown'] * len(idset)
messages = [''] * len(idset)
@@ -594,7 +594,7 @@ def sympy_check2():
if type(self.code)==str:
try:
exec self.code in self.context['global_context'], self.context
except Exception,err:
except Exception as err:
print "oops in customresponse (code) error %s" % err
print "context = ",self.context
print traceback.format_exc()
@@ -619,7 +619,7 @@ def sympy_check2():
log.debug('nargs=%d, args=%s, kwargs=%s' % (nargs,args,kwargs))
ret = fn(*args[:nargs],**kwargs)
except Exception,err:
except Exception as err:
log.error("oops in customresponse (cfn) error %s" % err)
# print "context = ",self.context
log.error(traceback.format_exc())
@@ -746,12 +746,20 @@ main()
xml = self.xml
self.url = xml.get('url') or "http://eecs1.mit.edu:8889/pyloncapa" # FIXME - hardcoded URL
answer = xml.xpath('//*[@id=$id]//answer',id=xml.get('id'))[0] # FIXME - catch errors
answer_src = answer.get('src')
if answer_src is not None:
self.code = self.system.filesystem.open('src/'+answer_src).read()
else:
self.code = answer.text
# answer = xml.xpath('//*[@id=$id]//answer',id=xml.get('id'))[0] # FIXME - catch errors
answer = xml.find('answer')
if answer is not None:
answer_src = answer.get('src')
if answer_src is not None:
self.code = self.system.filesystem.open('src/'+answer_src).read()
else:
self.code = answer.text
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 += "\nSee XML source line %s" % getattr(self.xml,'sourceline','<unavailable>')
raise LoncapaProblemError(msg)
self.tests = xml.get('tests')
@@ -774,7 +782,7 @@ main()
try:
r = requests.post(self.url,data=payload) # call external server
except Exception,err:
except Exception as err:
msg = 'Error %s - cannot connect to external server url=%s' % (err,self.url)
log.error(msg)
raise Exception(msg)
@@ -786,7 +794,7 @@ main()
try:
rxml = etree.fromstring(r.text) # response is XML; prase it
except Exception,err:
except Exception as err:
msg = 'Error %s - cannot parse response from external server r.text=%s' % (err,r.text)
log.error(msg)
raise Exception(msg)
@@ -798,7 +806,7 @@ main()
cmap = CorrectMap()
try:
submission = [student_answers[k] for k in idset]
except Exception,err:
except Exception as err:
log.error('Error %s: cannot get student answer for %s; student_answers=%s' % (err,self.answer_ids,student_answers))
raise Exception(err)
@@ -808,7 +816,7 @@ main()
try:
rxml = self.do_external_request('get_score',extra_payload)
except Exception, err:
except Exception as err:
log.error('Error %s' % err)
if self.system.DEBUG:
cmap.set_dict(dict(zip(sorted(self.answer_ids), ['incorrect'] * len(idset) )))
@@ -838,7 +846,7 @@ main()
try:
rxml = self.do_external_request('get_answers',{})
exans = json.loads(rxml.find('expected').text)
except Exception,err:
except Exception as err:
log.error('Error %s' % err)
if self.system.DEBUG:
msg = '<font color=red size=+2>%s</font>' % str(err).replace('<','&lt;')
@@ -935,7 +943,7 @@ class FormulaResponse(LoncapaResponse):
except UndefinedVariable as uv:
log.debug('formularesponse: undefined variable in given=%s' % given)
raise StudentInputError(uv.message+" not permitted in answer")
except Exception, err:
except Exception as err:
#traceback.print_exc()
log.debug('formularesponse: error %s in formula' % err)
raise StudentInputError("Error in formula")

View File

@@ -1,5 +1,9 @@
<section id="textbox_${id}" class="textbox">
<textarea rows="${rows}" cols="${cols}" name="input_${id}" id="input_${id}">${value|h}</textarea>
<textarea rows="${rows}" cols="${cols}" name="input_${id}" id="input_${id}"
% if hidden:
style="display:none;"
% endif
>${value|h}</textarea>
<span id="answer_${id}"></span>
@@ -12,6 +16,9 @@
% elif state == 'incomplete':
<span class="incorrect" id="status_${id}"></span>
% endif
% if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif
<br/>
<span class="debug">(${state})</span>
<br/>

View File

@@ -8,11 +8,17 @@
% elif state == 'incomplete':
<div class="incorrect" id="status_${id}">
% endif
% if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif
<input type="text" name="input_${id}" id="input_${id}" value="${value}"
% if size:
size="${size}"
% endif
% if hidden:
style="display:none;"
% endif
/>
<p class="status">

View File

@@ -10,9 +10,16 @@
<div class="incorrect" id="status_${id}">
% elif state == 'incomplete':
<div class="incorrect" id="status_${id}">
% endif
% if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif
<input type="text" name="input_${id}" id="input_${id}" value="${value}" class="math" size="${size if size else ''}" />
<input type="text" name="input_${id}" id="input_${id}" value="${value}" class="math" size="${size if size else ''}"
% if hidden:
style="display:none;"
% endif
/>
<p class="status">
% if state == 'unsubmitted':

8
common/lib/capa/setup.py Normal file
View File

@@ -0,0 +1,8 @@
from setuptools import setup, find_packages
setup(
name="capa",
version="0.1",
packages=find_packages(exclude=["tests"]),
install_requires=['distribute'],
)