From e17668540f78b289f2bf18316e0b5fb4461eb3fd Mon Sep 17 00:00:00 2001 From: ichuang Date: Fri, 1 Jun 2012 22:39:50 -0400 Subject: [PATCH] dynamic math - changes to javascript, responsetypes, and inputtypes main.html now includes mathjax_include for all mathjax stuff problem.js refreshes mathjax formulas on change all previous *_fromjs renamed to *_dynamath, eg textline_dynamath --- djangoapps/courseware/capa/inputtypes.py | 17 +++++--- djangoapps/courseware/capa/responsetypes.py | 47 +++++++++++++++------ lib/dogfood/check.py | 6 +++ lib/sympy_check/__init__.py | 2 + lib/sympy_check/formula.py | 5 ++- lib/sympy_check/sympy_check2.py | 12 +++++- templates/main.html | 10 +---- templates/mathjax_include.html | 9 ++-- templates/problem.js | 17 ++++++++ templates/quickedit.html | 13 ++---- 10 files changed, 92 insertions(+), 46 deletions(-) diff --git a/djangoapps/courseware/capa/inputtypes.py b/djangoapps/courseware/capa/inputtypes.py index ac8f2ac724..710ff04493 100644 --- a/djangoapps/courseware/capa/inputtypes.py +++ b/djangoapps/courseware/capa/inputtypes.py @@ -209,6 +209,11 @@ def choicegroup(element, value, status, msg=''): @register_render_function def textline(element, value, state, msg=""): + ''' + Simple text line input, with optional size specification. + ''' + if element.get('math') or element.get('dojs'): # 'dojs' flag is temporary, for backwards compatibility with 8.02x + return SimpleInput.xml_tags['textline_dynamath'](element,value,state,msg) eid=element.get('id') count = int(eid.split('_')[-2])-1 # HACK size = element.get('size') @@ -219,27 +224,25 @@ def textline(element, value, state, msg=""): #----------------------------------------------------------------------------- @register_render_function -def js_textline(element, value, status, msg=''): +def textline_dynamath(element, value, status, msg=''): ''' - Plan: We will inspect element to figure out type + Text line input with dynamic math display (equation rendered on client in real time during input). ''' # TODO: Make a wrapper for # 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 `{::}` + and a hidden textarea with id=input_eid_fromjs for the mathjax rendering and return. ''' eid=element.get('id') count = int(eid.split('_')[-2])-1 # HACK size = element.get('size') - dojs = element.get('dojs') # dojs is used for client-side javascript display & return - # when dojs=='math', a `{::}` - # and a hidden textarea with id=input_eid_fromjs will be output context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, - 'dojs':dojs, 'msg':msg, } - html=render_to_string("jstext.html", context) + html=render_to_string("textinput_dynamath.html", context) return etree.XML(html) #----------------------------------------------------------------------------- diff --git a/djangoapps/courseware/capa/responsetypes.py b/djangoapps/courseware/capa/responsetypes.py index 114b347a5a..fc8bc35120 100644 --- a/djangoapps/courseware/capa/responsetypes.py +++ b/djangoapps/courseware/capa/responsetypes.py @@ -8,7 +8,9 @@ Used by capa_problem.py ''' # standard library imports +import inspect import json +import logging import math import numbers import numpy @@ -34,6 +36,8 @@ import eia from util import contextualize_text +log = logging.getLogger("mitx.courseware") + def compare_with_tolerance(v1, v2, tol): ''' Compare v1 to v2 with maximum tolerance tol tol is relative if it ends in %; otherwise, it is absolute @@ -271,6 +275,9 @@ def sympy_check2(): self.expect = xml.get('expect') self.myid = xml.get('id') + if settings.DEBUG: + log.info('answer_ids=%s' % self.answer_ids) + # the ... stanza should be local to the current . So try looking there first. self.code = None answer = None @@ -283,7 +290,7 @@ def sympy_check2(): # ie the comparison function is defined in the stanza instead cfn = xml.get('cfn') if cfn: - if settings.DEBUG: print "[courseware.capa.responsetypes] cfn = ",cfn + if settings.DEBUG: log.info("[courseware.capa.responsetypes] cfn = %s" % cfn) if cfn in context: self.code = context[cfn] else: @@ -321,7 +328,8 @@ def sympy_check2(): msg += '\n idset = %s, error = %s' % (idset,err) raise Exception,msg - fromjs = [ getkey2(student_answers,k+'_fromjs',None) for k in idset ] # ordered list of fromjs_XXX responses (if exists) + # global variable in context which holds the Presentation MathML from dynamic math input + dynamath = [ student_answers.get(k+'_dynamath',None) for k in idset ] # ordered list of dynamath responses # if there is only one box, and it's empty, then don't evaluate if len(idset)==1 and not submission[0]: @@ -339,7 +347,7 @@ def sympy_check2(): 'expect': self.expect, # expected answer (if given as attribute) 'submission':submission, # ordered list of student answers from entry boxes in our subtree 'idset':idset, # ordered list of ID's of all entry boxes in our subtree - 'fromjs':fromjs, # ordered list of all javascript inputs in our subtree + 'dynamath':dynamath, # ordered list of all javascript inputs in our subtree 'answers':student_answers, # dict of student's responses, with keys being entry box IDs 'correct':correct, # the list to be filled in by the check function 'messages':messages, # the list of messages to be filled in by the check function @@ -359,30 +367,41 @@ def sympy_check2(): # this is an interface to the Tutor2 check functions fn = self.code ret = None + if settings.DEBUG: log.info(" submission = %s" % submission) try: answer_given = submission[0] if (len(idset)==1) else submission - if fn.func_code.co_argcount>=4: # does it want four arguments (the answers dict, myname)? - ret = fn(self.expect,answer_given,student_answers,self.answer_ids[0]) - elif fn.func_code.co_argcount>=3: # does it want a third argument (the answers dict)? - ret = fn(self.expect,answer_given,student_answers) - else: - ret = fn(self.expect,answer_given) + # handle variable number of arguments in check function, for backwards compatibility + # with various Tutor2 check functions + args = [self.expect,answer_given,student_answers,self.answer_ids[0]] + argspec = inspect.getargspec(fn) + nargs = len(argspec.args)-len(argspec.defaults or []) + kwargs = {} + for argname in argspec.args[nargs:]: + kwargs[argname] = self.context[argname] if argname in self.context else None + if settings.DEBUG: - print '[courseware.capa.responsetypes.customresponse] answer_given=%s' % answer_given + log.debug('[courseware.capa.responsetypes.customresponse] answer_given=%s' % answer_given) + # log.info('nargs=%d, args=%s' % (nargs,args)) + + ret = fn(*args[:nargs],**kwargs) except Exception,err: - print "oops in customresponse (cfn) error %s" % err + log.error("oops in customresponse (cfn) error %s" % err) # print "context = ",self.context - print traceback.format_exc() + log.error(traceback.format_exc()) raise Exception,"oops in customresponse (cfn) error %s" % err - if settings.DEBUG: print "[courseware.capa.responsetypes.customresponse.get_score] ret = ",ret + if settings.DEBUG: log.info("[courseware.capa.responsetypes.customresponse.get_score] ret = %s" % ret) if type(ret)==dict: correct = ['correct']*len(idset) if ret['ok'] else ['incorrect']*len(idset) msg = ret['msg'] if 1: # try to clean up message html + log.info('unicode2html(msg) = %s' % msg) msg = ''+msg+'' - msg = etree.tostring(fromstring_bs(msg),pretty_print=True) + msg = msg.replace('<','<') + #msg = msg.replace('<','<') + msg = etree.tostring(fromstring_bs(msg,convertEntities=None),pretty_print=True) + #msg = etree.tostring(fromstring_bs(msg),pretty_print=True) msg = msg.replace(' ','') #msg = re.sub('(.*)','\\1',msg,flags=re.M|re.DOTALL) # python 2.7 msg = re.sub('(?ms)(.*)','\\1',msg) diff --git a/lib/dogfood/check.py b/lib/dogfood/check.py index 4e67006430..1dae5b1e08 100644 --- a/lib/dogfood/check.py +++ b/lib/dogfood/check.py @@ -28,6 +28,12 @@ def check_problem_code(ans,the_lcp,correct_answers,false_answers): msg += '
' is_ok = True + if (not correct_answers) or (not false_answers): + ret = {'ok':is_ok, + 'msg': msg, + } + return ret + try: # check correctness fp = the_lcp.system.filestore.open('problems/%s.xml' % pfn) diff --git a/lib/sympy_check/__init__.py b/lib/sympy_check/__init__.py index e69de29bb2..0a990657ca 100644 --- a/lib/sympy_check/__init__.py +++ b/lib/sympy_check/__init__.py @@ -0,0 +1,2 @@ +from formula import * +from sympy_check2 import * diff --git a/lib/sympy_check/formula.py b/lib/sympy_check/formula.py index 44bd020c4e..81acef614d 100644 --- a/lib/sympy_check/formula.py +++ b/lib/sympy_check/formula.py @@ -234,7 +234,10 @@ class formula(object): if self.the_cmathml: return self.the_cmathml # pre-process the presentation mathml before sending it to snuggletex to convert to content mathml - xml = self.preprocess_pmathml(self.expr) + try: + xml = self.preprocess_pmathml(self.expr) + except Exception,err: + return "Error! Cannot process pmathml" pmathml = etree.tostring(xml,pretty_print=True) self.the_pmathml = pmathml diff --git a/lib/sympy_check/sympy_check2.py b/lib/sympy_check/sympy_check2.py index abb70ed165..320f70abe8 100644 --- a/lib/sympy_check/sympy_check2.py +++ b/lib/sympy_check/sympy_check2.py @@ -16,7 +16,11 @@ from formula import * #----------------------------------------------------------------------------- # check function interface -def sympy_check(expect,ans,adict={},symtab=None,extra_options=None): +def sympy_check_simple(expect,ans,adict={},symtab=None,extra_options=None): + ''' + Check a symbolic mathematical expression using sympy. + The input is an ascii string (not MathML) + ''' options = {'__MATRIX__':False,'__ABC__':False,'__LOWER__':False} if extra_options: options.update(extra_options) @@ -130,7 +134,11 @@ def check(expect,given,numerical=False,matrix=False,normphase=False,abcsym=False #----------------------------------------------------------------------------- # Check function interface, which takes pmathml input -def sympy_check2(expect,ans,adict={},abname=''): +def sympy_check(expect,ans,adict={},abname=''): + ''' + Check a symbolic mathematical expression using sympy. + The input may be presentation MathML + ''' msg = '' # msg += '

abname=%s' % abname diff --git a/templates/main.html b/templates/main.html index f39d6e862a..6e336ab0ad 100644 --- a/templates/main.html +++ b/templates/main.html @@ -36,18 +36,12 @@ - - <%block name="headextra"/> + <%block name="headextra"/> - + <%include file="mathjax_include.html" /> diff --git a/templates/mathjax_include.html b/templates/mathjax_include.html index a22f52fdc7..7298c681ef 100644 --- a/templates/mathjax_include.html +++ b/templates/mathjax_include.html @@ -28,8 +28,8 @@ // function to queue in MathJax to get put the MathML expression in in the right document element function UpdateMathML(jax,id) { toMathML(jax,function (mml) { - // document.getElementById(id+'_fromjs').value=math.originalText+ "\n\n=>\n\n"+ mml; - delem = document.getElementById("input_" + id + "_fromjs"); + // document.getElementById(id+'_dynamath').value=math.originalText+ "\n\n=>\n\n"+ mml; + delem = document.getElementById("input_" + id + "_dynamath"); if (delem) { delem.value=mml; }; mmlset[id] = mml; }) @@ -83,7 +83,8 @@ function DoUpdateMath(inputId) { - + diff --git a/templates/problem.js b/templates/problem.js index f0797692de..a8f2f999a6 100644 --- a/templates/problem.js +++ b/templates/problem.js @@ -2,6 +2,23 @@ function ${ id }_content_updated() { MathJax.Hub.Queue(["Typeset",MathJax.Hub]); update_schematics(); + // dynamic math display: add to jaxset for automatic rendering + $.each($("[id^=input_${ id }_]"), function(index,value){ + theid = value.id.replace("input_",""); // ID of the response + if (document.getElementById("display_" + theid)){ + MathJax.Hub.queue.Push(function () { + math = MathJax.Hub.getAllJax("display_" + theid)[0]; + if (math){ + jaxset[theid] = math; + math.Text(document.getElementById(value.id).defaultValue); + x = document.getElementById("input_" + theid + "_dynamath"); + UpdateMathML(math,theid); + } + }); + }; + }); + + $('#check_${ id }').unbind('click').click(function() { $("input.schematic").each(function(index,element){ element.schematic.update_value(); }); $(".CodeMirror").each(function(index,element){ if (element.CodeMirror.save) element.CodeMirror.save(); }); diff --git a/templates/quickedit.html b/templates/quickedit.html index f748911af6..08c9277bdd 100644 --- a/templates/quickedit.html +++ b/templates/quickedit.html @@ -24,20 +24,13 @@ -## <%include file="mathjax_include.html" /> - - - <%block name="headextra"/> +<%block name="headextra"/> - + <%include file="mathjax_include.html" /> +##