Commit f78be581 by Calen Pennington

Cleaning up pep8 issues, including extraneous imports

parent f2309b31
...@@ -12,7 +12,6 @@ import logging ...@@ -12,7 +12,6 @@ import logging
import math import math
import numpy import numpy
import os import os
import os.path
import random import random
import re import re
import scipy import scipy
...@@ -20,58 +19,58 @@ import struct ...@@ -20,58 +19,58 @@ import struct
from lxml import etree from lxml import etree
from lxml.etree import Element from lxml.etree import Element
from xml.sax.saxutils import escape, unescape from xml.sax.saxutils import unescape
from util import contextualize_text from util import contextualize_text
import inputtypes import inputtypes
from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, StudentInputError, TrueFalseResponse, ExternalResponse,ImageResponse,OptionResponse, SymbolicResponse from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse
import calc import calc
import eia import eia
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
response_types = {'numericalresponse':NumericalResponse, response_types = {'numericalresponse': NumericalResponse,
'formularesponse':FormulaResponse, 'formularesponse': FormulaResponse,
'customresponse':CustomResponse, 'customresponse': CustomResponse,
'schematicresponse':SchematicResponse, 'schematicresponse': SchematicResponse,
'externalresponse':ExternalResponse, 'externalresponse': ExternalResponse,
'multiplechoiceresponse':MultipleChoiceResponse, 'multiplechoiceresponse': MultipleChoiceResponse,
'truefalseresponse':TrueFalseResponse, 'truefalseresponse': TrueFalseResponse,
'imageresponse':ImageResponse, 'imageresponse': ImageResponse,
'optionresponse':OptionResponse, 'optionresponse': OptionResponse,
'symbolicresponse':SymbolicResponse, 'symbolicresponse': SymbolicResponse,
} }
entry_types = ['textline', 'schematic', 'choicegroup','textbox','imageinput','optioninput'] entry_types = ['textline', 'schematic', 'choicegroup', 'textbox', 'imageinput', 'optioninput']
solution_types = ['solution'] # extra things displayed after "show answers" is pressed solution_types = ['solution'] # extra things displayed after "show answers" is pressed
response_properties = ["responseparam", "answer"] # these get captured as student responses response_properties = ["responseparam", "answer"] # these get captured as student responses
# How to convert from original XML to HTML # How to convert from original XML to HTML
# We should do this with xlst later # We should do this with xlst later
html_transforms = {'problem': {'tag':'div'}, html_transforms = {'problem': {'tag': 'div'},
"numericalresponse": {'tag':'span'}, "numericalresponse": {'tag': 'span'},
"customresponse": {'tag':'span'}, "customresponse": {'tag': 'span'},
"externalresponse": {'tag':'span'}, "externalresponse": {'tag': 'span'},
"schematicresponse": {'tag':'span'}, "schematicresponse": {'tag': 'span'},
"formularesponse": {'tag':'span'}, "formularesponse": {'tag': 'span'},
"symbolicresponse": {'tag':'span'}, "symbolicresponse": {'tag': 'span'},
"multiplechoiceresponse": {'tag':'span'}, "multiplechoiceresponse": {'tag': 'span'},
"text": {'tag':'span'}, "text": {'tag': 'span'},
"math": {'tag':'span'}, "math": {'tag': 'span'},
} }
global_context={'random':random, global_context = {'random': random,
'numpy':numpy, 'numpy': numpy,
'math':math, 'math': math,
'scipy':scipy, 'scipy': scipy,
'calc':calc, 'calc': calc,
'eia':eia} 'eia': eia}
# These should be removed from HTML output, including all subelements # These should be removed from HTML output, including all subelements
html_problem_semantics = ["responseparam", "answer", "script"] html_problem_semantics = ["responseparam", "answer", "script"]
# These should be removed from HTML output, but keeping subelements # These should be removed from HTML output, but keeping subelements
html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text","externalresponse",'symbolicresponse'] html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text", "externalresponse", 'symbolicresponse']
# removed in MC # removed in MC
## These should be transformed ## These should be transformed
...@@ -82,6 +81,7 @@ html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formul ...@@ -82,6 +81,7 @@ html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formul
# "solution":inputtypes.solution.render, # "solution":inputtypes.solution.render,
# } # }
class LoncapaProblem(object): class LoncapaProblem(object):
def __init__(self, fileobject, id, state=None, seed=None, system=None): def __init__(self, fileobject, id, state=None, seed=None, system=None):
## Initialize class variables from state ## Initialize class variables from state
...@@ -107,22 +107,22 @@ class LoncapaProblem(object): ...@@ -107,22 +107,22 @@ class LoncapaProblem(object):
# TODO: Does this deplete the Linux entropy pool? Is this fast enough? # TODO: Does this deplete the Linux entropy pool? Is this fast enough?
if not self.seed: if not self.seed:
self.seed=struct.unpack('i', os.urandom(4))[0] self.seed = struct.unpack('i', os.urandom(4))[0]
## Parse XML file ## Parse XML file
if getattr(system,'DEBUG',False): if getattr(system, 'DEBUG', False):
log.info("[courseware.capa.capa_problem.lcp.init] fileobject = %s" % fileobject) log.info("[courseware.capa.capa_problem.lcp.init] fileobject = %s" % fileobject)
file_text = fileobject.read() file_text = fileobject.read()
self.fileobject = fileobject # save it, so we can use for debugging information later self.fileobject = fileobject # save it, so we can use for debugging information later
# Convert startouttext and endouttext to proper <text></text> # Convert startouttext and endouttext to proper <text></text>
# TODO: Do with XML operations # TODO: Do with XML operations
file_text = re.sub("startouttext\s*/","text",file_text) file_text = re.sub("startouttext\s*/", "text", file_text)
file_text = re.sub("endouttext\s*/","/text",file_text) file_text = re.sub("endouttext\s*/", "/text", file_text)
self.tree = etree.XML(file_text) self.tree = etree.XML(file_text)
self.preprocess_problem(self.tree, correct_map=self.correct_map, answer_map = self.student_answers) self.preprocess_problem(self.tree, correct_map=self.correct_map, answer_map=self.student_answers)
self.context = self.extract_context(self.tree, seed=self.seed) self.context = self.extract_context(self.tree, seed=self.seed)
for response in self.tree.xpath('//'+"|//".join(response_types)): for response in self.tree.xpath('//' + "|//".join(response_types)):
responder = response_types[response.tag](response, self.context, self.system) responder = response_types[response.tag](response, self.context, self.system)
responder.preprocess_response() responder.preprocess_response()
...@@ -133,10 +133,10 @@ class LoncapaProblem(object): ...@@ -133,10 +133,10 @@ class LoncapaProblem(object):
''' Stored per-user session data neeeded to: ''' Stored per-user session data neeeded to:
1) Recreate the problem 1) Recreate the problem
2) Populate any student answers. ''' 2) Populate any student answers. '''
return {'seed':self.seed, return {'seed': self.seed,
'student_answers':self.student_answers, 'student_answers': self.student_answers,
'correct_map':self.correct_map, 'correct_map': self.correct_map,
'done':self.done} 'done': self.done}
def get_max_score(self): def get_max_score(self):
''' '''
...@@ -144,20 +144,20 @@ class LoncapaProblem(object): ...@@ -144,20 +144,20 @@ class LoncapaProblem(object):
''' '''
sum = 0 sum = 0
for et in entry_types: for et in entry_types:
sum = sum + self.tree.xpath('count(//'+et+')') sum = sum + self.tree.xpath('count(//' + et + ')')
return int(sum) return int(sum)
def get_score(self): def get_score(self):
correct=0 correct = 0
for key in self.correct_map: for key in self.correct_map:
if self.correct_map[key] == u'correct': if self.correct_map[key] == u'correct':
correct += 1 correct += 1
if (not self.student_answers) or len(self.student_answers)==0: if (not self.student_answers) or len(self.student_answers) == 0:
return {'score':0, return {'score': 0,
'total':self.get_max_score()} 'total': self.get_max_score()}
else: else:
return {'score':correct, return {'score': correct,
'total':self.get_max_score()} 'total': self.get_max_score()}
def grade_answers(self, answers): def grade_answers(self, answers):
''' '''
...@@ -168,7 +168,6 @@ class LoncapaProblem(object): ...@@ -168,7 +168,6 @@ class LoncapaProblem(object):
Thus, for example, input_ID123 -> ID123, and input_fromjs_ID123 -> fromjs_ID123 Thus, for example, input_ID123 -> ID123, and input_fromjs_ID123 -> fromjs_ID123
''' '''
self.student_answers = answers self.student_answers = answers
context=self.extract_context(self.tree)
self.correct_map = dict() self.correct_map = dict()
problems_simple = self.extract_problems(self.tree) problems_simple = self.extract_problems(self.tree)
for response in problems_simple: for response in problems_simple:
...@@ -183,7 +182,6 @@ class LoncapaProblem(object): ...@@ -183,7 +182,6 @@ class LoncapaProblem(object):
not included. Called by "show answers" button JSON request not included. Called by "show answers" button JSON request
(see capa_module) (see capa_module)
""" """
context=self.extract_context(self.tree)
answer_map = dict() answer_map = dict()
problems_simple = self.extract_problems(self.tree) # purified (flat) XML tree of just response queries problems_simple = self.extract_problems(self.tree) # purified (flat) XML tree of just response queries
for response in problems_simple: for response in problems_simple:
...@@ -192,14 +190,14 @@ class LoncapaProblem(object): ...@@ -192,14 +190,14 @@ class LoncapaProblem(object):
answer_map.update(results) # dict of (id,correct_answer) answer_map.update(results) # dict of (id,correct_answer)
# example for the following: <textline size="5" correct_answer="saturated" /> # example for the following: <textline size="5" correct_answer="saturated" />
for entry in problems_simple.xpath("//"+"|//".join(response_properties+entry_types)): for entry in problems_simple.xpath("//" + "|//".join(response_properties + entry_types)):
answer = entry.get('correct_answer') # correct answer, when specified elsewhere, eg in a textline answer = entry.get('correct_answer') # correct answer, when specified elsewhere, eg in a textline
if answer: if answer:
answer_map[entry.get('id')] = contextualize_text(answer, self.context) answer_map[entry.get('id')] = contextualize_text(answer, self.context)
# include solutions from <solution>...</solution> stanzas # include solutions from <solution>...</solution> stanzas
# Tentative merge; we should figure out how we want to handle hints and solutions # Tentative merge; we should figure out how we want to handle hints and solutions
for entry in self.tree.xpath("//"+"|//".join(solution_types)): for entry in self.tree.xpath("//" + "|//".join(solution_types)):
answer = etree.tostring(entry) answer = etree.tostring(entry)
if answer: if answer:
answer_map[entry.get('id')] = answer answer_map[entry.get('id')] = answer
...@@ -211,7 +209,6 @@ class LoncapaProblem(object): ...@@ -211,7 +209,6 @@ class LoncapaProblem(object):
the dicts returned by grade_answers and get_question_answers. (Though the dicts returned by grade_answers and get_question_answers. (Though
get_question_answers may only return a subset of these.""" get_question_answers may only return a subset of these."""
answer_ids = [] answer_ids = []
context=self.extract_context(self.tree)
problems_simple = self.extract_problems(self.tree) problems_simple = self.extract_problems(self.tree)
for response in problems_simple: for response in problems_simple:
responder = response_types[response.tag](response, self.context) responder = response_types[response.tag](response, self.context)
...@@ -223,10 +220,8 @@ class LoncapaProblem(object): ...@@ -223,10 +220,8 @@ class LoncapaProblem(object):
return answer_ids return answer_ids
# ======= Private ======== # ======= Private ========
def extract_context(self, tree, seed=struct.unpack('i', os.urandom(4))[0]): # private
def extract_context(self, tree, seed = struct.unpack('i', os.urandom(4))[0]): # private
''' '''
Extract content of <script>...</script> from the problem.xml file, and exec it in the Extract content of <script>...</script> from the problem.xml file, and exec it in the
context of this problem. Provides ability to randomize problems, and also set context of this problem. Provides ability to randomize problems, and also set
...@@ -235,7 +230,7 @@ class LoncapaProblem(object): ...@@ -235,7 +230,7 @@ class LoncapaProblem(object):
Problem XML goes to Python execution context. Runs everything in script tags Problem XML goes to Python execution context. Runs everything in script tags
''' '''
random.seed(self.seed) random.seed(self.seed)
context = {'global_context':global_context} # save global context in here also context = {'global_context': global_context} # save global context in here also
context.update(global_context) # initialize context to have stuff in global_context context.update(global_context) # initialize context to have stuff in global_context
context['__builtins__'] = globals()['__builtins__'] # put globals there also context['__builtins__'] = globals()['__builtins__'] # put globals there also
context['the_lcp'] = self # pass instance of LoncapaProblem in context['the_lcp'] = self # pass instance of LoncapaProblem in
...@@ -244,12 +239,14 @@ class LoncapaProblem(object): ...@@ -244,12 +239,14 @@ class LoncapaProblem(object):
for script in tree.findall('.//script'): for script in tree.findall('.//script'):
stype = script.get('type') stype = script.get('type')
if stype: if stype:
if 'javascript' in stype: continue # skip javascript if 'javascript' in stype:
if 'perl' in stype: continue # skip perl continue # skip javascript
if 'perl' in stype:
continue # skip perl
# TODO: evaluate only python # TODO: evaluate only python
code = script.text code = script.text
XMLESC = {"&apos;": "'", "&quot;": '"'} XMLESC = {"&apos;": "'", "&quot;": '"'}
code = unescape(code,XMLESC) code = unescape(code, XMLESC)
try: 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: except Exception:
...@@ -296,36 +293,36 @@ class LoncapaProblem(object): ...@@ -296,36 +293,36 @@ class LoncapaProblem(object):
# do the rendering # do the rendering
# This should be broken out into a helper function # This should be broken out into a helper function
# that handles all input objects # that handles all input objects
render_object = inputtypes.SimpleInput(system = self.system, render_object = inputtypes.SimpleInput(system=self.system,
xml = problemtree, xml=problemtree,
state = {'value':value, state={'value': value,
'status': status, 'status': status,
'id':problemtree.get('id'), 'id': problemtree.get('id'),
'feedback':{'message':msg} 'feedback': {'message': msg}
}, },
use = 'capa_input') use='capa_input')
return render_object.get_html() #function(problemtree, value, status, msg) # render the special response (textline, schematic,...) return render_object.get_html() # function(problemtree, value, status, msg) # render the special response (textline, schematic,...)
tree=Element(problemtree.tag) tree = Element(problemtree.tag)
for item in problemtree: for item in problemtree:
subitems = self.extract_html(item) subitems = self.extract_html(item)
if subitems is not None: if subitems is not None:
for subitem in subitems: for subitem in subitems:
tree.append(subitem) tree.append(subitem)
for (key,value) in problemtree.items(): for (key, value) in problemtree.items():
tree.set(key, value) tree.set(key, value)
tree.text=problemtree.text tree.text = problemtree.text
tree.tail=problemtree.tail tree.tail = problemtree.tail
if problemtree.tag in html_transforms: if problemtree.tag in html_transforms:
tree.tag=html_transforms[problemtree.tag]['tag'] tree.tag = html_transforms[problemtree.tag]['tag']
# Reset attributes. Otherwise, we get metadata in HTML # Reset attributes. Otherwise, we get metadata in HTML
# (e.g. answers) # (e.g. answers)
# TODO: We should remove and not zero them. # TODO: We should remove and not zero them.
# I'm not sure how to do that quickly with lxml # I'm not sure how to do that quickly with lxml
for k in tree.keys(): for k in tree.keys():
tree.set(k,"") tree.set(k, "")
# TODO: Fix. This loses Element().tail # TODO: Fix. This loses Element().tail
#if problemtree.tag in html_skip: #if problemtree.tag in html_skip:
...@@ -340,58 +337,57 @@ class LoncapaProblem(object): ...@@ -340,58 +337,57 @@ class LoncapaProblem(object):
In-place transformation In-place transformation
''' '''
response_id = 1 response_id = 1
for response in tree.xpath('//'+"|//".join(response_types)): for response in tree.xpath('//' + "|//".join(response_types)):
response_id_str=self.problem_id+"_"+str(response_id) response_id_str = self.problem_id + "_" + str(response_id)
response.attrib['id']=response_id_str response.attrib['id'] = response_id_str
if response_id not in correct_map: if response_id not in correct_map:
correct = 'unsubmitted' correct = 'unsubmitted'
response.attrib['state'] = correct response.attrib['state'] = correct
response_id = response_id + 1 response_id = response_id + 1
answer_id = 1 answer_id = 1
for entry in tree.xpath("|".join(['//'+response.tag+'[@id=$id]//'+x for x in (entry_types + solution_types)]), for entry in tree.xpath("|".join(['//' + response.tag + '[@id=$id]//' + x for x in (entry_types + solution_types)]),
id=response_id_str): id=response_id_str):
# assign one answer_id for each entry_type or solution_type # assign one answer_id for each entry_type or solution_type
entry.attrib['response_id'] = str(response_id) entry.attrib['response_id'] = str(response_id)
entry.attrib['answer_id'] = str(answer_id) entry.attrib['answer_id'] = str(answer_id)
entry.attrib['id'] = "%s_%i_%i"%(self.problem_id, response_id, answer_id) entry.attrib['id'] = "%s_%i_%i" % (self.problem_id, response_id, answer_id)
answer_id=answer_id+1 answer_id = answer_id + 1
# <solution>...</solution> may not be associated with any specific response; give IDs for those separately # <solution>...</solution> may not be associated with any specific response; give IDs for those separately
# TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i). # TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i).
solution_id = 1 solution_id = 1
for solution in tree.findall('.//solution'): for solution in tree.findall('.//solution'):
solution.attrib['id'] = "%s_solution_%i"%(self.problem_id, solution_id) solution.attrib['id'] = "%s_solution_%i" % (self.problem_id, solution_id)
solution_id += 1 solution_id += 1
def extract_problems(self, problem_tree): def extract_problems(self, problem_tree):
''' Remove layout from the problem, and give a purified XML tree of just the problems ''' ''' Remove layout from the problem, and give a purified XML tree of just the problems '''
problem_tree=copy.deepcopy(problem_tree) problem_tree = copy.deepcopy(problem_tree)
tree=Element('problem') tree = Element('problem')
for response in problem_tree.xpath("//"+"|//".join(response_types)): for response in problem_tree.xpath("//" + "|//".join(response_types)):
newresponse = copy.copy(response) newresponse = copy.copy(response)
for e in newresponse: for e in newresponse:
newresponse.remove(e) newresponse.remove(e)
# copy.copy is needed to make xpath work right. Otherwise, it starts at the root # copy.copy is needed to make xpath work right. Otherwise, it starts at the root
# of the tree. We should figure out if there's some work-around # of the tree. We should figure out if there's some work-around
for e in copy.copy(response).xpath("//"+"|//".join(response_properties+entry_types)): for e in copy.copy(response).xpath("//" + "|//".join(response_properties + entry_types)):
newresponse.append(e) newresponse.append(e)
tree.append(newresponse) tree.append(newresponse)
return tree return tree
if __name__=='__main__': if __name__ == '__main__':
problem_id='simpleFormula' problem_id = 'simpleFormula'
filename = 'simpleFormula.xml' filename = 'simpleFormula.xml'
problem_id='resistor' problem_id = 'resistor'
filename = 'resistor.xml' filename = 'resistor.xml'
lcp = LoncapaProblem(filename, problem_id) lcp = LoncapaProblem(filename, problem_id)
context = lcp.extract_context(lcp.tree) context = lcp.extract_context(lcp.tree)
problem = lcp.extract_problems(lcp.tree) problem = lcp.extract_problems(lcp.tree)
print lcp.grade_problems({'resistor_2_1':'1.0','resistor_3_1':'2.0'}) print lcp.grade_problems({'resistor_2_1': '1.0', 'resistor_3_1': '2.0'})
#print lcp.grade_problems({'simpleFormula_2_1':'3*x^3'}) #print lcp.grade_problems({'simpleFormula_2_1':'3*x^3'})
#numericalresponse(problem, context) #numericalresponse(problem, context)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment