diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index c2babfa479..f49ad5b422 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -44,7 +44,6 @@ from lxml import etree import re import shlex # for splitting quoted strings import sys -import os import pyparsing from .registry import TagRegistry @@ -97,7 +96,8 @@ class Attribute(object): """ val = element.get(self.name) if self.default == self._sentinel and val is None: - raise ValueError('Missing required attribute {0}.'.format(self.name)) + raise ValueError( + 'Missing required attribute {0}.'.format(self.name)) if val is None: # not required, so return default @@ -149,7 +149,8 @@ class InputTypeBase(object): self.id = state.get('id', xml.get('id')) if self.id is None: - raise ValueError("input id state is None. xml is {0}".format(etree.tostring(xml))) + raise ValueError("input id state is None. xml is {0}".format( + etree.tostring(xml))) self.value = state.get('value', '') @@ -169,14 +170,15 @@ class InputTypeBase(object): self.process_requirements() # Call subclass "constructor" -- means they don't have to worry about calling - # super().__init__, and are isolated from changes to the input constructor interface. + # super().__init__, and are isolated from changes to the input + # constructor interface. self.setup() except Exception as err: # Something went wrong: add xml to message, but keep the traceback - msg = "Error in xml '{x}': {err} ".format(x=etree.tostring(xml), err=str(err)) + msg = "Error in xml '{x}': {err} ".format( + x=etree.tostring(xml), err=str(err)) raise Exception, msg, sys.exc_info()[2] - @classmethod def get_attributes(cls): """ @@ -186,7 +188,6 @@ class InputTypeBase(object): """ return [] - def process_requirements(self): """ Subclasses can declare lists of required and optional attributes. This @@ -196,7 +197,8 @@ class InputTypeBase(object): Processes attributes, putting the results in the self.loaded_attributes dictionary. Also creates a set self.to_render, containing the names of attributes that should be included in the context by default. """ - # Use local dicts and sets so that if there are exceptions, we don't end up in a partially-initialized state. + # Use local dicts and sets so that if there are exceptions, we don't + # end up in a partially-initialized state. loaded = {} to_render = set() for a in self.get_attributes(): @@ -226,7 +228,7 @@ class InputTypeBase(object): get: a dictionary containing the data that was sent with the ajax call Output: - a dictionary object that can be serialized into JSON. This will be sent back to the Javascript. + a dictionary object that can be serialized into JSON. This will be sent back to the Javascript. """ pass @@ -247,8 +249,9 @@ class InputTypeBase(object): 'value': self.value, 'status': self.status, 'msg': self.msg, - } - context.update((a, v) for (a, v) in self.loaded_attributes.iteritems() if a in self.to_render) + } + context.update((a, v) for ( + a, v) in self.loaded_attributes.iteritems() if a in self.to_render) context.update(self._extra_context()) return context @@ -371,7 +374,6 @@ class ChoiceGroup(InputTypeBase): return [Attribute("show_correctness", "always"), Attribute("submitted_message", "Answer received.")] - def _extra_context(self): return {'input_type': self.html_input_type, 'choices': self.choices, @@ -436,7 +438,6 @@ class JavascriptInput(InputTypeBase): Attribute('display_class', None), Attribute('display_file', None), ] - def setup(self): # Need to provide a value that JSON can parse if there is no # student-supplied value yet. @@ -459,7 +460,6 @@ class TextLine(InputTypeBase): template = "textline.html" tags = ['textline'] - @classmethod def get_attributes(cls): """ @@ -474,12 +474,12 @@ class TextLine(InputTypeBase): # Attributes below used in setup(), not rendered directly. Attribute('math', None, render=False), - # TODO: 'dojs' flag is temporary, for backwards compatibility with 8.02x + # TODO: 'dojs' flag is temporary, for backwards compatibility with + # 8.02x Attribute('dojs', None, render=False), Attribute('preprocessorClassName', None, render=False), Attribute('preprocessorSrc', None, render=False), - ] - + ] def setup(self): self.do_math = bool(self.loaded_attributes['math'] or @@ -490,12 +490,12 @@ class TextLine(InputTypeBase): self.preprocessor = None if self.do_math: # Preprocessor to insert between raw input and Mathjax - self.preprocessor = {'class_name': self.loaded_attributes['preprocessorClassName'], - 'script_src': self.loaded_attributes['preprocessorSrc']} + self.preprocessor = { + 'class_name': self.loaded_attributes['preprocessorClassName'], + 'script_src': self.loaded_attributes['preprocessorSrc']} if None in self.preprocessor.values(): self.preprocessor = None - def _extra_context(self): return {'do_math': self.do_math, 'preprocessor': self.preprocessor, } @@ -539,7 +539,8 @@ class FileSubmission(InputTypeBase): """ # Check if problem has been queued self.queue_len = 0 - # Flag indicating that the problem has been queued, 'msg' is length of queue + # Flag indicating that the problem has been queued, 'msg' is length of + # queue if self.status == 'incomplete': self.status = 'queued' self.queue_len = self.msg @@ -547,7 +548,6 @@ class FileSubmission(InputTypeBase): def _extra_context(self): return {'queue_len': self.queue_len, } - return context registry.register(FileSubmission) @@ -562,8 +562,9 @@ class CodeInput(InputTypeBase): template = "codeinput.html" tags = ['codeinput', - 'textbox', # Another (older) name--at some point we may want to make it use a - # non-codemirror editor. + 'textbox', + # Another (older) name--at some point we may want to make it use a + # non-codemirror editor. ] # pulled out for testing @@ -590,13 +591,15 @@ class CodeInput(InputTypeBase): """ Implement special logic: handle queueing state, and default input. """ - # if no student input yet, then use the default input given by the problem + # if no student input yet, then use the default input given by the + # problem if not self.value: self.value = self.xml.text # Check if problem has been queued self.queue_len = 0 - # Flag indicating that the problem has been queued, 'msg' is length of queue + # Flag indicating that the problem has been queued, 'msg' is length of + # queue if self.status == 'incomplete': self.status = 'queued' self.queue_len = self.msg @@ -610,8 +613,67 @@ registry.register(CodeInput) #----------------------------------------------------------------------------- + + +class MatlabInput(CodeInput): + ''' + InputType for handling Matlab code input + ''' + template = "matlabinput.html" + tags = ['matlabinput'] + + # pulled out for testing + submitted_msg = ("Submitted. As soon as your submission is" + " graded, this message will be replaced with the grader's feedback.") + + def setup(self): + ''' + Handle matlab-specific parsing + ''' + xml = self.xml + self.plot_payload = xml.findtext('./plot_payload') + # if no student input yet, then use the default input given by the + # problem + if not self.value: + self.value = self.xml.text + + # Check if problem has been queued + self.queue_len = 0 + # Flag indicating that the problem has been queued, 'msg' is length of + # queue + if self.status == 'incomplete': + self.status = 'queued' + self.queue_len = self.msg + self.msg = self.submitted_msg + + + + def handle_ajax(self, dispatch, get): + if dispatch == 'plot': + # put the data in the queue and ship it off + pass + elif dispatch == 'display': + # render the response + pass + + def plot_data(self, get): + ''' send data via xqueue to the mathworks backend''' + + # only send data if xqueue exists + if self.system.xqueue is not None: + pass + + + + +registry.register(MatlabInput) + + +#----------------------------------------------------------------------------- + class Schematic(InputTypeBase): """ + InputType for the schematic editor """ template = "schematicinput.html" @@ -630,7 +692,6 @@ class Schematic(InputTypeBase): Attribute('initial_value', None), Attribute('submit_analyses', None), ] - return context registry.register(Schematic) @@ -660,12 +721,12 @@ class ImageInput(InputTypeBase): Attribute('height'), Attribute('width'), ] - def setup(self): """ if value is of the form [x,y] then parse it and send along coordinates of previous answer """ - m = re.match('\[([0-9]+),([0-9]+)]', self.value.strip().replace(' ', '')) + m = re.match('\[([0-9]+),([0-9]+)]', + self.value.strip().replace(' ', '')) if m: # Note: we subtract 15 to compensate for the size of the dot on the screen. # (is a 30x30 image--lms/static/green-pointer.png). @@ -673,7 +734,6 @@ class ImageInput(InputTypeBase): else: (self.gx, self.gy) = (0, 0) - def _extra_context(self): return {'gx': self.gx, @@ -730,7 +790,7 @@ class VseprInput(InputTypeBase): registry.register(VseprInput) -#-------------------------------------------------------------------------------- +#------------------------------------------------------------------------- class ChemicalEquationInput(InputTypeBase): @@ -794,7 +854,8 @@ class ChemicalEquationInput(InputTypeBase): result['error'] = "Couldn't parse formula: {0}".format(p) except Exception: # this is unexpected, so log - log.warning("Error while previewing chemical formula", exc_info=True) + log.warning( + "Error while previewing chemical formula", exc_info=True) result['error'] = "Error while rendering preview" return result @@ -843,16 +904,16 @@ class DragAndDropInput(InputTypeBase): 'can_reuse': ""} tag_attrs['target'] = {'id': Attribute._sentinel, - 'x': Attribute._sentinel, - 'y': Attribute._sentinel, - 'w': Attribute._sentinel, - 'h': Attribute._sentinel} + 'x': Attribute._sentinel, + 'y': Attribute._sentinel, + 'w': Attribute._sentinel, + 'h': Attribute._sentinel} dic = dict() for attr_name in tag_attrs[tag_type].keys(): dic[attr_name] = Attribute(attr_name, - default=tag_attrs[tag_type][attr_name]).parse_from_xml(tag) + default=tag_attrs[tag_type][attr_name]).parse_from_xml(tag) if tag_type == 'draggable' and not self.no_labels: dic['label'] = dic['label'] or dic['id'] @@ -865,7 +926,7 @@ class DragAndDropInput(InputTypeBase): # add labels to images?: self.no_labels = Attribute('no_labels', - default="False").parse_from_xml(self.xml) + default="False").parse_from_xml(self.xml) to_js = dict() @@ -874,16 +935,16 @@ class DragAndDropInput(InputTypeBase): # outline places on image where to drag adn drop to_js['target_outline'] = Attribute('target_outline', - default="False").parse_from_xml(self.xml) + default="False").parse_from_xml(self.xml) # one draggable per target? to_js['one_per_target'] = Attribute('one_per_target', - default="True").parse_from_xml(self.xml) + default="True").parse_from_xml(self.xml) # list of draggables to_js['draggables'] = [parse(draggable, 'draggable') for draggable in - self.xml.iterchildren('draggable')] + self.xml.iterchildren('draggable')] # list of targets to_js['targets'] = [parse(target, 'target') for target in - self.xml.iterchildren('target')] + self.xml.iterchildren('target')] # custom background color for labels: label_bg_color = Attribute('label_bg_color', @@ -896,7 +957,7 @@ class DragAndDropInput(InputTypeBase): registry.register(DragAndDropInput) -#-------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------- class EditAMoleculeInput(InputTypeBase): @@ -934,6 +995,7 @@ registry.register(EditAMoleculeInput) #----------------------------------------------------------------------------- + class DesignProtein2dInput(InputTypeBase): """ An input type for design of a protein in 2D. Integrates with the Protex java applet. @@ -969,6 +1031,7 @@ registry.register(DesignProtein2dInput) #----------------------------------------------------------------------------- + class EditAGeneInput(InputTypeBase): """ An input type for editing a gene. Integrates with the genex java applet. @@ -1005,6 +1068,7 @@ registry.register(EditAGeneInput) #--------------------------------------------------------------------- + class AnnotationInput(InputTypeBase): """ Input type for annotations: students can enter some notes or other text @@ -1037,13 +1101,14 @@ class AnnotationInput(InputTypeBase): def setup(self): xml = self.xml - self.debug = False # set to True to display extra debug info with input - self.return_to_annotation = True # return only works in conjunction with annotatable xmodule + self.debug = False # set to True to display extra debug info with input + self.return_to_annotation = True # return only works in conjunction with annotatable xmodule self.title = xml.findtext('./title', 'Annotation Exercise') self.text = xml.findtext('./text') self.comment = xml.findtext('./comment') - self.comment_prompt = xml.findtext('./comment_prompt', 'Type a commentary below:') + self.comment_prompt = xml.findtext( + './comment_prompt', 'Type a commentary below:') self.tag_prompt = xml.findtext('./tag_prompt', 'Select one tag:') self.options = self._find_options() @@ -1061,7 +1126,7 @@ class AnnotationInput(InputTypeBase): 'id': index, 'description': option.text, 'choice': option.get('choice') - } for (index, option) in enumerate(elements) ] + } for (index, option) in enumerate(elements)] def _validate_options(self): ''' Raises a ValueError if the choice attribute is missing or invalid. ''' @@ -1071,7 +1136,8 @@ class AnnotationInput(InputTypeBase): if choice is None: raise ValueError('Missing required choice attribute.') elif choice not in valid_choices: - raise ValueError('Invalid choice attribute: {0}. Must be one of: {1}'.format(choice, ', '.join(valid_choices))) + raise ValueError('Invalid choice attribute: {0}. Must be one of: {1}'.format( + choice, ', '.join(valid_choices))) def _unpack(self, json_value): ''' Unpacks the json input state into a dict. ''' @@ -1089,20 +1155,20 @@ class AnnotationInput(InputTypeBase): return { 'options_value': options_value, - 'has_options_value': len(options_value) > 0, # for convenience + 'has_options_value': len(options_value) > 0, # for convenience 'comment_value': comment_value, } def _extra_context(self): extra_context = { - 'title': self.title, - 'text': self.text, - 'comment': self.comment, - 'comment_prompt': self.comment_prompt, - 'tag_prompt': self.tag_prompt, - 'options': self.options, - 'return_to_annotation': self.return_to_annotation, - 'debug': self.debug + 'title': self.title, + 'text': self.text, + 'comment': self.comment, + 'comment_prompt': self.comment_prompt, + 'tag_prompt': self.tag_prompt, + 'options': self.options, + 'return_to_annotation': self.return_to_annotation, + 'debug': self.debug } extra_context.update(self._unpack(self.value)) @@ -1110,4 +1176,3 @@ class AnnotationInput(InputTypeBase): return extra_context registry.register(AnnotationInput) - diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 6bf98999d8..62da901656 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -128,21 +128,25 @@ class LoncapaResponse(object): for abox in inputfields: if abox.tag not in self.allowed_inputfields: - msg = "%s: cannot have input field %s" % (unicode(self), abox.tag) - msg += "\nSee XML source line %s" % getattr(xml, 'sourceline', '') + msg = "%s: cannot have input field %s" % ( + unicode(self), abox.tag) + msg += "\nSee XML source line %s" % getattr( + xml, 'sourceline', '') raise LoncapaProblemError(msg) if self.max_inputfields and len(inputfields) > self.max_inputfields: msg = "%s: cannot have more than %s input fields" % ( unicode(self), self.max_inputfields) - msg += "\nSee XML source line %s" % getattr(xml, 'sourceline', '') + msg += "\nSee XML source line %s" % getattr( + xml, 'sourceline', '') raise LoncapaProblemError(msg) for prop in self.required_attributes: if not xml.get(prop): msg = "Error in problem specification: %s missing required attribute %s" % ( unicode(self), prop) - msg += "\nSee XML source line %s" % getattr(xml, 'sourceline', '') + msg += "\nSee XML source line %s" % getattr( + xml, 'sourceline', '') raise LoncapaProblemError(msg) # ordered list of answer_id values for this response @@ -163,7 +167,8 @@ class LoncapaResponse(object): for entry in self.inputfields: answer = entry.get('correct_answer') if answer: - self.default_answer_map[entry.get('id')] = contextualize_text(answer, self.context) + self.default_answer_map[entry.get( + 'id')] = contextualize_text(answer, self.context) if hasattr(self, 'setup_response'): self.setup_response() @@ -211,7 +216,8 @@ class LoncapaResponse(object): Returns the new CorrectMap, with (correctness,msg,hint,hintmode) for each answer_id. ''' new_cmap = self.get_score(student_answers) - self.get_hints(convert_files_to_filenames(student_answers), new_cmap, old_cmap) + self.get_hints(convert_files_to_filenames( + student_answers), new_cmap, old_cmap) # log.debug('new_cmap = %s' % new_cmap) return new_cmap @@ -241,14 +247,17 @@ class LoncapaResponse(object): # callback procedure to a social hint generation system. if not hintfn in self.context: msg = 'missing specified hint function %s in script context' % hintfn - msg += "\nSee XML source line %s" % getattr(self.xml, 'sourceline', '') + msg += "\nSee XML source line %s" % getattr( + self.xml, 'sourceline', '') raise LoncapaProblemError(msg) try: - self.context[hintfn](self.answer_ids, student_answers, new_cmap, old_cmap) + self.context[hintfn]( + self.answer_ids, student_answers, new_cmap, old_cmap) except Exception as err: msg = 'Error %s in evaluating hint function %s' % (err, hintfn) - msg += "\nSee XML source line %s" % getattr(self.xml, 'sourceline', '') + msg += "\nSee XML source line %s" % getattr( + self.xml, 'sourceline', '') raise ResponseError(msg) return @@ -270,17 +279,19 @@ class LoncapaResponse(object): if (self.hint_tag is not None and hintgroup.find(self.hint_tag) is not None - and hasattr(self, 'check_hint_condition')): + and hasattr(self, 'check_hint_condition')): rephints = hintgroup.findall(self.hint_tag) - hints_to_show = self.check_hint_condition(rephints, student_answers) + hints_to_show = self.check_hint_condition( + rephints, student_answers) # can be 'on_request' or 'always' (default) hintmode = hintgroup.get('mode', 'always') for hintpart in hintgroup.findall('hintpart'): if hintpart.get('on') in hints_to_show: hint_text = hintpart.find('text').text - # make the hint appear after the last answer box in this response + # make the hint appear after the last answer box in this + # response aid = self.answer_ids[-1] new_cmap.set_hint_and_mode(aid, hint_text, hintmode) log.debug('after hint: new_cmap = %s' % new_cmap) @@ -340,7 +351,6 @@ class LoncapaResponse(object): response_msg_div = etree.Element('div') response_msg_div.text = str(response_msg) - # Set the css class of the message
response_msg_div.set("class", "response_message") @@ -384,20 +394,20 @@ class JavascriptResponse(LoncapaResponse): # until we decide on exactly how to solve this issue. For now, files are # manually being compiled to DATA_DIR/js/compiled. - #latestTimestamp = 0 - #basepath = self.system.filestore.root_path + '/js/' - #for filename in (self.display_dependencies + [self.display]): + # latestTimestamp = 0 + # basepath = self.system.filestore.root_path + '/js/' + # for filename in (self.display_dependencies + [self.display]): # filepath = basepath + filename # timestamp = os.stat(filepath).st_mtime # if timestamp > latestTimestamp: # latestTimestamp = timestamp # - #h = hashlib.md5() - #h.update(self.answer_id + str(self.display_dependencies)) - #compiled_filename = 'compiled/' + h.hexdigest() + '.js' - #compiled_filepath = basepath + compiled_filename + # h = hashlib.md5() + # h.update(self.answer_id + str(self.display_dependencies)) + # compiled_filename = 'compiled/' + h.hexdigest() + '.js' + # compiled_filepath = basepath + compiled_filename - #if not os.path.exists(compiled_filepath) or os.stat(compiled_filepath).st_mtime < latestTimestamp: + # if not os.path.exists(compiled_filepath) or os.stat(compiled_filepath).st_mtime < latestTimestamp: # outfile = open(compiled_filepath, 'w') # for filename in (self.display_dependencies + [self.display]): # filepath = basepath + filename @@ -419,7 +429,7 @@ class JavascriptResponse(LoncapaResponse): id=self.xml.get('id'))[0] self.display_xml = self.xml.xpath('//*[@id=$id]//display', - id=self.xml.get('id'))[0] + id=self.xml.get('id'))[0] self.xml.remove(self.generator_xml) self.xml.remove(self.grader_xml) @@ -430,17 +440,20 @@ class JavascriptResponse(LoncapaResponse): self.display = self.display_xml.get("src") if self.generator_xml.get("dependencies"): - self.generator_dependencies = self.generator_xml.get("dependencies").split() + self.generator_dependencies = self.generator_xml.get( + "dependencies").split() else: self.generator_dependencies = [] if self.grader_xml.get("dependencies"): - self.grader_dependencies = self.grader_xml.get("dependencies").split() + self.grader_dependencies = self.grader_xml.get( + "dependencies").split() else: self.grader_dependencies = [] if self.display_xml.get("dependencies"): - self.display_dependencies = self.display_xml.get("dependencies").split() + self.display_dependencies = self.display_xml.get( + "dependencies").split() else: self.display_dependencies = [] @@ -461,10 +474,10 @@ class JavascriptResponse(LoncapaResponse): return subprocess.check_output(subprocess_args, env=self.get_node_env()) - def generate_problem_state(self): - generator_file = os.path.dirname(os.path.normpath(__file__)) + '/javascript_problem_generator.js' + generator_file = os.path.dirname(os.path.normpath( + __file__)) + '/javascript_problem_generator.js' output = self.call_node([generator_file, self.generator, json.dumps(self.generator_dependencies), @@ -478,17 +491,18 @@ class JavascriptResponse(LoncapaResponse): params = {} for param in self.xml.xpath('//*[@id=$id]//responseparam', - id=self.xml.get('id')): + id=self.xml.get('id')): raw_param = param.get("value") - params[param.get("name")] = json.loads(contextualize_text(raw_param, self.context)) + params[param.get("name")] = json.loads( + contextualize_text(raw_param, self.context)) return params def prepare_inputfield(self): for inputfield in self.xml.xpath('//*[@id=$id]//javascriptinput', - id=self.xml.get('id')): + id=self.xml.get('id')): escapedict = {'"': '"'} @@ -501,7 +515,7 @@ class JavascriptResponse(LoncapaResponse): escapedict) inputfield.set("problem_state", encoded_problem_state) - inputfield.set("display_file", self.display_filename) + inputfield.set("display_file", self.display_filename) inputfield.set("display_class", self.display_class) def get_score(self, student_answers): @@ -519,7 +533,8 @@ class JavascriptResponse(LoncapaResponse): if submission is None or submission == '': submission = json.dumps(None) - grader_file = os.path.dirname(os.path.normpath(__file__)) + '/javascript_problem_grader.js' + grader_file = os.path.dirname(os.path.normpath( + __file__)) + '/javascript_problem_grader.js' outputs = self.call_node([grader_file, self.grader, json.dumps(self.grader_dependencies), @@ -528,8 +543,8 @@ class JavascriptResponse(LoncapaResponse): json.dumps(self.params)]).split('\n') all_correct = json.loads(outputs[0].strip()) - evaluation = outputs[1].strip() - solution = outputs[2].strip() + evaluation = outputs[1].strip() + solution = outputs[2].strip() return (all_correct, evaluation, solution) def get_answers(self): @@ -539,9 +554,7 @@ class JavascriptResponse(LoncapaResponse): return {self.answer_id: self.solution} - #----------------------------------------------------------------------------- - class ChoiceResponse(LoncapaResponse): """ This response type is used when the student chooses from a discrete set of @@ -599,9 +612,10 @@ class ChoiceResponse(LoncapaResponse): self.assign_choice_names() correct_xml = self.xml.xpath('//*[@id=$id]//choice[@correct="true"]', - id=self.xml.get('id')) + id=self.xml.get('id')) - self.correct_choices = set([choice.get('name') for choice in correct_xml]) + self.correct_choices = set([choice.get( + 'name') for choice in correct_xml]) def assign_choice_names(self): ''' @@ -654,7 +668,8 @@ class MultipleChoiceResponse(LoncapaResponse): allowed_inputfields = ['choicegroup'] def setup_response(self): - # call secondary setup for MultipleChoice questions, to set name attributes + # call secondary setup for MultipleChoice questions, to set name + # attributes self.mc_setup_response() # define correct choices (after calling secondary setup) @@ -692,7 +707,7 @@ class MultipleChoiceResponse(LoncapaResponse): # log.debug('%s: student_answers=%s, correct_choices=%s' % ( # unicode(self), student_answers, self.correct_choices)) if (self.answer_id in student_answers - and student_answers[self.answer_id] in self.correct_choices): + and student_answers[self.answer_id] in self.correct_choices): return CorrectMap(self.answer_id, 'correct') else: return CorrectMap(self.answer_id, 'incorrect') @@ -760,7 +775,8 @@ class OptionResponse(LoncapaResponse): return cmap def get_answers(self): - amap = dict([(af.get('id'), contextualize_text(af.get('correct'), self.context)) for af in self.answer_fields]) + amap = dict([(af.get('id'), contextualize_text(af.get( + 'correct'), self.context)) for af in self.answer_fields]) # log.debug('%s: expected answers=%s' % (unicode(self),amap)) return amap @@ -780,8 +796,9 @@ class NumericalResponse(LoncapaResponse): context = self.context self.correct_answer = contextualize_text(xml.get('answer'), context) try: - self.tolerance_xml = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default', - id=xml.get('id'))[0] + self.tolerance_xml = xml.xpath( + '//*[@id=$id]//responseparam[@type="tolerance"]/@default', + id=xml.get('id'))[0] self.tolerance = contextualize_text(self.tolerance_xml, context) except Exception: self.tolerance = '0' @@ -798,17 +815,21 @@ class NumericalResponse(LoncapaResponse): try: correct_ans = complex(self.correct_answer) except ValueError: - log.debug("Content error--answer '{0}' is not a valid complex number".format(self.correct_answer)) - raise StudentInputError("There was a problem with the staff answer to this problem") + log.debug("Content error--answer '{0}' is not a valid complex number".format( + self.correct_answer)) + raise StudentInputError( + "There was a problem with the staff answer to this problem") try: - correct = compare_with_tolerance(evaluator(dict(), dict(), student_answer), - correct_ans, self.tolerance) + correct = compare_with_tolerance( + evaluator(dict(), dict(), student_answer), + correct_ans, self.tolerance) # We should catch this explicitly. # I think this is just pyparsing.ParseException, calc.UndefinedVariable: # But we'd need to confirm except: - # Use the traceback-preserving version of re-raising with a different type + # Use the traceback-preserving version of re-raising with a + # different type import sys type, value, traceback = sys.exc_info() @@ -837,7 +858,8 @@ class StringResponse(LoncapaResponse): max_inputfields = 1 def setup_response(self): - self.correct_answer = contextualize_text(self.xml.get('answer'), self.context).strip() + self.correct_answer = contextualize_text( + self.xml.get('answer'), self.context).strip() def get_score(self, student_answers): '''Grade a string response ''' @@ -846,7 +868,8 @@ class StringResponse(LoncapaResponse): return CorrectMap(self.answer_id, 'correct' if correct else 'incorrect') def check_string(self, expected, given): - if self.xml.get('type') == 'ci': return given.lower() == expected.lower() + if self.xml.get('type') == 'ci': + return given.lower() == expected.lower() return given == expected def check_hint_condition(self, hxml_set, student_answers): @@ -854,8 +877,10 @@ class StringResponse(LoncapaResponse): hints_to_show = [] for hxml in hxml_set: name = hxml.get('name') - correct_answer = contextualize_text(hxml.get('answer'), self.context).strip() - if self.check_string(correct_answer, given): hints_to_show.append(name) + correct_answer = contextualize_text( + hxml.get('answer'), self.context).strip() + if self.check_string(correct_answer, given): + hints_to_show.append(name) log.debug('hints_to_show = %s' % hints_to_show) return hints_to_show @@ -889,7 +914,7 @@ class CustomResponse(LoncapaResponse): correct[0] ='incorrect' """}, - {'snippet': """ + diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.coffee b/common/lib/xmodule/xmodule/js/src/capa/display.coffee index 158c2b98d0..4173f424b6 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/capa/display.coffee @@ -70,6 +70,7 @@ class @Problem @bind() @num_queued_items = @new_queued_items.length + @updateProgress response if @num_queued_items == 0 delete window.queuePollerID else